mirror of
https://github.com/silverstripe/silverstripe-cms
synced 2024-10-22 08:05:56 +02:00
2507 lines
81 KiB
JavaScript
2507 lines
81 KiB
JavaScript
|
/*
|
||
|
* 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: 200
|
||
|
, 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] = $("<span></span>");
|
||
|
|
||
|
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] = $("<div></div>");
|
||
|
$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
|
||
|
$("<span>"+ o.togglerContent_open +"</span>")
|
||
|
.addClass("content content-open")
|
||
|
.css("display", s.isClosed ? "none" : "block")
|
||
|
.appendTo( $T )
|
||
|
;
|
||
|
if (o.togglerContent_closed) // ui-layout-closed
|
||
|
$("<span>"+ o.togglerContent_closed +"</span>")
|
||
|
.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() {
|
||
|
$('<div class="ui-layout-mask"/>')
|
||
|
.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 );
|