Page 2 of 3

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 1:00 pm
by inghamn
Just recently something clicked for me with AJAX applications. It was what the term "application" is referring to in the first place. All along I've been doing web applications, which had a ton of screens each at a URL.

A recent conference proceeding made the point for me that, with AJAX stuff, what you're really doing is providing a complete, task-oriented rich "application" at each URL of your website.

So all along, these JavaScript folks have been thinking of applications as just one URL. (Like Yahoo home pages or such.) PHP's job in all this is to deliver the source code for these applications to the browser. And the source code is mix of HTML, CSS, and JavaScript.

PHP also has the job of delivering the data (through web services) needed by these applications.

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 1:02 pm
by VladSun
arborint wrote:
VladSun wrote:My web-apps are fully AJAX featured. That is, except for the main page HTML View I have no more HTML code. Indeed, I don't have any server side Views - everything is sent to the user as JSON data (if you consider an JSON formatter helper a View ... I have one View ;) ). That is, the View part is always client side - no templates, no code duplication (except for the validation, which I think can't be done in other way).
So you have one piece of Javascript code that you load with the main page and that builds the page from JSON data for every page in your site?
Hm ... yes!
But it's more like a desktop application rather than a collection of web pages ...
A typical web application of mine looks like this:
http://www.extjs.com/deploy/dev/example ... sktop.html

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 1:07 pm
by Christopher
So following ingham's "every URL is an application" idea ... when do you send a new Javascript? Do you have multiple "main pages"? Or a huge do-everything main Javascript app? Or small focused websites?

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 1:37 pm
by VladSun
One single main page. It loads the core JS libraries and the main Application class. Then it fetches the list of modules available to the current user's role and loads their "meta data". If a module is going to be executed an AJAX call fetches its "real" class and it is run.
There is even a dependency register (in every class declaration), so JS files needed by the class to be loaded are preloaded first.

Also, I won't consider my web applications "small focused" web sites. One of them is a fully featured small ISP management system for back and front office. While one may call it "huge do-everything main Javascript", I think it's modular enough and it works flawlessly.

