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