/* * jquery.layout 1.2.0 * * Copyright (c) 2008 * Fabrizio Balliano (http://www.fabrizioballiano.net) * Kevin Dalman (http://allpro.net) * * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. * * $Date: 2008-12-27 02:17:22 +0100 (sab, 27 dic 2008) $ * $Rev: 203 $ * * NOTE: For best code readability, view this with a fixed-space font and tabs equal to 4-chars */ (function($) { $.fn.layout = function (opts) { /* * ########################### * WIDGET CONFIG & OPTIONS * ########################### */ // DEFAULTS for options var prefix = "ui-layout-" // prefix for ALL selectors and classNames , defaults = { // misc default values paneClass: prefix+"pane" // ui-layout-pane , resizerClass: prefix+"resizer" // ui-layout-resizer , togglerClass: prefix+"toggler" // ui-layout-toggler , togglerInnerClass: prefix+"" // ui-layout-open / ui-layout-closed , buttonClass: prefix+"button" // ui-layout-button , contentSelector: "."+prefix+"content"// ui-layout-content , contentIgnoreSelector: "."+prefix+"ignore" // ui-layout-mask } ; // DEFAULT PANEL OPTIONS - CHANGE IF DESIRED var options = { name: "" // FUTURE REFERENCE - not used right now , scrollToBookmarkOnLoad: true // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark) , defaults: { // default options for 'all panes' - will be overridden by 'per-pane settings' applyDefaultStyles: false // apply basic styles directly to resizers & buttons? If not, then stylesheet must handle it , closable: true // pane can open & close , resizable: true // when open, pane can be resized , slidable: true // when closed, pane can 'slide' open over other panes - closes on mouse-out //, paneSelector: [ ] // MUST be pane-specific! , contentSelector: defaults.contentSelector // INNER div/element to auto-size so only it scrolls, not the entire pane! , contentIgnoreSelector: defaults.contentIgnoreSelector // elem(s) to 'ignore' when measuring 'content' , paneClass: defaults.paneClass // border-Pane - default: 'ui-layout-pane' , resizerClass: defaults.resizerClass // Resizer Bar - default: 'ui-layout-resizer' , togglerClass: defaults.togglerClass // Toggler Button - default: 'ui-layout-toggler' , buttonClass: defaults.buttonClass // CUSTOM Buttons - default: 'ui-layout-button-toggle/-open/-close/-pin' , resizerDragOpacity: 1 // option for ui.draggable //, resizerCursor: "" // MUST be pane-specific - cursor when over resizer-bar , maskIframesOnResize: true // true = all iframes OR = iframe-selector(s) - adds masking-div during resizing/dragging //, size: 100 // inital size of pane - defaults are set 'per pane' , minSize: 0 // when manually resizing a pane , maxSize: 0 // ditto, 0 = no limit , spacing_open: 6 // space between pane and adjacent panes - when pane is 'open' , spacing_closed: 6 // ditto - when pane is 'closed' , togglerLength_open: 50 // Length = WIDTH of toggler button on north/south edges - HEIGHT on east/west edges , togglerLength_closed: 50 // 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden' , togglerAlign_open: "center" // top/left, bottom/right, center, OR... , togglerAlign_closed: "center" // 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right , togglerTip_open: "Close" // Toggler tool-tip (title) , togglerTip_closed: "Open" // ditto , resizerTip: "Resize" // Resizer tool-tip (title) , sliderTip: "Slide Open" // resizer-bar triggers 'sliding' when pane is closed , sliderCursor: "pointer" // cursor when resizer-bar will trigger 'sliding' , slideTrigger_open: "click" // click, dblclick, mouseover , slideTrigger_close: "mouseout" // click, mouseout , hideTogglerOnSlide: false // when pane is slid-open, should the toggler show? , togglerContent_open: "" // text or HTML to put INSIDE the toggler , togglerContent_closed: "" // ditto , showOverflowOnHover: false // will bind allowOverflow() utility to pane.onMouseOver , enableCursorHotkey: true // enabled 'cursor' hotkeys //, customHotkey: "" // MUST be pane-specific - EITHER a charCode OR a character , customHotkeyModifier: "SHIFT" // either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT' // NOTE: fxSss_open & fxSss_close options (eg: fxName_open) are auto-generated if not passed , fxName: "slide" // ('none' or blank), slide, drop, scale , fxSpeed: null // slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration , fxSettings: {} // can be passed, eg: { easing: "easeOutBounce", duration: 1500 } , initClosed: false // true = init pane as 'closed' , initHidden: false // true = init pane as 'hidden' - no resizer or spacing /* callback options do not have to be set - listed here for reference only , onshow_start: "" // CALLBACK when pane STARTS to Show - BEFORE onopen/onhide_start , onshow_end: "" // CALLBACK when pane ENDS being Shown - AFTER onopen/onhide_end , onhide_start: "" // CALLBACK when pane STARTS to Close - BEFORE onclose_start , onhide_end: "" // CALLBACK when pane ENDS being Closed - AFTER onclose_end , onopen_start: "" // CALLBACK when pane STARTS to Open , onopen_end: "" // CALLBACK when pane ENDS being Opened , onclose_start: "" // CALLBACK when pane STARTS to Close , onclose_end: "" // CALLBACK when pane ENDS being Closed , onresize_start: "" // CALLBACK when pane STARTS to be ***MANUALLY*** Resized , onresize_end: "" // CALLBACK when pane ENDS being Resized ***FOR ANY REASON*** */ } , north: { paneSelector: "."+prefix+"north" // default = .ui-layout-north , size: "auto" , resizerCursor: "n-resize" } , south: { paneSelector: "."+prefix+"south" // default = .ui-layout-south , size: "auto" , resizerCursor: "s-resize" } , east: { paneSelector: "."+prefix+"east" // default = .ui-layout-east , size: 200 , resizerCursor: "e-resize" } , west: { paneSelector: "."+prefix+"west" // default = .ui-layout-west , size: 260 , resizerCursor: "w-resize" } , center: { paneSelector: "."+prefix+"center" // default = .ui-layout-center } }; var effects = { // LIST *PREDEFINED EFFECTS* HERE, even if effect has no settings slide: { all: { duration: "fast" } // eg: duration: 1000, easing: "easeOutBounce" , north: { direction: "up" } , south: { direction: "down" } , east: { direction: "right"} , west: { direction: "left" } } , drop: { all: { duration: "slow" } // eg: duration: 1000, easing: "easeOutQuint" , north: { direction: "up" } , south: { direction: "down" } , east: { direction: "right"} , west: { direction: "left" } } , scale: { all: { duration: "fast" } } }; // STATIC, INTERNAL CONFIG - DO NOT CHANGE THIS! var config = { allPanes: "north,south,east,west,center" , borderPanes: "north,south,east,west" , zIndex: { // set z-index values here resizer_normal: 1 // normal z-index for resizer-bars , pane_normal: 2 // normal z-index for panes , mask: 4 // overlay div used to mask pane(s) during resizing , sliding: 100 // applied to both the pane and its resizer when a pane is 'slid open' , resizing: 10000 // applied to the CLONED resizer-bar when being 'dragged' , animation: 10000 // applied to the pane when being animated - not applied to the resizer } , resizers: { cssReq: { position: "absolute" , padding: 0 , margin: 0 , fontSize: "1px" , textAlign: "left" // to counter-act "center" alignment! , overflow: "hidden" // keep toggler button from overflowing , zIndex: 1 } , cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true background: "#DDD" , border: "none" } } , togglers: { cssReq: { position: "absolute" , display: "block" , padding: 0 , margin: 0 , overflow: "hidden" , textAlign: "center" , fontSize: "1px" , cursor: "pointer" , zIndex: 1 } , cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true background: "#AAA" } } , content: { cssReq: { overflow: "auto" } , cssDef: {} } , defaults: { // defaults for ALL panes - overridden by 'per-pane settings' below cssReq: { position: "absolute" , margin: 0 , zIndex: 2 } , cssDef: { padding: "10px" , background: "#FFF" , border: "1px solid #BBB" , overflow: "auto" } } , north: { edge: "top" , sizeType: "height" , dir: "horz" , cssReq: { top: 0 , bottom: "auto" , left: 0 , right: 0 , width: "auto" // height: DYNAMIC } } , south: { edge: "bottom" , sizeType: "height" , dir: "horz" , cssReq: { top: "auto" , bottom: 0 , left: 0 , right: 0 , width: "auto" // height: DYNAMIC } } , east: { edge: "right" , sizeType: "width" , dir: "vert" , cssReq: { left: "auto" , right: 0 , top: "auto" // DYNAMIC , bottom: "auto" // DYNAMIC , height: "auto" // width: DYNAMIC } } , west: { edge: "left" , sizeType: "width" , dir: "vert" , cssReq: { left: 0 , right: "auto" , top: "auto" // DYNAMIC , bottom: "auto" // DYNAMIC , height: "auto" // width: DYNAMIC } } , center: { dir: "center" , cssReq: { left: "auto" // DYNAMIC , right: "auto" // DYNAMIC , top: "auto" // DYNAMIC , bottom: "auto" // DYNAMIC , height: "auto" , width: "auto" } } }; // DYNAMIC DATA var state = { // generate random 'ID#' to identify layout - used to create global namespace for timers id: Math.floor(Math.random() * 10000) , container: {} , north: {} , south: {} , east: {} , west: {} , center: {} }; var altEdge = { top: "bottom" , bottom: "top" , left: "right" , right: "left" } , altSide = { north: "south" , south: "north" , east: "west" , west: "east" } ; /* * ########################### * INTERNAL HELPER FUNCTIONS * ########################### */ /** * isStr * * Returns true if passed param is EITHER a simple string OR a 'string object' - otherwise returns false */ var isStr = function (o) { if (typeof o == "string") return true; else if (typeof o == "object") { try { var match = o.constructor.toString().match(/string/i); return (match !== null); } catch (e) {} } return false; }; /** * str * * Returns a simple string if the passed param is EITHER a simple string OR a 'string object', * else returns the original object */ var str = function (o) { if (typeof o == "string" || isStr(o)) return $.trim(o); // trim converts 'String object' to a simple string else return o; }; /** * min / max * * Alias for Math.min/.max to simplify coding */ var min = function (x,y) { return Math.min(x,y); }; var max = function (x,y) { return Math.max(x,y); }; /** * transformData * * Processes the options passed in and transforms them into the format used by layout() * Missing keys are added, and converts the data if passed in 'flat-format' (no sub-keys) * In flat-format, pane-specific-settings are prefixed like: north__optName (2-underscores) * To update effects, options MUST use nested-keys format, with an effects key * * @callers initOptions() * @params JSON d Data/options passed by user - may be a single level or nested levels * @returns JSON Creates a data struture that perfectly matches 'options', ready to be imported */ var transformData = function (d) { var json = { defaults:{fxSettings:{}}, north:{fxSettings:{}}, south:{fxSettings:{}}, east:{fxSettings:{}}, west:{fxSettings:{}}, center:{fxSettings:{}} }; d = d || {}; if (d.effects || d.defaults || d.north || d.south || d.west || d.east || d.center) json = $.extend( json, d ); // already in json format - add to base keys else // convert 'flat' to 'nest-keys' format - also handles 'empty' user-options $.each( d, function (key,val) { a = key.split("__"); json[ a[1] ? a[0] : "defaults" ][ a[1] ? a[1] : a[0] ] = val; }); return json; }; /** * setFlowCallback * * Set an INTERNAL callback to avoid simultaneous animation * Runs only if needed and only if all callbacks are not 'already set'! * * @param String action Either 'open' or 'close' * @pane String pane A valid border-pane name, eg 'west' * @pane Boolean param Extra param for callback (optional) */ var setFlowCallback = function (action, pane, param) { var cb = action +","+ pane +","+ (param ? 1 : 0) , cP, cbPane ; $.each(c.borderPanes.split(","), function (i,p) { if (c[p].isMoving) { bindCallback(p); // TRY to bind a callback return false; // BREAK } }); function bindCallback (p, test) { cP = c[p]; if (!cP.doCallback) { cP.doCallback = true; cP.callback = cb; } else { // try to 'chain' this callback cpPane = cP.callback.split(",")[1]; // 2nd param is 'pane' if (cpPane != p && cpPane != pane) // callback target NOT 'itself' and NOT 'this pane' bindCallback (cpPane, true); // RECURSE } } }; /** * execFlowCallback * * RUN the INTERNAL callback for this pane - if one exists * * @param String action Either 'open' or 'close' * @pane String pane A valid border-pane name, eg 'west' * @pane Boolean param Extra param for callback (optional) */ var execFlowCallback = function (pane) { var cP = c[pane]; // RESET flow-control flaGs c.isLayoutBusy = false; delete cP.isMoving; if (!cP.doCallback || !cP.callback) return; cP.doCallback = false; // RESET logic flag // EXECUTE the callback var cb = cP.callback.split(",") , param = (cb[2] > 0 ? true : false) ; if (cb[0] == "open") open( cb[1], param ); else if (cb[0] == "close") close( cb[1], param ); if (!cP.doCallback) cP.callback = null; // RESET - unless callback above enabled it again! }; /** * execUserCallback * * Executes a Callback function after a trigger event, like resize, open or close * * @param String pane This is passed only so we can pass the 'pane object' to the callback * @param String v_fn Accepts a function name, OR a comma-delimited array: [0]=function name, [1]=argument */ var execUserCallback = function (pane, v_fn) { if (!v_fn) return; var fn; try { if (typeof v_fn == "function") fn = v_fn; else if (typeof v_fn != "string") return; else if (v_fn.indexOf(",") > 0) { // function name cannot contain a comma, so must be a function name AND a 'name' parameter var args = v_fn.split(",") , fn = eval(args[0]) ; if (typeof fn=="function" && args.length > 1) return fn(args[1]); // pass the argument parsed from 'list' } else // just the name of an external function? fn = eval(v_fn); if (typeof fn=="function") // pass data: pane-name, pane-element, pane-state, pane-options, and layout-name return fn( pane, $Ps[pane], $.extend({},state[pane]), $.extend({},options[pane]), options.name ); } catch (ex) {} }; /** * cssNum * * Returns the 'current CSS value' for an element - returns 0 if property does not exist * * @callers Called by many methods * @param jQuery $Elem Must pass a jQuery object - first element is processed * @param String property The name of the CSS property, eg: top, width, etc. * @returns Variant Usually is used to get an integer value for position (top, left) or size (height, width) */ var cssNum = function ($E, prop) { var val = 0 , hidden = false , visibility = "" ; if (!$.browser.msie) { // IE CAN read dimensions of 'hidden' elements - FF CANNOT if ($.curCSS($E[0], "display", true) == "none") { hidden = true; visibility = $.curCSS($E[0], "visibility", true); // SAVE current setting $E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so we can measure it } } val = parseInt($.curCSS($E[0], prop, true), 10) || 0; if (hidden) { // WAS hidden, so put back the way it was $E.css({ display: "none" }); if (visibility && visibility != "hidden") $E.css({ visibility: visibility }); // reset 'visibility' } return val; }; /** * cssW / cssH / cssSize * * Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype * * @callers initPanes(), sizeMidPanes(), initHandles(), sizeHandles() * @param Variant elem Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object * @param Integer outerWidth/outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized * @returns Integer Returns the innerHeight of the elem by subtracting padding and borders * * @TODO May need to add additional logic to handle more browser/doctype variations? */ var cssW = function (e, outerWidth) { var $E; if (isStr(e)) { e = str(e); $E = $Ps[e]; } else $E = $(e); // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed if (outerWidth <= 0) return 0; else if (!(outerWidth>0)) outerWidth = isStr(e) ? getPaneSize(e) : $E.outerWidth(); if (!$.boxModel) return outerWidth; else // strip border and padding size from outerWidth to get CSS Width return outerWidth - cssNum($E, "paddingLeft") - cssNum($E, "paddingRight") - ($.curCSS($E[0], "borderLeftStyle", true) == "none" ? 0 : cssNum($E, "borderLeftWidth")) - ($.curCSS($E[0], "borderRightStyle", true) == "none" ? 0 : cssNum($E, "borderRightWidth")) ; }; var cssH = function (e, outerHeight) { var $E; if (isStr(e)) { e = str(e); $E = $Ps[e]; } else $E = $(e); // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed if (outerHeight <= 0) return 0; else if (!(outerHeight>0)) outerHeight = (isStr(e)) ? getPaneSize(e) : $E.outerHeight(); if (!$.boxModel) return outerHeight; else // strip border and padding size from outerHeight to get CSS Height return outerHeight - cssNum($E, "paddingTop") - cssNum($E, "paddingBottom") - ($.curCSS($E[0], "borderTopStyle", true) == "none" ? 0 : cssNum($E, "borderTopWidth")) - ($.curCSS($E[0], "borderBottomStyle", true) == "none" ? 0 : cssNum($E, "borderBottomWidth")) ; }; var cssSize = function (pane, outerSize) { if (c[pane].dir=="horz") // pane = north or south return cssH(pane, outerSize); else // pane = east or west return cssW(pane, outerSize); }; /** * getPaneSize * * Calculates the current 'size' (width or height) of a border-pane - optionally with 'pane spacing' added * * @returns Integer Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser */ var getPaneSize = function (pane, inclSpace) { var $P = $Ps[pane] , o = options[pane] , s = state[pane] , oSp = (inclSpace ? o.spacing_open : 0) , cSp = (inclSpace ? o.spacing_closed : 0) ; if (!$P || s.isHidden) return 0; else if (s.isClosed || (s.isSliding && inclSpace)) return cSp; else if (c[pane].dir == "horz") return $P.outerHeight() + oSp; else // dir == "vert" return $P.outerWidth() + oSp; }; var setPaneMinMaxSizes = function (pane) { var d = cDims , edge = c[pane].edge , dir = c[pane].dir , o = options[pane] , s = state[pane] , $P = $Ps[pane] , $altPane = $Ps[ altSide[pane] ] , paneSpacing = o.spacing_open , altPaneSpacing = options[ altSide[pane] ].spacing_open , altPaneSize = (!$altPane ? 0 : (dir=="horz" ? $altPane.outerHeight() : $altPane.outerWidth())) , containerSize = (dir=="horz" ? d.innerHeight : d.innerWidth) // limitSize prevents this pane from 'overlapping' opposite pane - even if opposite pane is currently closed , limitSize = containerSize - paneSpacing - altPaneSize - altPaneSpacing , minSize = s.minSize || 0 , maxSize = Math.min(s.maxSize || 9999, limitSize) , minPos, maxPos // used to set resizing limits ; switch (pane) { case "north": minPos = d.offsetTop + minSize; maxPos = d.offsetTop + maxSize; break; case "west": minPos = d.offsetLeft + minSize; maxPos = d.offsetLeft + maxSize; break; case "south": minPos = d.offsetTop + d.innerHeight - maxSize; maxPos = d.offsetTop + d.innerHeight - minSize; break; case "east": minPos = d.offsetLeft + d.innerWidth - maxSize; maxPos = d.offsetLeft + d.innerWidth - minSize; break; } // save data to pane-state $.extend(s, { minSize: minSize, maxSize: maxSize, minPosition: minPos, maxPosition: maxPos }); }; /** * getPaneDims * * Returns data for setting the size/position of center pane. Date is also used to set Height for east/west panes * * @returns JSON Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height */ var getPaneDims = function () { var d = { top: getPaneSize("north", true) // true = include 'spacing' value for p , bottom: getPaneSize("south", true) , left: getPaneSize("west", true) , right: getPaneSize("east", true) , width: 0 , height: 0 }; with (d) { width = cDims.innerWidth - left - right; height = cDims.innerHeight - bottom - top; // now add the 'container border/padding' to get final positions - relative to the container top += cDims.top; bottom += cDims.bottom; left += cDims.left; right += cDims.right; } return d; }; /** * getElemDims * * Returns data for setting size of an element (container or a pane). * * @callers create(), onWindowResize() for container, plus others for pane * @returns JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc */ var getElemDims = function ($E) { var d = {} // dimensions hash , e, b, p // edge, border, padding ; $.each("Left,Right,Top,Bottom".split(","), function () { e = str(this); b = d["border" +e] = cssNum($E, "border"+e+"Width"); p = d["padding"+e] = cssNum($E, "padding"+e); d["offset" +e] = b + p; // total offset of content from outer edge // if BOX MODEL, then 'position' = PADDING (ignore borderWidth) if ($E == $Container) d[e.toLowerCase()] = ($.boxModel ? p : 0); }); d.innerWidth = d.outerWidth = $E.outerWidth(); d.innerHeight = d.outerHeight = $E.outerHeight(); if ($.boxModel) { d.innerWidth -= (d.offsetLeft + d.offsetRight); d.innerHeight -= (d.offsetTop + d.offsetBottom); } return d; }; var setTimer = function (pane, action, fn, ms) { var Layout = window.layout = window.layout || {} , Timers = Layout.timers = Layout.timers || {} , name = "layout_"+ state.id +"_"+ pane +"_"+ action // UNIQUE NAME for every layout-pane-action ; if (Timers[name]) return; // timer already set! else Timers[name] = setTimeout(fn, ms); }; var clearTimer = function (pane, action) { var Layout = window.layout = window.layout || {} , Timers = Layout.timers = Layout.timers || {} , name = "layout_"+ state.id +"_"+ pane +"_"+ action // UNIQUE NAME for every layout-pane-action ; if (Timers[name]) { clearTimeout( Timers[name] ); delete Timers[name]; return true; } else return false; }; /* * ########################### * INITIALIZATION METHODS * ########################### */ /** * create * * Initialize the layout - called automatically whenever an instance of layout is created * * @callers NEVER explicity called * @returns An object pointer to the instance created */ var create = function () { // initialize config/options initOptions(); // initialize all objects initContainer(); // set CSS as needed and init state.container dimensions initPanes(); // size & position all panes initHandles(); // create and position all resize bars & togglers buttons initResizable(); // activate resizing on all panes where resizable=true sizeContent("all"); // AFTER panes & handles have been initialized, size 'content' divs if (options.scrollToBookmarkOnLoad) with (self.location) if (hash) replace( hash ); // scrollTo Bookmark // bind hotkey function - keyDown - if required initHotkeys(); // bind resizeAll() for 'this layout instance' to window.resize event $(window).resize(function () { var timerID = "timerLayout_"+state.id; if (window[timerID]) clearTimeout(window[timerID]); window[timerID] = null; if (true || $.browser.msie) // use a delay for IE because the resize event fires repeatly window[timerID] = setTimeout(resizeAll, 100); else // most other browsers have a built-in delay before firing the resize event resizeAll(); // resize all layout elements NOW! }); }; /** * initContainer * * Validate and initialize container CSS and events * * @callers create() */ var initContainer = function () { try { // format html/body if this is a full page layout if ($Container[0].tagName == "BODY") { $("html").css({ height: "100%" , overflow: "hidden" }); $("body").css({ position: "relative" , height: "100%" , overflow: "hidden" , margin: 0 , padding: 0 // TODO: test whether body-padding could be handled? , border: "none" // a body-border creates problems because it cannot be measured! }); } else { // set required CSS - overflow and position var CSS = { overflow: "hidden" } // make sure container will not 'scroll' , p = $Container.css("position") , h = $Container.css("height") ; // if this is a NESTED layout, then outer-pane ALREADY has position and height if (!$Container.hasClass("ui-layout-pane")) { if (!p || "fixed,absolute,relative".indexOf(p) < 0) CSS.position = "relative"; // container MUST have a 'position' if (!h || h=="auto") CSS.height = "100%"; // container MUST have a 'height' } $Container.css( CSS ); } } catch (ex) {} // get layout-container dimensions (updated when necessary) cDims = state.container = getElemDims( $Container ); // update data-pointer too }; /** * initHotkeys * * Bind layout hotkeys - if options enabled * * @callers create() */ var initHotkeys = function () { // bind keyDown to capture hotkeys, if option enabled for ANY pane $.each(c.borderPanes.split(","), function (i,pane) { var o = options[pane]; if (o.enableCursorHotkey || o.customHotkey) { $(document).keydown( keyDown ); // only need to bind this ONCE return false; // BREAK - binding was done } }); }; /** * initOptions * * Build final CONFIG and OPTIONS data * * @callers create() */ var initOptions = function () { // simplify logic by making sure passed 'opts' var has basic keys opts = transformData( opts ); // update default effects, if case user passed key if (opts.effects) { $.extend( effects, opts.effects ); delete opts.effects; } // see if any 'global options' were specified $.each("name,scrollToBookmarkOnLoad".split(","), function (idx,key) { if (opts[key] !== undefined) options[key] = opts[key]; else if (opts.defaults[key] !== undefined) { options[key] = opts.defaults[key]; delete opts.defaults[key]; } }); // remove any 'defaults' that MUST be set 'per-pane' $.each("paneSelector,resizerCursor,customHotkey".split(","), function (idx,key) { delete opts.defaults[key]; } // is OK if key does not exist ); // now update options.defaults $.extend( options.defaults, opts.defaults ); // make sure required sub-keys exist //if (typeof options.defaults.fxSettings != "object") options.defaults.fxSettings = {}; // merge all config & options for the 'center' pane c.center = $.extend( true, {}, c.defaults, c.center ); $.extend( options.center, opts.center ); // Most 'default options' do not apply to 'center', so add only those that DO var o_Center = $.extend( true, {}, options.defaults, opts.defaults, options.center ); // TEMP data $.each("paneClass,contentSelector,contentIgnoreSelector,applyDefaultStyles,showOverflowOnHover".split(","), function (idx,key) { options.center[key] = o_Center[key]; } ); var defs = options.defaults; // create a COMPLETE set of options for EACH border-pane $.each(c.borderPanes.split(","), function(i,pane) { // apply 'pane-defaults' to CONFIG.PANE c[pane] = $.extend( true, {}, c.defaults, c[pane] ); // apply 'pane-defaults' + user-options to OPTIONS.PANE o = options[pane] = $.extend( true, {}, options.defaults, options[pane], opts.defaults, opts[pane] ); // make sure we have base-classes if (!o.paneClass) o.paneClass = defaults.paneClass; if (!o.resizerClass) o.resizerClass = defaults.resizerClass; if (!o.togglerClass) o.togglerClass = defaults.togglerClass; // create FINAL fx options for each pane, ie: options.PANE.fxName/fxSpeed/fxSettings[_open|_close] $.each(["_open","_close",""], function (i,n) { var sName = "fxName"+n , sSpeed = "fxSpeed"+n , sSettings = "fxSettings"+n ; // recalculate fxName according to specificity rules o[sName] = opts[pane][sName] // opts.west.fxName_open || opts[pane].fxName // opts.west.fxName || opts.defaults[sName] // opts.defaults.fxName_open || opts.defaults.fxName // opts.defaults.fxName || o[sName] // options.west.fxName_open || o.fxName // options.west.fxName || defs[sName] // options.defaults.fxName_open || defs.fxName // options.defaults.fxName || "none" ; // validate fxName to be sure is a valid effect var fxName = o[sName]; if (fxName == "none" || !$.effects || !$.effects[fxName] || (!effects[fxName] && !o[sSettings] && !o.fxSettings)) fxName = o[sName] = "none"; // effect not loaded, OR undefined FX AND fxSettings not passed // set vars for effects subkeys to simplify logic var fx = effects[fxName] || {} // effects.slide , fx_all = fx.all || {} // effects.slide.all , fx_pane = fx[pane] || {} // effects.slide.west ; // RECREATE the fxSettings[_open|_close] keys using specificity rules o[sSettings] = $.extend( {} , fx_all // effects.slide.all , fx_pane // effects.slide.west , defs.fxSettings || {} // options.defaults.fxSettings , defs[sSettings] || {} // options.defaults.fxSettings_open , o.fxSettings // options.west.fxSettings , o[sSettings] // options.west.fxSettings_open , opts.defaults.fxSettings // opts.defaults.fxSettings , opts.defaults[sSettings] || {} // opts.defaults.fxSettings_open , opts[pane].fxSettings // opts.west.fxSettings , opts[pane][sSettings] || {} // opts.west.fxSettings_open ); // recalculate fxSpeed according to specificity rules o[sSpeed] = opts[pane][sSpeed] // opts.west.fxSpeed_open || opts[pane].fxSpeed // opts.west.fxSpeed (pane-default) || opts.defaults[sSpeed] // opts.defaults.fxSpeed_open || opts.defaults.fxSpeed // opts.defaults.fxSpeed || o[sSpeed] // options.west.fxSpeed_open || o[sSettings].duration // options.west.fxSettings_open.duration || o.fxSpeed // options.west.fxSpeed || o.fxSettings.duration // options.west.fxSettings.duration || defs.fxSpeed // options.defaults.fxSpeed || defs.fxSettings.duration// options.defaults.fxSettings.duration || fx_pane.duration // effects.slide.west.duration || fx_all.duration // effects.slide.all.duration || "normal" // DEFAULT ; // DEBUG: if (pane=="east") debugData( $.extend({}, {speed: o[sSpeed], fxSettings_duration: o[sSettings].duration}, o[sSettings]), pane+"."+sName+" = "+fxName ); }); }); }; /** * initPanes * * Initialize module objects, styling, size and position for all panes * * @callers create() */ var initPanes = function () { // NOTE: do north & south FIRST so we can measure their height - do center LAST $.each(c.allPanes.split(","), function() { var pane = str(this) , o = options[pane] , s = state[pane] , fx = s.fx , dir = c[pane].dir // if o.size is not > 0, then we will use MEASURE the pane and use that as it's 'size' , size = o.size=="auto" || isNaN(o.size) ? 0 : o.size , minSize = o.minSize || 1 , maxSize = o.maxSize || 9999 , spacing = o.spacing_open || 0 , sel = o.paneSelector , isIE6 = ($.browser.msie && $.browser.version < 7) , CSS = {} , $P, $C ; $Cs[pane] = false; // init if (sel.substr(0,1)==="#") // ID selector // NOTE: elements selected 'by ID' DO NOT have to be 'children' $P = $Ps[pane] = $Container.find(sel+":first"); else { // class or other selector $P = $Ps[pane] = $Container.children(sel+":first"); // look for the pane nested inside a 'form' element if (!$P.length) $P = $Ps[pane] = $Container.children("form:first").children(sel+":first"); } if (!$P.length) { $Ps[pane] = false; // logic return true; // SKIP to next } // add basic classes & attributes $P .attr("pane", pane) // add pane-identifier .addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector' ; // init pane-logic vars, etc. if (pane != "center") { s.isClosed = false; // true = pane is closed s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes s.isResizing= false; // true = pane is in process of being resized s.isHidden = false; // true = pane is hidden - no spacing, resizer or toggler is visible! s.noRoom = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically // create special keys for internal use c[pane].pins = []; // used to track and sync 'pin-buttons' for border-panes } CSS = $.extend({ visibility: "visible", display: "block" }, c.defaults.cssReq, c[pane].cssReq ); if (o.applyDefaultStyles) $.extend( CSS, c.defaults.cssDef, c[pane].cssDef ); // cosmetic defaults $P.css(CSS); // add base-css BEFORE 'measuring' to calc size & position CSS = {}; // reset var // set css-position to account for container borders & padding switch (pane) { case "north": CSS.top = cDims.top; CSS.left = cDims.left; CSS.right = cDims.right; break; case "south": CSS.bottom = cDims.bottom; CSS.left = cDims.left; CSS.right = cDims.right; break; case "west": CSS.left = cDims.left; // top, bottom & height set by sizeMidPanes() break; case "east": CSS.right = cDims.right; // ditto break; case "center": // top, left, width & height set by sizeMidPanes() } if (dir == "horz") { // north or south pane if (size === 0 || size == "auto") { $P.css({ height: "auto" }); size = $P.outerHeight(); } size = max(size, minSize); size = min(size, maxSize); size = min(size, cDims.innerHeight - spacing); CSS.height = max(1, cssH(pane, size)); s.size = size; // update state // make sure minSize is sufficient to avoid errors s.maxSize = maxSize; // init value s.minSize = max(minSize, size - CSS.height + 1); // = pane.outerHeight when css.height = 1px // handle IE6 //if (isIE6) CSS.width = cssW($P, cDims.innerWidth); $P.css(CSS); // apply size & position } else if (dir == "vert") { // east or west pane if (size === 0 || size == "auto") { $P.css({ width: "auto", float: "left" }); // float = FORCE pane to auto-size size = $P.outerWidth(); $P.css({ float: "none" }); // RESET } size = max(size, minSize); size = min(size, maxSize); size = min(size, cDims.innerWidth - spacing); CSS.width = max(1, cssW(pane, size)); s.size = size; // update state s.maxSize = maxSize; // init value // make sure minSize is sufficient to avoid errors s.minSize = max(minSize, size - CSS.width + 1); // = pane.outerWidth when css.width = 1px $P.css(CSS); // apply size - top, bottom & height set by sizeMidPanes sizeMidPanes(pane, null, true); // true = onInit } else if (pane == "center") { $P.css(CSS); // top, left, width & height set by sizeMidPanes... sizeMidPanes("center", null, true); // true = onInit } // close or hide the pane if specified in settings if (o.initClosed && o.closable) { $P.hide().addClass("closed"); s.isClosed = true; } else if (o.initHidden || o.initClosed) { hide(pane, true); // will be completely invisible - no resizer or spacing s.isHidden = true; } else $P.addClass("open"); // check option for auto-handling of pop-ups & drop-downs if (o.showOverflowOnHover) $P.hover( allowOverflow, resetOverflow ); /* * see if this pane has a 'content element' that we need to auto-size */ if (o.contentSelector) { $C = $Cs[pane] = $P.children(o.contentSelector+":first"); // match 1-element only if (!$C.length) { $Cs[pane] = false; return true; // SKIP to next } $C.css( c.content.cssReq ); if (o.applyDefaultStyles) $C.css( c.content.cssDef ); // cosmetic defaults // NO PANE-SCROLLING when there is a content-div $P.css({ overflow: "hidden" }); } }); }; /** * initHandles * * Initialize module objects, styling, size and position for all resize bars and toggler buttons * * @callers create() */ var initHandles = function () { // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV $.each(c.borderPanes.split(","), function() { var pane = str(this) , o = options[pane] , s = state[pane] , rClass = o.resizerClass , tClass = o.togglerClass , $P = $Ps[pane] ; $Rs[pane] = false; // INIT $Ts[pane] = false; if (!$P || (!o.closable && !o.resizable)) return; // pane does not exist - skip var edge = c[pane].edge , isOpen = $P.is(":visible") , spacing = (isOpen ? o.spacing_open : o.spacing_closed) , _pane = "-"+ pane // used for classNames , _state = (isOpen ? "-open" : "-closed") // used for classNames , $R, $T ; // INIT RESIZER BAR $R = $Rs[pane] = $(""); if (isOpen && o.resizable) ; // this is handled by initResizable else if (!isOpen && o.slidable) $R.attr("title", o.sliderTip).css("cursor", o.sliderCursor); $R // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer" .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-resizer" : "")) .attr("resizer", pane) // so we can read this from the resizer .css(c.resizers.cssReq) // add base/required styles // POSITION of resizer bar - allow for container border & padding .css(edge, cDims[edge] + getPaneSize(pane)) // ADD CLASSNAMES - eg: class="resizer resizer-west resizer-open" .addClass( rClass +" "+ rClass+_pane +" "+ rClass+_state +" "+ rClass+_pane+_state ) .appendTo($Container) // append DIV to container ; // ADD VISUAL STYLES if (o.applyDefaultStyles) $R.css(c.resizers.cssDef); if (o.closable) { // INIT COLLAPSER BUTTON $T = $Ts[pane] = $("
"); $T // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-toggler" .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-toggler" : "")) .css(c.togglers.cssReq) // add base/required styles .attr("title", (isOpen ? o.togglerTip_open : o.togglerTip_closed)) .click(function(evt){ toggle(pane); evt.stopPropagation(); }) .mouseover(function(evt){ evt.stopPropagation(); }) // prevent resizer event // ADD CLASSNAMES - eg: class="toggler toggler-west toggler-west-open" .addClass( tClass +" "+ tClass+_pane +" "+ tClass+_state +" "+ tClass+_pane+_state ) .appendTo($R) // append SPAN to resizer DIV ; // ADD INNER-SPANS TO TOGGLER if (o.togglerContent_open) // ui-layout-open $(""+ o.togglerContent_open +"") .addClass("content content-open") .css("display", s.isClosed ? "none" : "block") .appendTo( $T ) ; if (o.togglerContent_closed) // ui-layout-closed $(""+ o.togglerContent_closed +"") .addClass("content content-closed") .css("display", s.isClosed ? "block" : "none") .appendTo( $T ) ; // ADD BASIC VISUAL STYLES if (o.applyDefaultStyles) $T.css(c.togglers.cssDef); if (!isOpen) bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true } }); // SET ALL HANDLE SIZES & LENGTHS sizeHandles("all", true); // true = onInit }; /** * initResizable * * Add resize-bars to all panes that specify it in options * * @dependancies $.fn.resizable - will abort if not found * @callers create() */ var initResizable = function () { var draggingAvailable = (typeof $.fn.draggable == "function") , minPosition, maxPosition, edge // set in start() ; $.each(c.borderPanes.split(","), function() { var pane = str(this) , o = options[pane] , s = state[pane] ; if (!draggingAvailable || !$Ps[pane] || !o.resizable) { o.resizable = false; return true; // skip to next } var rClass = o.resizerClass // 'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process , dragClass = rClass+"-drag" // resizer-drag , dragPaneClass = rClass+"-"+pane+"-drag" // resizer-north-drag // 'dragging' class is applied to the CLONED resizer-bar while it is being dragged , draggingClass = rClass+"-dragging" // resizer-dragging , draggingPaneClass = rClass+"-"+pane+"-dragging" // resizer-north-dragging , draggingClassSet = false // logic var , $P = $Ps[pane] , $R = $Rs[pane] ; if (!s.isClosed) $R .attr("title", o.resizerTip) .css("cursor", o.resizerCursor) // n-resize, s-resize, etc ; $R.draggable({ containment: $Container[0] // limit resizing to layout container , axis: (c[pane].dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis , delay: 200 , distance: 1 // basic format for helper - style it using class: .ui-draggable-dragging , helper: "clone" , opacity: o.resizerDragOpacity //, iframeFix: o.draggableIframeFix // TODO: consider using when bug is fixed , zIndex: c.zIndex.resizing , start: function (e, ui) { // onresize_start callback - will CANCEL hide if returns false // TODO: CONFIRM that dragging can be cancelled like this??? if (false === execUserCallback(pane, o.onresize_start)) return false; s.isResizing = true; // prevent pane from closing while resizing clearTimer(pane, "closeSlider"); // just in case already triggered $R.addClass( dragClass +" "+ dragPaneClass ); // add drag classes draggingClassSet = false; // reset logic var - see drag() // SET RESIZING LIMITS - used in drag() var resizerWidth = (pane=="east" || pane=="south" ? o.spacing_open : 0); setPaneMinMaxSizes(pane); // update pane-state s.minPosition -= resizerWidth; s.maxPosition -= resizerWidth; edge = (c[pane].dir=="horz" ? "top" : "left"); // MASK PANES WITH IFRAMES OR OTHER TROUBLESOME ELEMENTS $(o.maskIframesOnResize === true ? "iframe" : o.maskIframesOnResize).each(function() { $('') .css({ background: "#fff" , opacity: "0.001" , zIndex: 9 , position: "absolute" , width: this.offsetWidth+"px" , height: this.offsetHeight+"px" }) .css($(this).offset()) // top & left .appendTo(this.parentNode) // put div INSIDE pane to avoid zIndex issues ; }); } , drag: function (e, ui) { if (!draggingClassSet) { // can only add classes after clone has been added to the DOM $(".ui-draggable-dragging") .addClass( draggingClass +" "+ draggingPaneClass ) // add dragging classes .children().css("visibility","hidden") // hide toggler inside dragged resizer-bar ; draggingClassSet = true; // draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane! if (s.isSliding) $Ps[pane].css("zIndex", c.zIndex.sliding); } // CONTAIN RESIZER-BAR TO RESIZING LIMITS if (ui.position[edge] < s.minPosition) ui.position[edge] = s.minPosition; else if (ui.position[edge] > s.maxPosition) ui.position[edge] = s.maxPosition; } , stop: function (e, ui) { var dragPos = ui.position , resizerPos , newSize ; $R.removeClass( dragClass +" "+ dragPaneClass ); // remove drag classes switch (pane) { case "north": resizerPos = dragPos.top; break; case "west": resizerPos = dragPos.left; break; case "south": resizerPos = cDims.outerHeight - dragPos.top - $R.outerHeight(); break; case "east": resizerPos = cDims.outerWidth - dragPos.left - $R.outerWidth(); break; } // remove container margin from resizer position to get the pane size newSize = resizerPos - cDims[ c[pane].edge ]; sizePane(pane, newSize); // UN-MASK PANES MASKED IN drag.start $("div.ui-layout-mask").remove(); // Remove iframe masks s.isResizing = false; } }); }); }; /* * ########################### * ACTION METHODS * ########################### */ /** * hide / show * * Completely 'hides' a pane, including its spacing - as if it does not exist * The pane is not actually 'removed' from the source, so can use 'show' to un-hide it * * @param String pane The pane being hidden, ie: north, south, east, or west */ var hide = function (pane, onInit) { var o = options[pane] , s = state[pane] , $P = $Ps[pane] , $R = $Rs[pane] ; if (!$P || s.isHidden) return; // pane does not exist OR is already hidden // onhide_start callback - will CANCEL hide if returns false if (false === execUserCallback(pane, o.onhide_start)) return; s.isSliding = false; // just in case // now hide the elements if ($R) $R.hide(); // hide resizer-bar if (onInit || s.isClosed) { s.isClosed = true; // to trigger open-animation on show() s.isHidden = true; $P.hide(); // no animation when loading page sizeMidPanes(c[pane].dir == "horz" ? "all" : "center"); execUserCallback(pane, o.onhide_end || o.onhide); } else { s.isHiding = true; // used by onclose close(pane, false); // adjust all panes to fit //s.isHidden = true; - will be set by close - if not cancelled } }; var show = function (pane, openPane) { var o = options[pane] , s = state[pane] , $P = $Ps[pane] , $R = $Rs[pane] ; if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden // onhide_start callback - will CANCEL hide if returns false if (false === execUserCallback(pane, o.onshow_start)) return; s.isSliding = false; // just in case s.isShowing = true; // used by onopen/onclose //s.isHidden = false; - will be set by open/close - if not cancelled // now show the elements if ($R && o.spacing_open > 0) $R.show(); if (openPane === false) close(pane, true); // true = force else open(pane); // adjust all panes to fit }; /** * toggle * * Toggles a pane open/closed by calling either open or close * * @param String pane The pane being toggled, ie: north, south, east, or west */ var toggle = function (pane) { var s = state[pane]; if (s.isHidden) show(pane); // will call 'open' after unhiding it else if (s.isClosed) open(pane); else close(pane); }; /** * close * * Close the specified pane (animation optional), and resize all other panes as needed * * @param String pane The pane being closed, ie: north, south, east, or west */ var close = function (pane, force, noAnimation) { var $P = $Ps[pane] , $R = $Rs[pane] , $T = $Ts[pane] , o = options[pane] , s = state[pane] , doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none") , edge = c[pane].edge , rClass = o.resizerClass , tClass = o.togglerClass , _pane = "-"+ pane // used for classNames , _open = "-open" , _sliding= "-sliding" , _closed = "-closed" // transfer logic vars to temp vars , isShowing = s.isShowing , isHiding = s.isHiding ; // now clear the logic vars delete s.isShowing; delete s.isHiding; if (!$P || (!o.resizable && !o.closable)) return; // invalid request else if (!force && s.isClosed && !isShowing) return; // already closed if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation setFlowCallback("close", pane, force); // set a callback for this action, if possible return; // ABORT } // onclose_start callback - will CANCEL hide if returns false // SKIP if just 'showing' a hidden pane as 'closed' if (!isShowing && false === execUserCallback(pane, o.onclose_start)) return; // SET flow-control flags c[pane].isMoving = true; c.isLayoutBusy = true; s.isClosed = true; // update isHidden BEFORE sizing panes if (isHiding) s.isHidden = true; else if (isShowing) s.isHidden = false; // sync any 'pin buttons' syncPinBtns(pane, false); // resize panes adjacent to this one if (!s.isSliding) sizeMidPanes(c[pane].dir == "horz" ? "all" : "center"); // if this pane has a resizer bar, move it now if ($R) { $R .css(edge, cDims[edge]) // move the resizer bar .removeClass( rClass+_open +" "+ rClass+_pane+_open ) .removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) .addClass( rClass+_closed +" "+ rClass+_pane+_closed ) ; // DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent if (o.resizable) $R .draggable("disable") .css("cursor", "default") .attr("title","") ; // if pane has a toggler button, adjust that too if ($T) { $T .removeClass( tClass+_open +" "+ tClass+_pane+_open ) .addClass( tClass+_closed +" "+ tClass+_pane+_closed ) .attr("title", o.togglerTip_closed) // may be blank ; } sizeHandles(); // resize 'length' and position togglers for adjacent panes } // ANIMATE 'CLOSE' - if no animation, then was ALREADY shown above if (doFX) { lockPaneForFX(pane, true); // need to set left/top so animation will work $P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () { lockPaneForFX(pane, false); // undo if (!s.isClosed) return; // pane was opened before animation finished! close_2(); }); } else { $P.hide(); // just hide pane NOW close_2(); } // SUBROUTINE function close_2 () { bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true // onclose callback - UNLESS just 'showing' a hidden pane as 'closed' if (!isShowing) execUserCallback(pane, o.onclose_end || o.onclose); // onhide OR onshow callback if (isShowing) execUserCallback(pane, o.onshow_end || o.onshow); if (isHiding) execUserCallback(pane, o.onhide_end || o.onhide); // internal flow-control callback execFlowCallback(pane); } }; /** * open * * Open the specified pane (animation optional), and resize all other panes as needed * * @param String pane The pane being opened, ie: north, south, east, or west */ var open = function (pane, slide, noAnimation) { var $P = $Ps[pane] , $R = $Rs[pane] , $T = $Ts[pane] , o = options[pane] , s = state[pane] , doFX = !noAnimation && s.isClosed && (o.fxName_open != "none") , edge = c[pane].edge , rClass = o.resizerClass , tClass = o.togglerClass , _pane = "-"+ pane // used for classNames , _open = "-open" , _closed = "-closed" , _sliding= "-sliding" // transfer logic var to temp var , isShowing = s.isShowing ; // now clear the logic var delete s.isShowing; if (!$P || (!o.resizable && !o.closable)) return; // invalid request else if (!s.isClosed && !s.isSliding) return; // already open // pane can ALSO be unhidden by just calling show(), so handle this scenario if (s.isHidden && !isShowing) { show(pane, true); return; } if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation setFlowCallback("open", pane, slide); // set a callback for this action, if possible return; // ABORT } // onopen_start callback - will CANCEL hide if returns false if (false === execUserCallback(pane, o.onopen_start)) return; // SET flow-control flags c[pane].isMoving = true; c.isLayoutBusy = true; // 'PIN PANE' - stop sliding if (s.isSliding && !slide) // !slide = 'open pane normally' - NOT sliding bindStopSlidingEvents(pane, false); // will set isSliding=false s.isClosed = false; // update isHidden BEFORE sizing panes if (isShowing) s.isHidden = false; // Container size may have changed - shrink the pane if now 'too big' setPaneMinMaxSizes(pane); // update pane-state if (s.size > s.maxSize) // pane is too big! resize it before opening $P.css( c[pane].sizeType, max(1, cssSize(pane, s.maxSize)) ); bindStartSlidingEvent(pane, false); // remove trigger event from resizer-bar if (doFX) { // ANIMATE lockPaneForFX(pane, true); // need to set left/top so animation will work $P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() { lockPaneForFX(pane, false); // undo if (s.isClosed) return; // pane was closed before animation finished! open_2(); // continue }); } else {// no animation $P.show(); // just show pane and... open_2(); // continue } // SUBROUTINE function open_2 () { // NOTE: if isSliding, then other panes are NOT 'resized' if (!s.isSliding) // resize all panes adjacent to this one sizeMidPanes(c[pane].dir=="vert" ? "center" : "all"); // if this pane has a toggler, move it now if ($R) { $R .css(edge, cDims[edge] + getPaneSize(pane)) // move the toggler .removeClass( rClass+_closed +" "+ rClass+_pane+_closed ) .addClass( rClass+_open +" "+ rClass+_pane+_open ) .addClass( !s.isSliding ? "" : rClass+_sliding +" "+ rClass+_pane+_sliding ) ; if (o.resizable) $R .draggable("enable") .css("cursor", o.resizerCursor) .attr("title", o.resizerTip) ; else $R.css("cursor", "default"); // n-resize, s-resize, etc // if pane also has a toggler button, adjust that too if ($T) { $T .removeClass( tClass+_closed +" "+ tClass+_pane+_closed ) .addClass( tClass+_open +" "+ tClass+_pane+_open ) .attr("title", o.togglerTip_open) // may be blank ; } sizeHandles("all"); // resize resizer & toggler sizes for all panes } // resize content every time pane opens - to be sure sizeContent(pane); // sync any 'pin buttons' syncPinBtns(pane, !s.isSliding); // onopen callback execUserCallback(pane, o.onopen_end || o.onopen); // onshow callback if (isShowing) execUserCallback(pane, o.onshow_end || o.onshow); // internal flow-control callback execFlowCallback(pane); } }; /** * lockPaneForFX * * Must set left/top on East/South panes so animation will work properly * * @param String pane The pane to lock, 'east' or 'south' - any other is ignored! * @param Boolean doLock true = set left/top, false = remove */ var lockPaneForFX = function (pane, doLock) { var $P = $Ps[pane]; if (doLock) { $P.css({ zIndex: c.zIndex.animation }); // overlay all elements during animation if (pane=="south") $P.css({ top: cDims.top + cDims.innerHeight - $P.outerHeight() }); else if (pane=="east") $P.css({ left: cDims.left + cDims.innerWidth - $P.outerWidth() }); } else { if (!state[pane].isSliding) $P.css({ zIndex: c.zIndex.pane_normal }); if (pane=="south") $P.css({ top: "auto" }); else if (pane=="east") $P.css({ left: "auto" }); } }; /** * bindStartSlidingEvent * * Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger * * @callers open(), close() * @param String pane The pane to enable/disable, 'north', 'south', etc. * @param Boolean enable Enable or Disable sliding? */ var bindStartSlidingEvent = function (pane, enable) { var o = options[pane] , $R = $Rs[pane] , trigger = o.slideTrigger_open ; if (!$R || !o.slidable) return; // make sure we have a valid event if (trigger != "click" && trigger != "dblclick" && trigger != "mouseover") trigger = "click"; $R // add or remove trigger event [enable ? "bind" : "unbind"](trigger, slideOpen) // set the appropriate cursor & title/tip .css("cursor", (enable ? o.sliderCursor: "default")) .attr("title", (enable ? o.sliderTip : "")) ; }; /** * bindStopSlidingEvents * * Add or remove 'mouseout' events to 'slide close' when pane is 'sliding' open or closed * Also increases zIndex when pane is sliding open * See bindStartSlidingEvent for code to control 'slide open' * * @callers slideOpen(), slideClosed() * @param String pane The pane to process, 'north', 'south', etc. * @param Boolean isOpen Is pane open or closed? */ var bindStopSlidingEvents = function (pane, enable) { var o = options[pane] , s = state[pane] , trigger = o.slideTrigger_close , action = (enable ? "bind" : "unbind") // can't make 'unbind' work! - see disabled code below , $P = $Ps[pane] , $R = $Rs[pane] ; s.isSliding = enable; // logic clearTimer(pane, "closeSlider"); // just in case // raise z-index when sliding $P.css({ zIndex: (enable ? c.zIndex.sliding : c.zIndex.pane_normal) }); $R.css({ zIndex: (enable ? c.zIndex.sliding : c.zIndex.resizer_normal) }); // make sure we have a valid event if (trigger != "click" && trigger != "mouseout") trigger = "mouseout"; // when trigger is 'mouseout', must cancel timer when mouse moves between 'pane' and 'resizer' if (enable) { // BIND trigger events $P.bind(trigger, slideClosed ); $R.bind(trigger, slideClosed ); if (trigger = "mouseout") { $P.bind("mouseover", cancelMouseOut ); $R.bind("mouseover", cancelMouseOut ); } } else { // UNBIND trigger events // TODO: why does unbind of a 'single function' not work reliably? //$P[action](trigger, slideClosed ); $P.unbind(trigger); $R.unbind(trigger); if (trigger = "mouseout") { //$P[action]("mouseover", cancelMouseOut ); $P.unbind("mouseover"); $R.unbind("mouseover"); clearTimer(pane, "closeSlider"); } } // SUBROUTINE for mouseout timer clearing function cancelMouseOut (evt) { clearTimer(pane, "closeSlider"); evt.stopPropagation(); } }; var slideOpen = function () { var pane = $(this).attr("resizer"); // attr added by initHandles if (state[pane].isClosed) { // skip if already open! bindStopSlidingEvents(pane, true); // pane is opening, so BIND trigger events to close it open(pane, true); // true = slide - ie, called from here! } }; var slideClosed = function () { var $E = $(this) , pane = $E.attr("pane") || $E.attr("resizer") , o = options[pane] , s = state[pane] ; if (s.isClosed || s.isResizing) return; // skip if already closed OR in process of resizing else if (o.slideTrigger_close == "click") close_NOW(); // close immediately onClick else // trigger = mouseout - use a delay setTimer(pane, "closeSlider", close_NOW, 300); // .3 sec delay // SUBROUTINE for timed close function close_NOW () { bindStopSlidingEvents(pane, false); // pane is being closed, so UNBIND trigger events if (!s.isClosed) close(pane); // skip if already closed! } }; /** * sizePane * * @callers initResizable.stop() * @param String pane The pane being resized - usually west or east, but potentially north or south * @param Integer newSize The new size for this pane - will be validated */ var sizePane = function (pane, size) { // TODO: accept "auto" as size, and size-to-fit pane content var edge = c[pane].edge , dir = c[pane].dir , o = options[pane] , s = state[pane] , $P = $Ps[pane] , $R = $Rs[pane] ; // calculate 'current' min/max sizes setPaneMinMaxSizes(pane); // update pane-state // compare/update calculated min/max to user-options s.minSize = max(s.minSize, o.minSize); if (o.maxSize > 0) s.maxSize = min(s.maxSize, o.maxSize); // validate passed size size = max(size, s.minSize); size = min(size, s.maxSize); s.size = size; // update state // move the resizer bar and resize the pane $R.css( edge, size + cDims[edge] ); $P.css( c[pane].sizeType, max(1, cssSize(pane, size)) ); // resize all the adjacent panes, and adjust their toggler buttons if (!s.isSliding) sizeMidPanes(dir=="horz" ? "all" : "center"); sizeHandles(); sizeContent(pane); execUserCallback(pane, o.onresize_end || o.onresize); }; /** * sizeMidPanes * * @callers create(), open(), close(), onWindowResize() */ var sizeMidPanes = function (panes, overrideDims, onInit) { if (!panes || panes == "all") panes = "east,west,center"; var d = getPaneDims(); if (overrideDims) $.extend( d, overrideDims ); $.each(panes.split(","), function() { if (!$Ps[this]) return; // NO PANE - skip var pane = str(this) , o = options[pane] , s = state[pane] , $P = $Ps[pane] , $R = $Rs[pane] , hasRoom = true , CSS = {} ; if (pane == "center") { d = getPaneDims(); // REFRESH Dims because may have just 'unhidden' East or West pane after a 'resize' CSS = $.extend( {}, d ); // COPY ALL of the paneDims CSS.width = max(1, cssW(pane, CSS.width)); CSS.height = max(1, cssH(pane, CSS.height)); hasRoom = (CSS.width > 1 && CSS.height > 1); /* * Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes * Normally these panes have only 'left' & 'right' positions so pane auto-sizes */ if ($.browser.msie && (!$.boxModel || $.browser.version < 7)) { if ($Ps.north) $Ps.north.css({ width: cssW($Ps.north, cDims.innerWidth) }); if ($Ps.south) $Ps.south.css({ width: cssW($Ps.south, cDims.innerWidth) }); } } else { // for east and west, set only the height CSS.top = d.top; CSS.bottom = d.bottom; CSS.height = max(1, cssH(pane, d.height)); hasRoom = (CSS.height > 1); } if (hasRoom) { $P.css(CSS); if (s.noRoom) { s.noRoom = false; if (s.isHidden) return; else show(pane, !s.isClosed); /* OLD CODE - keep until sure line above works right! if (!s.isClosed) $P.show(); // in case was previously hidden due to NOT hasRoom if ($R) $R.show(); */ } if (!onInit) { sizeContent(pane); execUserCallback(pane, o.onresize_end || o.onresize); } } else if (!s.noRoom) { // no room for pane, so just hide it (if not already) s.noRoom = true; // update state if (s.isHidden) return; if (onInit) { // skip onhide callback and other logic onLoad $P.hide(); if ($R) $R.hide(); } else hide(pane); } }); }; var sizeContent = function (panes) { if (!panes || panes == "all") panes = c.allPanes; $.each(panes.split(","), function() { if (!$Cs[this]) return; // NO CONTENT - skip var pane = str(this) , ignore = options[pane].contentIgnoreSelector , $P = $Ps[pane] , $C = $Cs[pane] , e_C = $C[0] // DOM element , height = cssH($P); // init to pane.innerHeight ; $P.children().each(function() { if (this == e_C) return; // Content elem - skip var $E = $(this); if (!ignore || !$E.is(ignore)) height -= $E.outerHeight(); }); if (height > 0) height = cssH($C, height); if (height < 1) $C.hide(); // no room for content! else $C.css({ height: height }).show(); }); }; /** * sizeHandles * * Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary * * @callers initHandles(), open(), close(), resizeAll() */ var sizeHandles = function (panes, onInit) { if (!panes || panes == "all") panes = c.borderPanes; $.each(panes.split(","), function() { var pane = str(this) , o = options[pane] , s = state[pane] , $P = $Ps[pane] , $R = $Rs[pane] , $T = $Ts[pane] ; if (!$P || !$R || (!o.resizable && !o.closable)) return; // skip var dir = c[pane].dir , _state = (s.isClosed ? "_closed" : "_open") , spacing = o["spacing"+ _state] , togAlign = o["togglerAlign"+ _state] , togLen = o["togglerLength"+ _state] , paneLen , offset , CSS = {} ; if (spacing == 0) { $R.hide(); return; } else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason $R.show(); // in case was previously hidden // Resizer Bar is ALWAYS same width/height of pane it is attached to if (dir == "horz") { // north/south paneLen = $P.outerWidth(); $R.css({ width: max(1, cssW($R, paneLen)) // account for borders & padding , height: max(1, cssH($R, spacing)) // ditto , left: cssNum($P, "left") }); } else { // east/west paneLen = $P.outerHeight(); $R.css({ height: max(1, cssH($R, paneLen)) // account for borders & padding , width: max(1, cssW($R, spacing)) // ditto , top: cDims.top + getPaneSize("north", true) //, top: cssNum($Ps["center"], "top") }); } if ($T) { if (togLen == 0 || (s.isSliding && o.hideTogglerOnSlide)) { $T.hide(); // always HIDE the toggler when 'sliding' return; } else $T.show(); // in case was previously hidden if (!(togLen > 0) || togLen == "100%" || togLen > paneLen) { togLen = paneLen; offset = 0; } else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed if (typeof togAlign == "string") { switch (togAlign) { case "top": case "left": offset = 0; break; case "bottom": case "right": offset = paneLen - togLen; break; case "middle": case "center": default: offset = Math.floor((paneLen - togLen) / 2); // 'default' catches typos } } else { // togAlign = number var x = parseInt(togAlign); // if (togAlign >= 0) offset = x; else offset = paneLen - togLen + x; // NOTE: x is negative! } } var $TC_o = (o.togglerContent_open ? $T.children(".content-open") : false) , $TC_c = (o.togglerContent_closed ? $T.children(".content-closed") : false) , $TC = (s.isClosed ? $TC_c : $TC_o) ; if ($TC_o) $TC_o.css("display", s.isClosed ? "none" : "block"); if ($TC_c) $TC_c.css("display", s.isClosed ? "block" : "none"); if (dir == "horz") { // north/south var width = cssW($T, togLen); $T.css({ width: max(0, width) // account for borders & padding , height: max(1, cssH($T, spacing)) // ditto , left: offset // TODO: VERIFY that toggler positions correctly for ALL values }); if ($TC) // CENTER the toggler content SPAN $TC.css("marginLeft", Math.floor((width-$TC.outerWidth())/2)); // could be negative } else { // east/west var height = cssH($T, togLen); $T.css({ height: max(0, height) // account for borders & padding , width: max(1, cssW($T, spacing)) // ditto , top: offset // POSITION the toggler }); if ($TC) // CENTER the toggler content SPAN $TC.css("marginTop", Math.floor((height-$TC.outerHeight())/2)); // could be negative } } // DONE measuring and sizing this resizer/toggler, so can be 'hidden' now if (onInit && o.initHidden) { $R.hide(); if ($T) $T.hide(); } }); }; /** * resizeAll * * @callers window.onresize(), callbacks or custom code */ var resizeAll = function () { var oldW = cDims.innerWidth , oldH = cDims.innerHeight ; cDims = state.container = getElemDims($Container); // UPDATE container dimensions var checkH = (cDims.innerHeight < oldH) , checkW = (cDims.innerWidth < oldW) , s, dir ; if (checkH || checkW) // NOTE special order for sizing: S-N-E-W $.each(["south","north","east","west"], function(i,pane) { s = state[pane]; dir = c[pane].dir; if (!s.isClosed && ((checkH && dir=="horz") || (checkW && dir=="vert"))) { setPaneMinMaxSizes(pane); // update pane-state // shrink pane if 'too big' to fit if (s.size > s.maxSize) sizePane(pane, s.maxSize); } }); sizeMidPanes("all"); sizeHandles("all"); // reposition the toggler elements }; /** * keyDown * * Capture keys when enableCursorHotkey - toggle pane if hotkey pressed * * @callers document.keydown() */ function keyDown (evt) { if (!evt) return true; var code = evt.keyCode; if (code < 33) return true; // ignore special keys: ENTER, TAB, etc var PANE = { 38: "north" // Up Cursor , 40: "south" // Down Cursor , 37: "west" // Left Cursor , 39: "east" // Right Cursor } , isCursorKey = (code >= 37 && code <= 40) , ALT = evt.altKey // no worky! , SHIFT = evt.shiftKey , CTRL = evt.ctrlKey , pane = false , s, o, k, m, el ; if (!CTRL && !SHIFT) return true; // no modifier key - abort else if (isCursorKey && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey pane = PANE[code]; else // check to see if this matches a custom-hotkey $.each(c.borderPanes.split(","), function(i,p) { // loop each pane to check its hotkey o = options[p]; k = o.customHotkey; m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT" if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches if (k && code == (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches pane = p; return false; // BREAK } } }); if (!pane) return true; // no hotkey - abort // validate pane o = options[pane]; // get pane options s = state[pane]; // get pane options if (!o.enableCursorHotkey || s.isHidden || !$Ps[pane]) return true; // see if user is in a 'form field' because may be 'selecting text'! el = evt.target || evt.srcElement; if (el && SHIFT && isCursorKey && (el.tagName=="TEXTAREA" || (el.tagName=="INPUT" && (code==37 || code==39)))) return true; // allow text-selection // SYNTAX NOTES // use "returnValue=false" to abort keystroke but NOT abort function - can run another command afterwards // use "return false" to abort keystroke AND abort function toggle(pane); evt.stopPropagation(); evt.returnValue = false; // CANCEL key return false; }; /* * ########################### * UTILITY METHODS * called externally only * ########################### */ function allowOverflow (elem) { if (this && this.tagName) elem = this; // BOUND to element var $P; if (typeof elem=="string") $P = $Ps[elem]; else { if ($(elem).attr("pane")) $P = $(elem); else $P = $(elem).parents("div[pane]:first"); } if (!$P.length) return; // INVALID var pane = $P.attr("pane") , s = state[pane] ; // if pane is already raised, then reset it before doing it again! // this would happen if allowOverflow is attached to BOTH the pane and an element if (s.cssSaved) resetOverflow(pane); // reset previous CSS before continuing // if pane is raised by sliding or resizing, or it's closed, then abort if (s.isSliding || s.isResizing || s.isClosed) { s.cssSaved = false; return; } var newCSS = { zIndex: (c.zIndex.pane_normal + 1) } , curCSS = {} , of = $P.css("overflow") , ofX = $P.css("overflowX") , ofY = $P.css("overflowY") ; // determine which, if any, overflow settings need to be changed if (of != "visible") { curCSS.overflow = of; newCSS.overflow = "visible"; } if (ofX && ofX != "visible" && ofX != "auto") { curCSS.overflowX = ofX; newCSS.overflowX = "visible"; } if (ofY && ofY != "visible" && ofY != "auto") { curCSS.overflowY = ofX; newCSS.overflowY = "visible"; } // save the current overflow settings - even if blank! s.cssSaved = curCSS; // apply new CSS to raise zIndex and, if necessary, make overflow 'visible' $P.css( newCSS ); // make sure the zIndex of all other panes is normal $.each(c.allPanes.split(","), function(i, p) { if (p != pane) resetOverflow(p); }); }; function resetOverflow (elem) { if (this && this.tagName) elem = this; // BOUND to element var $P; if (typeof elem=="string") $P = $Ps[elem]; else { if ($(elem).hasClass("ui-layout-pane")) $P = $(elem); else $P = $(elem).parents("div[pane]:first"); } if (!$P.length) return; // INVALID var pane = $P.attr("pane") , s = state[pane] , CSS = s.cssSaved || {} ; // reset the zIndex if (!s.isSliding && !s.isResizing) $P.css("zIndex", c.zIndex.pane_normal); // reset Overflow - if necessary $P.css( CSS ); // clear var s.cssSaved = false; }; /** * getBtn * * Helper function to validate params received by addButton utilities * * @param String selector jQuery selector for button, eg: ".ui-layout-north .toggle-button" * @param String pane Name of the pane the button is for: 'north', 'south', etc. * @returns If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise 'false' */ function getBtn(selector, pane, action) { var $E = $(selector) , err = "Error Adding Button \n\nInvalid " ; if (!$E.length) // element not found alert(err+"selector: "+ selector); else if (c.borderPanes.indexOf(pane) == -1) // invalid 'pane' sepecified alert(err+"pane: "+ pane); else { // VALID var btn = options[pane].buttonClass +"-"+ action; $E.addClass( btn +" "+ btn +"-"+ pane ); return $E; } return false; // INVALID }; /** * addToggleBtn * * Add a custom Toggler button for a pane * * @param String selector jQuery selector for button, eg: ".ui-layout-north .toggle-button" * @param String pane Name of the pane the button is for: 'north', 'south', etc. */ function addToggleBtn (selector, pane) { var $E = getBtn(selector, pane, "toggle"); if ($E) $E .attr("title", state[pane].isClosed ? "Open" : "Close") .click(function (evt) { toggle(pane); evt.stopPropagation(); }) ; }; /** * addOpenBtn * * Add a custom Open button for a pane * * @param String selector jQuery selector for button, eg: ".ui-layout-north .open-button" * @param String pane Name of the pane the button is for: 'north', 'south', etc. */ function addOpenBtn (selector, pane) { var $E = getBtn(selector, pane, "open"); if ($E) $E .attr("title", "Open") .click(function (evt) { open(pane); evt.stopPropagation(); }) ; }; /** * addCloseBtn * * Add a custom Close button for a pane * * @param String selector jQuery selector for button, eg: ".ui-layout-north .close-button" * @param String pane Name of the pane the button is for: 'north', 'south', etc. */ function addCloseBtn (selector, pane) { var $E = getBtn(selector, pane, "close"); if ($E) $E .attr("title", "Close") .click(function (evt) { close(pane); evt.stopPropagation(); }) ; }; /** * addPinBtn * * Add a custom Pin button for a pane * * Four classes are added to the element, based on the paneClass for the associated pane... * Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin: * - ui-layout-pane-pin * - ui-layout-pane-west-pin * - ui-layout-pane-pin-up * - ui-layout-pane-west-pin-up * * @param String selector jQuery selector for button, eg: ".ui-layout-north .ui-layout-pin" * @param String pane Name of the pane the pin is for: 'north', 'south', etc. */ function addPinBtn (selector, pane) { var $E = getBtn(selector, pane, "pin"); if ($E) { var s = state[pane]; $E.click(function (evt) { setPinState($(this), pane, (s.isSliding || s.isClosed)); if (s.isSliding || s.isClosed) open( pane ); // change from sliding to open else close( pane ); // slide-closed evt.stopPropagation(); }); // add up/down pin attributes and classes setPinState ($E, pane, (!s.isClosed && !s.isSliding)); // add this pin to the pane data so we can 'sync it' automatically // PANE.pins key is an array so we can store multiple pins for each pane c[pane].pins.push( selector ); // just save the selector string } }; /** * syncPinBtns * * INTERNAL function to sync 'pin buttons' when pane is opened or closed * Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes * * @callers open(), close() * @params pane These are the params returned to callbacks by layout() * @params doPin True means set the pin 'down', False means 'up' */ function syncPinBtns (pane, doPin) { $.each(c[pane].pins, function (i, selector) { setPinState($(selector), pane, doPin); }); }; /** * setPinState * * Change the class of the pin button to make it look 'up' or 'down' * * @callers addPinBtn(), syncPinBtns() * @param Element $Pin The pin-span element in a jQuery wrapper * @param Boolean doPin True = set the pin 'down', False = set it 'up' * @param String pinClass The root classname for pins - will add '-up' or '-down' suffix */ function setPinState ($Pin, pane, doPin) { var updown = $Pin.attr("pin"); if (updown && doPin == (updown=="down")) return; // already in correct state var root = options[pane].buttonClass , class1 = root +"-pin" , class2 = class1 +"-"+ pane , UP1 = class1 + "-up" , UP2 = class2 + "-up" , DN1 = class1 + "-down" , DN2 = class2 + "-down" ; $Pin .attr("pin", doPin ? "down" : "up") // logic .attr("title", doPin ? "Un-Pin" : "Pin") .removeClass( doPin ? UP1 : DN1 ) .removeClass( doPin ? UP2 : DN2 ) .addClass( doPin ? DN1 : UP1 ) .addClass( doPin ? DN2 : UP2 ) ; }; /* * ########################### * CREATE/RETURN BORDER-LAYOUT * ########################### */ // init global vars var $Container = $(this).css({ overflow: "hidden" }) // Container elem , $Ps = {} // Panes x4 - set in initPanes() , $Cs = {} // Content x4 - set in initPanes() , $Rs = {} // Resizers x4 - set in initHandles() , $Ts = {} // Togglers x4 - set in initHandles() // object aliases , c = config // alias for config hash , cDims = state.container // alias for easy access to 'container dimensions' ; // create the border layout NOW create(); // return object pointers to expose data & option Properties, and primary action Methods return { options: options // property - options hash , state: state // property - dimensions hash , panes: $Ps // property - object pointers for ALL panes: panes.north, panes.center , toggle: toggle // method - pass a 'pane' ("north", "west", etc) , open: open // method - ditto , close: close // method - ditto , hide: hide // method - ditto , show: show // method - ditto , resizeContent: sizeContent // method - ditto , sizePane: sizePane // method - pass a 'pane' AND a 'size' in pixels , resizeAll: resizeAll // method - no parameters , addToggleBtn: addToggleBtn // utility - pass element selector and 'pane' , addOpenBtn: addOpenBtn // utility - ditto , addCloseBtn: addCloseBtn // utility - ditto , addPinBtn: addPinBtn // utility - ditto , allowOverflow: allowOverflow // utility - pass calling element , resetOverflow: resetOverflow // utility - ditto , cssWidth: cssW , cssHeight: cssH }; } })( jQuery );