2 * @class Ext.ux.LiveSearchGridPanel
3 * @extends Ext.grid.Panel
4 * <p>A GridPanel class with live search support.</p>
5 * @author Nicolas Ferrero
7 Ext.define('Ext.ux.LiveSearchGridPanel', {
8 extend: 'Ext.grid.Panel',
10 'Ext.toolbar.TextItem',
11 'Ext.form.field.Checkbox',
12 'Ext.form.field.Text',
13 'Ext.ux.statusbar.StatusBar'
18 * search value initialization
24 * The row indexes where matching strings are found. (used by previous and next buttons)
30 * The row index of the first search, it could change if next or previous buttons are used.
36 * The generated regular expression used for searching.
42 * Case sensitive mode.
48 * Regular expression mode.
53 * @cfg {String} matchCls
54 * The matched string css classe.
56 matchCls: 'x-livesearch-match',
58 defaultStatusText: 'Nothing Found',
60 // Component initialization override: adds the top and bottom toolbars and setup headers renderer.
61 initComponent: function() {
70 fn: me.onTextFieldChange,
78 tooltip: 'Find Previous Row',
79 handler: me.onPreviousClick,
84 tooltip: 'Find Next Row',
85 handler: me.onNextClick,
91 handler: me.regExpToggle,
93 }, 'Regular expression', {
97 handler: me.caseSensitiveToggle,
101 me.bbar = Ext.create('Ext.ux.StatusBar', {
102 defaultText: me.defaultStatusText,
103 name: 'searchStatusBar'
106 me.callParent(arguments);
109 // afterRender override: it adds textfield and statusbar reference and start monitoring keydown events in textfield input
110 afterRender: function() {
112 me.callParent(arguments);
113 me.textField = me.down('textfield[name=searchField]');
114 me.statusBar = me.down('statusbar[name=searchStatusBar]');
122 // detects regexp reserved word
123 regExpProtect: /\\|\/|\+|\\|\.|\[|\]|\{|\}|\?|\$|\*|\^|\|/gm,
126 * In normal mode it returns the value with protected regexp characters.
127 * In regular expression mode it returns the raw value except if the regexp is invalid.
128 * @return {String} The value to process or null if the textfield value is blank or invalid.
131 getSearchValue: function() {
133 value = me.textField.getValue();
138 if (!me.regExpMode) {
139 value = value.replace(me.regExpProtect, function(m) {
146 me.statusBar.setStatus({
148 iconCls: 'x-status-error'
153 if (value === '^' || value === '$') {
158 var length = value.length,
159 resultArray = [me.tagsProtect + '*'],
163 for(; i < length; i++) {
167 resultArray.push(me.tagsProtect + '*');
170 return resultArray.join('');
174 * Finds all strings that matches the searched value in each grid cells.
177 onTextFieldChange: function() {
182 // reset the statusbar
183 me.statusBar.setStatus({
184 text: me.defaultStatusText,
188 me.searchValue = me.getSearchValue();
190 me.currentIndex = null;
192 if (me.searchValue !== null) {
193 me.searchRegExp = new RegExp(me.searchValue, 'g' + (me.caseSensitive ? '' : 'i'));
196 me.store.each(function(record, idx) {
197 var td = Ext.fly(me.view.getNode(idx)).down('td'),
198 cell, matches, cellHTML;
200 cell = td.down('.x-grid-cell-inner');
201 matches = cell.dom.innerHTML.match(me.tagsRe);
202 cellHTML = cell.dom.innerHTML.replace(me.tagsRe, me.tagsProtect);
204 // populate indexes array, set currentIndex, and replace wrap matched string in a span
205 cellHTML = cellHTML.replace(me.searchRegExp, function(m) {
207 if (Ext.Array.indexOf(me.indexes, idx) === -1) {
208 me.indexes.push(idx);
210 if (me.currentIndex === null) {
211 me.currentIndex = idx;
213 return '<span class="' + me.matchCls + '">' + m + '</span>';
215 // restore protected tags
216 Ext.each(matches, function(match) {
217 cellHTML = cellHTML.replace(me.tagsProtect, match);
220 cell.dom.innerHTML = cellHTML;
226 if (me.currentIndex !== null) {
227 me.getSelectionModel().select(me.currentIndex);
228 me.statusBar.setStatus({
229 text: count + ' matche(s) found.',
230 iconCls: 'x-status-valid'
236 if (me.currentIndex === null) {
237 me.getSelectionModel().deselectAll();
240 // force textfield focus
241 me.textField.focus();
245 * Selects the previous row containing a match.
248 onPreviousClick: function() {
252 if ((idx = Ext.Array.indexOf(me.indexes, me.currentIndex)) !== -1) {
253 me.currentIndex = me.indexes[idx - 1] || me.indexes[me.indexes.length - 1];
254 me.getSelectionModel().select(me.currentIndex);
259 * Selects the next row containing a match.
262 onNextClick: function() {
266 if ((idx = Ext.Array.indexOf(me.indexes, me.currentIndex)) !== -1) {
267 me.currentIndex = me.indexes[idx + 1] || me.indexes[0];
268 me.getSelectionModel().select(me.currentIndex);
273 * Switch to case sensitive mode.
276 caseSensitiveToggle: function(checkbox, checked) {
277 this.caseSensitive = checked;
278 this.onTextFieldChange();
282 * Switch to regular expression mode
285 regExpToggle: function(checkbox, checked) {
286 this.regExpMode = checked;
287 this.onTextFieldChange();