Showing posts with label ExtJS. Show all posts
Showing posts with label ExtJS. Show all posts

10/16/2014

[ExtJS 5] Migrate from ComponentColumn to WidgetColumn

In Ext 4, we use componentcolumn to render Ext component in a grid cell.
{
  dataIndex: 'age',
  xtype: 'componentcolumn',
  renderer: function(value, m, record) {
    if (value < 30) {
      return {
        xtype: 'myWidget1';
      };
    } else {
      return {
        xtype: 'myWidget2';
      };
    }
  }
}
In Ext 5, the built-in widgetcolumn have the same ability to do so. But the widgetcolumn have better rendering performance.
{
  xtype: 'widgetcolumn',
  widget: {
    xtype: 'container',
  },
  onWidgetAttach: function(widget, record) {
    var items = [];
    if (record.get('age') < 30) {
      items.push({
        xtype: 'myWidget1'
      })
    } else {
      items.push({
        xtype: 'myWidget2'
      })
    }  
    widget.removeAll();
    widget.add(items);
  }
}
Note that onWidgetAttach() is a public but not documented method.

10/09/2014

[ExtJS] Add Custom CSS Class to Grid Cell

You can make Ext JS 5 gird cell more pretty by applying twitter bootstrap.css to grid cell. It look like:

All you have to do is override the renderer function which returns a span element with bootstrap's label css class.
{
  header: 'Status',
  dataIndex: 'status',
  width: 100,
  renderer: function(value, meta, record) {
    var cls;
    if (value === 'Success') {
      cls = 'label label-success';
    } else if (value === 'Failure') {
      cls = 'label label-important';
    } else if (value === 'Processing') {
      cls = 'label label-warning';
    }
    return Ext.String.format('<span class="{0}">{1}</span>', cls, value);
  }
}
The code above is tested with Ext JS 5.0.1.

5/25/2014

[ExtJS] Close Window if Click Anywhere Other Than The Window Itself

Some web application shows a window for presenting more detail information. It allows user to close the window not only via clicking the close button but also via clicking anywhere other than the window itself.


Click to Close Ext JS Window on Focus Out
If you'd like to have this behavior for a window in ExtJS, you can add the following code to your application.
Ext.define('TonyTuan.window.Window', {
    override: 'Ext.window.Window',
    closeOnFocusOut: false,
    initComponent: function() {
        var me = this;
        if (me.closeOnFocusOut === true) {
            me.mon(Ext.getBody(), 'click', function(el, e) {
                me.close(me.closeAction);
            }, me, {
                delegate: '.x-mask'
            });
        }
        me.callParent(arguments);
    }
});
And set closeOnFocusOut:true for the window you want.
Ext.widget('window', {
    closeOnFocusOut: true,
    title: 'Hello',
    height: 200,
    width: 400,
    layout: 'fit',
    items: {       
        html: 'hello world'
    }
}).show();

Please note that this kind of behavior is not suitable for a window with a input form, since user may click somewhere other than the window incautiously, leading to the loss of input data.

5/23/2014

[ExtJS] RowExpander doesn't show a whole long word

I have a RowExpander grid with a summary field which may contains a very long word. If there's no space between this word, RowExpander cannot show full word. For example, the summery field is '11111 222222 33333344444444444....555555555555'. But the number "5555.." doesn't present.

If a word is too long, I'd like to let the row expander break it a new line instead of hiding it.


Live Example
: RowExpander doesn't show a whole long word

To solve this issue, we can just add a attribute word-wrap:break-word; to x-grid-rowbody class like:

.x-grid-rowbody p {
 word-wrap: break-word;
}
Result:

Force a Long Line of Word (without spaces) to Line Break in ExtJS RowExpander

The solution is verified in ExtJS 4.1.1, 4.2, and 5.0.

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/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

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
                };
            }
        });
    }
});