sencha touch 带本地搜索功能的selectfield(选择插件)

时间:2024-03-11 20:45:01

带本地搜索功能的选择插件,效果图:

在使用selectfield的过程中,数据过大时,数据加载缓慢,没有模糊查询用户体验也不好,

在selectfield的基础上上稍作修改而成,使用方式同selectfield,代码:

  1 Ext.define(\'ux.field.Select\', {
  2     extend: \'Ext.field.Text\',
  3     xtype: \'uxSelectfield\',
  4     alternateClassName: \'ux.form.Select\',
  5     requires: [
  6         \'Ext.Panel\',
  7         \'Ext.picker.Picker\',
  8         \'Ext.data.Store\',
  9         \'Ext.data.StoreManager\',
 10         \'Ext.dataview.List\'
 11     ],
 12 
 13     /**
 14      * @event change
 15      * Fires when an option selection has changed
 16      * @param {Ext.field.Select} this
 17      * @param {Mixed} newValue The new value
 18      * @param {Mixed} oldValue The old value
 19      */
 20 
 21     /**
 22      * @event focus
 23      * Fires when this field receives input focus. This happens both when you tap on the field and when you focus on the field by using
 24      * \'next\' or \'tab\' on a keyboard.
 25      *
 26      * Please note that this event is not very reliable on Android. For example, if your Select field is second in your form panel,
 27      * you cannot use the Next button to get to this select field. This functionality works as expected on iOS.
 28      * @param {Ext.field.Select} this This field
 29      * @param {Ext.event.Event} e
 30      */
 31 
 32     config: {
 33         /**
 34          * @cfg
 35          * @inheritdoc
 36          */
 37         ui: \'select\',
 38 
 39         /**
 40          * @cfg {Boolean} useClearIcon
 41          * @hide
 42          */
 43 
 44         /**
 45          * @cfg {String/Number} valueField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this
 46          * Select control.
 47          * @accessor
 48          */
 49         valueField: \'value\',
 50 
 51         /**
 52          * @cfg {String/Number} displayField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this
 53          * Select control. This resolved value is the visibly rendered value of the available selection options.
 54          * @accessor
 55          */
 56         displayField: \'text\',
 57 
 58         /**
 59          * @cfg {Ext.data.Store/Object/String} store The store to provide selection options data.
 60          * Either a Store instance, configuration object or store ID.
 61          * @accessor
 62          */
 63         store: null,
 64 
 65         /**
 66          * @cfg {Array} options An array of select options.
 67          *
 68          *     [
 69          *         {text: \'First Option\',  value: \'first\'},
 70          *         {text: \'Second Option\', value: \'second\'},
 71          *         {text: \'Third Option\',  value: \'third\'}
 72          *     ]
 73          *
 74          * __Note:__ Option object member names should correspond with defined {@link #valueField valueField} and {@link #displayField displayField} values.
 75          * This config will be ignored if a {@link #store store} instance is provided.
 76          * @accessor
 77          */
 78         options: null,
 79 
 80         /**
 81          * @cfg {String} hiddenName Specify a `hiddenName` if you\'re using the {@link Ext.form.Panel#standardSubmit standardSubmit} option.
 82          * This name will be used to post the underlying value of the select to the server.
 83          * @accessor
 84          */
 85         hiddenName: null,
 86 
 87         /**
 88          * @cfg {Object} component
 89          * @accessor
 90          * @hide
 91          */
 92         component: {
 93             useMask: true
 94         },
 95 
 96         /**
 97          * @cfg {Boolean} clearIcon
 98          * @hide
 99          * @accessor
100          */
101         clearIcon: false,
102 
103         /**
104          * 请勿改动此配置
105          */
106         usePicker: false,
107 
108         /**
109          * @cfg {Boolean} autoSelect
110          * `true` to auto select the first value in the {@link #store} or {@link #options} when they are changed. Only happens when
111          * the {@link #value} is set to `null`.
112          */
113         autoSelect: true,
114 
115         /**
116          * @cfg {Object} defaultPhonePickerConfig
117          * The default configuration for the picker component when you are on a phone.
118          */
119         defaultPhonePickerConfig: null,
120 
121         /**
122          * @cfg {Object} defaultTabletPickerConfig
123          * The default configuration for the picker component when you are on a tablet.
124          */
125         defaultTabletPickerConfig: null,
126 
127         /**
128          * @cfg
129          * @inheritdoc
130          */
131         name: \'picker\',
132 
133         /**
134          * @cfg {String} pickerSlotAlign
135          * The alignment of text in the picker created by this Select
136          * @private
137          */
138         pickerSlotAlign: \'center\'
139     },
140 
141     platformConfig: [
142         {
143             theme: [\'Windows\'],
144             pickerSlotAlign: \'left\'
145         },
146         {
147             theme: [\'Tizen\'],
148             usePicker: false
149         }
150     ],
151 
152     // @private
153     initialize: function () {
154         var me = this,
155             component = me.getComponent();
156 
157         me.callParent();
158 
159         component.on({
160             scope: me,
161             masktap: \'onMaskTap\'
162         });
163 
164         component.doMaskTap = Ext.emptyFn;
165 
166         if (Ext.browser.is.AndroidStock2) {
167             component.input.dom.disabled = true;
168         }
169 
170         if (Ext.theme.is.Blackberry) {
171             this.label.on({
172                 scope: me,
173                 tap: "onFocus"
174             });
175         }
176     },
177 
178     getElementConfig: function () {
179         if (Ext.theme.is.Blackberry) {
180             var prefix = Ext.baseCSSPrefix;
181 
182             return {
183                 reference: \'element\',
184                 className: \'x-container\',
185                 children: [
186                     {
187                         reference: \'innerElement\',
188                         cls: prefix + \'component-outer\',
189                         children: [
190                             {
191                                 reference: \'label\',
192                                 cls: prefix + \'form-label\',
193                                 children: [{
194                                     reference: \'labelspan\',
195                                     tag: \'span\'
196                                 }]
197                             }
198                         ]
199                     }
200                 ]
201             };
202         } else {
203             return this.callParent(arguments);
204         }
205     },
206 
207     /**
208      * @private
209      */
210     updateDefaultPhonePickerConfig: function (newConfig) {
211         var picker = this.picker;
212         if (picker) {
213             picker.setConfig(newConfig);
214         }
215     },
216 
217     /**
218      * @private
219      */
220     updateDefaultTabletPickerConfig: function (newConfig) {
221         var listPanel = this.listPanel;
222         if (listPanel) {
223             listPanel.setConfig(newConfig);
224         }
225     },
226 
227     /**
228      * @private
229      * Checks if the value is `auto`. If it is, it only uses the picker if the current device type
230      * is a phone.
231      */
232     applyUsePicker: function (usePicker) {
233         if (usePicker == "auto") {
234             usePicker = (Ext.os.deviceType == \'Phone\');
235         }
236 
237         return Boolean(usePicker);
238     },
239 
240     syncEmptyCls: Ext.emptyFn,
241 
242     /**
243      * @private
244      */
245     applyValue: function (value) {
246         var record = value,
247             index, store;
248 
249         //we call this so that the options configruation gets intiailized, so that a store exists, and we can
250         //find the correct value
251         this.getOptions();
252 
253         store = this.getStore();
254 
255         if ((value != undefined && !value.isModel) && store) {
256             index = store.find(this.getValueField(), value, null, null, null, true);
257 
258             if (index == -1) {
259                 index = store.find(this.getDisplayField(), value, null, null, null, true);
260             }
261 
262             record = store.getAt(index);
263         }
264 
265         return record;
266     },
267 
268     updateValue: function (newValue, oldValue) {
269         this.record = newValue;
270         this.callParent([(newValue && newValue.isModel) ? newValue.get(this.getDisplayField()) : \'\']);
271     },
272 
273     getValue: function () {
274         var record = this.record;
275         return (record && record.isModel) ? record.get(this.getValueField()) : null;
276     },
277 
278     /**
279      * Returns the current selected {@link Ext.data.Model record} instance selected in this field.
280      * @return {Ext.data.Model} the record.
281      */
282     getRecord: function () {
283         return this.record;
284     },
285 
286     // @private
287     getPhonePicker: function () {
288         var config = this.getDefaultPhonePickerConfig();
289 
290         if (!this.picker) {
291             this.picker = Ext.create(\'Ext.picker.Picker\', Ext.apply({
292                 slots: [
293                     {
294                         align: this.getPickerSlotAlign(),
295                         name: this.getName(),
296                         valueField: this.getValueField(),
297                         displayField: this.getDisplayField(),
298                         value: this.getValue(),
299                         store: this.getStore()
300                     }
301                 ],
302                 listeners: {
303                     change: this.onPickerChange,
304                     scope: this
305                 }
306             }, config));
307         }
308 
309         return this.picker;
310     },
311 
312     // @private
313     getTabletPicker: function () {
314         var config = this.getDefaultTabletPickerConfig();
315 
316         if (!this.listPanel) {
317             this.listPanel = Ext.create(\'Ext.Panel\', Ext.apply({
318                 left: 0,
319                 top: 0,
320                 modal: true,
321                 cls: Ext.baseCSSPrefix + \'select-overlay\',
322                 layout: \'fit\',
323                 hideOnMaskTap: true,
324                 width: Ext.os.is.Phone ? \'14em\' : \'18em\',
325                 height: (Ext.os.is.BlackBerry && Ext.os.version.getMajor() === 10) ? \'12em\' : (Ext.os.is.Phone ? \'12.5em\' : \'22em\'),
326                 items: [{
327                     xtype: \'toolbar\',
328                     docked: \'top\',
329                     items: [
330                         //新增的搜索栏,用于支持模糊查询
331                         {
332                             xtype: \'searchfield\',
333                             placeHolder: \'请输入关键词\',
334                             width:\'100%\',
335                             clearIcon:false,
336                             listeners: {
337                                 keyup: \'onSearch\',
338                                 scope: this
339                             }
340                         }
341                     ]
342                 }, {
343                     xtype: \'list\',
344                     store: this.getStore(),
345                     itemTpl: \'<span class="x-list-label">{\' + this.getDisplayField() + \':htmlEncode}</span>\',
346                     listeners: {
347                         select: this.onListSelect,
348                         itemtap: this.onListTap,
349                         scope: this
350                     }
351                 }]
352             }, config));
353         }
354 
355         return this.listPanel;
356     },
357     //进行模糊查询
358     onSearchKeyUp: function (value) {
359         //得到数据仓库和搜索关键词
360         var store = this.getStore();
361 
362         //如果是新的关键词,则清除过滤
363         store.clearFilter(!!value);
364         //检查值是否存在
365         if (value) {
366             //the user could have entered spaces, so we must split them so we can loop through them all
367             var key = this.getDisplayField(),
368              searches = value.split(\',\'),
369                 regexps = [],
370                 //获取现实值的name
371                 i, regex;
372 
373             //loop them all
374             for (i = 0; i < searches.length; i++) {
375                 //if it is nothing, continue
376                 if (!searches[i]) continue;
377 
378                 regex = searches[i].trim();
379                 regex = regex.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
380 
381                 //if found, create a new regular expression which is case insenstive
382                 regexps.push(new RegExp(regex.trim(), \'i\'));
383             }
384 
385             //now filter the store by passing a method
386             //the passed method will be called for each record in the store
387             store.filter(function (record) {
388                 var matched = [];
389 
390                 //loop through each of the regular expressions
391                 for (i = 0; i < regexps.length; i++) {
392                     var search = regexps[i],
393                         didMatch = search.test(record.get(key));
394 
395                     //if it matched the first or last name, push it into the matches array
396                     matched.push(didMatch);
397                 }
398 
399                 return (regexps.length && matched.indexOf(true) !== -1);
400             });
401         }
402     },
403     //进行模糊查询
404     onSearch: function (field) {
405         this.onSearchKeyUp(field.getValue());
406     },
407     // @private
408     onMaskTap: function () {
409         this.onFocus();
410 
411         return false;
412     },
413 
414     /**
415      * Shows the picker for the select field, whether that is a {@link Ext.picker.Picker} or a simple
416      * {@link Ext.List list}.
417      */
418     showPicker: function () {
419         var me = this,
420             store = me.getStore(),
421             value = me.getValue();
422 
423         //check if the store is empty, if it is, return
424         if (!store || store.getCount() === 0) {
425             return;
426         }
427         if (me.getReadOnly()) {
428             return;
429         }
430         me.isFocused = true;
431 
432         if (me.getUsePicker()) {
433             var picker = me.getPhonePicker(),
434                 name = me.getName(),
435                 pickerValue = {};
436 
437             pickerValue[name] = value;
438             picker.setValue(pickerValue);
439 
440             if (!picker.getParent()) {
441                 Ext.Viewport.add(picker);
442             }
443 
444             picker.show();
445         } else {
446             //先过滤一下避免加载过慢
447             var record = this.getRecord(),
448                 text=\'请搜索\';
449             if (record) {
450                  text = record.get(this.getDisplayField());
451             }
452             this.onSearchKeyUp(text);
453 
454             var listPanel = me.getTabletPicker(),
455                 list = listPanel.down(\'list\'),
456                 index, record;
457 
458             if (!listPanel.getParent()) {
459                 Ext.Viewport.add(listPanel);
460             }
461             //为搜索栏赋值
462             listPanel.down(\'searchfield\').setValue(text);
463             listPanel.showBy(me.getComponent(), null);
464             if (value || me.getAutoSelect()) {
465                 store = list.getStore();
466                 index = store.find(me.getValueField(), value, null, null, null, true);
467                 record = store.getAt(index);
468 
469                 if (record) {
470                     list.select(record, null, true);
471                 }
472             }
473         }
474     },
475 
476     // @private
477     onListSelect: function (item, record) {
478         var me = this;
479         if (record) {
480             me.setValue(record);
481         }
482     },
483 
484     onListTap: function () {
485         this.listPanel.hide({
486             type: \'fade\',
487             out: true,
488             scope: this
489         });
490     },
491 
492     // @private
493     onPickerChange: function (picker, value) {
494         var me = this,
495             newValue = value[me.getName()],
496             store = me.getStore(),
497             index = store.find(me.getValueField(), newValue, null, null, null, true),
498             record = store.getAt(index);
499 
500         me.setValue(record);
501     },
502 
503     onChange: function (component, newValue, oldValue) {
504         var me = this,
505             store = me.getStore(),
506             index = (store) ? store.find(me.getDisplayField(), oldValue, null, null, null, true) : -1,
507             valueField = me.getValueField(),
508             record = (store) ? store.getAt(index) : null;
509 
510         oldValue = (record) ? record.get(valueField) : null;
511 
512         me.fireEvent(\'change\', me, me.getValue(), oldValue);
513     },
514 
515     /**
516      * Updates the underlying `<options>` list with new values.
517      *
518      * @param {Array} newOptions An array of options configurations to insert or append.
519      *
520      *     selectBox.setOptions([
521      *         {text: \'First Option\',  value: \'first\'},
522      *         {text: \'Second Option\', value: \'second\'},
523      *         {text: \'Third Option\',  value: \'third\'}
524      *     ]).setValue(\'third\');
525      *
526      * __Note:__ option object member names should correspond with defined {@link #valueField valueField} and
527      * {@link #displayField displayField} values.
528      *
529      * @return {Ext.field.Select} this
530      */
531     updateOptions: function (newOptions) {
532         var store = this.getStore();
533 
534         if (!store) {
535             this.setStore(true);
536             store = this._store;
537         }
538 
539         if (!newOptions) {
540             store.clearData();
541         }
542         else {
543             store.setData(newOptions);
544             this.onStoreDataChanged(store);
545         }
546         return this;
547     },
548 
549     applyStore: function (store) {
550         if (store === true) {
551             store = Ext.create(\'Ext.data.Store\', {
552                 fields: [this.getValueField(), this.getDisplayField()],
553                 autoDestroy: true
554             });
555         }
556 
557         if (store) {
558             store = Ext.data.StoreManager.lookup(store);
559 
560             store.on({
561                 scope: this,
562                 addrecords: \'onStoreDataChanged\',
563                 removerecords: \'onStoreDataChanged\',
564                 updaterecord: \'onStoreDataChanged\',
565                 refresh: \'onStoreDataChanged\'
566             });
567         }
568 
569         return store;
570     },
571 
572     updateStore: function (newStore) {
573         if (newStore) {
574             this.onStoreDataChanged(newStore);
575         }
576 
577         if (this.getUsePicker() && this.picker) {
578             this.picker.down(\'pickerslot\').setStore(newStore);
579         } else if (this.listPanel) {
580             this.listPanel.down(\'dataview\').setStore(newStore);
581         }
582     },
583 
584     /**
585      * Called when the internal {@link #store}\'s data has changed.
586      */
587     onStoreDataChanged: function (store) {
588         var initialConfig = this.getInitialConfig(),
589             value = this.getValue();
590 
591         if (value || value == 0) {
592             this.updateValue(this.applyValue(value));
593         }
594 
595         if (this.getValue() === null) {
596             if (initialConfig.hasOwnProperty(\'value\')) {
597                 this.setValue(initialConfig.value);
598             }
599 
600             if (this.getValue() === null && this.getAutoSelect()) {
601                 if (store.getCount() > 0) {
602                     this.setValue(store.getAt(0));
603                 }
604             }
605         }
606     },
607 
608     /**
609      * @private
610      */
611     doSetDisabled: function (disabled) {
612         var component = this.getComponent();
613         if (component) {
614             component.setDisabled(disabled);
615         }
616         Ext.Component.prototype.doSetDisabled.apply(this, arguments);
617     },
618 
619     /**
620      * @private
621      */
622     setDisabled: function () {
623         Ext.Component.prototype.setDisabled.apply(this, arguments);
624     },
625 
626     // @private
627     updateLabelWidth: function () {
628         if (Ext.theme.is.Blackberry) {
629             return;
630         } else {
631             this.callParent(arguments);
632         }
633     },
634 
635     // @private
636     updateLabelAlign: function () {
637         if (Ext.theme.is.Blackberry) {
638             return;
639         } else {
640             this.callParent(arguments);
641         }
642     },
643 
644     /**
645      * Resets the Select field to the value of the first record in the store.
646      * @return {Ext.field.Select} this
647      * @chainable
648      */
649     reset: function () {
650         var me = this,
651             record;
652 
653         if (me.getAutoSelect()) {
654             var store = me.getStore();
655 
656             record = (me.originalValue) ? me.originalValue : store.getAt(0);
657         } else {
658             var usePicker = me.getUsePicker(),
659                 picker = usePicker ? me.picker : me.listPanel;
660 
661             if (picker) {
662                 picker = picker.child(usePicker ? \'pickerslot\' : \'dataview\');
663 
664                 picker.deselectAll();
665             }
666 
667             record = null;
668         }
669 
670         me.setValue(record);
671 
672         return me;
673     },
674 
675     onFocus: function (e) {
676         if (this.getDisabled()) {
677             return false;
678         }
679         var component = this.getComponent();
680         this.fireEvent(\'focus\', this, e);
681 
682         if (Ext.os.is.Android4) {
683             component.input.dom.focus();
684         }
685         component.input.dom.blur();
686 
687         this.isFocused = true;
688 
689         this.showPicker();
690     },
691 
692     destroy: function () {
693         this.callParent(arguments);
694         var store = this.getStore();
695 
696         if (store && store.getAutoDestroy()) {
697             Ext.destroy(store);
698         }
699 
700         Ext.destroy(this.listPanel, this.picker);
701     }
702 });