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();
- }
- });
|