3/28/2014

[ExtJS] Cancel Duplicate Store Loading Request Automatically

In the previous article, we mentioned how to abort a store request manually. It's used when programmer want to cancel a store loading request manually.


"Refresh" button in grid

Image there's a "Refresh" button in your grid, the user may press the button quickly multiple times. This behavior make store send multiple requests at a short period of time. So it's better to abort the previous requests for performance and data correctness issue.


Cancel Duplicate Store Loading Request Automatically

The following code overrides ExtJS store behavior, making all of stores in web application abort duplicated store loading request automatically. Just put it to somewhere your application initialized.

Ext JS 4.1.1a:

Ext.define('Tonytuan.data.Store', {
  override: 'Ext.data.Store',
  abort: function () {
    var me = this;
    if (me.loading && me.lastOperation) {
      var requests = Ext.Ajax.requests;
      for (id in requests) {
        if (requests.hasOwnProperty(id) && requests[id].options == me.lastOperation.request) {
          Ext.Ajax.abort(requests[id]);
          delete requests[id];
          break;
        }
      }
    }
  },
  constructor: function (config) {
    var me = this;
    me.callParent([config]);
    me.on({
      'beforeload': function (store, operation) {
        // Abort previous request if it has not been completed.
        if (me.loading) {
          me.abort();
        }
        store.lastOperation = operation;
      }
    });
  }
});

Ext JS 5.0.1:

Ext.define('Ext.enhance.data.Store', {
  override: 'Ext.data.Store',
  abort: function() {
    var me = this;
    if (me.isLoading() && me.lastOperation) {
      var requests = Ext.Ajax.requests;
      for (id in requests) {
        if (requests.hasOwnProperty(id) && requests[id].options.url == me.lastOperation.request._url) {
          Ext.Ajax.abort(requests[id]);
          delete requests[id];
          break;
        }
      }
    }
  },
  constructor: function(config) {
    var me = this;
    me.callParent([config]);
    me.on({
      'beforeload': function(store, operation) {
        // abort previous request
        if (me.isLoading()) {
          me.abort();        }
        store.lastOperation = operation;
      }
    });
  }
});

3/27/2014

[ExtJS] Capture All Events Fired by a Component

If you want to capture all events fired by a component, just call Ext.util.Observable.capture after the component is created.

Ext.util.Observable.capture(grid, function(eventName, signature) {
    console.log('Event:', eventName, signature);
});

Capture All Events Fired by a Component in ExtJS

Online Demo on Sencha Fiddle

3/05/2014

[ExtJS] RowEditing: Save Data to Server Immediately When Clicking Update Button.

The RowEditiing in ExtJS doesn’t send a request after a user click Update button. The records in grid will become dirty state after user click. But it doesn’t not mean that records in remote DB are changed. Even you call grid.store.commitChanges(), this would not send a request to server as well. The method commitChanges() only clear dirty state in store.

How to send a request whenever the grid is edited?
Let’s explain by giving an simple user management example. Here is User model with fake data. The model is binding with a REST proxy which is used for building request to server.
Ext.define('TonyTuan.User', {
    extend : 'Ext.data.Store',
    fields : [ 'id', 'name', 'role' ],
    data : [ {
        id : '1',
        name : 'Tony',
        role : 'Programmer'
    }, {
        id : '2',
        name : 'Steve',
        role : 'Programmer'
    }, {
        id : '3',
        name : 'Alice',
        role : 'Manager'
    } ],
    proxy : {
        type : 'rest',
        url : 'rest/users',
        reader : {
            type : 'json'
        }
    }
});
The Role store for combobox.
Ext.define('TonyTuan.Role', {
    extend : 'Ext.data.Store',
    fields : [ 'display', 'value' ],
    // fake data
    data : [ {
        display : 'Programmer',
        value : 'Programmer'
    }, {
        display : 'Designer',
        value : 'Designer'
    }, {
        display : 'Project Manager',
        value : 'Manager'
    } ]
});
The user grid with a rowEditing plugin listens on an edit event which is fired whenever records in grid are changed. Therefore, we can send a http PUT request by calling record.save() in the edit event handler.
Ext.define('TonyTuan.UserGrid', {
    extend : 'Ext.grid.Panel',
    width : 300,
    store : Ext.create('TonyTuan.User'),
    selType : 'rowmodel',
    plugins : [ {
        ptype : 'rowediting' // enable row editing
    } ],
    initComponent : function() {
        var me = this;

        me.columns = [ {
            header : 'id',
            dataIndex : 'id'
       
        }, {
            header : 'name',
            dataIndex : 'name'
        }, {
            header : 'Role',
            dataIndex : 'role',       
            editor : { 
                // for editable field, set a proper field xtype for it
                xtype : 'combobox',
                store : Ext.create('TonyTuan.Role'),
                queryMode : 'local',
                editable : false,
                displayField : 'display',
                valueField : 'value',
                allowBlank : false
            }
        } ];

        // Listen on the edit event to send a request to server
        me.on('edit', function(editor, e) {
            e.record.save({
                success : function(record, operation) {
                    // (show successful message here)
                    me.store.load();
                },
                failure : function(record, operation) {
                    // (show error message here)

                    // clear the dirty state and rollback the records
                    me.store.rejectChanges(); 
                }
            });
        });
        me.callParent(arguments);
    }
});
Ext.create('TonyTuan.UserGrid', {
    renderTo : Ext.getBody()
}); 
Online Demo on Sehcna Fiddle

