]> git.proxmox.com Git - extjs.git/blame - extjs/modern/modern/src/dataview/NestedList.js
add extjs 6.0.1 sources
[extjs.git] / extjs / modern / modern / src / dataview / NestedList.js
CommitLineData
6527f429
DM
1/**\r
2 * NestedList provides a miller column interface to navigate between nested sets\r
3 * and provide a clean interface with limited screen real-estate.\r
4 *\r
5 * @example miniphone preview\r
6 * var data = {\r
7 * text: 'Groceries',\r
8 * items: [{\r
9 * text: 'Drinks',\r
10 * items: [{\r
11 * text: 'Water',\r
12 * items: [{\r
13 * text: 'Sparkling',\r
14 * leaf: true\r
15 * }, {\r
16 * text: 'Still',\r
17 * leaf: true\r
18 * }]\r
19 * }, {\r
20 * text: 'Coffee',\r
21 * leaf: true\r
22 * }, {\r
23 * text: 'Espresso',\r
24 * leaf: true\r
25 * }, {\r
26 * text: 'Redbull',\r
27 * leaf: true\r
28 * }, {\r
29 * text: 'Coke',\r
30 * leaf: true\r
31 * }, {\r
32 * text: 'Diet Coke',\r
33 * leaf: true\r
34 * }]\r
35 * }, {\r
36 * text: 'Fruit',\r
37 * items: [{\r
38 * text: 'Bananas',\r
39 * leaf: true\r
40 * }, {\r
41 * text: 'Lemon',\r
42 * leaf: true\r
43 * }]\r
44 * }, {\r
45 * text: 'Snacks',\r
46 * items: [{\r
47 * text: 'Nuts',\r
48 * leaf: true\r
49 * }, {\r
50 * text: 'Pretzels',\r
51 * leaf: true\r
52 * }, {\r
53 * text: 'Wasabi Peas',\r
54 * leaf: true\r
55 * }]\r
56 * }]\r
57 * };\r
58 *\r
59 * Ext.define('ListItem', {\r
60 * extend: 'Ext.data.Model',\r
61 * config: {\r
62 * fields: [{\r
63 * name: 'text',\r
64 * type: 'string'\r
65 * }]\r
66 * }\r
67 * });\r
68 *\r
69 * var store = Ext.create('Ext.data.TreeStore', {\r
70 * model: 'ListItem',\r
71 * defaultRootProperty: 'items',\r
72 * root: data\r
73 * });\r
74 *\r
75 * var nestedList = Ext.create('Ext.NestedList', {\r
76 * fullscreen: true,\r
77 * title: 'Groceries',\r
78 * displayField: 'text',\r
79 * store: store\r
80 * });\r
81 */\r
82Ext.define('Ext.dataview.NestedList', {\r
83 alternateClassName: 'Ext.NestedList',\r
84 extend: 'Ext.Container',\r
85 xtype: 'nestedlist',\r
86 requires: [\r
87 'Ext.dataview.List',\r
88 'Ext.TitleBar',\r
89 'Ext.Button',\r
90 'Ext.XTemplate',\r
91 'Ext.data.StoreManager',\r
92 'Ext.data.TreeStore',\r
93 'Ext.data.NodeStore'\r
94 ],\r
95\r
96 config: {\r
97 /**\r
98 * @cfg\r
99 * @inheritdoc\r
100 */\r
101 baseCls: Ext.baseCSSPrefix + 'nested-list',\r
102\r
103 /**\r
104 * @cfg {String/Object/Boolean} cardSwitchAnimation\r
105 * Animation to be used during transitions of cards.\r
106 * @removed 2.0.0 please use {@link Ext.layout.Card#animation}\r
107 */\r
108\r
109 /**\r
110 * @cfg {String} backText\r
111 * The label to display for the back button.\r
112 * @accessor\r
113 */\r
114 backText: 'Back',\r
115\r
116 /**\r
117 * @cfg {Boolean} useTitleAsBackText\r
118 * `true` to use title as a label for back button.\r
119 * @accessor\r
120 */\r
121 useTitleAsBackText: true,\r
122\r
123 /**\r
124 * @cfg {Boolean} updateTitleText\r
125 * Update the title with the currently selected category.\r
126 * @accessor\r
127 */\r
128 updateTitleText: true,\r
129\r
130 /**\r
131 * @cfg {String} displayField\r
132 * Display field to use when setting item text and title.\r
133 * This configuration is ignored when overriding {@link #getItemTextTpl} or\r
134 * {@link #getTitleTextTpl} for the item text or title.\r
135 * @accessor\r
136 */\r
137 displayField: 'text',\r
138\r
139 /**\r
140 * @cfg {String} loadingText\r
141 * Loading text to display when a subtree is loading.\r
142 * @accessor\r
143 */\r
144 loadingText: 'Loading...',\r
145\r
146 /**\r
147 * @cfg {String} emptyText\r
148 * Empty text to display when a subtree is empty.\r
149 * @accessor\r
150 */\r
151 emptyText: 'No items available.',\r
152\r
153 /**\r
154 * @cfg {Boolean/Function} onItemDisclosure\r
155 * Maps to the {@link Ext.List#onItemDisclosure} configuration for individual lists.\r
156 * @accessor\r
157 */\r
158 onItemDisclosure: false,\r
159\r
160 /**\r
161 * @cfg {Boolean} allowDeselect\r
162 * Set to `true` to allow the user to deselect leaf items via interaction.\r
163 * @accessor\r
164 */\r
165 allowDeselect: false,\r
166\r
167 /**\r
168 * @deprecated 2.0.0 Please set the {@link #toolbar} configuration to `false` instead\r
169 * @cfg {Boolean} useToolbar `true` to show the header toolbar.\r
170 * @accessor\r
171 */\r
172 useToolbar: null,\r
173\r
174 /**\r
175 * @cfg {Ext.Toolbar/Object/Boolean} toolbar\r
176 * The configuration to be used for the toolbar displayed in this nested list.\r
177 * @accessor\r
178 */\r
179 toolbar: {\r
180 docked: 'top',\r
181 xtype: 'titlebar',\r
182 ui: 'light',\r
183 inline: true\r
184 },\r
185\r
186 /**\r
187 * @cfg {String} title The title of the toolbar\r
188 * @accessor\r
189 */\r
190 title: '',\r
191\r
192 /**\r
193 * @cfg {String} layout\r
194 * @hide\r
195 * @accessor\r
196 */\r
197 layout: {\r
198 type: 'card',\r
199 animation: {\r
200 type: 'slide',\r
201 duration: 250,\r
202 direction: 'left'\r
203 }\r
204 },\r
205\r
206 /**\r
207 * @cfg {Ext.data.TreeStore/String} store The tree store to be used for this nested list.\r
208 */\r
209 store: null,\r
210\r
211 /**\r
212 * @cfg {Ext.Container} detailContainer The container of the `detailCard`.\r
213 * A detailContainer is a reference to the container where a detail card\r
214 * displays.\r
215 *\r
216 * See http://docs.sencha.com/touch/2-2/#!/guide/nested_list-section-4\r
217 * and http://en.wikipedia.org/wiki/Miller_columns\r
218 *\r
219 * The two possible values for a detailContainer are undefined (default),\r
220 * which indicates that a detailCard appear in the same container, or you\r
221 * can specify a new container location. The default condition uses the\r
222 * current List container.\r
223 *\r
224 * The following example shows creating a location for a detailContainer:\r
225 *\r
226 * var detailContainer = Ext.create('Ext.Container', {\r
227 * layout: 'card'\r
228 * });\r
229 *\r
230 * var nestedList = Ext.create('Ext.NestedList', {\r
231 * store: treeStore,\r
232 * detailCard: true,\r
233 * detailContainer: detailContainer\r
234 * });\r
235 *\r
236 * The default value is typically used for phone devices in portrait mode\r
237 * where the small screen size dictates that the detailCard replace the\r
238 * current container.\r
239 * @accessor\r
240 */\r
241 detailContainer: undefined,\r
242\r
243 /**\r
244 * @cfg {Ext.Component} detailCard provides the information for a leaf\r
245 * in a Miller column list. In a Miller column, users follow a\r
246 * hierarchial tree structure to a leaf, which provides information\r
247 * about the item in the list. The detailCard lists the information at\r
248 * the leaf.\r
249 *\r
250 * See http://docs.sencha.com/touch/2-2/#!/guide/nested_list-section-3\r
251 * and http://en.wikipedia.org/wiki/Miller_columns\r
252 *\r
253 * @accessor\r
254 */\r
255 detailCard: null,\r
256\r
257 /**\r
258 * @cfg {Object} backButton The configuration for the back button used in the nested list.\r
259 */\r
260 backButton: {\r
261 ui: 'back',\r
262 hidden: true\r
263 },\r
264\r
265 /**\r
266 * @cfg {Object} listConfig An optional config object which is merged with the default\r
267 * configuration used to create each nested list.\r
268 */\r
269 listConfig: null,\r
270\r
271 /**\r
272 * @cfg {Boolean} useSimpleItems\r
273 * Set this to false if you want the lists in this NestedList to create complex container list items.\r
274 */\r
275 useSimpleItems: true,\r
276\r
277 /**\r
278 * @cfg {Number} itemHeight\r
279 * This allows you to set the default item height and is used to roughly calculate the amount\r
280 * of items needed to fill the list. By default items are around 50px high. If you set this\r
281 * configuration in combination with setting the {@link #variableHeights} to false you\r
282 * can improve the scrolling speed\r
283 */\r
284 itemHeight: null,\r
285\r
286 /**\r
287 * @cfg {Boolean} variableHeights\r
288 * This configuration allows you optimize the picker by not having it read the DOM heights of list items.\r
289 * Instead it will assume (and set) the height to be the {@link #itemHeight}.\r
290 */\r
291 variableHeights: false,\r
292\r
293 /**\r
294 * @private\r
295 */\r
296 lastNode: null,\r
297\r
298 /**\r
299 * @private\r
300 */\r
301 lastActiveList: null,\r
302\r
303 ui: null,\r
304\r
305 clearSelectionOnListChange: true\r
306 },\r
307\r
308 /**\r
309 * @event itemtap\r
310 * Fires when a node is tapped on.\r
311 * @param {Ext.dataview.NestedList} this\r
312 * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.\r
313 * @param {Number} index The index of the item tapped.\r
314 * @param {Ext.dom.Element} target The element tapped.\r
315 * @param {Ext.data.Record} record The record tapped.\r
316 * @param {Ext.event.Event} e The event object.\r
317 */\r
318\r
319 /**\r
320 * @event itemdoubletap\r
321 * Fires when a node is double tapped on.\r
322 * @param {Ext.dataview.NestedList} this\r
323 * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.\r
324 * @param {Number} index The index of the item that was tapped.\r
325 * @param {Ext.dom.Element} target The element tapped.\r
326 * @param {Ext.data.Record} record The record tapped.\r
327 * @param {Ext.event.Event} e The event object.\r
328 */\r
329\r
330 /**\r
331 * @event containertap\r
332 * Fires when a tap occurs and it is not on a template node.\r
333 * @param {Ext.dataview.NestedList} this\r
334 * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.\r
335 * @param {Ext.event.Event} e The raw event object.\r
336 */\r
337\r
338 /**\r
339 * @event selectionchange\r
340 * Fires when the selected nodes change.\r
341 * @param {Ext.dataview.NestedList} this\r
342 * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.\r
343 * @param {Array} selections Array of the selected nodes.\r
344 */\r
345\r
346 /**\r
347 * @event beforeselectionchange\r
348 * Fires before a selection is made.\r
349 * @param {Ext.dataview.NestedList} this\r
350 * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.\r
351 * @param {HTMLElement} node The node to be selected.\r
352 * @param {Array} selections Array of currently selected nodes.\r
353 * @deprecated 2.0.0 Please listen to the {@link #selectionchange} event with an order of `before` instead.\r
354 */\r
355\r
356 /**\r
357 * @event listchange\r
358 * Fires when the user taps a list item.\r
359 * @param {Ext.dataview.NestedList} this\r
360 * @param {Object} listitem The new active list.\r
361 */\r
362\r
363 /**\r
364 * @event leafitemtap\r
365 * Fires when the user taps a leaf list item.\r
366 * @param {Ext.dataview.NestedList} this\r
367 * @param {Ext.List} list The subList the item is on.\r
368 * @param {Number} index The index of the item tapped.\r
369 * @param {Ext.dom.Element} target The element tapped.\r
370 * @param {Ext.data.Record} record The record tapped.\r
371 * @param {Ext.event.Event} e The event.\r
372 */\r
373\r
374 /**\r
375 * @event back\r
376 * @preventable\r
377 * Fires when the user taps Back.\r
378 * @param {Ext.dataview.NestedList} this\r
379 * @param {HTMLElement} node The node to be selected.\r
380 * @param {Ext.dataview.List} lastActiveList The Ext.dataview.List that was last active.\r
381 * @param {Boolean} detailCardActive Flag set if the detail card is currently active.\r
382 */\r
383\r
384 /**\r
385 * @event beforeload\r
386 * Fires before a request is made for a new data object.\r
387 * @param {Ext.dataview.NestedList} this\r
388 * @param {Ext.data.Store} store The store instance.\r
389 * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to\r
390 * load the Store.\r
391 */\r
392\r
393 /**\r
394 * @event load\r
395 * Fires whenever records have been loaded into the store.\r
396 * @param {Ext.dataview.NestedList} this\r
397 * @param {Ext.data.Store} store The store instance.\r
398 * @param {Ext.util.Grouper[]} records An array of records.\r
399 * @param {Boolean} successful `true` if the operation was successful.\r
400 * @param {Ext.data.Operation} operation The associated operation.\r
401 */\r
402 constructor: function (config) {\r
403 if (Ext.isObject(config)) {\r
404 if (config.getTitleTextTpl) {\r
405 this.getTitleTextTpl = config.getTitleTextTpl;\r
406 }\r
407 if (config.getItemTextTpl) {\r
408 this.getItemTextTpl = config.getItemTextTpl;\r
409 }\r
410 }\r
411 this.callParent([config]);\r
412 },\r
413\r
414 onItemInteraction: function () {\r
415 if (this.isGoingTo) {\r
416 return false;\r
417 }\r
418 },\r
419\r
420 applyDetailContainer: function (config) {\r
421 if (!config) {\r
422 config = this;\r
423 }\r
424\r
425 return config;\r
426 },\r
427\r
428 updateDetailContainer: function (newContainer, oldContainer) {\r
429 if (newContainer) {\r
430 newContainer.on('beforeactiveitemchange', 'onBeforeDetailContainerChange', this);\r
431 newContainer.on('activeitemchange', 'onDetailContainerChange', this);\r
432 }\r
433 },\r
434\r
435 onBeforeDetailContainerChange: function () {\r
436 this.isGoingTo = true;\r
437 },\r
438\r
439 onDetailContainerChange: function () {\r
440 this.isGoingTo = false;\r
441 },\r
442\r
443 /**\r
444 * Called when an list item has been tapped.\r
445 * @param {Ext.List} list The subList the item is on.\r
446 * @param {Number} index The id of the item tapped.\r
447 * @param {Ext.Element} target The list item tapped.\r
448 * @param {Ext.data.Record} record The record which as tapped.\r
449 * @param {Ext.event.Event} e The event.\r
450 */\r
451 onItemTap: function (list, index, target, record, e) {\r
452 var me = this,\r
453 store = list.getStore(),\r
454 node = store.getAt(index);\r
455\r
456 me.fireEvent('itemtap', this, list, index, target, record, e);\r
457 if (node.isLeaf()) {\r
458 me.fireEvent('leafitemtap', this, list, index, target, record, e);\r
459 me.goToLeaf(node);\r
460 }\r
461 else {\r
462 this.goToNode(node);\r
463 }\r
464 },\r
465\r
466 onBeforeSelect: function () {\r
467 this.fireEvent.apply(this, [].concat('beforeselect', this, Array.prototype.slice.call(arguments)));\r
468 },\r
469\r
470 onContainerTap: function () {\r
471 this.fireEvent.apply(this, [].concat('containertap', this, Array.prototype.slice.call(arguments)));\r
472 },\r
473\r
474 onSelectionChange: function () {\r
475 this.fireEvent.apply(this, [].concat('selectionchange', this, Array.prototype.slice.call(arguments)));\r
476 },\r
477\r
478 onItemDoubleTap: function () {\r
479 this.fireEvent.apply(this, [].concat('itemdoubletap', this, Array.prototype.slice.call(arguments)));\r
480 },\r
481\r
482 onStoreBeforeLoad: function () {\r
483 var loadingText = this.getLoadingText(),\r
484 scroller = this.getScrollable();\r
485\r
486 if (loadingText) {\r
487 this.setMasked({\r
488 xtype: 'loadmask',\r
489 message: loadingText\r
490 });\r
491\r
492 //disable scrolling while it is masked\r
493 if (scroller) {\r
494 scroller.setDisabled(true);\r
495 }\r
496 }\r
497\r
498 this.fireEvent.apply(this, [].concat('beforeload', this, Array.prototype.slice.call(arguments)));\r
499 },\r
500\r
501 onStoreLoad: function (store, records, successful, operation, parentNode) {\r
502 this.setMasked(false);\r
503 this.fireEvent.apply(this, [].concat('load', this, Array.prototype.slice.call(arguments)));\r
504\r
505 if (store.indexOf(this.getLastNode()) === -1) {\r
506 this.goToNode(store.getRoot());\r
507 }\r
508 },\r
509\r
510 /**\r
511 * Called when the backButton has been tapped.\r
512 */\r
513 onBackTap: function () {\r
514 var me = this,\r
515 node = me.getLastNode(),\r
516 detailCard = me.getDetailCard(),\r
517 detailCardActive = detailCard && me.getActiveItem() == detailCard,\r
518 lastActiveList = me.getLastActiveList();\r
519\r
520 this.fireAction('back', [this, node, lastActiveList, detailCardActive], 'doBack');\r
521 },\r
522\r
523 doBack: function (me, node, lastActiveList, detailCardActive) {\r
524 var layout = me.getLayout(),\r
525 animation = layout ? layout.getAnimation() : null;\r
526\r
527 if (detailCardActive && lastActiveList) {\r
528 if (animation) {\r
529 animation.setReverse(true);\r
530 }\r
531 me.setActiveItem(lastActiveList);\r
532 me.setLastNode(node.parentNode);\r
533 me.syncToolbar();\r
534 } else {\r
535 me.goToNode(node.parentNode);\r
536 }\r
537 },\r
538\r
539 updateData: function (data) {\r
540 if (!this.getStore()) {\r
541 this.setStore(new Ext.data.TreeStore({\r
542 root: data\r
543 }));\r
544 }\r
545 },\r
546\r
547 applyStore: function (store) {\r
548 if (store) {\r
549 if (Ext.isString(store)) {\r
550 // store id\r
551 store = Ext.data.StoreManager.get(store);\r
552 } else {\r
553 // store instance or store config\r
554 if (!(store instanceof Ext.data.TreeStore)) {\r
555 store = Ext.factory(store, Ext.data.TreeStore, null);\r
556 }\r
557 }\r
558\r
559 // <debug>\r
560 if (!store) {\r
561 Ext.Logger.warn("The specified Store cannot be found", this);\r
562 }\r
563 //</debug>\r
564 }\r
565\r
566 return store;\r
567 },\r
568\r
569 storeListeners: {\r
570 rootchange: 'onStoreRootChange',\r
571 load: 'onStoreLoad',\r
572 beforeload: 'onStoreBeforeLoad'\r
573 },\r
574\r
575 updateStore: function (newStore, oldStore) {\r
576 var me = this,\r
577 listeners = this.storeListeners;\r
578\r
579 listeners.scope = me;\r
580\r
581 if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {\r
582 if (oldStore.autoDestroy) {\r
583 oldStore.destroy();\r
584 }\r
585 oldStore.un(listeners);\r
586 }\r
587\r
588 if (newStore) {\r
589 newStore.on(listeners);\r
590 me.goToNode(newStore.getRoot());\r
591 }\r
592 },\r
593\r
594 onStoreRootChange: function (store, node) {\r
595 this.goToNode(node);\r
596 },\r
597\r
598 applyDetailCard: function (detailCard, oldDetailCard) {\r
599 return Ext.factory(detailCard, Ext.Component, detailCard === null ? oldDetailCard : undefined);\r
600 },\r
601\r
602 applyBackButton: function (config) {\r
603 return Ext.factory(config, Ext.Button, this.getBackButton());\r
604 },\r
605\r
606 updateBackButton: function (newButton, oldButton) {\r
607 if (newButton) {\r
608 var me = this, \r
609 toolbar;\r
610\r
611 newButton.on('tap', me.onBackTap, me);\r
612 newButton.setText(me.getBackText());\r
613\r
614 toolbar = me.getToolbar();\r
615 if (me.$backButtonContainer) {\r
616 me.$backButtonContainer.insert(0, newButton);\r
617 } else {\r
618 toolbar.insert(0, newButton);\r
619 }\r
620 } else if (oldButton) {\r
621 oldButton.destroy();\r
622 }\r
623 },\r
624\r
625 applyToolbar: function (config) {\r
626 if (config && config.splitNavigation) {\r
627 Ext.apply(config, {\r
628 docked: 'top',\r
629 xtype: 'titlebar',\r
630 ui: 'light'\r
631 });\r
632\r
633 var containerConfig = (config.splitNavigation === true) ? {} : config.splitNavigation;\r
634\r
635 this.$backButtonContainer = this.add(Ext.apply({\r
636 xtype: 'toolbar',\r
637 docked: 'bottom',\r
638 hidden: true,\r
639 ui: 'dark'\r
640 }, containerConfig));\r
641 }\r
642\r
643 return Ext.factory(config, Ext.TitleBar, this.getToolbar());\r
644 },\r
645\r
646 updateToolbar: function (newToolbar, oldToolbar) {\r
647 var me = this;\r
648 if (newToolbar) {\r
649 newToolbar.setTitle(me.getTitle());\r
650 if (!newToolbar.getParent()) {\r
651 me.add(newToolbar);\r
652 }\r
653 } else if (oldToolbar) {\r
654 oldToolbar.destroy();\r
655 }\r
656 },\r
657\r
658 updateUseToolbar: function (newUseToolbar, oldUseToolbar) {\r
659 if (!newUseToolbar) {\r
660 this.setToolbar(false);\r
661 }\r
662 },\r
663\r
664 updateTitle: function (newTitle) {\r
665 var me = this,\r
666 toolbar = me.getToolbar();\r
667\r
668 if (toolbar && me.getUpdateTitleText()) {\r
669 toolbar.setTitle(newTitle);\r
670 }\r
671 },\r
672\r
673 /**\r
674 * Override this method to provide custom template rendering of individual\r
675 * nodes. The template will receive all data within the Record and will also\r
676 * receive whether or not it is a leaf node.\r
677 * @param {Ext.data.Record} node\r
678 * @return {String}\r
679 */\r
680 getItemTextTpl: function (node) {\r
681 return '{' + this.getDisplayField() + '}';\r
682 },\r
683\r
684 /**\r
685 * Override this method to provide custom template rendering of titles/back\r
686 * buttons when {@link #useTitleAsBackText} is enabled.\r
687 * @param {Ext.data.Record} node\r
688 * @return {String}\r
689 */\r
690 getTitleTextTpl: function (node) {\r
691 return '{' + this.getDisplayField() + '}';\r
692 },\r
693\r
694 /**\r
695 * @private\r
696 */\r
697 renderTitleText: function (node, forBackButton) {\r
698 if (!node.titleTpl) {\r
699 node.titleTpl = Ext.create('Ext.XTemplate', this.getTitleTextTpl(node));\r
700 }\r
701\r
702 if (node.isRoot()) {\r
703 var initialTitle = this.getInitialConfig('title');\r
704 return (forBackButton && initialTitle === '') ? this.getInitialConfig('backText') : initialTitle;\r
705 }\r
706\r
707 return node.titleTpl.applyTemplate(node.data);\r
708 },\r
709\r
710 /**\r
711 * Method to handle going to a specific node within this nested list. Node must be part of the\r
712 * internal {@link #store}.\r
713 * @param {Ext.data.NodeInterface} node The specified node to navigate to.\r
714 */\r
715 goToNode: function (node) {\r
716 if (!node) {\r
717 return;\r
718 }\r
719\r
720 var me = this,\r
721 activeItem = me.getActiveItem(),\r
722 detailCard = me.getDetailCard(),\r
723 detailCardActive = detailCard && me.getActiveItem() == detailCard,\r
724 reverse = me.goToNodeReverseAnimation(node),\r
725 firstList = me.firstList,\r
726 secondList = me.secondList,\r
727 layout = me.getLayout(),\r
728 animation = layout ? layout.getAnimation() : null,\r
729 list;\r
730\r
731 //if the node is a leaf, throw an error\r
732 if (node.isLeaf()) {\r
733 throw new Error('goToNode: passed a node which is a leaf.');\r
734 }\r
735\r
736 //if we are currently at the passed node, do nothing.\r
737 if (node === me.getLastNode() && !detailCardActive) {\r
738 return;\r
739 }\r
740\r
741 if (detailCardActive) {\r
742 if (animation) {\r
743 animation.setReverse(true);\r
744 }\r
745 list = me.getLastActiveList();\r
746 list.getStore().setNode(node);\r
747 node.expand();\r
748 me.setActiveItem(list);\r
749 } else {\r
750 if (animation) {\r
751 animation.setReverse(reverse);\r
752 }\r
753\r
754 if (firstList && secondList) {\r
755 //firstList and secondList have both been created\r
756 activeItem = me.getActiveItem();\r
757\r
758 me.setLastActiveList(activeItem);\r
759 list = (activeItem == firstList) ? secondList : firstList;\r
760\r
761 list.getStore().setNode(node);\r
762 node.expand();\r
763\r
764 me.setActiveItem(list);\r
765 if (me.getClearSelectionOnListChange()) {\r
766 list.deselectAll();\r
767 }\r
768 }\r
769 else if (firstList) {\r
770 //only firstList has been created\r
771 me.setLastActiveList(me.getActiveItem());\r
772 me.setActiveItem(me.getList(node));\r
773 me.secondList = me.getActiveItem();\r
774 }\r
775 else {\r
776 //no lists have been created\r
777 me.setActiveItem(me.getList(node));\r
778 me.firstList = me.getActiveItem();\r
779 }\r
780 }\r
781\r
782 me.fireEvent('listchange', me, me.getActiveItem());\r
783\r
784 me.setLastNode(node);\r
785\r
786 me.syncToolbar();\r
787 },\r
788\r
789\r
790 /**\r
791 * The leaf you want to navigate to. You should pass a node instance.\r
792 * @param {Ext.data.NodeInterface} node The specified node to navigate to.\r
793 */\r
794 goToLeaf: function (node) {\r
795 if (!node.isLeaf()) {\r
796 throw new Error('goToLeaf: passed a node which is not a leaf.');\r
797 }\r
798\r
799 var me = this,\r
800 card = me.getDetailCard(),\r
801 container = me.getDetailContainer(),\r
802 sharedContainer = container === me,\r
803 layout = me.getLayout(),\r
804 animation = layout ? layout.getAnimation() : false,\r
805 activeItem;\r
806\r
807 if (card) {\r
808 if (container.getItems().indexOf(card) === -1) {\r
809 container.add(card);\r
810 }\r
811 if (sharedContainer) {\r
812 activeItem = me.getActiveItem();\r
813 if (activeItem instanceof Ext.dataview.List) {\r
814 me.setLastActiveList(activeItem);\r
815 }\r
816 me.setLastNode(node);\r
817 }\r
818 if (animation) {\r
819 animation.setReverse(false);\r
820 }\r
821 container.setActiveItem(card);\r
822 me.syncToolbar();\r
823 }\r
824 },\r
825\r
826 /**\r
827 * @private\r
828 * Method which updates the {@link #backButton} and {@link #toolbar} with the latest information from\r
829 * the current node.\r
830 */\r
831 syncToolbar: function (forceDetail) {\r
832 var me = this,\r
833 detailCard = me.getDetailCard(),\r
834 node = me.getLastNode(),\r
835 detailActive = forceDetail || (detailCard && (me.getActiveItem() == detailCard)),\r
836 parentNode = (detailActive) ? node : node.parentNode,\r
837 backButton = me.getBackButton();\r
838\r
839 //show/hide the backButton, and update the backButton text, if one exists\r
840 if (backButton) {\r
841 var toolbar = me.getToolbar(),\r
842 splitNavigation = toolbar.getInitialConfig("splitNavigation");\r
843\r
844 if (splitNavigation) {\r
845 me.$backButtonContainer[parentNode ? 'show' : 'hide']();\r
846 }\r
847\r
848 backButton[parentNode ? 'show' : 'hide']();\r
849 if (parentNode && me.getUseTitleAsBackText()) {\r
850 backButton.setText(me.renderTitleText(node.parentNode, true));\r
851 }\r
852 }\r
853\r
854 if (node) {\r
855 me.setTitle(me.renderTitleText(node));\r
856 }\r
857 },\r
858\r
859 updateBackText: function (newText) {\r
860 this.getBackButton().setText(newText);\r
861 },\r
862\r
863 /**\r
864 * @private\r
865 * Returns `true` if the passed node should have a reverse animation from the previous current node.\r
866 * @param {Ext.data.NodeInterface} node\r
867 */\r
868 goToNodeReverseAnimation: function (node) {\r
869 var lastNode = this.getLastNode();\r
870\r
871 if (!lastNode) {\r
872 return false;\r
873 }\r
874\r
875 return (!lastNode.contains(node) && lastNode.isAncestor(node)) ? true : false;\r
876 },\r
877\r
878 /**\r
879 * @private\r
880 * Returns the list config for a specified node.\r
881 * @param {HTMLElement} node The node for the list config.\r
882 */\r
883 getList: function (node) {\r
884 var me = this,\r
885 treeStore = new Ext.data.NodeStore({\r
886 recursive: false,\r
887 node: node,\r
888 rootVisible: false,\r
889 model: me.getStore().getModel(),\r
890 proxy: 'memory'\r
891 });\r
892\r
893 node.expand();\r
894\r
895 return Ext.Object.merge({\r
896 xtype: 'list',\r
897 useSimpleItems: me.getUseSimpleItems(),\r
898 pressedDelay: 250,\r
899 autoDestroy: true,\r
900 store: treeStore,\r
901 onItemDisclosure: me.getOnItemDisclosure(),\r
902 allowDeselect: me.getAllowDeselect(),\r
903 itemHeight: me.getItemHeight(),\r
904 variableHeights: me.getVariableHeights(),\r
905 emptyText: me.getEmptyText(),\r
906 listeners: [{\r
907 itemdoubletap: 'onItemDoubleTap',\r
908 itemtap: 'onItemTap',\r
909 beforeselectionchange: 'onBeforeSelect',\r
910 containertap: 'onContainerTap',\r
911 scope: me\r
912 }, {\r
913 selectionchange: 'onSelectionChange',\r
914 itemtouchstart: 'onItemInteraction',\r
915 itemtap: 'onItemInteraction',\r
916 order: 'before',\r
917 scope: me\r
918 }],\r
919 itemTpl: '<span<tpl if="leaf == true"> class="x-list-item-leaf"</tpl>>' + me.getItemTextTpl(node) + '</span>'\r
920 }, me.getListConfig());\r
921 }\r
922});\r
923\r