X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/layout/container/Accordion.js diff --git a/src/layout/container/Accordion.js b/src/layout/container/Accordion.js new file mode 100644 index 00000000..cabc1b9b --- /dev/null +++ b/src/layout/container/Accordion.js @@ -0,0 +1,385 @@ +/** + * @class Ext.layout.container.Accordion + * @extends Ext.layout.container.VBox + *

This is a layout that manages multiple Panels in an expandable accordion style such that only + * one Panel can be expanded at any given time. Each Panel has built-in support for expanding and collapsing.

+ *

Note: Only Ext.Panels and all subclasses of Ext.panel.Panel may be used in an accordion layout Container.

+ * {@img Ext.layout.container.Accordion/Ext.layout.container.Accordion.png Ext.layout.container.Accordion container layout} + *

Example usage:

+ *

+    Ext.create('Ext.panel.Panel', {
+        title: 'Accordion Layout',
+        width: 300,
+        height: 300,
+        layout:'accordion',
+        defaults: {
+            // applied to each contained panel
+            bodyStyle: 'padding:15px'
+        },
+        layoutConfig: {
+            // layout-specific configs go here
+            titleCollapse: false,
+            animate: true,
+            activeOnTop: true
+        },
+        items: [{
+            title: 'Panel 1',
+            html: '

Panel content!

' + },{ + title: 'Panel 2', + html: '

Panel content!

' + },{ + title: 'Panel 3', + html: '

Panel content!

' + }], + renderTo: Ext.getBody() + }); +
+ */ +Ext.define('Ext.layout.container.Accordion', { + extend: 'Ext.layout.container.VBox', + alias: ['layout.accordion'], + alternateClassName: 'Ext.layout.AccordionLayout', + + align: 'stretch', + + /** + * @cfg {Boolean} fill + * True to adjust the active item's height to fill the available space in the container, false to use the + * item's current height, or auto height if not explicitly set (defaults to true). + */ + fill : true, + /** + * @cfg {Boolean} autoWidth + *

This config is ignored in ExtJS 4.x.

+ * Child Panels have their width actively managed to fit within the accordion's width. + */ + autoWidth : true, + /** + * @cfg {Boolean} titleCollapse + *

Not implemented in PR2.

+ * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow + * expand/collapse only when the toggle tool button is clicked (defaults to true). When set to false, + * {@link #hideCollapseTool} should be false also. + */ + titleCollapse : true, + /** + * @cfg {Boolean} hideCollapseTool + * True to hide the contained Panels' collapse/expand toggle buttons, false to display them (defaults to false). + * When set to true, {@link #titleCollapse} is automatically set to true. + */ + hideCollapseTool : false, + /** + * @cfg {Boolean} collapseFirst + * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools + * in the contained Panels' title bars, false to render it last (defaults to false). + */ + collapseFirst : false, + /** + * @cfg {Boolean} animate + * True to slide the contained panels open and closed during expand/collapse using animation, false to open and + * close directly with no animation (defaults to true). Note: The layout performs animated collapsing + * and expanding, not the child Panels. + */ + animate : true, + /** + * @cfg {Boolean} activeOnTop + *

Not implemented in PR4.

+ *

Only valid when {@link #multi" is false.

+ * True to swap the position of each panel as it is expanded so that it becomes the first item in the container, + * false to keep the panels in the rendered order. This is NOT compatible with "animate:true" (defaults to false). + */ + activeOnTop : false, + /** + * @cfg {Boolean} multi + * Defaults to false. Set to true to enable multiple accordion items to be open at once. + */ + multi: false, + + constructor: function() { + var me = this; + + me.callParent(arguments); + + // animate flag must be false during initial render phase so we don't get animations. + me.initialAnimate = me.animate; + me.animate = false; + + // Child Panels are not absolutely positioned if we are not filling, so use a different itemCls. + if (me.fill === false) { + me.itemCls = Ext.baseCSSPrefix + 'accordion-item'; + } + }, + + // Cannot lay out a fitting accordion before we have been allocated a height. + // So during render phase, layout will not be performed. + beforeLayout: function() { + var me = this; + + me.callParent(arguments); + if (me.fill) { + if (!me.owner.el.dom.style.height) { + return false; + } + } else { + me.owner.componentLayout.monitorChildren = false; + me.autoSize = true; + me.owner.setAutoScroll(true); + } + }, + + renderItems : function(items, target) { + var me = this, + ln = items.length, + i = 0, + comp, + targetSize = me.getLayoutTargetSize(), + renderedPanels = [], + border; + + for (; i < ln; i++) { + comp = items[i]; + if (!comp.rendered) { + renderedPanels.push(comp); + + // Set up initial properties for Panels in an accordion. + if (me.collapseFirst) { + comp.collapseFirst = me.collapseFirst; + } + if (me.hideCollapseTool) { + comp.hideCollapseTool = me.hideCollapseTool; + comp.titleCollapse = true; + } + else if (me.titleCollapse) { + comp.titleCollapse = me.titleCollapse; + } + + delete comp.hideHeader; + comp.collapsible = true; + comp.title = comp.title || ' '; + comp.setBorder(false); + + // Set initial sizes + comp.width = targetSize.width; + if (me.fill) { + delete comp.height; + delete comp.flex; + + // If there is an expanded item, all others must be rendered collapsed. + if (me.expandedItem !== undefined) { + comp.collapsed = true; + } + // Otherwise expand the first item with collapsed explicitly configured as false + else if (comp.collapsed === false) { + comp.flex = 1; + me.expandedItem = i; + } else { + comp.collapsed = true; + } + } else { + delete comp.flex; + comp.animCollapse = me.initialAnimate; + comp.autoHeight = true; + comp.autoScroll = false; + } + } + } + + // If no collapsed:false Panels found, make the first one expanded. + if (ln && me.expandedItem === undefined) { + me.expandedItem = 0; + comp = items[0]; + comp.collapsed = false; + if (me.fill) { + comp.flex = 1; + } + } + + // Render all Panels. + me.callParent(arguments); + + // Postprocess rendered Panels. + ln = renderedPanels.length; + for (i = 0; i < ln; i++) { + comp = renderedPanels[i]; + + // Delete the dimension property so that our align: 'stretch' processing manages the width from here + delete comp.width; + + comp.header.addCls(Ext.baseCSSPrefix + 'accordion-hd'); + comp.body.addCls(Ext.baseCSSPrefix + 'accordion-body'); + + // If we are fitting, then intercept expand/collapse requests. + if (me.fill) { + me.owner.mon(comp, { + show: me.onComponentShow, + beforeexpand: me.onComponentExpand, + beforecollapse: me.onComponentCollapse, + scope: me + }); + } + } + }, + + onLayout: function() { + var me = this; + + me.updatePanelClasses(); + + if (me.fill) { + me.callParent(arguments); + } else { + var targetSize = me.getLayoutTargetSize(), + items = me.getVisibleItems(), + len = items.length, + i = 0, comp; + + for (; i < len; i++) { + comp = items[i]; + if (comp.collapsed) { + items[i].setWidth(targetSize.width); + } else { + items[i].setSize(null, null); + } + } + } + + return me; + }, + + updatePanelClasses: function() { + var children = this.getLayoutItems(), + ln = children.length, + siblingCollapsed = true, + i, child; + + for (i = 0; i < ln; i++) { + child = children[i]; + if (!siblingCollapsed) { + child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded'); + } + else { + child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded'); + } + if (i + 1 == ln && child.collapsed) { + child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed'); + } + else { + child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed'); + } + siblingCollapsed = child.collapsed; + } + }, + + // When a Component expands, adjust the heights of the other Components to be just enough to accommodate + // their headers. + // The expanded Component receives the only flex value, and so gets all remaining space. + onComponentExpand: function(toExpand) { + var me = this, + it = me.owner.items.items, + len = it.length, + i = 0, + comp; + + for (; i < len; i++) { + comp = it[i]; + if (comp === toExpand && comp.collapsed) { + me.setExpanded(comp); + } else if (!me.multi && (comp.rendered && comp.header.rendered && comp !== toExpand && !comp.collapsed)) { + me.setCollapsed(comp); + } + } + + me.animate = me.initialAnimate; + me.layout(); + me.animate = false; + return false; + }, + + onComponentCollapse: function(comp) { + var me = this, + toExpand = comp.next() || comp.prev(), + expanded = me.multi ? me.owner.query('>panel:not([collapsed])') : []; + + // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component, + // then ask the box layout to collapse it to its header. + if (me.multi) { + me.setCollapsed(comp); + + // If the collapsing Panel is the only expanded one, expand the following Component. + // All this is handling fill: true, so there must be at least one expanded, + if (expanded.length === 1 && expanded[0] === comp) { + me.setExpanded(toExpand); + } + + me.animate = me.initialAnimate; + me.layout(); + me.animate = false; + } + // Not allowing multi: expand the next sibling if possible, prev sibling if we collapsed the last + else if (toExpand) { + me.onComponentExpand(toExpand); + } + return false; + }, + + onComponentShow: function(comp) { + // Showing a Component means that you want to see it, so expand it. + this.onComponentExpand(comp); + }, + + setCollapsed: function(comp) { + var otherDocks = comp.getDockedItems(), + dockItem, + len = otherDocks.length, + i = 0; + + // Hide all docked items except the header + comp.hiddenDocked = []; + for (; i < len; i++) { + dockItem = otherDocks[i]; + if ((dockItem !== comp.header) && !dockItem.hidden) { + dockItem.hidden = true; + comp.hiddenDocked.push(dockItem); + } + } + comp.addCls(comp.collapsedCls); + comp.header.addCls(comp.collapsedHeaderCls); + comp.height = comp.header.getHeight(); + comp.el.setHeight(comp.height); + comp.collapsed = true; + delete comp.flex; + comp.fireEvent('collapse', comp); + if (comp.collapseTool) { + comp.collapseTool.setType('expand-' + comp.getOppositeDirection(comp.collapseDirection)); + } + }, + + setExpanded: function(comp) { + var otherDocks = comp.hiddenDocked, + len = otherDocks ? otherDocks.length : 0, + i = 0; + + // Show temporarily hidden docked items + for (; i < len; i++) { + otherDocks[i].hidden = false; + } + + // If it was an initial native collapse which hides the body + if (!comp.body.isVisible()) { + comp.body.show(); + } + delete comp.collapsed; + delete comp.height; + delete comp.componentLayout.lastComponentSize; + comp.suspendLayout = false; + comp.flex = 1; + comp.removeCls(comp.collapsedCls); + comp.header.removeCls(comp.collapsedHeaderCls); + comp.fireEvent('expand', comp); + if (comp.collapseTool) { + comp.collapseTool.setType('collapse-' + comp.collapseDirection); + } + comp.setAutoScroll(comp.initialConfig.autoScroll); + } +}); \ No newline at end of file