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 === '$') {
176 * Finds all strings that matches the searched value in each grid cells.
179 onTextFieldChange: function() {
184 // reset the statusbar
185 me.statusBar.setStatus({
186 text: me.defaultStatusText,
190 me.searchValue = me.getSearchValue();
192 me.currentIndex = null;
194 if (me.searchValue !== null) {
195 me.searchRegExp = new RegExp(me.searchValue, 'g' + (me.caseSensitive ? '' : 'i'));
198 me.store.each(function(record, idx) {
199 var td = Ext.fly(me.view.getNode(idx)).down('td'),
200 cell, matches, cellHTML;
202 cell = td.down('.x-grid-cell-inner');
203 matches = cell.dom.innerHTML.match(me.tagsRe);
204 cellHTML = cell.dom.innerHTML.replace(me.tagsRe, me.tagsProtect);
206 // populate indexes array, set currentIndex, and replace wrap matched string in a span
207 cellHTML = cellHTML.replace(me.searchRegExp, function(m) {
209 if (Ext.Array.indexOf(me.indexes, idx) === -1) {
210 me.indexes.push(idx);
212 if (me.currentIndex === null) {
213 me.currentIndex = idx;
215 return '<span class="' + me.matchCls + '">' + m + '</span>';
217 // restore protected tags
218 Ext.each(matches, function(match) {
219 cellHTML = cellHTML.replace(me.tagsProtect, match);
222 cell.dom.innerHTML = cellHTML;
228 if (me.currentIndex !== null) {
229 me.getSelectionModel().select(me.currentIndex);
230 me.statusBar.setStatus({
231 text: count + ' matche(s) found.',
232 iconCls: 'x-status-valid'
238 if (me.currentIndex === null) {
239 me.getSelectionModel().deselectAll();
242 // force textfield focus
243 me.textField.focus();
247 * Selects the previous row containing a match.
250 onPreviousClick: function() {
254 if ((idx = Ext.Array.indexOf(me.indexes, me.currentIndex)) !== -1) {
255 me.currentIndex = me.indexes[idx - 1] || me.indexes[me.indexes.length - 1];
256 me.getSelectionModel().select(me.currentIndex);
261 * Selects the next row containing a match.
264 onNextClick: function() {
268 if ((idx = Ext.Array.indexOf(me.indexes, me.currentIndex)) !== -1) {
269 me.currentIndex = me.indexes[idx + 1] || me.indexes[0];
270 me.getSelectionModel().select(me.currentIndex);
275 * Switch to case sensitive mode.
278 caseSensitiveToggle: function(checkbox, checked) {
279 this.caseSensitive = checked;
280 this.onTextFieldChange();
284 * Switch to regular expression mode
287 regExpToggle: function(checkbox, checked) {
288 this.regExpMode = checked;
289 this.onTextFieldChange();