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


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

    在使用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                  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                             '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 });
  • 相关阅读:
    angularjs中设置select的选中项
    axios 下载文件
    解决Springboot集成ActivitiModel提示输入用户名密码的问题
    VMWare14 安装Mac OS系统(图解)
    hexo 搜索功能
    Nginx禁止IP直接访问网站
    不确定理论与多传感器数据融合
    Bayes理论与多传感器数据融合
    从“中英文思维回译法”看中英思维差异
    不确定理论与多传感器数据融合
  • 原文地址:https://www.cnblogs.com/mlzs/p/4224194.html
Copyright © 2020-2023  润新知