]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/layout/container/Card.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / layout / container / Card.js
CommitLineData
6527f429
DM
1/**\r
2 * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be\r
3 * visible at any given time. This layout style is most commonly used for wizards, tab implementations, etc.\r
4 * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,\r
5 * and should generally not need to be created directly via the new keyword.\r
6 *\r
7 * The CardLayout's focal method is {@link #setActiveItem}. Since only one panel is displayed at a time,\r
8 * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display\r
9 * (or its id or index). The layout itself does not provide a user interface for handling this navigation,\r
10 * so that functionality must be provided by the developer.\r
11 *\r
12 * To change the active card of a container, call the setActiveItem method of its layout:\r
13 *\r
14 * @example\r
15 * var p = Ext.create('Ext.panel.Panel', {\r
16 * layout: 'card',\r
17 * items: [\r
18 * { html: 'Card 1' },\r
19 * { html: 'Card 2' }\r
20 * ],\r
21 * renderTo: Ext.getBody()\r
22 * });\r
23 *\r
24 * p.getLayout().setActiveItem(1);\r
25 * \r
26 * The {@link Ext.Component#beforedeactivate beforedeactivate} and {@link Ext.Component#beforeactivate beforeactivate}\r
27 * events can be used to prevent a card from activating or deactivating by returning `false`.\r
28 * \r
29 * @example \r
30 * var active = 0;\r
31 * var main = Ext.create('Ext.panel.Panel', {\r
32 * renderTo: Ext.getBody(),\r
33 * width: 200,\r
34 * height: 200,\r
35 * layout: 'card',\r
36 * tbar: [{\r
37 * text: 'Next',\r
38 * handler: function(){\r
39 * var layout = main.getLayout();\r
40 * ++active;\r
41 * layout.setActiveItem(active);\r
42 * active = main.items.indexOf(layout.getActiveItem());\r
43 * }\r
44 * }],\r
45 * items: [{\r
46 * title: 'P1'\r
47 * }, {\r
48 * title: 'P2'\r
49 * }, {\r
50 * title: 'P3',\r
51 * listeners: {\r
52 * beforeactivate: function(){\r
53 * return false;\r
54 * }\r
55 * }\r
56 * }]\r
57 * });\r
58 *\r
59 * In the following example, a simplistic wizard setup is demonstrated. A button bar is added\r
60 * to the footer of the containing panel to provide navigation buttons. The buttons will be handled by a\r
61 * common navigation routine. Note that other uses of a CardLayout (like a tab control) would require a\r
62 * completely different implementation. For serious implementations, a better approach would be to extend\r
63 * CardLayout to provide the custom functionality needed.\r
64 *\r
65 * @example\r
66 * var navigate = function(panel, direction){\r
67 * // This routine could contain business logic required to manage the navigation steps.\r
68 * // It would call setActiveItem as needed, manage navigation button state, handle any\r
69 * // branching logic that might be required, handle alternate actions like cancellation\r
70 * // or finalization, etc. A complete wizard implementation could get pretty\r
71 * // sophisticated depending on the complexity required, and should probably be\r
72 * // done as a subclass of CardLayout in a real-world implementation.\r
73 * var layout = panel.getLayout();\r
74 * layout[direction]();\r
75 * Ext.getCmp('move-prev').setDisabled(!layout.getPrev());\r
76 * Ext.getCmp('move-next').setDisabled(!layout.getNext());\r
77 * };\r
78 *\r
79 * Ext.create('Ext.panel.Panel', {\r
80 * title: 'Example Wizard',\r
81 * width: 300,\r
82 * height: 200,\r
83 * layout: 'card',\r
84 * bodyStyle: 'padding:15px',\r
85 * defaults: {\r
86 * // applied to each contained panel\r
87 * border: false\r
88 * },\r
89 * // just an example of one possible navigation scheme, using buttons\r
90 * bbar: [\r
91 * {\r
92 * id: 'move-prev',\r
93 * text: 'Back',\r
94 * handler: function(btn) {\r
95 * navigate(btn.up("panel"), "prev");\r
96 * },\r
97 * disabled: true\r
98 * },\r
99 * '->', // greedy spacer so that the buttons are aligned to each side\r
100 * {\r
101 * id: 'move-next',\r
102 * text: 'Next',\r
103 * handler: function(btn) {\r
104 * navigate(btn.up("panel"), "next");\r
105 * }\r
106 * }\r
107 * ],\r
108 * // the panels (or "cards") within the layout\r
109 * items: [{\r
110 * id: 'card-0',\r
111 * html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'\r
112 * },{\r
113 * id: 'card-1',\r
114 * html: '<p>Step 2 of 3</p>'\r
115 * },{\r
116 * id: 'card-2',\r
117 * html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'\r
118 * }],\r
119 * renderTo: Ext.getBody()\r
120 * });\r
121 */\r
122Ext.define('Ext.layout.container.Card', {\r
123\r
124 /* Begin Definitions */\r
125\r
126 extend: 'Ext.layout.container.Fit',\r
127\r
128 alternateClassName: 'Ext.layout.CardLayout',\r
129\r
130 alias: 'layout.card',\r
131\r
132 /* End Definitions */\r
133\r
134 type: 'card',\r
135\r
136 hideInactive: true,\r
137\r
138 /**\r
139 * @cfg {Boolean} deferredRender\r
140 * True to render each contained item at the time it becomes active, false to render all contained items\r
141 * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or\r
142 * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to\r
143 * true might improve performance.\r
144 */\r
145 deferredRender : false,\r
146\r
147 getRenderTree: function () {\r
148 var me = this,\r
149 activeItem = me.getActiveItem();\r
150\r
151 if (activeItem) {\r
152\r
153 // If they veto the activate, we have no active item\r
154 if (activeItem.hasListeners.beforeactivate && activeItem.fireEvent('beforeactivate', activeItem) === false) {\r
155 \r
156 // We must null our activeItem reference, AND the one in our owning Container.\r
157 // Because upon layout invalidation, renderChildren will use this.getActiveItem which\r
158 // uses this.activeItem || this.owner.activeItem\r
159 activeItem = me.activeItem = me.owner.activeItem = null;\r
160 }\r
161 \r
162 // Item is to be the active one. Fire event after it is first layed out\r
163 else if (activeItem.hasListeners.activate) {\r
164 activeItem.on({\r
165 boxready: function() {\r
166 activeItem.fireEvent('activate', activeItem);\r
167 },\r
168 single: true\r
169 });\r
170 }\r
171\r
172 if (me.deferredRender) {\r
173 if (activeItem) {\r
174 return me.getItemsRenderTree([activeItem]);\r
175 }\r
176 } else {\r
177 return me.callParent(arguments);\r
178 }\r
179 }\r
180 },\r
181\r
182 renderChildren: function () {\r
183 var me = this,\r
184 active = me.getActiveItem();\r
185\r
186 if (!me.deferredRender) {\r
187 me.callParent();\r
188 } else if (active) {\r
189 // ensure the active item is configured for the layout\r
190 me.renderItems([active], me.getRenderTarget());\r
191 }\r
192 },\r
193\r
194 isValidParent : function(item, target, position) {\r
195 // Note: Card layout does not care about order within the target because only one is ever visible.\r
196 // We only care whether the item is a direct child of the target.\r
197 var itemEl = item.el ? item.el.dom : Ext.getDom(item);\r
198 return (itemEl && itemEl.parentNode === (target.dom || target)) || false;\r
199 },\r
200\r
201 /**\r
202 * Return the active (visible) component in the layout.\r
203 * @return {Ext.Component}\r
204 */\r
205 getActiveItem: function() {\r
206 var me = this,\r
207 // It's necessary to check that me.activeItem is not undefined as it could be 0 (falsey). We're more interested in\r
208 // checking the layout's activeItem property, since that is the source of truth for an activeItem. If it's\r
209 // determined to be empty, check the owner. Note that a default item is returned if activeItem is `undefined` but\r
210 // not `null`. Also, note that `null` is legitimate value and completely different from `undefined`.\r
211 item = me.activeItem === undefined ? (me.owner && me.owner.activeItem) : me.activeItem,\r
212 result = me.parseActiveItem(item);\r
213\r
214 // Sanitize the result in case the active item is no longer there.\r
215 if (result && me.owner.items.indexOf(result) !== -1) {\r
216 me.activeItem = result;\r
217 }\r
218\r
219 // Note that in every use case me.activeItem will have a truthy value except for when a container or tabpanel is explicity\r
220 // configured with activeItem/Tab === null or when an out-of-range index is given for an active tab (as it will be undefined).\r
221 // In those cases, it is meaningful to return the null value, so do so.\r
222 return result == null ? null : (me.activeItem || me.owner.activeItem);\r
223 },\r
224\r
225 /**\r
226 * @private\r
227 */\r
228 parseActiveItem: function (item) {\r
229 var activeItem;\r
230\r
231 if (item && item.isComponent) {\r
232 activeItem = item;\r
233 } else if (typeof item === 'number' || item === undefined) {\r
234 activeItem = this.getLayoutItems()[item || 0];\r
235 } else if (item === null) {\r
236 activeItem = null;\r
237 } else {\r
238 activeItem = this.owner.getComponent(item);\r
239 }\r
240\r
241 return activeItem;\r
242 },\r
243\r
244 /**\r
245 * @private\r
246 * Called before both dynamic render, and bulk render.\r
247 * Ensure that the active item starts visible, and inactive ones start invisible.\r
248 */\r
249 configureItem: function(item) {\r
250 item.setHiddenState(item !== this.getActiveItem());\r
251 this.callParent(arguments);\r
252 },\r
253\r
254 onAdd: function (item, pos) {\r
255 this.callParent([item, pos]);\r
256 this.setItemHideMode(item);\r
257 },\r
258\r
259 onRemove: function(component) {\r
260 var me = this;\r
261\r
262 me.callParent([component]);\r
263 me.resetItemHideMode(component);\r
264\r
265 if (component === me.activeItem) {\r
266 // Note setting to `undefined` is intentional. Don't null it out since null now has a specific meaning in\r
267 // tab management (it specifies not setting an active item).\r
268 me.activeItem = undefined;\r
269 }\r
270 },\r
271\r
272 /**\r
273 * @private\r
274 */\r
275 getAnimation: function(newCard, owner) {\r
276 var newAnim = (newCard || {}).cardSwitchAnimation;\r
277 if (newAnim === false) {\r
278 return false;\r
279 }\r
280 return newAnim || owner.cardSwitchAnimation;\r
281 },\r
282\r
283 /**\r
284 * Return the active (visible) component in the layout to the next card\r
285 * @return {Ext.Component} The next component or false.\r
286 */\r
287 getNext: function() {\r
288 var wrap = arguments[0],\r
289 items = this.getLayoutItems(),\r
290 index = Ext.Array.indexOf(items, this.activeItem);\r
291 \r
292 return items[index + 1] || (wrap ? items[0] : false);\r
293 },\r
294\r
295 /**\r
296 * Sets the active (visible) component in the layout to the next card\r
297 * @return {Ext.Component} the activated component or false when nothing activated.\r
298 */\r
299 next: function() {\r
300 var anim = arguments[0], \r
301 wrap = arguments[1];\r
302 return this.setActiveItem(this.getNext(wrap), anim);\r
303 },\r
304\r
305 /**\r
306 * Return the active (visible) component in the layout to the previous card\r
307 * @return {Ext.Component} The previous component or false.\r
308 */\r
309 getPrev: function() {\r
310 var wrap = arguments[0],\r
311 items = this.getLayoutItems(),\r
312 index = Ext.Array.indexOf(items, this.activeItem);\r
313 \r
314 return items[index - 1] || (wrap ? items[items.length - 1] : false);\r
315 },\r
316\r
317 /**\r
318 * Sets the active (visible) component in the layout to the previous card\r
319 * @return {Ext.Component} the activated component or false when nothing activated.\r
320 */\r
321 prev: function() {\r
322 var anim = arguments[0], \r
323 wrap = arguments[1];\r
324 return this.setActiveItem(this.getPrev(wrap), anim);\r
325 },\r
326\r
327 /**\r
328 * Makes the given card active.\r
329 *\r
330 * var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});\r
331 * var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});\r
332 * var panel = Ext.create('Ext.panel.Panel', {\r
333 * layout: 'card',\r
334 * activeItem: 0,\r
335 * items: [card1, card2]\r
336 * });\r
337 * // These are all equivalent\r
338 * panel.getLayout().setActiveItem(card2);\r
339 * panel.getLayout().setActiveItem('card-2');\r
340 * panel.getLayout().setActiveItem(1);\r
341 *\r
342 * @param {Ext.Component/Number/String} newCard The component, component {@link Ext.Component#id id},\r
343 * {@link Ext.Component#itemId itemId}, or index of component.\r
344 * @return {Ext.Component} the activated component or false when nothing activated.\r
345 * False is returned also when trying to activate an already active card.\r
346 */\r
347 setActiveItem: function(newCard) {\r
348 var me = this,\r
349 owner = me.owner,\r
350 oldCard = me.activeItem,\r
351 rendered = owner.rendered,\r
352 newIndex, focusNewCard;\r
353\r
354 newCard = me.parseActiveItem(newCard);\r
355 newIndex = owner.items.indexOf(newCard);\r
356\r
357 // If the card is not a child of the owner, then add it.\r
358 // Without doing a layout!\r
359 if (newIndex === -1) {\r
360 newIndex = owner.items.items.length;\r
361 Ext.suspendLayouts();\r
362 newCard = owner.add(newCard);\r
363 Ext.resumeLayouts();\r
364 }\r
365\r
366 // Is this a valid, different card?\r
367 if (newCard && oldCard !== newCard) {\r
368 // Fire the beforeactivate and beforedeactivate events on the cards\r
369 if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {\r
370 return false;\r
371 }\r
372 if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {\r
373 return false;\r
374 }\r
375\r
376 if (rendered) {\r
377 Ext.suspendLayouts();\r
378\r
379 // If the card has not been rendered yet, now is the time to do so.\r
380 if (!newCard.rendered) {\r
381 me.renderItem(newCard, me.getRenderTarget(), owner.items.length);\r
382 }\r
383\r
384 if (oldCard) {\r
385 if (me.hideInactive) {\r
386 focusNewCard = oldCard.el.contains(Ext.Element.getActiveElement());\r
387\r
388 oldCard.hide();\r
389\r
390 if (oldCard.hidden) {\r
391 oldCard.hiddenByLayout = true;\r
392 oldCard.fireEvent('deactivate', oldCard, newCard);\r
393 }\r
394 // Hide was vetoed, we cannot change cards.\r
395 else {\r
396 return false;\r
397 }\r
398 }\r
399 }\r
400\r
401 // Make sure the new card is shown\r
402 if (newCard.hidden) {\r
403 newCard.show();\r
404 }\r
405\r
406 // Layout needs activeItem to be correct, so clear it if the show has been vetoed,\r
407 // set it if the show has *not* been vetoed.\r
408 if (newCard.hidden) {\r
409 me.activeItem = newCard = null;\r
410 } else {\r
411 me.activeItem = newCard;\r
412\r
413 // If the card being hidden contained focus, attempt to focus the new card\r
414 // So as not to leave focus undefined.\r
415 // The focus() call will focus the defaultFocus if it is a container\r
416 // so ensure there is a defaultFocus.\r
417 if (focusNewCard) {\r
418 if (!newCard.defaultFocus) {\r
419 newCard.defaultFocus = ':focusable';\r
420 }\r
421 newCard.focus();\r
422 }\r
423 }\r
424\r
425 Ext.resumeLayouts(true);\r
426 } else {\r
427 me.activeItem = newCard;\r
428 }\r
429\r
430 newCard.fireEvent('activate', newCard, oldCard);\r
431\r
432 return me.activeItem;\r
433 }\r
434 return false;\r
435 },\r
436\r
437 /**\r
438 * @private\r
439 * Reset back to initial config when item is removed from the panel.\r
440 */\r
441 resetItemHideMode: function (item) {\r
442 item.hideMode = item.originalHideMode;\r
443 delete item.originalHideMode;\r
444 },\r
445\r
446 /**\r
447 * @private\r
448 * A card layout items must have its visibility mode set to OFFSETS so its scroll\r
449 * positions isn't reset when hidden.\r
450 *\r
451 * Do this automatically when an item is added to the panel.\r
452 */\r
453 setItemHideMode: function (item) {\r
454 item.originalHideMode = item.hideMode;\r
455 item.hideMode = 'offsets';\r
456 }\r
457});\r