1. #1
    Sencha - Community Support Team jsakalos's Avatar
    Join Date
    Apr 2007
    Location
    Slovakia
    Posts
    27,509
    Vote Rating
    374
    jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future

      0  

    Default Buffering Http State Provider

    Buffering Http State Provider


    Hi all,

    I've just finished coding and first tests of HttpProvider (Ext.ux.HttpProvider) that saves state data on a server not in cookies. It buffers changes for a configurable time and then saves them to a server with Ajax request. There is no client/server traffic if there are no changes.

    Give it a try, however, testing this extension is not for beginners.

    PHP Code:
    // vim: ts=4:sw=4:nu:fdc=2:nospell
    /*global Ext, console */
    /**
     * @class Ext.ux.state.HttpProvider
     * @extends Ext.state.Provider
     *
     * Buffering state provider that sends and receives state information to/from server
     *
     * @author    Ing. Jozef Sakáloš
     * @copyright (c) 2008, Ing. Jozef Sakáloš
     * @version   1.2
     * @revision  $Id: Ext.ux.state.HttpProvider.js 728 2009-06-16 16:31:16Z jozo $
     * @depends   Ext.ux.util
     *
     * @license Ext.ux.state.HttpProvider is licensed under the terms of
     * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
     * that the code/component(s) do NOT become part of another Open Source or Commercially
     * licensed development library or toolkit without explicit permission.
     * 
     * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
     * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
     *
     * @forum     24970
     * @demo      http://cellactions.extjs.eu
     *
     * @donate
     * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
     * <input type="hidden" name="cmd" value="_s-xclick">
     * <input type="hidden" name="hosted_button_id" value="3430419">
     * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
     * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
     * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
     * </form>
     */

    Ext.ns('Ext.ux.state');

    /**
     * Creates new HttpProvider
     * @constructor
     * @param {Object} config Configuration object
     */
    // {{{
    Ext.ux.state.HttpProvider = function(config) {

        
    this.addEvents(
            
    /**
             * @event readsuccess
             * Fires after state has been successfully received from server and restored
             * @param {HttpProvider} this
             */
             
    'readsuccess'
            
    /**
             * @event readfailure
             * Fires in the case of an error when attempting to read state from server
             * @param {HttpProvider} this
             */
            
    ,'readfailure'
            
    /**
             * @event savesuccess
             * Fires after the state has been successfully saved to server
             * @param {HttpProvider} this
             */
            
    ,'savesuccess'
            
    /**
             * @event savefailure
             * Fires in the case of an error when attempting to save state to the server
             * @param {HttpProvider} this
             */
            
    ,'savefailure'
        
    );

        
    // call parent 
        
    Ext.ux.state.HttpProvider.superclass.constructor.call(this);

        
    Ext.apply(thisconfig, {
            
    // defaults
             
    delay:750 // buffer changes for 750 ms
            
    ,dirty:false
            
    ,started:false
            
    ,autoStart:true
            
    ,autoRead:true
            
    ,user:'user'
            
    ,id:1
            
    ,session:'session'
            
    ,logFailure:false
            
    ,logSuccess:false
            
    ,queue:[]
            ,
    url:'.'
            
    ,readUrl:undefined
            
    ,saveUrl:undefined
            
    ,method:'post'
            
    ,saveBaseParams:{}
            ,
    readBaseParams:{}
            ,
    paramNames:{
                 
    id:'id'
                
    ,name:'name'
                
    ,value:'value'
                
    ,user:'user'
                
    ,session:'session'
                
    ,data:'data'
            
    }
        }); 
    // eo apply

        
    if(this.autoRead) {
            
    this.readState();
        }

        
    this.dt = new Ext.util.DelayedTask(this.submitStatethis);
        if(
    this.autoStart) {
            
    this.start();
        }
    }; 
    // eo constructor
    // }}}

    Ext.extend(Ext.ux.state.HttpProviderExt.state.Provider, {

        
    // localizable texts
         
    saveSuccessText:'Save Success'
        
    ,saveFailureText:'Save Failure'
        
    ,readSuccessText:'Read Success'
        
    ,readFailureText:'Read Failure'
        
    ,dataErrorText:'Data Error'

        
    // {{{
        /**
         * Initializes state from the passed state object or array.
         * This method can be called early during page load having the state Array/Object
         * retrieved from database by server.
         * @param {Array/Object} state State to initialize state manager with
         */
        
    ,initState:function(state) {
            if(
    state instanceof Array) {
                
    Ext.each(state, function(item) {
                    
    this.state[item.name] = this.decodeValue(item[this.paramNames.value]);
                }, 
    this);
            }
            else {
                
    this.state state state : {};
            }
        } 
    // eo function initState
        // }}}
        // {{{
        /**
         * Sets the passed state variable name to the passed value and queues the change
         * @param {String} name Name of the state variable
         * @param {Mixed} value Value of the state variable
         */
        
    ,set:function(namevalue) {
            if(!
    name) {
                return;
            }

            
    this.queueChange(namevalue);

        } 
    // eo function set
        // }}}
        // {{{
        /**
         * Starts submitting state changes to server
         */
        
    ,start:function() {
            
    this.dt.delay(this.delay);
            
    this.started true;
        } 
    // eo function start
        // }}}
        // {{{
        /**
         * Stops submitting state changes
         */
        
    ,stop:function() {
            
    this.dt.cancel();
            
    this.started false;
        } 
    // eo function stop
        // }}}
        // {{{
        /**
         * private, queues the state change if state has changed
         */
        
    ,queueChange:function(namevalue) {
            var 
    = {};
            var 
    i;
            var 
    found false;

            
    // see http://extjs.com/forum/showthread.php?p=344233
            
    var lastValue this.state[name];
            for(
    0this.queue.lengthi++) {
                if(
    this.queue[i].name === name) {
                    
    lastValue this.decodeValue(this.queue[i].value);
                }
            }
            var 
    changed undefined === lastValue || lastValue !== value;

            if(
    changed) {
                
    o[this.paramNames.name] = name;
                
    o[this.paramNames.value] = this.encodeValue(value);
                for(
    0this.queue.lengthi++) {
                    if(
    this.queue[i].name === o.name) {
                        
    this.queue[i] = o;
                        
    found true;
                    }
                }
                if(
    false === found) {
                    
    this.queue.push(o);
                }
                
    this.dirty true;
            }
            if(
    this.started) {
                
    this.start();
            }
            return 
    changed;
        } 
    // eo function bufferChange
        // }}}
        // {{{
        /**
         * private, submits state to server by asynchronous Ajax request
         */
        
    ,submitState:function() {
            if(!
    this.dirty) {
                
    this.dt.delay(this.delay);
                return;
            }
            
    this.dt.cancel();

            var 
    = {
                 
    url:this.saveUrl || this.url
                
    ,method:this.method
                
    ,scope:this
                
    ,success:this.onSaveSuccess
                
    ,failure:this.onSaveFailure
                
    ,queue:Ext.ux.util.clone(this.queue)
                ,
    params:{}
            };

            var 
    params Ext.apply({}, this.saveBaseParams);
            
    params[this.paramNames.id] = this.id;
            
    params[this.paramNames.user] = this.user;
            
    params[this.paramNames.session] = this.session;
            
    params[this.paramNames.data] = Ext.encode(o.queue);

            
    Ext.apply(o.paramsparams);

            
    // be optimistic
            
    this.dirty false;

            
    Ext.Ajax.request(o);
        } 
    // eo function submitState
        // }}}
        // {{{
        /**
         * Clears the state variable
         * @param {String} name Name of the variable to clear
         */
        
    ,clear:function(name) {
            
    this.set(nameundefined);
        } 
    // eo function clear
        // }}}
        // {{{
        /**
         * private, save success callback
         */
        
    ,onSaveSuccess:function(responseoptions) {
            var 
    = {};
            try {
    Ext.decode(response.responseText);}
            catch(
    e) {
                if(
    true === this.logFailure) {
                    
    this.log(this.saveFailureTexteresponse);
                }
                
    this.dirty true;
                return;
            }
            if(
    true !== o.success) {
                if(
    true === this.logFailure) {
                    
    this.log(this.saveFailureTextoresponse);
                }
                
    this.dirty true;
            }
            else {
                
    Ext.each(options.queue, function(item) {
                    if(!
    item) {
                        return;
                    }
                    var 
    name item[this.paramNames.name];
                    var 
    value this.decodeValue(item[this.paramNames.value]);

                    if(
    undefined === value || null === value) {
                        
    Ext.ux.state.HttpProvider.superclass.clear.call(thisname);
                    }
                    else {
                        
    // parent sets value and fires event
                        
    Ext.ux.state.HttpProvider.superclass.set.call(thisnamevalue);
                    }
                }, 
    this);
                if(
    false === this.dirty) {
                    
    this.queue = [];
                }
                else {
                    var 
    ijfound;
                    for(
    0options.queue.lengthi++) {
                        
    found false;
                        for(
    0this.queue.lengthj++) {
                            if(
    options.queue[i].name === this.queue[j].name) {
                                
    found true;
                                break;
                            }
                        }
                        if(
    true === found && this.encodeValue(options.queue[i].value) === this.encodeValue(this.queue[j].value)) {
                            
    this.queue.remove(this.queue[j]);
                        }
                    }
                }
                if(
    true === this.logSuccess) {
                    
    this.log(this.saveSuccessTextoresponse);
                }
                
    this.fireEvent('savesuccess'this);
            }
        } 
    // eo function onSaveSuccess
        // }}}
        // {{{
        /**
         * private, save failure callback
         */
        
    ,onSaveFailure:function(responseoptions) {
            if(
    true === this.logFailure) {
                
    this.log(this.saveFailureTextresponse);
            }
            
    this.dirty true;
            
    this.fireEvent('savefailure'this);
        } 
    // eo function onSaveFailure
        // }}}
        // {{{
        /**
         * private, read state callback
         */
        
    ,onReadFailure:function(responseoptions) {
            if(
    true === this.logFailure) {
                
    this.log(this.readFailureTextresponse);
            }
            
    this.fireEvent('readfailure'this);

        } 
    // eo function onReadFailure
        // }}}
        // {{{
        /**
         * private, read success callback
         */
        
    ,onReadSuccess:function(responseoptions) {
            var 
    = {}, data;
            try {
    Ext.decode(response.responseText);}
            catch(
    e) {
                if(
    true === this.logFailure) {
                    
    this.log(this.readFailureTexteresponse);
                }
                return;
            }
            if(
    true !== o.success) {
                if(
    true === this.logFailure) {
                    
    this.log(this.readFailureTextoresponse);
                }
            }
            else {
                
    data o[this.paramNames.data];
                if(!(
    data instanceof Array) && true === this.logFailure) {
                    
    this.log(this.dataErrorTextdataresponse);
                    return;
                }
                
    Ext.each(data, function(item) {
                    
    this.state[item[this.paramNames.name]] = this.decodeValue(item[this.paramNames.value]);
                }, 
    this);
                
    this.queue = [];
                
    this.dirty false;
                if(
    true === this.logSuccess) {
                    
    this.log(this.readSuccessTextdataresponse);
                }
                
    this.fireEvent('readsuccess'this);
            }
        } 
    // eo function onReadSuccess
        // }}}
        // {{{
        /**
         * Reads saved state from server by sending asynchronous Ajax request and processing the response
         */
        
    ,readState:function() {
            var 
    = {
                 
    url:this.readUrl || this.url
                
    ,method:this.method
                
    ,scope:this
                
    ,success:this.onReadSuccess
                
    ,failure:this.onReadFailure
                
    ,params:{}
            };

            var 
    params Ext.apply({}, this.readBaseParams);
            
    params[this.paramNames.id] = this.id;
            
    params[this.paramNames.user] = this.user;
            
    params[this.paramNames.session] = this.session;

            
    Ext.apply(o.paramsparams);
            
    Ext.Ajax.request(o);
        } 
    // eo function readState
        // }}}
        // {{{
        /**
         * private, logs errors or successes
         */
        
    ,log:function() {
            if(
    console) {
                
    console.log.apply(consolearguments);
            }
        } 
    // eo log
        // }}}

    }); // eo extend

    // eof 
    Jozef Sakalos, aka Saki

    Education, extensions and services for developers at new http://extjs.eu
    News: Grid Search Plugin, ExtJS 5 Complex Data Binding using MVVM


  2. #2
    Sencha User galdaka's Avatar
    Join Date
    Mar 2007
    Location
    Spain
    Posts
    1,166
    Vote Rating
    -1
    galdaka is an unknown quantity at this point

      0  

    Default


    Sound interesting!!

    Will be a demo in future?

    Thanks in advance,

  3. #3
    Sencha - Community Support Team jsakalos's Avatar
    Join Date
    Apr 2007
    Location
    Slovakia
    Posts
    27,509
    Vote Rating
    374
    jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future

      0  

    Default


    Well, this is not very easy to "show" or "demonstrate" as it has no UI to be shown and everything runs in background. Anyway, should there be a big demand for a demo, I'd think of some and create.
    Jozef Sakalos, aka Saki

    Education, extensions and services for developers at new http://extjs.eu
    News: Grid Search Plugin, ExtJS 5 Complex Data Binding using MVVM


  4. #4
    Ext JS Premium Member
    Join Date
    Nov 2007
    Location
    Munich
    Posts
    30
    Vote Rating
    0
    sigaref is on a distinguished road

      0  

    Default


    Thank you for this code.

    I managed to store the state from a grid in a DB, and to read the state back into this.state when loading the page.

    But how can I set the grid state now with the settings from this.state? I have set the grid's stateId and stateful to true. Do I have to specify some kind of callback or call a special grid function?

    Thanks in advance!

  5. #5
    Sencha - Community Support Team jsakalos's Avatar
    Join Date
    Apr 2007
    Location
    Slovakia
    Posts
    27,509
    Vote Rating
    374
    jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future

      0  

    Default


    I'd say that grid.initState() should be enough but if that changes column widths or another visual parts you may need to refresh view, doLayout or similar.
    Jozef Sakalos, aka Saki

    Education, extensions and services for developers at new http://extjs.eu
    News: Grid Search Plugin, ExtJS 5 Complex Data Binding using MVVM


  6. #6
    Sencha - Community Support Team jsakalos's Avatar
    Join Date
    Apr 2007
    Location
    Slovakia
    Posts
    27,509
    Vote Rating
    374
    jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future

      0  

    Default


    One more remark: Provider request state from server upon construction. If you try to set grid state before it was received from server you see no effect as state is still empty.

    I've also faced this problem so I updated HttpProvider code a bit and I send the initial state with initial page load. The rendered html of main page contains:

    HTML Code:
    <script type="text/javascript">
    Ext.state.Manager.setProvider(new Ext.ux.HttpProvider({
         url:'/request.php?#state'
        ,user:'jozo@default'
        ,session:'Perseus.Stat'
        ,id:'1'
        ,readBaseParams:{cmd:'readState'}
        ,saveBaseParams:{cmd:'saveState'}
        ,autoRead:false
    //    ,logFailure:true
    //    ,logSuccess:true
    }));
    Ext.state.Manager.getProvider().initState([{"name":"client-mod-client-grid","value":"..."},{"name":"client-mod-win","value":"..."}]);
    </script>
    Of course, to create the initial state you need to iterate through your db server-side and to construct the above argument with states array for the provider.

    Don't forget to grab the updated code from the first post.
    Jozef Sakalos, aka Saki

    Education, extensions and services for developers at new http://extjs.eu
    News: Grid Search Plugin, ExtJS 5 Complex Data Binding using MVVM


  7. #7
    Ext User
    Join Date
    Jul 2007
    Posts
    3,128
    Vote Rating
    1
    devnull is an unknown quantity at this point

      0  

    Default


    Very cool stuff, I may start experimenting with it too at some point.
    One way to get around the the initial state issue I can think of is to have use a callback function with the initial state load. The typical app would then have most of its init code in this callback instead of in onReady.

  8. #8
    Sencha - Community Support Team jsakalos's Avatar
    Join Date
    Apr 2007
    Location
    Slovakia
    Posts
    27,509
    Vote Rating
    374
    jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future jsakalos has a brilliant future

      0  

    Default


    Well, I have already solved the initial state problem. First, state system doesn't need to run in onReady block as it doesn't need any DOM. Therefore, I generate the initial state very early in the page generation process, I create code for creation and initialization of the state manager and I output it in the header in <script> tag. See one post above.

    This way the state is available even before the body is loaded and w/o any extra client server round trip.
    Jozef Sakalos, aka Saki

    Education, extensions and services for developers at new http://extjs.eu
    News: Grid Search Plugin, ExtJS 5 Complex Data Binding using MVVM


  9. #9
    Ext User swagner's Avatar
    Join Date
    Jan 2008
    Location
    Munich, Germany
    Posts
    88
    Vote Rating
    0
    swagner is on a distinguished road

      0  

    Question


    Your custom Provider looks very good, but i still don't know in detail what it does.

    I also encountered the initial state problem while writing my own Provider. But in my project it does not seem to work the way you solved it (to set the initial setting at the start of the page). I think its because my page gets rendered into another page and the head of that page is already done (maybe i should use a delay-function after requesting the data from the server). How do you generate your initial state on top of your page? (inside the {})
    Code:
    Ext.state.Manager.getProvider().initState([{"name":"client-mod-client-grid","value":"..."}
    Do you use an AjaxRequest to get this data? Probably not. You creat it using php or an other script language, right?

  10. #10
    Ext User swagner's Avatar
    Join Date
    Jan 2008
    Location
    Munich, Germany
    Posts
    88
    Vote Rating
    0
    swagner is on a distinguished road

      0  

    Thumbs up


    Yeah , when i use a php-script to fetch the gridSettings from the DB i don't need an Ajax-Request cause the DB is requested (from the server-side) while the page gets rendered. And i can call the initState-Methode of my Provider right after i ordered Ext to use my Provider. All code is executed synchronously this way, which means i do not have to wait for an Ajax-Response.