processmaker-builder
Version:
The gulp task runner for ProcessMaker building
1,427 lines (1,313 loc) • 126 kB
JavaScript
/*
* jquery.layout 1.3.0 - Release Candidate 26
*
* Copyright (c) 2010
* 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.
*
* Docs: http://layout.jquery-dev.net/documentation.html
* Tips: http://layout.jquery-dev.net/tips.html
* Help: http://groups.google.com/group/jquery-ui-layout
*
* $Date: 2010-02-27 18:00:00 -1800 (Sat, 27 Feb 2010) $
* $Rev: 3026 $
*
* NOTE: For best code readability, view this with a fixed-width font and tabs equal to 4-chars
*/
;(function ($) {
$.fn.layout = function (opts) {
/*
* ###########################
* WIDGET CONFIG & OPTIONS
* ###########################
*/
// LANGUAGE CUSTOMIZATION - will be *externally customizable* in next version
var lang = {
Pane: "Pane"
, Open: "Open" // eg: "Open Pane"
, Close: "Close"
, Resize: "Resize"
, Slide: "Slide Open"
, Pin: "Pin"
, Unpin: "Un-Pin"
, selector: "selector"
, msgNoRoom: "Not enough room to show this pane."
, errContainerMissing: "UI Layout Initialization Error\n\nThe specified layout-container does not exist."
, errCenterPaneMissing: "UI Layout Initialization Error\n\nThe center-pane element does not exist.\n\nThe center-pane is a required element."
, errContainerHeight: "UI Layout Initialization Warning\n\nThe layout-container \"CONTAINER\" has no height.\n\nTherefore the layout is 0-height and hence 'invisible'!"
, errButton: "Error Adding Button \n\nInvalid "
};
// DEFAULT OPTIONS - CHANGE IF DESIRED
var options = {
name: "" // Not required, but useful for buttons and used for the state-cookie
, scrollToBookmarkOnLoad: true // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark)
, resizeWithWindow: true // bind thisLayout.resizeAll() to the window.resize event
, resizeWithWindowDelay: 200 // delay calling resizeAll because makes window resizing very jerky
, resizeWithWindowMaxDelay: 0 // 0 = none - force resize every XX ms while window is being resized
, onresizeall_start: null // CALLBACK when resizeAll() STARTS - NOT pane-specific
, onresizeall_end: null // CALLBACK when resizeAll() ENDS - NOT pane-specific
, onload: null // CALLBACK when Layout inits - after options initialized, but before elements
, onunload: null // CALLBACK when Layout is destroyed OR onWindowUnload
, autoBindCustomButtons: false // search for buttons with ui-layout-button class and auto-bind them
, zIndex: null // the PANE zIndex - resizers and masks will be +1
// PANE SETTINGS
, defaults: { // default options for 'all panes' - will be overridden by 'per-pane settings'
applyDemoStyles: false // NOTE: renamed from applyDefaultStyles for clarity
, 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
, initClosed: false // true = init pane as 'closed'
, initHidden: false // true = init pane as 'hidden' - no resizer-bar/spacing
// SELECTORS
//, paneSelector: "" // MUST be pane-specific - jQuery selector for pane
, contentSelector: ".ui-layout-content" // INNER div/element to auto-size so only it scrolls, not the entire pane!
// GENERIC ROOT-CLASSES - for auto-generated classNames
, paneClass: "ui-layout-pane" // border-Pane - default: 'ui-layout-pane'
, resizerClass: "ui-layout-resizer" // Resizer Bar - default: 'ui-layout-resizer'
, togglerClass: "ui-layout-toggler" // Toggler Button - default: 'ui-layout-toggler'
, buttonClass: "ui-layout-button" // CUSTOM Buttons - default: 'ui-layout-button-toggle/-open/-close/-pin'
// ELEMENT SIZE & SPACING
//, size: 100 // MUST be pane-specific -initial size of 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 sides - HEIGHT on east/west sides
, 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: lang.Close // Toggler tool-tip (title)
, togglerTip_closed: lang.Open // ditto
// RESIZING OPTIONS
, resizerDblClickToggle: true //
, noSelectionWhileDragging: true // set $(document).disableSelection to avoid selecting text while dragging the resizer
, autoResize: true // IF size is 'auto' or a percentage, then recalc 'pixel size' whenever the layout resizes
, autoReopen: true // IF a pane was auto-closed due to noRoom, reopen it when there is room? False = leave it closed
, 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
, resizeWhileDragging: false // true = LIVE Resizing as resizer is dragged
, resizeContentWhileDragging: false // true = re-measure header/footer heights as resizer is dragged
// TIPS & MESSAGES - also see lang object
, noRoomToOpenTip: lang.msgNoRoom
, resizerTip: lang.Resize // Resizer tool-tip (title)
, sliderTip: lang.Slide // 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
// HOT-KEYS & MISC
, showOverflowOnHover: false // will bind allowOverflow() utility to pane.onMouseOver
, trackMouseWhenSliding: false // true = check isMouseOver to avoid premature slide-closed
, 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'
// PANE ANIMATION
// 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 }
, fxOpacityFix: true // tries to fix opacity in IE to restore anti-aliasing after animation
// CALLBACKS
, triggerEventsOnLoad: false // true = trigger onopen OR onclose callbacks when layout initializes
, triggerEventsWhileDragging: true // true = trigger onresize callback REPEATEDLY if resizeWhileDragging==true
, onshow_start: null // CALLBACK when pane STARTS to Show - BEFORE onopen/onhide_start
, onshow_end: null // CALLBACK when pane ENDS being Shown - AFTER onopen/onhide_end
, onhide_start: null // CALLBACK when pane STARTS to Close - BEFORE onclose_start
, onhide_end: null // CALLBACK when pane ENDS being Closed - AFTER onclose_end
, onopen_start: null // CALLBACK when pane STARTS to Open
, onopen_end: null // CALLBACK when pane ENDS being Opened
, onclose_start: null // CALLBACK when pane STARTS to Close
, onclose_end: null // CALLBACK when pane ENDS being Closed
, onresize_start: null // CALLBACK when pane STARTS to be ***MANUALLY*** Resized
, onresize_end: null // CALLBACK when pane ENDS being Resized ***FOR ANY REASON***
}
, north: {
paneSelector: ".ui-layout-north"
, size: "auto" // eg: "auto", "30%", 200
, resizerCursor: "n-resize" // custom = url(myCursor.cur)
, customHotkey: "" // EITHER a charCode OR a character
}
, south: {
paneSelector: ".ui-layout-south"
, size: "auto"
, resizerCursor: "s-resize"
, customHotkey: ""
}
, east: {
paneSelector: ".ui-layout-east"
, size: 200
, resizerCursor: "e-resize"
, customHotkey: ""
}
, west: {
paneSelector: ".ui-layout-west"
, size: 200
, resizerCursor: "w-resize"
, customHotkey: ""
}
, center: {
paneSelector: ".ui-layout-center"
, minWidth: 0
, minHeight: 0
}
// STATE MANAGMENT
, useStateCookie: false // Enable cookie-based state-management - can fine-tune with cookie.autoLoad/autoSave
, cookie: {
name: "" // If not specified, will use Layout.name, else just "Layout"
, autoSave: true // Save a state cookie when page exits?
, autoLoad: true // Load the state cookie when Layout inits?
// Cookie Options
, domain: ""
, path: ""
, expires: "" // 'days' to keep cookie - leave blank for 'session cookie'
, secure: false
// List of options to save in the cookie - must be pane-specific
, keys: "north.size,south.size,east.size,west.size,"+
"north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+
"north.isHidden,south.isHidden,east.isHidden,west.isHidden"
}
};
// PREDEFINED EFFECTS / DEFAULTS
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" }
}
};
// DYNAMIC DATA - IS READ-ONLY EXTERNALLY!
var state = {
// generate unique ID to use for event.namespace so can unbind only events added by 'this layout'
id: "layout"+ new Date().getTime() // code uses alias: sID
, initialized: false
, container: {} // init all keys
, north: {}
, south: {}
, east: {}
, west: {}
, center: {}
, cookie: {} // State Managment data storage
};
// INTERNAL CONFIG DATA - DO NOT CHANGE THIS!
var _c = {
allPanes: "north,south,west,east,center"
, borderPanes: "north,south,west,east"
, altSide: {
north: "south"
, south: "north"
, east: "west"
, west: "east"
}
// CSS used in multiple places
, hidden: { visibility: "hidden" }
, visible: { visibility: "visible" }
// layout element settings
, zIndex: { // set z-index values here
pane_normal: 1 // normal z-index for panes
, resizer_normal: 2 // normal z-index for resizer-bars
, iframe_mask: 2 // overlay div used to mask pane(s) during resizing
, pane_sliding: 100 // applied to *BOTH* the pane and its resizer when a pane is 'slid open'
, pane_animate: 1000 // applied to the pane when being animated - not applied to the resizer
, resizer_drag: 10000 // applied to the CLONED resizer-bar when being 'dragged'
}
, resizers: {
cssReq: {
position: "absolute"
, padding: 0
, margin: 0
, fontSize: "1px"
, textAlign: "left" // to counter-act "center" alignment!
, overflow: "hidden" // prevent toggler-button from overflowing
// SEE c.zIndex.resizer_normal
}
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=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
}
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
background: "#AAA"
}
}
, content: {
cssReq: {
position: "relative" /* contain floated or positioned elements */
}
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
overflow: "auto"
, padding: "10px"
}
, cssDemoPane: { // DEMO CSS - REMOVE scrolling from 'pane' when it has a content-div
overflow: "hidden"
, padding: 0
}
}
, panes: { // defaults for ALL panes - overridden by 'per-pane settings' below
cssReq: {
position: "absolute"
, margin: 0
// SEE c.zIndex.pane_normal
}
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
padding: "10px"
, background: "#FFF"
, border: "1px solid #BBB"
, overflow: "auto"
}
}
, north: {
side: "Top"
, sizeType: "Height"
, dir: "horz"
, cssReq: {
top: 0
, bottom: "auto"
, left: 0
, right: 0
, width: "auto"
// height: DYNAMIC
}
, pins: [] // array of 'pin buttons' to be auto-updated on open/close (classNames)
}
, south: {
side: "Bottom"
, sizeType: "Height"
, dir: "horz"
, cssReq: {
top: "auto"
, bottom: 0
, left: 0
, right: 0
, width: "auto"
// height: DYNAMIC
}
, pins: []
}
, east: {
side: "Right"
, sizeType: "Width"
, dir: "vert"
, cssReq: {
left: "auto"
, right: 0
, top: "auto" // DYNAMIC
, bottom: "auto" // DYNAMIC
, height: "auto"
// width: DYNAMIC
}
, pins: []
}
, west: {
side: "Left"
, sizeType: "Width"
, dir: "vert"
, cssReq: {
left: 0
, right: "auto"
, top: "auto" // DYNAMIC
, bottom: "auto" // DYNAMIC
, height: "auto"
// width: DYNAMIC
}
, pins: []
}
, center: {
dir: "center"
, cssReq: {
left: "auto" // DYNAMIC
, right: "auto" // DYNAMIC
, top: "auto" // DYNAMIC
, bottom: "auto" // DYNAMIC
, height: "auto"
, width: "auto"
}
}
// internal tracking
, timers: {}
};
/*
* ###########################
* INTERNAL HELPER FUNCTIONS
* ###########################
*/
/**
* min / max
*
* Aliases for Math methods 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 = { cookie:{}, defaults:{fxSettings:{}}, north:{fxSettings:{}}, south:{fxSettings:{}}, east:{fxSettings:{}}, west:{fxSettings:{}}, center:{fxSettings:{}} };
d = d || {};
if (d.effects || d.cookie || d.defaults || d.north || d.south || d.west || d.east || d.center)
json = $.extend( true, 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("__");
if (!a[1] || json[a[0]]) // check for invalid keys
json[ a[1] ? a[0] : "defaults" ][ a[1] ? a[1] : a[0] ] = val;
});
return json;
};
/**
* _queue
*
* Set an INTERNAL callback to avoid simultaneous animation
* Runs only if needed and only if all callbacks are not 'already set'
* Called by open() and close() when isLayoutBusy=true
*
* @param String action Either 'open' or 'close'
* @param String pane A valid border-pane name, eg 'west'
* @param Boolean param Extra param for callback (optional)
*/
var _queue = function (action, pane, param) {
var tried = [];
// if isLayoutBusy, then some pane must be 'moving'
$.each(_c.borderPanes.split(","), function (i, p) {
if (_c[p].isMoving) {
bindCallback(p); // TRY to bind a callback
return false; // BREAK
}
});
// if pane does NOT have a callback, then add one, else follow the callback chain...
function bindCallback (p) {
var c = _c[p];
if (!c.doCallback) {
c.doCallback = true;
c.callback = action +","+ pane +","+ (param ? 1 : 0);
}
else { // try to 'chain' this callback
tried.push(p);
var cbPane = c.callback.split(",")[1]; // 2nd param of callback is 'pane'
// ensure callback target NOT 'itself' and NOT 'target pane' and NOT already tried (avoid loop)
if (cbPane != p && cbPane != pane && !$.inArray(p, tried))
bindCallback(cbPane); // RECURSE
}
}
};
/**
* _dequeue
*
* RUN the INTERNAL callback for this pane - if one exists
*
* @param String action Either 'open' or 'close'
* @param String pane A valid border-pane name, eg 'west'
* @param Boolean param Extra param for callback (optional)
*/
var _dequeue = function (pane) {
var c = _c[pane];
// RESET flow-control flags
_c.isLayoutBusy = false;
delete c.isMoving;
if (!c.doCallback || !c.callback) return;
c.doCallback = false; // RESET logic flag
// EXECUTE the callback
var
cb = c.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 (!c.doCallback) c.callback = null; // RESET - unless callback above enabled it again!
};
/**
* _execCallback
*
* 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 _execCallback = 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.match(/,/)) {
// 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") {
if (pane && $Ps[pane])
// pass data: pane-name, pane-element, pane-state (copy), pane-options, and layout-name
return fn( pane, $Ps[pane], $.extend({},state[pane]), options[pane], options.name );
else // must be a layout/container callback - pass suitable info
return fn( Instance, $.extend({},state), options, options.name );
}
}
catch (ex) {}
};
/**
* _showInvisibly
*
* Returns hash container 'display' and 'visibility'
*
* @TODO: SEE $.swap() - swaps CSS, runs callback, resets CSS
*/
var _showInvisibly = function ($E, force) {
if (!$E) return {};
if (!$E.jquery) $E = $($E);
var CSS = {
display: $E.css('display')
, visibility: $E.css('visibility')
};
if (force || CSS.display == "none") { // only if not *already hidden*
$E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so can be measured
return CSS;
}
else return {};
};
/**
* _fixIframe
*
* cure iframe display issues in IE & other browsers
*/
var _fixIframe = function (pane) {
if (state.browser.mozilla) return; // skip FireFox - it auto-refreshes iframes onShow
var $P = $Ps[pane];
// if the 'pane' is an iframe, do it
if (state[pane].tagName == "IFRAME")
$P.css(_c.hidden).css(_c.visible);
else // ditto for any iframes INSIDE the pane
$P.find('IFRAME').css(_c.hidden).css(_c.visible);
};
/**
* _cssNum
*
* Returns the 'current CSS numeric 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) {
if (!$E.jquery) $E = $($E);
var CSS = _showInvisibly($E);
var val = parseInt($.curCSS($E[0], prop, true), 10) || 0;
$E.css( CSS ); // RESET
return val;
};
var _borderWidth = function (E, side) {
if (E.jquery) E = E[0];
var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left
return $.curCSS(E, b+"Style", true) == "none" ? 0 : (parseInt($.curCSS(E, b+"Width", true), 10) || 0);
};
/**
* cssW / cssH / cssSize / cssMinDims
*
* Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype
*
* @callers initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
* @param Variant el 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 innerWidth/Height of the elem by subtracting padding and borders
*
* @TODO May need additional logic for other browser/doctype variations? Maybe use more jQuery methods?
*/
var cssW = function (el, outerWidth) {
var
str = typeof el == "string"
, $E = str ? $Ps[el] : $(el)
;
if (isNaN(outerWidth)) // not specified
outerWidth = str ? getPaneSize(el) : $E.outerWidth();
// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
if (outerWidth <= 0) return 0;
if (!state.browser.boxModel) return outerWidth;
// strip border and padding from outerWidth to get CSS Width
var W = outerWidth
- _borderWidth($E, "Left")
- _borderWidth($E, "Right")
- _cssNum($E, "paddingLeft")
- _cssNum($E, "paddingRight")
;
return W > 0 ? W : 0;
};
var cssH = function (el, outerHeight) {
var
str = typeof el == "string"
, $E = str ? $Ps[el] : $(el)
;
if (isNaN(outerHeight)) // not specified
outerHeight = str ? getPaneSize(el) : $E.outerHeight();
// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
if (outerHeight <= 0) return 0;
if (!state.browser.boxModel) return outerHeight;
// strip border and padding from outerHeight to get CSS Height
var H = outerHeight
- _borderWidth($E, "Top")
- _borderWidth($E, "Bottom")
- _cssNum($E, "paddingTop")
- _cssNum($E, "paddingBottom")
;
return H > 0 ? H : 0;
};
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);
};
var cssMinDims = function (pane) {
// minWidth/Height means CSS width/height = 1px
var
dir = _c[pane].dir
, d = {
minWidth: 1001 - cssW(pane, 1000)
, minHeight: 1001 - cssH(pane, 1000)
}
;
if (dir == "horz") d.minSize = d.minHeight;
if (dir == "vert") d.minSize = d.minWidth;
return d;
};
// TODO: see if these methods can be made more useful...
// TODO: *maybe* return cssW/H from these so caller can use this info
var setOuterWidth = function (el, outerWidth, autoHide) {
var $E = el, w;
if (typeof el == "string") $E = $Ps[el]; // west
else if (!el.jquery) $E = $(el);
w = cssW($E, outerWidth);
$E.css({ width: w });
if (w > 0) {
if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) {
$E.show().data('autoHidden', false);
if (!state.browser.mozilla) // FireFox refreshes iframes - IE doesn't
// make hidden, then visible to 'refresh' display after animation
$E.css(_c.hidden).css(_c.visible);
}
}
else if (autoHide && !$E.data('autoHidden'))
$E.hide().data('autoHidden', true);
};
var setOuterHeight = function (el, outerHeight, autoHide) {
var $E = el;
if (typeof el == "string") $E = $Ps[el]; // west
else if (!el.jquery) $E = $(el);
h = cssH($E, outerHeight);
$E.css({ height: h, visibility: "visible" }); // may have been 'hidden' by sizeContent
if (h > 0 && $E.innerWidth() > 0) {
if (autoHide && $E.data('autoHidden')) {
$E.show().data('autoHidden', false);
if (!state.browser.mozilla) // FireFox refreshes iframes - IE doesn't
$E.css(_c.hidden).css(_c.visible);
}
}
else if (autoHide && !$E.data('autoHidden'))
$E.hide().data('autoHidden', true);
};
var setOuterSize = function (el, outerSize, autoHide) {
if (_c[pane].dir=="horz") // pane = north or south
setOuterHeight(el, outerSize, autoHide);
else // pane = east or west
setOuterWidth(el, outerSize, autoHide);
};
/**
* _parseSize
*
* Converts any 'size' params to a pixel/integer size, if not already
* If 'auto' or a decimal/percentage is passed as 'size', a pixel-size is calculated
*
* @returns Integer
*/
var _parseSize = function (pane, size, dir) {
if (!dir) dir = _c[pane].dir;
if (typeof size=='string' && size.match(/%/))
size = parseInt(size) / 100; // convert % to decimal
if (size === 0)
return 0;
else if (size >= 1)
return parseInt(size,10);
else if (size > 0) { // percentage, eg: .25
var o = options, avail;
if (dir=="horz") // north or south or center.minHeight
avail = sC.innerHeight - ($Ps.north ? o.north.spacing_open : 0) - ($Ps.south ? o.south.spacing_open : 0);
else if (dir=="vert") // east or west or center.minWidth
avail = sC.innerWidth - ($Ps.west ? o.west.spacing_open : 0) - ($Ps.east ? o.east.spacing_open : 0);
return Math.floor(avail * size);
}
else if (pane=="center")
return 0;
else { // size < 0 || size=='auto' || size==Missing || size==Invalid
// auto-size the pane
var
$P = $Ps[pane]
, dim = (dir == "horz" ? "height" : "width")
, vis = _showInvisibly($P) // show pane invisibly if hidden
, s = $P.css(dim); // SAVE current size
;
$P.css(dim, "auto");
size = (dim == "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE
$P.css(dim, s).css(vis); // RESET size & visibility
return size;
}
};
/**
* getPaneSize
*
* Calculates current 'size' (outer-width or outer-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;
};
/**
* setSizeLimits
*
* Calculate min/max pane dimensions and limits for resizing
*/
var setSizeLimits = function (pane, slide) {
var
o = options[pane]
, s = state[pane]
, c = _c[pane]
, dir = c.dir
, side = c.side.toLowerCase()
, type = c.sizeType.toLowerCase()
, isSliding = (slide != undefined ? slide : s.isSliding) // only open() passes 'slide' param
, $P = $Ps[pane]
, paneSpacing = o.spacing_open
// measure the pane on the *opposite side* from this pane
, altPane = _c.altSide[pane]
, altS = state[altPane]
, $altP = $Ps[altPane]
, altPaneSize = (!$altP || altS.isVisible===false || altS.isSliding ? 0 : (dir=="horz" ? $altP.outerHeight() : $altP.outerWidth()))
, altPaneSpacing = ((!$altP || altS.isHidden ? 0 : options[altPane][ altS.isClosed !== false ? "spacing_closed" : "spacing_open" ]) || 0)
// limitSize prevents this pane from 'overlapping' opposite pane
, containerSize = (dir=="horz" ? sC.innerHeight : sC.innerWidth)
, minCenterDims = cssMinDims("center")
, minCenterSize = dir=="horz" ? max(options.center.minHeight, minCenterDims.minHeight) : max(options.center.minWidth, minCenterDims.minWidth)
// if pane is 'sliding', then ignore center and alt-pane sizes - because 'overlays' them
, limitSize = (containerSize - paneSpacing - (isSliding ? 0 : (_parseSize("center", minCenterSize, dir) + altPaneSize + altPaneSpacing)))
, minSize = s.minSize = max( _parseSize(pane, o.minSize), cssMinDims(pane).minSize )
, maxSize = s.maxSize = min( (o.maxSize ? _parseSize(pane, o.maxSize) : 100000), limitSize )
, r = s.resizerPosition = {} // used to set resizing limits
, top = sC.insetTop
, left = sC.insetLeft
, W = sC.innerWidth
, H = sC.innerHeight
, rW = o.spacing_open // subtract resizer-width to get top/left position for south/east
;
switch (pane) {
case "north": r.min = top + minSize;
r.max = top + maxSize;
break;
case "west": r.min = left + minSize;
r.max = left + maxSize;
break;
case "south": r.min = top + H - maxSize - rW;
r.max = top + H - minSize - rW;
break;
case "east": r.min = left + W - maxSize - rW;
r.max = left + W - minSize - rW;
break;
};
};
/**
* calcNewCenterPaneDims
*
* Returns data for setting the size/position of center pane. 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 calcNewCenterPaneDims = function () {
var d = {
top: getPaneSize("north", true) // true = include 'spacing' value for pane
, bottom: getPaneSize("south", true)
, left: getPaneSize("west", true)
, right: getPaneSize("east", true)
, width: 0
, height: 0
};
with (d) { // NOTE: sC = state.container
// calc center-pane's outer dimensions
width = sC.innerWidth - left - right; // outerWidth
height = sC.innerHeight - bottom - top; // outerHeight
// add the 'container border/padding' to get final positions relative to the container
top += sC.insetTop;
bottom += sC.insetBottom;
left += sC.insetLeft;
right += sC.insetRight;
}
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
, x = d.css = {} // CSS hash
, i = {} // TEMP insets
, b, p // TEMP border, padding
, off = $E.offset()
;
d.offsetLeft = off.left;
d.offsetTop = off.top;
$.each("Left,Right,Top,Bottom".split(","), function (idx, e) {
b = x["border" + e] = _borderWidth($E, e);
p = x["padding"+ e] = _cssNum($E, "padding"+e);
i[e] = b + p; // total offset of content from outer side
d["inset"+ e] = p;
/* WRONG ???
// if BOX MODEL, then 'position' = PADDING (ignore borderWidth)
if ($E == $Container)
d["inset"+ e] = (state.browser.boxModel ? p : 0);
*/
});
d.offsetWidth = $E.innerWidth(true); // true=include Padding
d.offsetHeight = $E.innerHeight(true);
d.outerWidth = $E.outerWidth();
d.outerHeight = $E.outerHeight();
d.innerWidth = d.outerWidth - i.Left - i.Right;
d.innerHeight = d.outerHeight - i.Top - i.Bottom;
// TESTING
x.width = $E.width();
x.height = $E.height();
return d;
};
var getElemCSS = function ($E, list) {
var
CSS = {}
, style = $E[0].style
, props = list.split(",")
, sides = "Top,Bottom,Left,Right".split(",")
, attrs = "Color,Style,Width".split(",")
, p, s, a, i, j, k
;
for (i=0; i < props.length; i++) {
p = props[i];
if (p.match(/(border|padding|margin)$/))
for (j=0; j < 4; j++) {
s = sides[j];
if (p == "border")
for (k=0; k < 3; k++) {
a = attrs[k];
CSS[p+s+a] = style[p+s+a];
}
else
CSS[p+s] = style[p+s];
}
else
CSS[p] = style[p];
};
return CSS
};
var setTimer = function (name, fn, ms) {
clearTimer(name); // clear previous timer if exists
_c.timers[name] = setTimeout(fn, ms);
};
var clearTimer = function (name) {
if (_c.timers[name]) {
clearTimeout(_c.timers[name]);
delete _c.timers[name];
}
};
var isTimerRunning = function (name) {
return !!_c.timers[name];
}
var getHoverClasses = function (el, allStates) {
var
$El = $(el)
, type = $El.data("layoutRole")
, pane = $El.data("layoutEdge")
, o = options[pane]
, root = o[type +"Class"]
, _pane = "-"+ pane // eg: "-west"
, _open = "-open"
, _closed = "-closed"
, _slide = "-sliding"
, _hover = "-hover " // NOTE the trailing space
, _state = $El.hasClass(root+_closed) ? _closed : _open
, _alt = _state == _closed ? _open : _closed
, classes = (root+_hover) + (root+_pane+_hover) + (root+_state+_hover) + (root+_pane+_state+_hover)
;
if (allStates) // when 'removing' classes, also remove alternate-state classes
classes += (root+_alt+_hover) + (root+_pane+_alt+_hover);
if (type=="resizer" && $El.hasClass(root+_slide))
classes += (root+_slide+_hover) + (root+_pane+_slide+_hover);
return $.trim(classes);
};
var addHover = function (evt, el) {
var e = el || this;
$(e).addClass( getHoverClasses(e) );
//if (evt && $(e).data("layoutRole") == "toggler") evt.stopPropagation();
};
var removeHover = function (evt, el) {
var e = el || this;
$(e).removeClass( getHoverClasses(e, true) );
};
/*
* ###########################
* INITIALIZATION METHODS
* ###########################
*/
/**
* _create
*
* Initialize the layout - called automatically whenever an instance of layout is created
*
* @callers none - triggered onInit
* @returns An object pointer to the instance created
*/
var _create = function () {
// initialize config/options
initOptions();
var o = options;
// onload will CANCEL resizing if returns false
if (false === _execCallback(null, o.onload)) return false;
// a center pane is required, so make sure it exists
if (!getPane('center').length) {
alert( lang.errCenterPaneMissing );
return null;
}
// update options with saved state, if option enabled
if (o.useStateCookie && o.cookie.autoLoad)
loadCookie(); // Update options from state-cookie
// set environment - can update code here if $.browser is phased out
state.browser = {
mozilla: $.browser.mozilla
, msie: $.browser.msie
, isIE6: $.browser.msie && $.browser.version == 6
, boxModel: $.support.boxModel
//, version: $.browser.version - not used
};
// initialize all layout elements
initContainer(); // set CSS as needed and init state.container dimensions
initPanes(); // size & position all panes - calls initHandles()
//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 (o.scrollToBookmarkOnLoad)
with (self.location) if (hash) replace( hash ); // scrollTo Bookmark
// search for and bind custom-buttons
if (o.autoBindCustomButtons) initButtons();
// bind hotkey function - keyDown - if required
initHotkeys();
// track mouse position so we can use it anytime we need it
initMouseTracking();
// bind resizeAll() for 'this layout instance' to window.resize event
if (o.resizeWithWindow && !$Container.data("layoutRole")) // skip if 'nested' inside a pane
$(window).bind("resize."+ sID, windowResize);
// bind window.onunload
$(window).bind("unload."+ sID, unload);
state.initialized = true;
};
var windowResize = function () {
var delay = Number(options.resizeWithWindowDelay) || 100; // there MUST be some delay!
if (delay > 0) {
// resizing uses a delay-loop because the resize event fires repeatly - except in FF, but delay anyway
clearTimer("winResize"); // if already running
setTimer("winResize", function(){ clearTimer("winResize"); clearTimer("winResizeRepeater"); resizeAll(); }, delay);
// ALSO set fixed-delay timer, if not already running
if (!_c.timers["winResizeRepeater"]) setWindowResizeRepeater();
}
};
var setWindowResizeRepeater = function () {
var delay = Number(options.resizeWithWindowMaxDelay);
if (delay > 0)
setTimer("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay);
};
var unload = function () {
var o = options;
state.cookie = getState(); // save state in case onunload has custom state-management
if (o.useStateCookie && o.cookie.autoSave) saveCookie();
_execCallback(null, o.onunload);
};
/**
* initMouseTracking / trackMouse / isMouseOver
*
* Bound to document.mousemove - updates window.mouseCoords.X/Y
*
* TODO: use ui.isOver(y, x, top, left, height, width)
*/
var initMouseTracking = function () {
if (!window.mouseCoords) { // only need 1 mouse tracker!
window.mouseCoords = { X: 0, Y: 0 }; // init
$(document).bind("mousemove."+ sID, trackMouse);
}
};
var trackMouse = function (evt) {
var m = window.mouseCoords;
m.X = evt.pageX;
m.Y = evt.pageY;
};
var isMouseOver = function (el) {
var $E = (typeof el == "string" && $Ps[el]) ? $Ps[el] : $(el);
if (!$E.length) return false;
var
_ = this
, d = $E.offset()
, T = d.top
, L = d.left
, R = L + $E.outerWidth()
, B = T + $E.outerHeight()
, m = window.mouseCoords
;
return ((m.X >= L && m.X <= R) && (m.Y >= T && m.Y <= B));
};
/**
* initContainer
*
* Validate and initialize container CSS and events
*
* @callers _create()
*/
var initContainer = function () {
sC.tagName = $Container.attr("tagName");
var
isFullPage = (sC.tagName == "BODY")
, $C = $Container // alias
, props = "position,margin,padding,border"
, CSS = {}
;
sC.ref = sC.tagName + ($C.selector || "").split(".slice")[0];
// the layoutContainer key is used to store the unique layoutID
$C
.data("layoutContainer", sID) // unique identifier for internal use
.data("layoutName", options.name) // add user's layout-name - even if blank!
;
// SAVE original container CSS for use in destroy()
if (!$C.data("layoutCSS")) {
// handle props like overflow different for BODY & HTML - has 'system default' values
if (isFullPage) {
CSS = $.extend( getElemCSS($C, props), {
height: $C.css("height")
, overflow: $C.css("overflow")
, overflowX: $C.css("overflowX")
, overflowY: $C.css("overflowY")
});
// ALSO SAVE <HTML> CSS
var $H = $("html");
$H.data("layoutCSS", {
height: "auto" // FF would return a fixed px-size!
, overflow: $H.css("overflow")
, overflowX: $H.css("overflowX")
, overflowY: $H.css("overflowY")
});
}
else // handle props normally for non-body elements
CSS = getElemCSS($C, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY");
$C.data("layoutCSS", CSS);
}
try { // format html/body if this is a full page layout
if (isFullPage) {
$("html").css({
height: "100%"
, overflow: "hidden"
, overflowX: "hidden"
, overflowY: "hidden"
});
$("body").css({
position: "relative"
, height: "100%"
, overflow: "hidden"
, overflowX: "hidden"
, overflowY: "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 for overflow and position
var
CSS = { overflow: "hidden" } // make sure container will not 'scroll'
, p = $C.css("position")
, h = $C.css("height")
;
// if this is a NESTED layout, then container/outer-pane ALREADY has position and height
if (!$C.data("layoutRole")) {
if (!p || !p.match(/fixed|absolute|relative/))
CSS.position = "relative"; // container MUST have a 'position'
/*
if (!h || h=="auto")
CSS.height = "100%"; // container MUST have a 'height'
*/
}
$C.css( CSS );
if ($C.is(":visible") && $C.innerHeight() < 2)
alert( lang.errContainerHeight.replace(/CONTAINER/, sC.ref) );
}
} catch (ex) {}
// set current layout-container dimensions
$.extend(state.container, getElemDims( $C ));
};
/**
* 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).bind("keydown."+ sID, keyDown); // only need to bind this ONCE
return false; // BREAK - binding was done
}
});
};
/**
* initOptions
*
* Build final OPTIONS data
*
* @callers _create()
*/
var initOptions = function () {
// simplify logic by making sure passed 'opts' var has basic keys
opts = _transformData( opts );
// TODO: create a compatibility add-on for new UI widget that will transform old option syntax
var newOpts = {
applyDefaultStyles: "applyDemoStyles"
};
renameOpts(opts.defaults);
$.each(_c.allPanes.split(","), function (i, pane) {
renameOpts(opts[pane]);
});
// update default effects, if case user passed key
if (opts.effects) {
$.extend( effects, opts.effects );
delete opts.effects;
}
$.extend( options.cookie, opts.cookie );
// see if any 'global options' were specified
var globals = "name,zIndex,scrollToBookmarkOnLoad,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"+
"onresizeall,onresizeall_start,onresizeall_end,onload,onunload,autoBindCustomButtons,useStateCookie";
$.each(globals.split(","), function (i, 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 (i, key) { delete opts.defaults[key]; } // is OK if key does not exist
);
// now update options.defaults
$.extend( true, options.defaults, opts.defaults );
// merge config for 'center-pane' - border-panes handled in the loop below
_c.center = $.extend( true, {}, _c.panes, _c.center );
// update config.zIndex values if zIndex option specified
var z = options.zIndex;
if (z === 0 || z > 0) {
_c.zIndex.pane_normal = z;
_c.zIndex.resizer_normal = z+1;
_c.zIndex.iframe_mask = z+1;
}
// merge options for 'center-pane' - border-panes handled in the loop below
$.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,applyDemoStyles,showOverflowOnHover,triggerEventsOnLoad".split(","),
function (i, key) { options.center[key] = o_Center[key]; }
);
var o, 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.panes, _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 = "ui-layout-pane";
if (!o.resizerClass) o.resizerClass = "ui-layout-resizer";
if (!o.togglerClass) o.togglerClass = "ui-layout-toggler";
// 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
;
});
});
function renameOpts (O) {
for (var key in newOpts) {
if (O[key] != undefined) {
O[newOpts[key]] = O[key];
delete O[key];
}
}
}
};
/**
* initPanes
*
* Initialize module objects, styling, size and position for all panes
*
* @callers _create()
*/
var getPane = function (pane) {
var sel = options[pane].paneSelector
if (sel.substr(0,1)==="#") // ID selector
// NOTE: elements selected 'by ID' DO NOT have to be 'children'
return $Container.find(sel).eq(0);
else { // class or other selector
var $P = $Container.children(sel).eq(0);
// look for the pane nested inside a 'form' element
return $P.length ? $P : $Container.children("form:first").children(sel).eq(0);
}
};
var initPanes = function () {
// NOTE: do north & south FIRST so we can measure their height - do center LAST
$.each(_c.allPanes.split(","), function (idx, pane) {
var
o = options[pane]
, s = state[pane]
, c = _c[pane]
, fx = s.fx
, dir = c.dir
, spacing = o.spacing_open || 0
, isCenter = (pane == "center")
, CSS = {}
, $P, $C
, size, minSize, maxSize
;
$Cs[pane] = false; // init
$P = $Ps[pane] = getPane(pane);
if (!$P.length) {
$Ps[pane] = false; // logic
return true; // SKIP to next
}
// SAVE original Pane CSS
if (!$P.data("layoutCSS")) {
var props = "position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border";
$P.data("layoutCSS", getElemCSS($P, props));
}
// add basic classes & attributes
$P
.data("