JavaXT

ExtJS 4 Tips and Tricks

I recently starting playing with ExtJS 4.x and ran into a couple issues that I had difficulty finding answers to. I have created this wiki entry to document some of the solutions I found. I am not an ExtJS expert and I don't know if some the solutions here are necessarily "right" but they work for me!

How to Perform Synchronous Requests to Get Model Data

By default, readers, proxies, etc. make asynchronous requests to the server. However, in certain situations, you might actually want to make a synchronous request and stop executing code until you get a response. Here's an example of how to perform synchronous requests to get model data.

          //Define function to perform synchronous requests to get model data
            var getData = function(url){
                var request = ((window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
                request.open("GET", url, false); //<-- false makes it a synchonous request!
                request.send(null);
                return Ext.decode(request.responseText);
            };

How to Configure a Proxy to Use an HTTP Cache

By default, the http/ajax proxy is configured to make unique requests to the server, bypassing the the browser cache. However, in certain situations, you might want to use the browser cache to speed up performance. For example, if the server is returning a Last-Modified attribute and an ETag, you should be able to use the browser cache. To do so, you need to update the Proxy "headers" and "noCache" config settings. If the data is cached, the browser will send a "If-Modified-Since" request header and the server may respond with a 304 status code.


        new Ext.data.Store({
            model: 'Case',
            proxy: {
                type: 'ajax',
                url : '/WebServices/GetCases',
                reader: {
                    type: 'json',
                    root: 'cases'
                },
              //Use cache if we can...
                headers : { 'Cache-Control': 'max-age' }, //<--See note on iOS below!
                noCache: false
            },
            pageSize: 200,
            remoteSort: true,
            sorters: [{
                property: 'patient',
                direction: 'ASC'
            }],
            groupers: [{
                property: 'patient'
            }],
            autoLoad: true
        });

Note that iOS6 web clients (iPhone and iPad) are very aggressive with thier browser caching. Most web clients will ping the server to check if the cache is out of date. If the Cache-Control=max-age, iOS6 doesn't ping the server! Luckily, I found this header to work around this issue:

headers : {'Cache-Control': 'max-age=86400, s-maxage=86400, public, must-revalidate, proxy-revalidate'}
Translation:
  • tell your browser that the content is going to be fresh for one day (86.400 seconds)
  • tell your cache the very same thing
  • tell everyone that the content is public for everybody to see, so it's safe to save it
  • tell your browser that, once the day is over, it must check (revalidate) to see if the response is still fresh; contrary to common sense, the standard requires revalidation only after the maximum age has passed, not for each request
  • tell the proxy the same thing it told your browser about revalidation

How to Disable HTTP Caching When Posting Data

By default, Ext.Ajax requests append a date to the request to circumvent browser caching (see "disableCaching" and "disableCachingParam"). Unfortunately, with "disableCaching=true", the date is only appended to "GET" requests. As a result, "POST" requests are almost always cached by iOS web clients (iPhone and iPad). Do circumvent this issue, you can override the Ext.data.Connection.setOptions() method to append a date to "POST" requests.

    (function() {
        var _setOptions = Ext.data.Connection.prototype.setOptions;
        Ext.override(Ext.data.Connection, {
            setOptions: function(options, scope) {
                var opt = _setOptions.apply(this, arguments);
                var method = opt.method;
                if (method === 'POST'){
                    var url = opt.url;
                    var me = this;
                    var disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
                    if (disableCache) {
                        url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
                    }
                    opt.url = url;
                }
                return opt;
            }
        });
    })();

How to Get Server-Side Error Messages Back to the Client

ExtJS 4 doesn't seem to return the body of an HTTP response when the server returns an error. In my case, the server actually sends some useful error messages that I wanted to propogate back to the client. To do so, I had to override the setException method in the Proxy Server class.


  /** Overrides the native ExtJS setException method. Added responseText to
   *  propogate server-side error messages back to the client.
   */
    Ext.data.proxy.Server.override({
        setException: function(operation, response){
            operation.setException({
                status: response.status,
                statusText: response.statusText,
                responseText: response.responseText //<--Added this line!
            });
        }
    });

Working With Nested Data

One of the key features of ExtJS 4.0.x is the new Data Model class. Readers, Writers, Proxies, Stores all rely on the Model class. To my shock and dismay, the Model class does not seem to support nested data so I wrote the following overrides to get/set nested data.


  //**************************************************************************
  //** Model Overrides
  //*************************************************************************/
  /** Overrides the native get() and set() methods in the Ext.data.Model class
   *  to handle nested data (i.e. Models within Models).
   */
    (function() {
        var _get = Ext.data.Model.prototype.get;
        var _set = Ext.data.Model.prototype.set;

        Ext.override(Ext.data.Model, {


          /** Returns the value for a given field. Note that nested data is 
           *  returned in an array (Ext.data.Model[]).
           */
            get: function(field) {
                
                var val = _get.apply(this, arguments); //this[this.persistenceProperty][field]; //<--org method bails here...
                if (val==null){
                    var associations = this.associations.items;
                    for (var i=0; i<associations.length; i++){
                        var association = associations[i];
                        if (association.name==field){
                            var store = this[association.storeName];
                            if (store!=null) return store.getRange(0, store.count());
                        }
                    }
                }
                return val;
            },


          /** Sets the value for a given field. Extends the native set() method
           *  by allowing users to save nested data (Ext.data.Model). Note that
           *  in order to send the nested data to the server, you must override
           *  the Json Writer getRecordData() method. References:
           *  http://www.sencha.com/forum/showthread.php?148250-Saving-Model-with-Nested-Data
           *  http://www.sencha.com/forum/showthread.php?124362-Nested-loading-nested-saving-in-new-data-package
           */
            set: function(fieldName, value) {

                var associatedStore;
                var storeName;
                var associations = this.associations.items;
                for (var i=0; i<associations.length; i++){
                    var association = associations[i];
                    storeName = association.storeName;

                    if (association.name==fieldName){
                        associatedStore = this[storeName];
                        if (associatedStore==null){
                            this[storeName] = associatedStore =
                                Ext.create('Ext.data.Store', {
                                model: association.model
                            })
                        }
                        break;
                    }
                }

                if (associatedStore!=null){
                    associatedStore.removeAll();
                    associatedStore.add(value);
                    this[storeName] = associatedStore;
                }
                else{
                  //call the original set function
                    _set.apply(this, arguments);
                }



            }
        });
    })();



  //**************************************************************************
  //** JSON Writer Override
  //*************************************************************************/
  /** Overrides the native Json Writer getRecordData method to allow nested
   *  data to be send to the server via the Model.save() command. Reference:
   *  http://www.sencha.com/forum/showthread.php?124362-Nested-loading-nested-saving-in-new-data-package&p=627595&viewfull=1#post627595
   */
    Ext.data.writer.Json.override({
        getRecordData: function(record) {
            Ext.apply(record.data, record.getAssociatedData());
            return record.data;
        }
    });


Get Model Associated With a ComboBox Value

The ExtJS ComboBox doesn't seem to provide a mechanism to get the Model associated with a selected picklist value so I implemented a getSelectedRecord() method.

    Ext.form.field.ComboBox.prototype.getSelectedRecord = function(){
        var rec = this.findRecord(
            this.valueField || this.displayField,
            this.getValue()
        );
        if (rec==false) return null; else return rec;
    };