| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 | /** * @class Ext.ux.LiveSearchGridPanel * @extends Ext.grid.Panel * <p>A GridPanel class with live search support.</p> * @author Nicolas Ferrero */Ext.define('Ext.ux.LiveSearchGridPanel', {    extend: 'Ext.grid.Panel',    requires: [        'Ext.toolbar.TextItem',        'Ext.form.field.Checkbox',        'Ext.form.field.Text',        'Ext.ux.statusbar.StatusBar'    ],        /**     * @private     * search value initialization     */    searchValue: null,        /**     * @private     * The row indexes where matching strings are found. (used by previous and next buttons)     */    indexes: [],        /**     * @private     * The row index of the first search, it could change if next or previous buttons are used.     */    currentIndex: null,        /**     * @private     * The generated regular expression used for searching.     */    searchRegExp: null,        /**     * @private     * Case sensitive mode.     */    caseSensitive: false,        /**     * @private     * Regular expression mode.     */    regExpMode: false,        /**     * @cfg {String} matchCls     * The matched string css classe.     */    matchCls: 'x-livesearch-match',        defaultStatusText: 'Nothing Found',        // Component initialization override: adds the top and bottom toolbars and setup headers renderer.    initComponent: function() {        var me = this;        me.tbar = ['Search',{                 xtype: 'textfield',                 name: 'searchField',                 hideLabel: true,                 width: 200,                 listeners: {                     change: {                         fn: me.onTextFieldChange,                         scope: this,                         buffer: 100                     }                 }            }, {                xtype: 'button',                text: '<',                tooltip: 'Find Previous Row',                handler: me.onPreviousClick,                scope: me            },{                xtype: 'button',                text: '>',                tooltip: 'Find Next Row',                handler: me.onNextClick,                scope: me            }, '-', {                xtype: 'checkbox',                hideLabel: true,                margin: '0 0 0 4px',                handler: me.regExpToggle,                scope: me                            }, 'Regular expression', {                xtype: 'checkbox',                hideLabel: true,                margin: '0 0 0 4px',                handler: me.caseSensitiveToggle,                scope: me            }, 'Case sensitive'];        me.bbar = Ext.create('Ext.ux.StatusBar', {            defaultText: me.defaultStatusText,            name: 'searchStatusBar'        });                me.callParent(arguments);    },        // afterRender override: it adds textfield and statusbar reference and start monitoring keydown events in textfield input     afterRender: function() {        var me = this;        me.callParent(arguments);        me.textField = me.down('textfield[name=searchField]');        me.statusBar = me.down('statusbar[name=searchStatusBar]');    },    // detects html tag    tagsRe: /<[^>]*>/gm,        // DEL ASCII code    tagsProtect: '\x0f',        // detects regexp reserved word    regExpProtect: /\\|\/|\+|\\|\.|\[|\]|\{|\}|\?|\$|\*|\^|\|/gm,        /**     * In normal mode it returns the value with protected regexp characters.     * In regular expression mode it returns the raw value except if the regexp is invalid.     * @return {String} The value to process or null if the textfield value is blank or invalid.     * @private     */    getSearchValue: function() {        var me = this,            value = me.textField.getValue();                    if (value === '') {            return null;        }        if (!me.regExpMode) {            value = value.replace(me.regExpProtect, function(m) {                return '\\' + m;            });        } else {            try {                new RegExp(value);            } catch (error) {                me.statusBar.setStatus({                    text: error.message,                    iconCls: 'x-status-error'                });                return null;            }            // this is stupid            if (value === '^' || value === '$') {                return null;            }        }        return value;    },        /**     * Finds all strings that matches the searched value in each grid cells.     * @private     */     onTextFieldChange: function() {         var me = this,             count = 0;         me.view.refresh();         // reset the statusbar         me.statusBar.setStatus({             text: me.defaultStatusText,             iconCls: ''         });         me.searchValue = me.getSearchValue();         me.indexes = [];         me.currentIndex = null;         if (me.searchValue !== null) {             me.searchRegExp = new RegExp(me.searchValue, 'g' + (me.caseSensitive ? '' : 'i'));                                       me.store.each(function(record, idx) {                 var td = Ext.fly(me.view.getNode(idx)).down('td'),                     cell, matches, cellHTML;                 while(td) {                     cell = td.down('.x-grid-cell-inner');                     matches = cell.dom.innerHTML.match(me.tagsRe);                     cellHTML = cell.dom.innerHTML.replace(me.tagsRe, me.tagsProtect);                                          // populate indexes array, set currentIndex, and replace wrap matched string in a span                     cellHTML = cellHTML.replace(me.searchRegExp, function(m) {                        count += 1;                        if (Ext.Array.indexOf(me.indexes, idx) === -1) {                            me.indexes.push(idx);                        }                        if (me.currentIndex === null) {                            me.currentIndex = idx;                        }                        return '<span class="' + me.matchCls + '">' + m + '</span>';                     });                     // restore protected tags                     Ext.each(matches, function(match) {                        cellHTML = cellHTML.replace(me.tagsProtect, match);                      });                     // update cell html                     cell.dom.innerHTML = cellHTML;                     td = td.next();                 }             }, me);             // results found             if (me.currentIndex !== null) {                 me.getSelectionModel().select(me.currentIndex);                 me.statusBar.setStatus({                     text: count + ' matche(s) found.',                     iconCls: 'x-status-valid'                 });             }         }         // no results found         if (me.currentIndex === null) {             me.getSelectionModel().deselectAll();         }         // force textfield focus         me.textField.focus();     },        /**     * Selects the previous row containing a match.     * @private     */       onPreviousClick: function() {        var me = this,            idx;                    if ((idx = Ext.Array.indexOf(me.indexes, me.currentIndex)) !== -1) {            me.currentIndex = me.indexes[idx - 1] || me.indexes[me.indexes.length - 1];            me.getSelectionModel().select(me.currentIndex);         }    },        /**     * Selects the next row containing a match.     * @private     */        onNextClick: function() {         var me = this,             idx;                      if ((idx = Ext.Array.indexOf(me.indexes, me.currentIndex)) !== -1) {            me.currentIndex = me.indexes[idx + 1] || me.indexes[0];            me.getSelectionModel().select(me.currentIndex);         }    },        /**     * Switch to case sensitive mode.     * @private     */        caseSensitiveToggle: function(checkbox, checked) {        this.caseSensitive = checked;        this.onTextFieldChange();    },        /**     * Switch to regular expression mode     * @private     */    regExpToggle: function(checkbox, checked) {        this.regExpMode = checked;        this.onTextFieldChange();    }});
 |