/* Copyright (c) 2011, Geert Bergman (geert@scrivo.nl) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of "Scrivo" nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $Id: Accordion.js 743 2013-07-18 10:12:39Z geert $ */ "use strict"; SUI.Accordion = SUI.defineClass( /** @lends SUI.Accordion.prototype */{ /** @ignore */ baseClass: SUI.Box, /** * @class * SUI.Accordion is a component that helps you to save space by stacking a * number of panels/boxes and let the user swap them. It's sort of a * vertical tab list. The SUI.Accordion object can host a number of SUI * boxes, each of them has a title bar for the user to identify and select * them. * * @augments SUI.Box * * @description * Construct a SUI.Accordion object. The titles of the headers and * (optional) the client boxes are given as argument to this constructor. * It is also possible to select the initial box. * * @constructs * @param {inherit} arg An argument object. * @param {Object[]} arg.items An array of object containing data for the * accordion items. * @param {String} [arg.items[].title="Item x"] Header title. * @param {SUI.Box} [arg.items[].box] A client box. * @param {int} [arg.selected] Index of the client box that is initially * shown. */ initializer: function(arg) { // anchors default to all sides if (!arg.anchor) { arg.anchor = {left:true,right:true,top:true,bottom:true}; } SUI.Accordion.initializeBase(this, arg); if (!arg.selected) { arg.selected = 0; } this._buildControl(arg); }, /** * Line thickness of the lines separating the different parts of the * control. * @constant * @type int * @private */ ITEM_BORDER_BOTTOM_WIDTH: 1, /** * Height of an header. * @constant * @type int * @private */ ITEM_HEIGHT: 24, /** * Padding top of text and icon in the header. * @constant * @type int * @private */ ITEM_PADDING_TOP: 4, /** * Padding left and right of text and icon in the header. * @constant * @type int * @private */ ITEM_PADDING_LR: 5, /** * Size (width/height) of the icon. * @constant * @type int * @private */ ICON_SIZE: 16, /** * Add a box to one of the client areas of the control. * @param {SUI.Box} child The box to add to the control. * @param {int} i Index position of the client container to at the box to. */ add: function(child, i) { this._items[i].content.add(child); }, /** * Display the accordion control. Set the CSS size and position of all the * headers and for the currently displayed content box. */ display: function() { // size and position of the current control this.setDim(); // size and position of the header for (var i=0; i<this._items.length; i++) { this._items[i].header.setDim(); this._items[i].headertext.setDim(); } // and the client area this._selectedItem.content.display(); }, /** * Lay out the accordion control. Calculate the size and position of all * the headers and for the currently displayed content box. */ layOut: function() { for (var i=0, t=0; i<this._items.length; i++) { // get the size and position of all the header parts this._items[i].header.setRect( t, 0, this.width(), this.ITEM_HEIGHT ); this._items[i].headertext.setRect( this.ITEM_PADDING_TOP, this.ITEM_PADDING_LR, this.width() - 2 * this.ITEM_PADDING_LR - this.ICON_SIZE, this.ITEM_HEIGHT - this.ITEM_PADDING_TOP ); // set CSS positions of the icon SUI.style.setRect(this._items[i].headersign, this.ITEM_PADDING_TOP, this.width() - this.ITEM_PADDING_LR - this.ICON_SIZE, this.ICON_SIZE, this.ICON_SIZE ); // increase top with header height t += this.ITEM_HEIGHT; // by default we use a close sign ... this._items[i].headersign.src = SUI.imgDir + "/" + SUI.resource.acClosed; // ... and do not display the content this._items[i].content.el().style.display = "none"; // but if the item is selected ... if (this._selectedItem === this._items[i]) { // ... then show the open sign ... this._items[i].headersign.src = SUI.imgDir + "/" + SUI.resource.acDown; // ... and display the content this._items[i].content.el().style.display = "block"; // get the available height for the content ... var h = this._contentHeight(); // ... and use it to set the client area of the control this._items[i].content.setRect(t, 0, this.width(), h); // ... and to set the new top t += h; } } // and layOut the control's client area this._selectedItem.content.layOut(); }, /** * The list of accordion items. * @type Object[] * @private */ _items: null, /** * Reference to the currently selected item. * @type Object * @private */ _selectedItem: null, /** * Add the onclick event handler on the header. * @param {Object} item Accordion item object. * @private */ _addOnClickHeader: function(item) { var that = this; // 'that' and 'item' are two closures SUI.browser.addEventListener(item.header.el(), "click", function(e) { if (!that._doClick(item)) { SUI.browser.noPropagation(e); } } ); }, /** * Make all required boxes for the control. * @param {Object[]} arg Argument object as passed to the constructor. * @private */ _buildControl: function(arg) { // start with an empty list this._items = []; // and no selected item this._selectedItem = null; // read in the items form the arguments object and store them in // a standardized way in the item list for (var i=0; i<arg.items.length; i++) { // start with a default profile var def = { title: "Item "+i, box: null }; for (var prop in arg.items[i]) { // and overwrite the default profile with the entries set // in the arguments if (arg.items[i].hasOwnProperty(prop)) { def[prop] = arg.items[i][prop]; } } this._items.push(def); } // Work trough the items and make the necessary boxes for (var i=0; i<this._items.length; i++) { // create a header box which gets the onclick this._items[i].header = new SUI.Box({parent: this}); this._addOnClickHeader(this._items[i]); this._items[i].header.addClass("sui-ac-header"); this._items[i].header.border( new SUI.Border(0, 0, this.ITEM_BORDER_BOTTOM_WIDTH)); // a simple box for the header text this._items[i].headertext = new SUI.Box( {parent: this._items[i].header}); this._items[i].headertext.el().innerHTML = this._items[i].title; // and add the open/close icon to the header this._items[i].headersign = SUI.browser.createElement("IMG"); this._items[i].header.el().appendChild(this._items[i].headersign); // create a container to use as client panel this._items[i].content = new SUI.AnchorLayout({parent: this}); this._items[i].content.addClass("sui-ac-content"); this._items[i].content.border( new SUI.Border(0, 0, this.ITEM_BORDER_BOTTOM_WIDTH)); this._items[i].content.el().style.overflow = "auto"; this._items[i].content.el().style.display = "none"; // if there is already content, then add it to the container if (this._items[i].box) { this.add(this._items[i].box, i); } // if this is the one, set the selected item if (i === arg.selected) { this._selectedItem = this._items[i]; } } if (!this._selectedItem) { this._selectedItem = this._items[0]; } }, /** * On onclick set the selected item and redraw the control. * @param {Object} item Accordion item object. * @private */ _doClick: function(item) { this._selectedItem = item; this.draw(); }, /** * Calculate the available height for the content box. * @return {int} Available height for the content box. * @private */ _contentHeight: function() { return this.height() - this._items.length * this.ITEM_HEIGHT + this.ITEM_BORDER_BOTTOM_WIDTH; } });