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

11/04/2013

[ExtJS] Partially Select Text in textfield Programmatically

We are trying to build a FTP client via ExtJS.
When users want to create a new folder on the ftp server, we'd like to pop a form which contains a path input text field. The default value of path field is set as {current_foder}/New_Folder. While New_Folder is the default new folder name, of course, it could be modified by user before submitting.
In this case, selecting the text "New_Folder" automatically makes user change the new folder name by just typing a new name without selecting  by themselves.
Partially Select Text in textfield 
Here is implementation for selecting a a portion of a text in textfield programmatically in ExtJS.
Ext.define('Ext.enhance.form.field.Text', {
   override: 'Ext.form.field.Text',
   selectText: function (start, end) {
      var field = this.inputEl.dom;
      if (field.createTextRange) {
         var selRange = field.createTextRange();
         selRange.collapse(true);
         selRange.moveStart('character', start);
         selRange.moveEnd('character', end);
         selRange.select();
      } else if (field.setSelectionRange) {
         field.setSelectionRange(start, end);
      } else if (field.selectionStart) {
         field.selectionStart = start;
         field.selectionEnd = end;
      }
      field.focus();
   }
});
Ext.define('TonyTuan.CreateFolderWin', {
   extend: 'Ext.window.Window',
   width: 300,
   layout: {
      type: 'fit'
   },
   title: 'Window',
   initComponent: function () {
      var me = this;
      // set focus before call selectText()
      me.defaultFocus = 'path';
      me.items = [{
         xtype: 'form',
         bodyPadding: 10,
         layout: 'anchor',
         defaultType: 'textfield',
         items: [{
            fieldLabel: 'path',
            name: 'path',
            itemId: 'path'
         }]
      }];
      me.callParent();
      me.on({
         // set default value for path
         show: function () {
            me.down('#path').setValue('folder1/New_Folder');
            me.down('#path').selectText(6, 18);
         }
      });
   }
});
Note that setting defaultFocus properly in Window makes users be able to enter text in field without clicking on the filed.

8/21/2013

[ExtJS] Remove "No file chosen" Tooltip for File Input Field

When I was implementing file uploading feature in js, I found the file field button always shows "No file chosen" in Chrome when the field is empty. I had tried many solutions described in stackoverflow. Unfortunately, none of them could override or remove the "No file chosen" Tooltip. There’s no chance for javasript developer to remove it directly.
No file chosen
"No file chosen" tooltip
<html>
    <head>
    </head>
    <body>
        <input type="file" multiple/>
    </body>
</html>

But when user upload files in gmail, it can show custom tooltip such as “Attach files”.
Gmail file uploading with custom tooltip.
So I checked the source of gmail in firebug, I found the “Attach files” button is defined as a div tag. However, a div tag is not able to open file picker for security issue. How can google make it? Actually, the div tag is just a wrapper. And the file field is hidden in the body. Whenever div tag is clicked, just click the file field programmably. Then you can listen onchange event for file field to get the file metadata. For ExtJS developer, you can refer to the following code in you project.
Custom Tooltip for File Uploading in ExtJS
Ext.define('TonyTuan.BrowseButton', {
    extend : 'Ext.button.Button',
    icon : 'css/images/arrow_up_16x16.png',
    text : 'Browse...',
    constructor : function(config) {
        var me = this;
        // Show custom tooltip instead of "No file chosen".
        config.tooltip = 'Attach Files';
        me.callParent([ config ]);
    },
    handler : function() {
        // To trigger the file field click event
        document.getElementById('upload-field').click();
    }
});

Ext.define('TonyTuan.FileGrid', {
    extend : 'Ext.grid.Panel',
    alias : 'widget.fileGrid',
    title : 'Files',
    store : 'File',
    initComponent : function() {
        var me = this;
        me.columns = [ {
            dataIndex : 'filename',
            header : 'Filename',
        } ];
        me.tbar = [ Ext.create('TonyTuan.BrowseButton', {
            itemId : 'browseButton'
        }) ];
        me.callParent(arguments);
    }
}); 

Ext.define('TonyTuan.FilePanel', {
    extend : 'Ext.panel.Panel',
    title : 'Files',
    layout : 'border',
    initComponent : function() {
        var me = this;
        me.items = [ {
            region : 'center',
            xtype : 'fileGrid',
            layout : 'fit',
            flex : 3,
            margins : '5 5 0 0'
        }, {
            hidden : true,
            region : 'north',
            xtype : 'component',
            html : '<input type="file" multiple id="upload-field"/>'
        } ];
        me.callParent(arguments);
        me.on({
            afterrender : function() {
                document.getElementById('upload-field').onchange = function() {
                    var files = document.getElementById('upload-field').files;
                    console.log('files', files); 
                    // Add files to your fileGrid store
                };
            }
        });
    }
});