此扩展基于官方selectfield控件修改而来,变动并不大,使用方法类似。
代码如下:
1 Ext.define('ux.SelectBtn', { 2 extend: 'Ext.Button', 3 xtype: 'selectBtn', 4 alternateClassName: 'selectBtn', 5 requires: [ 6 'Ext.Panel', 7 'Ext.picker.Picker', 8 'Ext.data.Store', 9 'Ext.data.StoreManager', 10 'Ext.dataview.List' 11 ], 12 config: { 13 /** 14 * @cfg 15 * @inheritdoc 16 */ 17 ui: 'select', 18 19 20 /** 21 * @cfg {String/Number} valueField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this 22 * Select control. 23 * @accessor 24 */ 25 valueField: 'value', 26 27 /** 28 * @cfg {String/Number} displayField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this 29 * Select control. This resolved value is the visibly rendered value of the available selection options. 30 * @accessor 31 */ 32 displayField: 'text', 33 34 /** 35 * @cfg {Ext.data.Store/Object/String} store The store to provide selection options data. 36 * Either a Store instance, configuration object or store ID. 37 * @accessor 38 */ 39 store: null, 40 41 /** 42 * @cfg {Array} options An array of select options. 43 * 44 * [ 45 * {text: 'First Option', value: 'first'}, 46 * {text: 'Second Option', value: 'second'}, 47 * {text: 'Third Option', value: 'third'} 48 * ] 49 * 50 * __Note:__ Option object member names should correspond with defined {@link #valueField valueField} and {@link #displayField displayField} values. 51 * This config will be ignored if a {@link #store store} instance is provided. 52 * @accessor 53 */ 54 options: null, 55 56 /** 57 * @cfg {String} hiddenName Specify a `hiddenName` if you're using the {@link Ext.form.Panel#standardSubmit standardSubmit} option. 58 * This name will be used to post the underlying value of the select to the server. 59 * @accessor 60 */ 61 hiddenName: null, 62 63 /** 64 * @cfg {Object} component 65 * @accessor 66 * @hide 67 */ 68 component: { 69 useMask: true 70 }, 71 72 73 /** 74 * @cfg {String/Boolean} usePicker 75 * `true` if you want this component to always use a {@link Ext.picker.Picker}. 76 * `false` if you want it to use a popup overlay {@link Ext.List}. 77 * `auto` if you want to show a {@link Ext.picker.Picker} only on phones. 78 */ 79 usePicker: 'auto', 80 81 /** 82 * @cfg {Boolean} autoSelect 83 * `true` to auto select the first value in the {@link #store} or {@link #options} when they are changed. Only happens when 84 * the {@link #value} is set to `null`. 85 */ 86 autoSelect: true, 87 88 /** 89 * @cfg {Object} defaultPhonePickerConfig 90 * The default configuration for the picker component when you are on a phone. 91 */ 92 defaultPhonePickerConfig: null, 93 94 /** 95 * @cfg {Object} defaultTabletPickerConfig 96 * The default configuration for the picker component when you are on a tablet. 97 */ 98 defaultTabletPickerConfig: null, 99 100 /** 101 * @cfg 102 * @inheritdoc 103 */ 104 name: 'picker', 105 106 /** 107 * @cfg {String} pickerSlotAlign 108 * The alignment of text in the picker created by this Select 109 * @private 110 */ 111 pickerSlotAlign: 'center', 112 value:null 113 }, 114 115 platformConfig: [ 116 { 117 theme: ['Windows'], 118 pickerSlotAlign: 'left' 119 }, 120 { 121 theme: ['Tizen'], 122 usePicker: false 123 } 124 ], 125 126 // @private 127 initialize: function () { 128 var me = this; 129 me.callParent(); 130 //监听按钮点击事件 131 this.on({ 132 scope: me, 133 tap: "showPicker" 134 }); 135 }, 136 137 /** 138 * @private 139 */ 140 updateDefaultPhonePickerConfig: function (newConfig) { 141 var picker = this.picker; 142 if (picker) { 143 picker.setConfig(newConfig); 144 } 145 }, 146 147 /** 148 * @private 149 */ 150 updateDefaultTabletPickerConfig: function (newConfig) { 151 var listPanel = this.listPanel; 152 if (listPanel) { 153 listPanel.setConfig(newConfig); 154 } 155 }, 156 157 /** 158 * @private 159 * Checks if the value is `auto`. If it is, it only uses the picker if the current device type 160 * is a phone. 161 */ 162 applyUsePicker: function (usePicker) { 163 if (usePicker == "auto") { 164 usePicker = (Ext.os.deviceType == 'Phone'); 165 } 166 167 return Boolean(usePicker); 168 }, 169 170 /** 171 * @private 172 */ 173 applyValue: function (value) { 174 var record = value, 175 index, store; 176 177 //we call this so that the options configruation gets intiailized, so that a store exists, and we can 178 //find the correct value 179 this.getOptions(); 180 181 store = this.getStore(); 182 183 if ((value != undefined && !value.isModel) && store) { 184 index = store.find(this.getValueField(), value, null, null, null, true); 185 186 if (index == -1) { 187 index = store.find(this.getDisplayField(), value, null, null, null, true); 188 } 189 190 record = store.getAt(index); 191 } 192 193 return record; 194 }, 195 196 updateValue: function (newValue, oldValue) { 197 this.record = newValue; 198 }, 199 200 getValue: function () { 201 var record = this.record; 202 return (record && record.isModel) ? record.get(this.getValueField()) : null; 203 }, 204 205 /** 206 * Returns the current selected {@link Ext.data.Model record} instance selected in this field. 207 * @return {Ext.data.Model} the record. 208 */ 209 getRecord: function () { 210 return this.record; 211 }, 212 213 // @private 214 getPhonePicker: function () { 215 var config = this.getDefaultPhonePickerConfig(); 216 217 if (!this.picker) { 218 this.picker = Ext.create('Ext.picker.Picker', Ext.apply({ 219 slots: [ 220 { 221 align: this.getPickerSlotAlign(), 222 name: this.getName(), 223 valueField: this.getValueField(), 224 displayField: this.getDisplayField(), 225 value: this.getValue(), 226 store: this.getStore() 227 } 228 ], 229 listeners: { 230 change: this.onPickerChange, 231 scope: this 232 } 233 }, config)); 234 } 235 236 return this.picker; 237 }, 238 239 // @private 240 getTabletPicker: function () { 241 var config = this.getDefaultTabletPickerConfig(); 242 243 if (!this.listPanel) { 244 this.listPanel = Ext.create('Ext.Panel', Ext.apply({ 245 left: 0, 246 top: 0, 247 modal: true, 248 cls: Ext.baseCSSPrefix + 'select-overlay', 249 layout: 'fit', 250 hideOnMaskTap: true, 251 Ext.os.is.Phone ? '14em' : '18em', 252 height: (Ext.os.is.BlackBerry && Ext.os.version.getMajor() === 10) ? '12em' : (Ext.os.is.Phone ? '12.5em' : '22em'), 253 items: { 254 xtype: 'list', 255 store: this.getStore(), 256 itemTpl: '<span class="x-list-label">{' + this.getDisplayField() + ':htmlEncode}</span>', 257 listeners: { 258 select: this.onListSelect, 259 itemtap: this.onListTap, 260 scope: this 261 } 262 } 263 }, config)); 264 } 265 266 return this.listPanel; 267 }, 268 269 /** 270 * 显示选择器, whether that is a {@link Ext.picker.Picker} or a simple 271 * {@link Ext.List list}. 272 */ 273 showPicker: function () { 274 var me = this, 275 store = me.getStore(), 276 value = me.getValue(); 277 //check if the store is empty, if it is, return 278 if (!store || store.getCount() === 0) { 279 return; 280 } 281 282 if (me.getUsePicker()) { 283 var picker = me.getPhonePicker(), 284 name = me.getName(), 285 pickerValue = {}; 286 287 pickerValue[name] = value; 288 picker.setValue(pickerValue); 289 290 if (!picker.getParent()) { 291 Ext.Viewport.add(picker); 292 } 293 294 picker.show(); 295 } else { 296 var listPanel = me.getTabletPicker(), 297 list = listPanel.down('list'), 298 index, record; 299 300 if (!listPanel.getParent()) { 301 Ext.Viewport.add(listPanel); 302 } 303 //基于按钮显示 304 listPanel.showBy(me); 305 306 if (value || me.getAutoSelect()) { 307 store = list.getStore(); 308 index = store.find(me.getValueField(), value, null, null, null, true); 309 record = store.getAt(index); 310 311 if (record) { 312 list.select(record, null, true); 313 } 314 } 315 } 316 }, 317 318 // @private 319 onListSelect: function (item, record) { 320 var me = this; 321 if (record) { 322 me.setValue(record); 323 //选择成功触发事件 324 this.fireEvent('selecSuccess', this, this.getValue(), record); 325 } 326 }, 327 328 onListTap: function () { 329 this.listPanel.hide({ 330 type: 'fade', 331 out: true, 332 scope: this 333 }); 334 }, 335 336 // @private 337 onPickerChange: function (picker, value) { 338 var me = this, 339 newValue = value[me.getName()], 340 store = me.getStore(), 341 index = store.find(me.getValueField(), newValue, null, null, null, true), 342 record = store.getAt(index); 343 344 me.setValue(record); 345 //选择成功触发事件 346 this.fireEvent('selecSuccess', this, this.getValue(), record); 347 }, 348 349 onChange: function (component, newValue, oldValue) { 350 var me = this, 351 store = me.getStore(), 352 index = (store) ? store.find(me.getDisplayField(), oldValue, null, null, null, true) : -1, 353 valueField = me.getValueField(), 354 record = (store) ? store.getAt(index) : null; 355 356 oldValue = (record) ? record.get(valueField) : null; 357 358 me.fireEvent('change', me, me.getValue(), oldValue); 359 }, 360 361 /** 362 * Updates the underlying `<options>` list with new values. 363 * 364 * @param {Array} newOptions An array of options configurations to insert or append. 365 * 366 * selectBox.setOptions([ 367 * {text: 'First Option', value: 'first'}, 368 * {text: 'Second Option', value: 'second'}, 369 * {text: 'Third Option', value: 'third'} 370 * ]).setValue('third'); 371 * 372 * __Note:__ option object member names should correspond with defined {@link #valueField valueField} and 373 * {@link #displayField displayField} values. 374 * 375 * @return {Ext.field.Select} this 376 */ 377 updateOptions: function (newOptions) { 378 var store = this.getStore(); 379 380 if (!store) { 381 this.setStore(true); 382 store = this._store; 383 } 384 385 if (!newOptions) { 386 store.clearData(); 387 } 388 else { 389 store.setData(newOptions); 390 this.onStoreDataChanged(store); 391 } 392 return this; 393 }, 394 395 applyStore: function (store) { 396 if (store === true) { 397 store = Ext.create('Ext.data.Store', { 398 fields: [this.getValueField(), this.getDisplayField()], 399 autoDestroy: true 400 }); 401 } 402 403 if (store) { 404 store = Ext.data.StoreManager.lookup(store); 405 406 store.on({ 407 scope: this, 408 addrecords: 'onStoreDataChanged', 409 removerecords: 'onStoreDataChanged', 410 updaterecord: 'onStoreDataChanged', 411 refresh: 'onStoreDataChanged' 412 }); 413 } 414 415 return store; 416 }, 417 418 updateStore: function (newStore) { 419 if (newStore) { 420 this.onStoreDataChanged(newStore); 421 } 422 423 if (this.getUsePicker() && this.picker) { 424 this.picker.down('pickerslot').setStore(newStore); 425 } else if (this.listPanel) { 426 this.listPanel.down('dataview').setStore(newStore); 427 } 428 }, 429 430 /** 431 * Called when the internal {@link #store}'s data has changed. 432 */ 433 onStoreDataChanged: function (store) { 434 var initialConfig = this.getInitialConfig(), 435 value = this.getValue(); 436 437 if (value || value == 0) { 438 this.updateValue(this.applyValue(value)); 439 } 440 441 if (this.getValue() === null) { 442 if (initialConfig.hasOwnProperty('value')) { 443 this.setValue(initialConfig.value); 444 } 445 446 if (this.getValue() === null && this.getAutoSelect()) { 447 if (store.getCount() > 0) { 448 this.setRecord(store.getAt(0)); 449 } 450 } 451 } 452 }, 453 454 /** 455 * Resets the Select field to the value of the first record in the store. 456 * @return {Ext.field.Select} this 457 * @chainable 458 */ 459 reset: function () { 460 var store = this.getStore(), 461 record = (this.originalValue) ? this.originalValue : store.getAt(0); 462 463 if (store && record) { 464 this.setValue(record); 465 } 466 467 return this; 468 }, 469 470 destroy: function () { 471 this.callParent(arguments); 472 var store = this.getStore(); 473 474 if (store && store.getAutoDestroy()) { 475 Ext.destroy(store); 476 } 477 478 Ext.destroy(this.listPanel, this.picker); 479 } 480 });
使用示例
引用:
requires: ['ux.SelectBtn'],
使用(可以参考官方selectfield控件用法):
1 { 2 xtype: 'selectBtn', 3 text: '分享', 4 align: 'right', 5 ui: 'decline', 6 valueField: 'name', 7 displayField: 'name', 8 action:'share', 9 store: 'shareList' 10 }
控制层监听
引用:
1 refs: { 2 shareBtn: 'button[action=share]' 3 }
监听(选择成功):
1 control: { 2 shareBtn: { 3 selecSuccess: function (t, value, record) { 4 console.log(value,record); 5 } 6 } 7 }
效果图(点击按钮后):