X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/examples/ux/LiveSearchGridPanel.js diff --git a/examples/ux/LiveSearchGridPanel.js b/examples/ux/LiveSearchGridPanel.js new file mode 100644 index 00000000..794534c5 --- /dev/null +++ b/examples/ux/LiveSearchGridPanel.js @@ -0,0 +1,289 @@ +/** + * @class Ext.ux.LiveSearchGridPanel + * @extends Ext.grid.Panel + *

A GridPanel class with live search support.

+ * @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; + } + } + + var length = value.length, + resultArray = [me.tagsProtect + '*'], + i = 0, + c; + + for(; i < length; i++) { + c = value.charAt(i); + resultArray.push(c); + if (c !== '\\') { + resultArray.push(me.tagsProtect + '*'); + } + } + return resultArray.join(''); + }, + + /** + * 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 '' + m + ''; + }); + // 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(); + } +}); \ No newline at end of file