/** * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be * visible at any given time. This layout style is most commonly used for wizards, tab implementations, etc. * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config, * and should generally not need to be created directly via the new keyword. * * The CardLayout's focal method is {@link #setActiveItem}. Since only one panel is displayed at a time, * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display * (or its id or index). The layout itself does not provide a user interface for handling this navigation, * so that functionality must be provided by the developer. * * To change the active card of a container, call the setActiveItem method of its layout: * * @example * var p = Ext.create('Ext.panel.Panel', { * layout: 'card', * items: [ * { html: 'Card 1' }, * { html: 'Card 2' } * ], * renderTo: Ext.getBody() * }); * * p.getLayout().setActiveItem(1); * * The {@link Ext.Component#beforedeactivate beforedeactivate} and {@link Ext.Component#beforeactivate beforeactivate} * events can be used to prevent a card from activating or deactivating by returning `false`. * * @example * var active = 0; * var main = Ext.create('Ext.panel.Panel', { * renderTo: Ext.getBody(), * width: 200, * height: 200, * layout: 'card', * tbar: [{ * text: 'Next', * handler: function(){ * var layout = main.getLayout(); * ++active; * layout.setActiveItem(active); * active = main.items.indexOf(layout.getActiveItem()); * } * }], * items: [{ * title: 'P1' * }, { * title: 'P2' * }, { * title: 'P3', * listeners: { * beforeactivate: function(){ * return false; * } * } * }] * }); * * In the following example, a simplistic wizard setup is demonstrated. A button bar is added * to the footer of the containing panel to provide navigation buttons. The buttons will be handled by a * common navigation routine. Note that other uses of a CardLayout (like a tab control) would require a * completely different implementation. For serious implementations, a better approach would be to extend * CardLayout to provide the custom functionality needed. * * @example * var navigate = function(panel, direction){ * // This routine could contain business logic required to manage the navigation steps. * // It would call setActiveItem as needed, manage navigation button state, handle any * // branching logic that might be required, handle alternate actions like cancellation * // or finalization, etc. A complete wizard implementation could get pretty * // sophisticated depending on the complexity required, and should probably be * // done as a subclass of CardLayout in a real-world implementation. * var layout = panel.getLayout(); * layout[direction](); * Ext.getCmp('move-prev').setDisabled(!layout.getPrev()); * Ext.getCmp('move-next').setDisabled(!layout.getNext()); * }; * * Ext.create('Ext.panel.Panel', { * title: 'Example Wizard', * width: 300, * height: 200, * layout: 'card', * bodyStyle: 'padding:15px', * defaults: { * // applied to each contained panel * border: false * }, * // just an example of one possible navigation scheme, using buttons * bbar: [ * { * id: 'move-prev', * text: 'Back', * handler: function(btn) { * navigate(btn.up("panel"), "prev"); * }, * disabled: true * }, * '->', // greedy spacer so that the buttons are aligned to each side * { * id: 'move-next', * text: 'Next', * handler: function(btn) { * navigate(btn.up("panel"), "next"); * } * } * ], * // the panels (or "cards") within the layout * items: [{ * id: 'card-0', * html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>' * },{ * id: 'card-1', * html: '<p>Step 2 of 3</p>' * },{ * id: 'card-2', * html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>' * }], * renderTo: Ext.getBody() * }); */Ext.define('Ext.layout.container.Card', { /* Begin Definitions */ extend: 'Ext.layout.container.Fit', alternateClassName: 'Ext.layout.CardLayout', alias: 'layout.card', /* End Definitions */ type: 'card', hideInactive: true, /** * @cfg {Boolean} deferredRender * True to render each contained item at the time it becomes active, false to render all contained items * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to * true might improve performance. */ deferredRender : false, getRenderTree: function () { var me = this, activeItem = me.getActiveItem(); if (activeItem) { // If they veto the activate, we have no active item if (activeItem.hasListeners.beforeactivate && activeItem.fireEvent('beforeactivate', activeItem) === false) { // We must null our activeItem reference, AND the one in our owning Container. // Because upon layout invalidation, renderChildren will use this.getActiveItem which // uses this.activeItem || this.owner.activeItem activeItem = me.activeItem = me.owner.activeItem = null; } // Item is to be the active one. Fire event after it is first layed out else if (activeItem.hasListeners.activate) { activeItem.on({ boxready: function() { activeItem.fireEvent('activate', activeItem); }, single: true }); } if (me.deferredRender) { if (activeItem) { return me.getItemsRenderTree([activeItem]); } } else { return me.callParent(arguments); } } }, renderChildren: function () { var me = this, active = me.getActiveItem(); if (!me.deferredRender) { me.callParent(); } else if (active) { // ensure the active item is configured for the layout me.renderItems([active], me.getRenderTarget()); } }, isValidParent : function(item, target, position) { // Note: Card layout does not care about order within the target because only one is ever visible. // We only care whether the item is a direct child of the target. var itemEl = item.el ? item.el.dom : Ext.getDom(item); return (itemEl && itemEl.parentNode === (target.dom || target)) || false; }, /** * Return the active (visible) component in the layout. * @returns {Ext.Component} */ getActiveItem: function() { var me = this, // Ensure the calculated result references a Component result = me.parseActiveItem(me.activeItem || (me.owner && me.owner.activeItem)); // Sanitize the result in case the active item is no longer there. if (result && me.owner.items.indexOf(result) != -1) { me.activeItem = result; } else { me.activeItem = null; } return me.activeItem; }, // @private parseActiveItem: function(item) { if (item && item.isComponent) { return item; } else if (typeof item == 'number' || item === undefined) { return this.getLayoutItems()[item || 0]; } else { return this.owner.getComponent(item); } }, // @private. Called before both dynamic render, and bulk render. // Ensure that the active item starts visible, and inactive ones start invisible configureItem: function(item) { if (item === this.getActiveItem()) { item.hidden = false; } else { item.hidden = true; } this.callParent(arguments); }, onRemove: function(component) { this.callParent(arguments); if (component === this.activeItem) { this.activeItem = null; } }, // @private getAnimation: function(newCard, owner) { var newAnim = (newCard || {}).cardSwitchAnimation; if (newAnim === false) { return false; } return newAnim || owner.cardSwitchAnimation; }, /** * Return the active (visible) component in the layout to the next card * @returns {Ext.Component} The next component or false. */ getNext: function() { var wrap = arguments[0], items = this.getLayoutItems(), index = Ext.Array.indexOf(items, this.activeItem); return items[index + 1] || (wrap ? items[0] : false); }, /** * Sets the active (visible) component in the layout to the next card * @return {Ext.Component} the activated component or false when nothing activated. */ next: function() { var anim = arguments[0], wrap = arguments[1]; return this.setActiveItem(this.getNext(wrap), anim); }, /** * Return the active (visible) component in the layout to the previous card * @returns {Ext.Component} The previous component or false. */ getPrev: function() { var wrap = arguments[0], items = this.getLayoutItems(), index = Ext.Array.indexOf(items, this.activeItem); return items[index - 1] || (wrap ? items[items.length - 1] : false); }, /** * Sets the active (visible) component in the layout to the previous card * @return {Ext.Component} the activated component or false when nothing activated. */ prev: function() { var anim = arguments[0], wrap = arguments[1]; return this.setActiveItem(this.getPrev(wrap), anim); }, /** * Makes the given card active. * * var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'}); * var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'}); * var panel = Ext.create('Ext.panel.Panel', { * layout: 'card', * activeItem: 0, * items: [card1, card2] * }); * // These are all equivalent * panel.getLayout().setActiveItem(card2); * panel.getLayout().setActiveItem('card-2'); * panel.getLayout().setActiveItem(1); * * @param {Ext.Component/Number/String} newCard The component, component {@link Ext.Component#id id}, * {@link Ext.Component#itemId itemId}, or index of component. * @return {Ext.Component} the activated component or false when nothing activated. * False is returned also when trying to activate an already active card. */ setActiveItem: function(newCard) { var me = this, owner = me.owner, oldCard = me.activeItem, rendered = owner.rendered, newIndex; newCard = me.parseActiveItem(newCard); newIndex = owner.items.indexOf(newCard); // If the card is not a child of the owner, then add it. // Without doing a layout! if (newIndex == -1) { newIndex = owner.items.items.length; Ext.suspendLayouts(); newCard = owner.add(newCard); Ext.resumeLayouts(); } // Is this a valid, different card? if (newCard && oldCard != newCard) { // Fire the beforeactivate and beforedeactivate events on the cards if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) { return false; } if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) { return false; } if (rendered) { Ext.suspendLayouts(); // If the card has not been rendered yet, now is the time to do so. if (!newCard.rendered) { me.renderItem(newCard, me.getRenderTarget(), owner.items.length); } if (oldCard) { if (me.hideInactive) { oldCard.hide(); oldCard.hiddenByLayout = true; } oldCard.fireEvent('deactivate', oldCard, newCard); } // Make sure the new card is shown if (newCard.hidden) { newCard.show(); } // Layout needs activeItem to be correct, so set it if the show has not been vetoed if (!newCard.hidden) { me.activeItem = newCard; } Ext.resumeLayouts(true); } else { me.activeItem = newCard; } newCard.fireEvent('activate', newCard, oldCard); return me.activeItem; } return false; }});