[ExtJS] Remote Validator for Form Field (Server Side Validator)

ExtJS usually validates form field locally. For example, In register form, you may like to check if the password has more than 8 characters. This could be done easily.

{
 fieldLabel : 'Password',
 name : 'password',
 allowBlank : false,
 validFlag : true,
 validator : function(value) {
    if (value != '' && value.length < 8) {
      return 'Password should has more than 8 characters';
    }
    return true; 
 }
}

However, in some situation, you have to validate the input values in server side. For instance, the username in a website should be unique. Thus, you can let the field validator return true by default, but whenever the inputs has be changed, make the listener trigger an AJAX request to check if the value is valid and validate the field explicitly in the callback function.

{
 fieldLabel : 'Username',
 name : 'username',
 allowBlank : false,
 validFlag : true,
 validator : function() {
  return this.validFlag;
 },
 listeners : {
  'change' : function(textfield, newValue, oldValue) {
   var me = this;
   Ext.Ajax.request({
    url : 'rest/users?action=validate&username=' + newValue,
    success : function(response) {
     // Ausuming responseText is {"valid" : true}
     me.validFlag = Ext.decode(response.responseText).valid ? true : 'The username is duplicated!';
     me.validate();
    }
   });
  }
 }
}

[ExtJS] Get Server Response in Model.save() or Model.destroy() Failure Callback Function

Many people have the same question: How to get server response in model's failure function? It's easy to get response from server when operation success. But it's a little bit tricky to get response when operation fails. I saw a solution which add a listener to store's proxy to listen on exception event. But I found a easier way below.

model.save({
    success: function (record, operation) {
        // json response from server         
        console.log(operation.response);                  
    },
    failure: function (record, operation) {
        // undefined
        console.log(operation.response); 
        // json response from server
        console.log(operation.request.scope.reader.jsonData);
    }
});

[ExtJS] Abort Store Request / Connection / Loading

It's kind of intuitive to abort a Ext.Ajax.request.

var req = Ext.Ajax.request({
    url: 'page.aspx'
});
Ext.Ajax.abort(req);

However, aborting store's request isn't easy since store doesn't return request object when loading. Thus, we need to keep request object on beforeload event. Then cancel the store's request later on.

Let's just override the store's constructor to make every store keep their request object before sending request and implement a method call abort() in store.

Ext.define('Ext.enhance.data.Store', {
    override: 'Ext.data.Store',
    constructor: function(config) {
        var me = this;
        me.callParent([config]);
        me.on({
            'beforeload': function(store, operation) {
                // keep the operation which has request object
                store.lastOperation = operation;
            }
        });
    },
    abort: function() {
        var me = this;
        if (me.loading && me.lastOperation) {
            var requests = Ext.Ajax.requests;
            for (id in requests) {
                if (requests.hasOwnProperty(id) && requests[id].options == me.lastOperation.request) {
                    Ext.Ajax.abort(requests[id]);
                    delete requests[id];
                    break;
                }
            }
        }
    }

});

Then we are able to abort a store request easily.

grid.store.abort();

Reference: http://stackoverflow.com/questions/13251440/selectively-aborting-an-ajax-request-sent-via-extjs-direct-proxy