1 /** 2 * A Picker field that contains a tree panel on its popup, enabling selection of tree nodes. 3 * 动态绑定store,修复火狐点击穿透bug 4 * 水平有限,可能有新坑 5 */ 6 Ext.define('ux.form.field.TreePicker', { 7 extend: 'Ext.form.field.Picker', 8 xtype: 'uxTreepicker', 9 mixins: ['Ext.util.StoreHolder'], 10 uses: ['Ext.tree.Panel'], 11 triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger', 12 13 config: { 14 /** 15 * @cfg {Ext.data.TreeStore} store 16 * A tree store that the tree picker will be bound to 17 */ 18 store: null, 19 20 /** 21 * @cfg {String} displayField 22 * The field inside the model that will be used as the node's text. 23 * Defaults to the default value of {@link Ext.tree.Panel}'s `displayField` configuration. 24 */ 25 displayField: null, 26 27 /** 28 * @cfg {Array} columns 29 * An optional array of columns for multi-column trees 30 */ 31 columns: null, 32 33 /** 34 * @cfg {Boolean} selectOnTab 35 * Whether the Tab key should select the currently highlighted item. Defaults to `true`. 36 */ 37 selectOnTab: true, 38 39 /** 40 * @cfg {Number} maxPickerHeight 41 * The maximum height of the tree dropdown. Defaults to 300. 42 */ 43 maxPickerHeight: 300, 44 45 /** 46 * @cfg {Number} minPickerHeight 47 * The minimum height of the tree dropdown. Defaults to 100. 48 */ 49 minPickerHeight: 100 50 }, 51 rootVisible:false, 52 editable: false, 53 /** 54 * @event select 55 * Fires when a tree node is selected 56 * @param {Ext.ux.TreePicker} picker This tree picker 57 * @param {Ext.data.Model} record The selected record 58 */ 59 60 initComponent: function () { 61 var me = this, 62 store = me.store; 63 64 me.callParent(arguments); 65 me.delayhide = Ext.create('Ext.util.DelayedTask', 66 function () { 67 //console.log('鼠标离开'); 68 me.collapse(true); 69 }); 70 //如果store是string类型,寻找对应的store 71 if (Ext.isString(store)) { 72 store = me.store = Ext.data.StoreManager.lookup(store); 73 } 74 //绑定store 75 if (store) { 76 me.setStore(store); 77 } else { 78 //动态绑定store 79 me.bindStore(me.store, true); 80 } 81 }, 82 83 /** 84 * Creates and returns the tree panel to be used as this field's picker. 85 */ 86 createPicker: function () { 87 var me = this, 88 picker = new Ext.tree.Panel({ 89 baseCls: Ext.baseCSSPrefix + 'boundlist', 90 shrinkWrapDock: 2, 91 store: me.store, 92 floating: true, 93 displayField: me.displayField, 94 columns: me.columns, 95 rootVisible:me.rootVisible, 96 minHeight: me.minPickerHeight, 97 //maxHeight: me.maxPickerHeight, 98 //固定高度,防止展开树后滚动到顶部 99 height: me.maxPickerHeight, 100 manageHeight: false, 101 shadow: false, 102 cls: 'uxTreepicker', 103 listeners: { 104 scope: me, 105 itemclick: me.onItemClick, 106 itemkeydown: me.onPickerKeyDown, 107 focusenter: function () { 108 me.delayhide.cancel(); 109 //console.log('鼠标进入'); 110 } 111 } 112 }), 113 view = picker.getView(); 114 115 if (Ext.isIE9 && Ext.isStrict) { 116 // In IE9 strict mode, the tree view grows by the height of the horizontal scroll bar when the items are highlighted or unhighlighted. 117 // Also when items are collapsed or expanded the height of the view is off. Forcing a repaint fixes the problem. 118 view.on({ 119 scope: me, 120 highlightitem: me.repaintPickerView, 121 unhighlightitem: me.repaintPickerView, 122 afteritemexpand: me.repaintPickerView, 123 afteritemcollapse: me.repaintPickerView 124 }); 125 } 126 return picker; 127 }, 128 129 /** 130 * repaints the tree view 131 */ 132 repaintPickerView: function () { 133 var style = this.picker.getView().getEl().dom.style; 134 135 // can't use Element.repaint because it contains a setTimeout, which results in a flicker effect 136 style.display = style.display; 137 }, 138 139 /** 140 * Handles a click even on a tree node 141 * @private 142 * @param {Ext.tree.View} view 143 * @param {Ext.data.Model} record 144 * @param {HTMLElement} node 145 * @param {Number} rowIndex 146 * @param {Ext.event.Event} e 147 */ 148 onItemClick: function (view, record, node, rowIndex, e) { 149 this.selectItem(record); 150 }, 151 152 /** 153 * Handles a keypress event on the picker element 154 * @private 155 * @param {Ext.event.Event} e 156 * @param {HTMLElement} el 157 */ 158 onPickerKeyDown: function (treeView, record, item, index, e) { 159 var key = e.getKey(); 160 161 if (key === e.ENTER || (key === e.TAB && this.selectOnTab)) { 162 this.selectItem(record); 163 } 164 }, 165 166 /** 167 * Changes the selection to a given record and closes the picker 168 * @private 169 * @param {Ext.data.Model} record 170 */ 171 selectItem: function (record) { 172 var me = this; 173 me.setValue(record.getId()); 174 me.fireEvent('select', me, record); 175 me.collapse(true); 176 }, 177 178 /** 179 * Runs when the picker is expanded. Selects the appropriate tree node based on the value of the input element, 180 * and focuses the picker so that keyboard navigation will work. 181 * @private 182 */ 183 onExpand: function () { 184 var picker = this.picker, 185 store = picker.store, 186 value = this.value, 187 node; 188 189 if (value) { 190 node = store.getNodeById(value); 191 } 192 193 if (!node) { 194 //这里顶级节点被隐藏了不能选中它,否则会出错 195 // node = store.getRoot(); 196 } else { 197 picker.ensureVisible(node, { 198 select: true, 199 focus: true 200 }); 201 } 202 }, 203 204 /** 205 * Sets the specified value into the field 206 * @param {Mixed} value 207 * @return {Ext.ux.TreePicker} this 208 */ 209 setValue: function (value) { 210 var me = this, 211 record; 212 me.value = value; 213 //针对动态绑定的情况,这里判断store是否存在 214 if (!me.store || me.store.loading) { 215 // Called while the Store is loading. Ensure it is processed by the onLoad method. 216 return me; 217 } 218 219 // try to find a record in the store that matches the value 220 record = value ? me.store.getNodeById(value) : me.store.getRoot(); 221 if (value === undefined) { 222 record = me.store.getRoot(); 223 me.value = record.getId(); 224 } else { 225 record = me.store.getNodeById(value); 226 } 227 228 // set the raw value to the record's display field if a record was found 229 me.setRawValue(record ? record.get(me.displayField) : ''); 230 231 return me; 232 }, 233 234 getSubmitValue: function () { 235 return this.value; 236 }, 237 238 /** 239 * Returns the current data value of the field (the idProperty of the record) 240 * @return {Number} 241 */ 242 getValue: function () { 243 return this.value; 244 }, 245 246 /** 247 * 数据加载成功时 248 * @private 249 */ 250 onLoad: function () { 251 var value = this.value; 252 if (value||value==0) { 253 this.setValue(value); 254 } 255 }, 256 257 onUpdate: function (store, rec, type, modifiedFieldNames) { 258 var display = this.displayField; 259 console.log(store); 260 if (type === 'edit' && modifiedFieldNames && Ext.Array.contains(modifiedFieldNames, display) && this.value === rec.getId()) { 261 this.setRawValue(rec.get(display)); 262 } 263 }, 264 onFocusLeave: function (e) { 265 this.collapse(); 266 this.delayhide.delay(100); 267 }, 268 collapse: function (is) { 269 var me = this; 270 271 if (me.isExpanded && !me.destroyed && !me.destroying && is) { 272 var openCls = me.openCls, 273 picker = me.picker, 274 aboveSfx = '-above'; 275 276 // hide the picker and set isExpanded flag 277 picker.hide(); 278 me.isExpanded = false; 279 280 // remove the openCls 281 me.bodyEl.removeCls([openCls, openCls + aboveSfx]); 282 picker.el.removeCls(picker.baseCls + aboveSfx); 283 284 if (me.ariaRole) { 285 me.ariaEl.dom.setAttribute('aria-expanded', false); 286 } 287 288 // remove event listeners 289 me.touchListeners.destroy(); 290 me.scrollListeners.destroy(); 291 Ext.un('resize', me.alignPicker, me); 292 me.fireEvent('collapse', me); 293 me.onCollapse(); 294 } 295 }, 296 setStore: function (store) { 297 if (store) { 298 this.store = store; 299 this.onLoad(); 300 } 301 }, 302 bindStore: function (store, initial) { 303 this.mixins.storeholder.bindStore.apply(this, arguments); 304 } 305 });