PS: do you remember this topic : viewtopic.php?f=19&t=93937 :)

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 1:45 pm
by josh
So it is 1 class or or not? You said 1 main application class but what is a module meta data? Is all your code abstracted and your modules are simply listings of fields to render in a grid? and things like that?

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 2:00 pm
by VladSun
josh wrote:So it is 1 class or or not?
Well, the Application class depends on several classes:
[js]Ext.dependencies ={    application :    [        'ex/app/CurrentUser',        'ex/app/CurrentSettings',        'desktop/AppWatch',        'desktop/StartMenu',        'desktop/TaskBar',        'desktop/Desktop',        'desktop/Module',        'desktop/MRTGModule',        'desktop/ReportModule',        'desktop/Shortcut',        'desktop/Notification',        'desktop/App',        'ex/Sound',        'ex/Image',        'ex/Printer'    ],.... [/js]
But there are no classes not related to the framework itself loaded. See what I meant by "meta data" below.
josh wrote:You said 1 main application class but what is a module meta data?
You know that JS classes are prototype based, so you can extend them and their instances at runtime. So ... this is a "meta-class" (see below its "real" class):

personnelview-launcher.js
[js](function (){    return (    {        translationContext  : 'ledger',        translationSource   : null,         dependencies        : null,         create  : function ()        {            Ext.namespace('Ext.Module');            Ext.Module.Ledger = Ext.extend(Ext.app.Module,            {                moduleId    : 'mod-ledger',                menuPath    : 'StartMenu/' + Ext.Translation.get('menu', 'ledger_menu'),                 launcher    :                {                    iconCls         : 'ledger-icon',                    shortcutIconCls : 'ledger-shortcut',                    text            : Ext.Translation.get('ledger', 'title'),                    tooltip         : Ext.Translation.get('ledger', 'module_description', true)                }            });             return new Ext.Module.Ledger();        }    })})();[/js]

josh wrote:Is all your code abstracted and your modules are simply listings of fields to render in a grid? and things like that?
I didn't get this one ... Are you talking about the JS "modules" or for PHP modules?

If you are talking about JS modules - well, they are not so thin:
[js](function (){    return (    {        translationContext  : 'ledger',        translationSource   : null,         dependencies        : Ext.dependencies.singleEditGrid,         create  : function ()        {            Ext.namespace('Ext.Module');            Ext.override(Ext.Module.Ledger,            {                init        : function ()                {                /*                **  ledger JsonStore extended                **                */                    var ledgerDataStore = new Ext.data.JsonStore(                    {                        proxy: new Ext.data.HttpProxy(                        {                            url     : '/ledger/personnelView',                            method  : 'POST'                        }),                         root        : 'response',                        totalProperty : 'totalCount',                         recordModel : Ext.data.Record.create(                        [                            {name: 'id',            type: 'int',    defaultValue: 0},                            {name: 'operation_id',  type: 'int',    defaultValue: ''},                            {name: 'value',         type: 'int',    defaultValue: ''},                            {name: 'notice',        type: 'string', defaultValue: ''}                        ]),                        fields      :                        [                            {name: 'id',            type: 'int'     },                            {name: 'time',          type: 'date',   dateFormat  : 'Y-m-d H:i:s' },                            {name: 'operation_id',  type: 'int'     },                            {name: 'operation_sign',type: 'int'},                            {name: 'value',         type: 'float'   },                            {name: 'notice',        type: 'string'  },                            {name: 'username',      type: 'string'  },                            {name: 'fullname',      type: 'string'  }                        ],                        sortInfo    : {field: 'time', direction: 'ASC'},                        remoteSort  : true,                        autoLoad    : true,                        listeners   :                        {                            loadexception :                            {                                fn      : function ()                                {                                    Ext.Msg.error(Ext.Translation.get('common', 'load_data_failure'));                                }                            }                        }                    });                    ledgerDataStore.setDefaultSort('time', 'asc');                     this.ledgerOperationDataStore = new Ext.data.JsonStore(                    {                        proxy: new Ext.data.HttpProxy(                        {                            url     : '/ledgeroperation/load',                            method  : 'POST'                        }),                        root        : 'response',                         fields      :                        [                            {name: 'id',            type: 'int'},                            {name: 'sign',          type: 'int'},                            {name: 'visible',       type: 'int'},                            {name: 'notice',        type: 'string'}                        ],                        sortInfo    : {field: 'notice', direction: 'ASC'},                        remoteSort  : false,                        autoLoad    : true,                        listeners   :                        {                            loadexception :                            {                                fn      : function ()                                {                                    Ext.Msg.error(Ext.Translation.get('common', 'load_data_failure'));                                }                            },                            load :                            {                                fn      : function ()                                {                                    ledgerDataStore.fireEvent('datachanged', ledgerDataStore);                                }                            }                        }                    });                     this.ledgerSignedOperationDataStore = new Ext.data.JsonStore(                    {                        proxy: new Ext.data.HttpProxy(                        {                            url     : '/ledgeroperation/load',                            method  : 'POST'                        }),                        root        : 'response',                         fields      :                        [                            {name: 'id',            type: 'int'},                            {name: 'sign',          type: 'int'},                            {name: 'visible',       type: 'int'},                            {name: 'notice',        type: 'string'}                        ],                        sortInfo    : {field: 'notice', direction: 'ASC'},                        remoteSort  : false,                        autoLoad    : true,                        listeners   :                        {                            loadexception :                            {                                fn      : function ()                                {                                    Ext.Msg.error(Ext.Translation.get('common', 'load_data_failure'));                                }                            },                            load :                            {                                scope   : this,                                fn      : function ()                                {                                    var sign_income = Ext.Translation.get('ledger', 'sign_income');                                    var sign_outgo = Ext.Translation.get('ledger', 'sign_outgo');                                     this.ledgerSignedOperationDataStore.each(function (record)                                    {                                        if (record.get('sign') == 1)                                            record.set('notice', sign_income + ' - ' + record.get('notice'));                                        else                                            record.set('notice', sign_outgo + ' - ' + record.get('notice'));                                    });                                     this.ledgerSignedOperationDataStore.fireEvent('datachanged', this.ledgerSignedOperationDataStore);                                }                            }                        }                    });                     this.totalsDataStore = new Ext.data.JsonStore(                    {                        proxy: new Ext.data.HttpProxy(                        {                            url     : '/ledger/currentCash',                            method  : 'POST'                        }),                        root        : 'response',                         fields      :                        [                            {name: 'id',        type: 'int'},                            {name: 'profit',    type: 'int'},                            {name: 'income',    type: 'int'},                            {name: 'outgo',     type: 'int'}                        ],                        sortInfo    : {field: 'id', direction: 'ASC'},                        remoteSort  : false,                        autoLoad    : true,                        listeners   :                        {                            loadexception :                            {                                fn      : function ()                                {                                    Ext.Msg.error(Ext.Translation.get('common', 'load_data_failure'));                                }                            }                        }                    });                     var totalsColumnModel = new Ext.grid.ColumnModel(                    {                        columns:                        [                            {                                id          : 'profit',                                dataIndex   : 'profit',                                header      : Ext.Translation.get('ledger', 'totals_profit_header_text'),                                renderer    : function (val, meta)                                {                                    if (val.toFixed)                                    {                                        if (val < 0)                                            meta.attr = 'style="color:red;"';                                        return val.toFixed(2);                                    }                                    else                                        return val;                                }                            },                            {                                dataIndex   : 'income',                                header      : Ext.Translation.get('ledger', 'totals_income_header_text'),                                renderer    : function (val)                                {                                    if (val.toFixed)                                        return val.toFixed(2);                                    else                                        return val;                                }                            },                            {                                dataIndex   : 'outgo',                                header      : Ext.Translation.get('ledger', 'totals_outgo_header_text'),                                renderer    : function (val)                                {                                    if (val.toFixed)                                        return val.toFixed(2);                                    else                                        return val;                                }                            }                        ]                    });                    totalsColumnModel.defaultSortable = false;                     var totalsGrid = new Ext.grid.GridPanel(                    {                        store           : this.totalsDataStore,                        colModel        : totalsColumnModel,                        loadMask        : true,                         enableColLock   : true,                        enableColumnHide: false,                        enableHdMenu    : false,                         viewConfig  :                        {                            forceFit    : true                        },                         autoExpandColumn: 'profit'                    });                 /*                **  Column model                **                */                    var expander = new Ext.grid.RowExpander(                    {                        tpl : new Ext.Template(                            '<pre class="expander">{notice}</pre>'                        )                    });                     var cm = new Ext.grid.ColumnModel(                    {                        columns:                        [                            expander,                            {                                dataIndex   : 'time',                                header      : Ext.Translation.get('ledger', 'time_header_text'),                                renderer    : function (val)                                {                                    if (val.format)                                        return val.format('Y-m-d H:i:s');                                    else                                        return val;                                }                            },                            {                                dataIndex   : 'username',                                header      : Ext.Translation.get('ledger', 'username_header_text'),                                renderer    : function (val, meta, rec)                                {                                    return rec.get('fullname') + ' (' + val + ')';                                }                            },                            {                                dataIndex   : 'operation_sign',                                header      : Ext.Translation.get('ledger', 'operation_sign_header_text'),                                renderer    : function (val, meta)                                {                                    if (val > 0)                                    {                                        meta.attr = 'style="color:green"';                                        return Ext.Translation.get('ledger', 'sign_income');                                    }                                    else                                    {                                        meta.attr = 'style="color:red"';                                        return Ext.Translation.get('ledger', 'sign_outgo');                                    }                                }                            },                            {                                dataIndex   : 'operation_id',                                header      : Ext.Translation.get('ledger', 'operation_header_text'),                                id          : 'operation_id',                                store       : this.ledgerOperationDataStore,                                displayField: 'notice',                                valueField  : 'id'                            },                            {                                dataIndex   : 'value',                                header      : Ext.Translation.get('ledger', 'value_header_text'),                                renderer    : function (val)                                {                                    if (val.toFixed)                                        return val.toFixed(2);                                    else                                        return val;                                }                            }                        ]                    });                    cm.defaultSortable = true;                 /*                **  Grid                **                */                    var grid = new Ext.ex.grid.SingleEditGridPanel(                    {                        scope           : this,                        baseUrl         : 'ledger',                         toolButtons     :                            + Ext.ex.grid.SingleEditGridPanel.constant.hasAddButton,                         store           : ledgerDataStore,                        colModel        : cm,                         enableColLock   : false,                         plugins         :                        [                            new Ext.ex.grid.ComboField(),                            expander                        ],                         autoExpandColumn: 'notice',                                                getEditWindowConfig : function ()                        {                            return (                            {                                title : Ext.Translation.get('ledger', 'ledger_properties_window_title')                            });                        },                         getEditFormConfig   : function (record, self)                        {                            return (                            {                                items :                                [                                    {                                        name        : 'operation_id',                                        xtype       : 'combo',                                        fieldLabel  : Ext.Translation.get('ledger', 'operation_header_text'),                                        mode        : 'local',                                        autoWidth   : true,                                        store       : self.ledgerSignedOperationDataStore,                                        editable    : false,                                        allowBlank  : false,                                        triggerAction: 'all',                                        displayField: 'notice',                                        valueField  : 'id',                                        listClass   : 'x-combo-list-small'                                    },                                    {                                        name        : 'notice',                                        xtype       : 'textarea',                                        maxLength   : 500,                                        fieldLabel  : Ext.Translation.get('ledger', 'notice_header_text'),                                        allowBlank  : false                                    },                                    {                                        name        : 'value',                                        vtype       : 'money',                                        minValue    : 0.01,                                        xtype       : 'numberfield',                                        fieldLabel  : Ext.Translation.get('ledger', 'value_header_text'),                                        allowBlank  : false                                    }                                ]                            });                        }                    });                     this.winConfig =                    {                        items   :                        {                            xtype   : 'panel',                            layout  : 'border',                            items   :                            [                                {                                    xtype   : 'panel',                                    layout  : 'fit',                                    items   : grid,                                    region  : 'center'                                },                                {                                    xtype   : 'panel',                                    layout  : 'fit',                                    items   : totalsGrid,                                    height  : 70,                                    region  : 'south'                                }                            ]                        },                        title   : Ext.Translation.get('ledger', 'title')                    };                }            });        }    })})(); [/js]

While most of the back office modules are grid based, front office modules are much more complex:

[js]...                    this.tabPanel = new Ext.TabPanel(                    {                        resizeTabs      : true,                        minTabWidth     : 115,                        tabWidth        : 135,                        autoWidth       : true,                        enableTabScroll : true,                        activeTab       : 0,                        defaults        :                        {                            autoScroll  : true,                            labelWidth  : 120                        },                        bbar            :                        [                            this.resetButton                        ],                        items           :                        [                            this.personalDataForm,                            this.internetDataForm                        ]                    });                     this.searchPanel    = new Ext.Panel(                    {                        region      : 'west',                        width       : 320,                        layout      : 'fit',                        items       : this.tabPanel                    });                     this.resultsPanel   = new Ext.Panel(                    {                        region      : 'center',                        width       : 400,                        items       : this.userGrid                    });                     this.container = new Ext.Panel(                    {                        layout  : 'border',                        frame   : true,                        defaults        :                        {                            layout  : 'fit'                        },                        items   :                        [                            this.searchPanel,                            this.resultsPanel                        ]                    });                     this.winConfig =                    {                        items   : this.container,                        title   : Ext.Translation.get('payment', 'title')                    };                }[/js]
It's just the visual layout ...

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 2:39 pm
by alex.barylski
Hm ... yes!
But it's more like a desktop application rather than a collection of web pages ...
A typical web application of mine looks like this:
http://www.extjs.com/deploy/dev/example ... sktop.html
I`m right in the process of looking for an interesting web desktop interface for my own framework...that was so slick...I started crying :P

Thanks man :)

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 4:02 pm
by Christopher
Vlad, you showed a lot of code that seems pretty Ext-centric. Is this stuff that could be done with any Javascript library? Or does Ext provide the functionality that makes what you are doing easy?

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 4:09 pm
by VladSun
Yes, most of it is ExtJS specific ...

I do love ExtJS ;) and I always try to show its power to other developers - I think you've noticed it ;)
http://www.extjs.com/deploy/dev/docs/

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 4:25 pm
by Eran
In my opinion, developing all the views in Javascript is very unportable. Often, I need to create low-FI versions of my web apps, for mobile consumption or other edge cases, in which case I'd need it to run similarly but without the Javascript. I use Javascript to create an enhanced interface for those with modern browsers, but keep the option to reuse view elements in other usecases.

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 4:28 pm
by VladSun
PCSpectra wrote:
My web-apps are fully AJAX featured. That is, except for the main page HTML View I have no more HTML code. Indeed, I don't have any server side Views - everything is sent to the user as JSON data (if you consider an JSON formatter helper a View ... I have one View ). That is, the View part is always client side - no templates, no code duplication (except for the validation, which I think can't be done in other way).
So what happens when someone visits your site on a mobile device like a blackberry, which might not have JS enabled by default? If templates rendered via client side (using XSLT, or whatever) chances are a mobile will not render anything but simple HTML.

No to mention SEF/SEO. Unless your pages are purely XML data structures which are rendered into HTML (but I recall JSON being the term) how does that affect SE's???
Sorry, missed that one.

I develop desktop web applications which are run in an Intranet environment, strictly specified browser (usually Prism), etc.
So it's easy for me - no SEO, no cross-browser issues ;) Even almost no screen resolution issues ;)

Also, I've really never cared about mobile devices - people using such devices 1) are too rare in our country and 2) are simply not in our target group. And SEO may be achieved in several ways -e.g. by using the Google XML sitemap tool. Also, I do think search engines should follow the Internet evolution, not the opposite - I mean SE should use RIA-enabled spiders ;)

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 4:33 pm
by VladSun
Back to the topic ;)
VladSun wrote:Just today, I've read a short article about MVC and AJAX - http://wiki.apidesign.org/wiki/MVC
To MVP Again
Obviously, people tend to repeat the same thing again and again, just with new technologies. Thus with the rise of Ajax, where the GUI is represented inside a browser we are again closer to MVP as the user deals with the view elements directly.
So, we have MVP here ;)
Any comments on this one?

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 4:58 pm
by matthijs
Haven't followed the details exactly, but I do think that if you throw out URLs completely by using a full ajax approach for web apps, you loose a lot. Easy to remember or predictable urls, back-button functionality, good inexibility by search engines, etc

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 5:26 pm
by Eran
I would say Vlad's case is not applicable to most of us since he develops desktop applications which run in a browser. They work over an intranet and specific browser only. Most of us develop web applications / websites that are served to the mass population and have to worry about cross-browser support, mobile support, various resolutions etc.

Re: Ajax design patterns

Posted: Fri Nov 06, 2009 5:56 pm
by alex.barylski
I wanna figure out a way so my users can switch between an AJAX interface like those supplied by that library and one which is statically displayed, such as HTML.

Have you looked at some of those screenies? Oh man...thats awesome :)