3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.ux.LiveSearchGridPanel
17 * @extends Ext.grid.Panel
18 * <p>A GridPanel class with live search support.</p>
19 * @author Nicolas Ferrero
21 Ext.define('Ext.ux.LiveSearchGridPanel', {
22 extend: 'Ext.grid.Panel',
24 'Ext.toolbar.TextItem',
25 'Ext.form.field.Checkbox',
26 'Ext.form.field.Text',
27 'Ext.ux.statusbar.StatusBar'
32 * search value initialization
38 * The row indexes where matching strings are found. (used by previous and next buttons)
44 * The row index of the first search, it could change if next or previous buttons are used.
50 * The generated regular expression used for searching.
56 * Case sensitive mode.
62 * Regular expression mode.
67 * @cfg {String} matchCls
68 * The matched string css classe.
70 matchCls: 'x-livesearch-match',
72 defaultStatusText: 'Nothing Found',
74 // Component initialization override: adds the top and bottom toolbars and setup headers renderer.
75 initComponent: function() {
84 fn: me.onTextFieldChange,
92 tooltip: 'Find Previous Row',
93 handler: me.onPreviousClick,
98 tooltip: 'Find Next Row',
99 handler: me.onNextClick,
105 handler: me.regExpToggle,
107 }, 'Regular expression', {
111 handler: me.caseSensitiveToggle,
113 }, 'Case sensitive'];
115 me.bbar = Ext.create('Ext.ux.StatusBar', {
116 defaultText: me.defaultStatusText,
117 name: 'searchStatusBar'
120 me.callParent(arguments);
123 // afterRender override: it adds textfield and statusbar reference and start monitoring keydown events in textfield input
124 afterRender: function() {
126 me.callParent(arguments);
127 me.textField = me.down('textfield[name=searchField]');
128 me.statusBar = me.down('statusbar[name=searchStatusBar]');
136 // detects regexp reserved word
137 regExpProtect: /\\|\/|\+|\\|\.|\[|\]|\{|\}|\?|\$|\*|\^|\|/gm,
140 * In normal mode it returns the value with protected regexp characters.
141 * In regular expression mode it returns the raw value except if the regexp is invalid.
142 * @return {String} The value to process or null if the textfield value is blank or invalid.
145 getSearchValue: function() {
147 value = me.textField.getValue();
152 if (!me.regExpMode) {
153 value = value.replace(me.regExpProtect, function(m) {
160 me.statusBar.setStatus({
162 iconCls: 'x-status-error'
167 if (value === '^' || value === '$') {
172 var length = value.length,
173 resultArray = [me.tagsProtect + '*'],
177 for(; i < length; i++) {
181 resultArray.push(me.tagsProtect + '*');
184 return resultArray.join('');
188 * Finds all strings that matches the searched value in each grid cells.
191 onTextFieldChange: function() {
196 // reset the statusbar
197 me.statusBar.setStatus({
198 text: me.defaultStatusText,
202 me.searchValue = me.getSearchValue();
204 me.currentIndex = null;
206 if (me.searchValue !== null) {
207 me.searchRegExp = new RegExp(me.searchValue, 'g' + (me.caseSensitive ? '' : 'i'));
210 me.store.each(function(record, idx) {
211 var td = Ext.fly(me.view.getNode(idx)).down('td'),
212 cell, matches, cellHTML;
214 cell = td.down('.x-grid-cell-inner');
215 matches = cell.dom.innerHTML.match(me.tagsRe);
216 cellHTML = cell.dom.innerHTML.replace(me.tagsRe, me.tagsProtect);
218 // populate indexes array, set currentIndex, and replace wrap matched string in a span
219 cellHTML = cellHTML.replace(me.searchRegExp, function(m) {
221 if (Ext.Array.indexOf(me.indexes, idx) === -1) {
222 me.indexes.push(idx);
224 if (me.currentIndex === null) {
225 me.currentIndex = idx;
227 return '<span class="' + me.matchCls + '">' + m + '</span>';
229 // restore protected tags
230 Ext.each(matches, function(match) {
231 cellHTML = cellHTML.replace(me.tagsProtect, match);
234 cell.dom.innerHTML = cellHTML;
240 if (me.currentIndex !== null) {
241 me.getSelectionModel().select(me.currentIndex);
242 me.statusBar.setStatus({
243 text: count + ' matche(s) found.',
244 iconCls: 'x-status-valid'
250 if (me.currentIndex === null) {
251 me.getSelectionModel().deselectAll();
254 // force textfield focus
255 me.textField.focus();
259 * Selects the previous row containing a match.
262 onPreviousClick: function() {
266 if ((idx = Ext.Array.indexOf(me.indexes, me.currentIndex)) !== -1) {
267 me.currentIndex = me.indexes[idx - 1] || me.indexes[me.indexes.length - 1];
268 me.getSelectionModel().select(me.currentIndex);
273 * Selects the next row containing a match.
276 onNextClick: function() {
280 if ((idx = Ext.Array.indexOf(me.indexes, me.currentIndex)) !== -1) {
281 me.currentIndex = me.indexes[idx + 1] || me.indexes[0];
282 me.getSelectionModel().select(me.currentIndex);
287 * Switch to case sensitive mode.
290 caseSensitiveToggle: function(checkbox, checked) {
291 this.caseSensitive = checked;
292 this.onTextFieldChange();
296 * Switch to regular expression mode
299 regExpToggle: function(checkbox, checked) {
300 this.regExpMode = checked;
301 this.onTextFieldChange();