source: dashboard/lib/js/jquery.layout-latest.js@ 352

Last change on this file since 352 was 335, checked in by Rick van der Zwet, 13 years ago

New layout and UI

File size: 147.2 KB
RevLine 
[335]1/**
2 * @preserve jquery.layout 1.3.0 - Release Candidate 29.15
3 * $Date: 2011-06-25 08:00:00 (Sat, 25 Jun 2011) $
4 * $Rev: 302915 $
5 *
6 * Copyright (c) 2010
7 * Fabrizio Balliano (http://www.fabrizioballiano.net)
8 * Kevin Dalman (http://allpro.net)
9 *
10 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
11 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
12 *
13 * Changelog: http://layout.jquery-dev.net/changelog.cfm#1.3.0.rc29.15
14 *
15 * Docs: http://layout.jquery-dev.net/documentation.html
16 * Tips: http://layout.jquery-dev.net/tips.html
17 * Help: http://groups.google.com/group/jquery-ui-layout
18 */
19
20// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars
21
22;(function ($) {
23
24/*
25 * GENERIC $.layout METHODS - used by all layouts
26 */
27$.layout = {
28
29 version: "1.3.rc29.15"
30, revision: 0.032915 // 1.3.0 final = 1.0300 - major(n+).minor(nn)+patch(nn+)
31
32 // LANGUAGE CUSTOMIZATION
33, language: {
34 // Tips and messages for resizers, togglers, custom buttons, etc.
35 Open: "Open" // eg: "Open Pane"
36 , Close: "Close"
37 , Resize: "Resize"
38 , Slide: "Slide Open"
39 , Pin: "Pin"
40 , Unpin: "Un-Pin"
41 , noRoomToOpenTip: "Not enough room to show this pane."
42 // Developer error messages
43 , pane: "pane" // description of "layout pane element"
44 , selector: "selector" // description of "jQuery-selector"
45 , errButton: "Error Adding Button \n\nInvalid "
46 , errContainerMissing: "UI Layout Initialization Error\n\nThe specified layout-container does not exist."
47 , errCenterPaneMissing: "UI Layout Initialization Error\n\nThe center-pane element does not exist.\n\nThe center-pane is a required element."
48 , errContainerHeight: "UI Layout Initialization Warning\n\nThe layout-container \"CONTAINER\" has no height.\n\nTherefore the layout is 0-height and hence 'invisible'!"
49 }
50
51 // can update code here if $.browser is phased out
52, browser: {
53 mozilla: !!$.browser.mozilla
54 , webkit: !!$.browser.webkit || !!$.browser.safari // webkit = jQ 1.4
55 , msie: !!$.browser.msie
56 , isIE6: !!$.browser.msie && $.browser.version == 6
57 , boxModel: false // page must load first, so will be updated set by _create
58 //, version: $.browser.version - not used
59 }
60
61 /*
62 * GENERIC UTILITY METHODS
63 */
64
65 // calculate and return the scrollbar width, as an integer
66, scrollbarWidth: function () { return window.scrollbarWidth || $.layout.getScrollbarSize('width'); }
67, scrollbarHeight: function () { return window.scrollbarHeight || $.layout.getScrollbarSize('height'); }
68, getScrollbarSize: function (dim) {
69 var $c = $('<div style="position: absolute; top: -10000px; left: -10000px; width: 100px; height: 100px; overflow: scroll;"></div>').appendTo("body");
70 var d = { width: $c.width() - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight };
71 $c.remove();
72 window.scrollbarWidth = d.width;
73 window.scrollbarHeight = d.height;
74 return dim.match(/^(width|height)$/i) ? d[dim] : d;
75 }
76
77
78 /**
79 * Returns hash container 'display' and 'visibility'
80 *
81 * @see $.swap() - swaps CSS, runs callback, resets CSS
82 */
83, showInvisibly: function ($E, force) {
84 if (!$E) return {};
85 if (!$E.jquery) $E = $($E);
86 var CSS = {
87 display: $E.css('display')
88 , visibility: $E.css('visibility')
89 };
90 if (force || CSS.display == "none") { // only if not *already hidden*
91 $E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so can be measured
92 return CSS;
93 }
94 else return {};
95 }
96
97 /**
98 * Returns data for setting size of an element (container or a pane).
99 *
100 * @see _create(), onWindowResize() for container, plus others for pane
101 * @return JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
102 */
103, getElementDimensions: function ($E) {
104 var
105 d = {} // dimensions hash
106 , x = d.css = {} // CSS hash
107 , i = {} // TEMP insets
108 , b, p // TEMP border, padding
109 , N = $.layout.cssNum
110 , off = $E.offset()
111 ;
112 d.offsetLeft = off.left;
113 d.offsetTop = off.top;
114
115 $.each("Left,Right,Top,Bottom".split(","), function (idx, e) { // e = edge
116 b = x["border" + e] = $.layout.borderWidth($E, e);
117 p = x["padding"+ e] = $.layout.cssNum($E, "padding"+e);
118 i[e] = b + p; // total offset of content from outer side
119 d["inset"+ e] = p;
120 });
121
122 d.offsetWidth = $E.innerWidth();
123 d.offsetHeight = $E.innerHeight();
124 d.outerWidth = $E.outerWidth();
125 d.outerHeight = $E.outerHeight();
126 d.innerWidth = Math.max(0, d.outerWidth - i.Left - i.Right);
127 d.innerHeight = Math.max(0, d.outerHeight - i.Top - i.Bottom);
128
129 x.width = $E.width();
130 x.height = $E.height();
131 x.top = N($E,"top",true);
132 x.bottom = N($E,"bottom",true);
133 x.left = N($E,"left",true);
134 x.right = N($E,"right",true);
135
136 //d.visible = $E.is(":visible");// && x.width > 0 && x.height > 0;
137
138 return d;
139 }
140
141, getElementCSS: function ($E, list) {
142 var
143 CSS = {}
144 , style = $E[0].style
145 , props = list.split(",")
146 , sides = "Top,Bottom,Left,Right".split(",")
147 , attrs = "Color,Style,Width".split(",")
148 , p, s, a, i, j, k
149 ;
150 for (i=0; i < props.length; i++) {
151 p = props[i];
152 if (p.match(/(border|padding|margin)$/))
153 for (j=0; j < 4; j++) {
154 s = sides[j];
155 if (p == "border")
156 for (k=0; k < 3; k++) {
157 a = attrs[k];
158 CSS[p+s+a] = style[p+s+a];
159 }
160 else
161 CSS[p+s] = style[p+s];
162 }
163 else
164 CSS[p] = style[p];
165 };
166 return CSS
167 }
168
169 /**
170 * Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype
171 *
172 * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
173 * @param {Array.<Object>} $E Must pass a jQuery object - first element is processed
174 * @param {number=} outerWidth/outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized
175 * @return {number} Returns the innerWidth/Height of the elem by subtracting padding and borders
176 */
177, cssWidth: function ($E, outerWidth) {
178 var
179 b = $.layout.borderWidth
180 , n = $.layout.cssNum
181 ;
182 // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
183 if (outerWidth <= 0) return 0;
184
185 if (!$.layout.browser.boxModel) return outerWidth;
186
187 // strip border and padding from outerWidth to get CSS Width
188 var W = outerWidth
189 - b($E, "Left")
190 - b($E, "Right")
191 - n($E, "paddingLeft")
192 - n($E, "paddingRight")
193 ;
194
195 return Math.max(0,W);
196 }
197
198, cssHeight: function ($E, outerHeight) {
199 var
200 b = $.layout.borderWidth
201 , n = $.layout.cssNum
202 ;
203 // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
204 if (outerHeight <= 0) return 0;
205
206 if (!$.layout.browser.boxModel) return outerHeight;
207
208 // strip border and padding from outerHeight to get CSS Height
209 var H = outerHeight
210 - b($E, "Top")
211 - b($E, "Bottom")
212 - n($E, "paddingTop")
213 - n($E, "paddingBottom")
214 ;
215
216 return Math.max(0,H);
217 }
218
219 /**
220 * Returns the 'current CSS numeric value' for a CSS property - 0 if property does not exist
221 *
222 * @see Called by many methods
223 * @param {Array.<Object>} $E Must pass a jQuery object - first element is processed
224 * @param {string} prop The name of the CSS property, eg: top, width, etc.
225 * @param {boolean=} allowAuto true = return 'auto' if that is value; false = return 0
226 * @return {(string|number)} Usually used to get an integer value for position (top, left) or size (height, width)
227 */
228, cssNum: function ($E, prop, allowAuto) {
229 if (!$E.jquery) $E = $($E);
230 var CSS = $.layout.showInvisibly($E)
231 , p = $.curCSS($E[0], prop, true)
232 , v = allowAuto && p=="auto" ? p : (parseInt(p, 10) || 0);
233 $E.css( CSS ); // RESET
234 return v;
235 }
236
237, borderWidth: function (el, side) {
238 if (el.jquery) el = el[0];
239 var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left
240 return $.curCSS(el, b+"Style", true) == "none" ? 0 : (parseInt($.curCSS(el, b+"Width", true), 10) || 0);
241 }
242
243
244 /**
245 * UTLITY for mouse tracking - FUTURE REFERENCE
246 *
247 * init: if (!window.mouse) {
248 * window.mouse = { x: 0, y: 0 };
249 * $(document).mousemove( $.layout.trackMouse );
250 * }
251 *
252 * @param {Object} evt
253 *
254, trackMouse: function (evt) {
255 window.mouse = { x: evt.clientX, y: evt.clientY };
256 }
257 */
258
259 /**
260 * SUBROUTINE for preventPrematureSlideClose option
261 *
262 * @param {Object} evt
263 * @param {Object=} el
264 */
265, isMouseOverElem: function (evt, el) {
266 var
267 $E = $(el || this)
268 , d = $E.offset()
269 , T = d.top
270 , L = d.left
271 , R = L + $E.outerWidth()
272 , B = T + $E.outerHeight()
273 , x = evt.pageX // evt.clientX ?
274 , y = evt.pageY // evt.clientY ?
275 ;
276 // if X & Y are < 0, probably means is over an open SELECT
277 return ($.layout.browser.msie && x < 0 && y < 0) || ((x >= L && x <= R) && (y >= T && y <= B));
278 }
279
280};
281
282$.fn.layout = function (opts) {
283
284/*
285 * ###########################
286 * WIDGET CONFIG & OPTIONS
287 * ###########################
288 */
289 var
290
291 // LANGUAGE - for tips & messages
292 lang = $.layout.language // internal alias
293
294 // DEFAULT OPTIONS - CHANGE IF DESIRED
295, options = {
296 name: "" // Not required, but useful for buttons and used for the state-cookie
297 , containerClass: "ui-layout-container" // layout-container element
298 , scrollToBookmarkOnLoad: true // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark)
299 , resizeWithWindow: true // bind thisLayout.resizeAll() to the window.resize event
300 , resizeWithWindowDelay: 200 // delay calling resizeAll because makes window resizing very jerky
301 , resizeWithWindowMaxDelay: 0 // 0 = none - force resize every XX ms while window is being resized
302 , onresizeall_start: null // CALLBACK when resizeAll() STARTS - NOT pane-specific
303 , onresizeall_end: null // CALLBACK when resizeAll() ENDS - NOT pane-specific
304 , onload_start: null // CALLBACK when Layout inits - after options initialized, but before elements
305 , onload_end: null // CALLBACK when Layout inits - after EVERYTHING has been initialized
306 , onunload_start: null // CALLBACK when Layout is destroyed OR onWindowUnload
307 , onunload_end: null // CALLBACK when Layout is destroyed OR onWindowUnload
308 , autoBindCustomButtons: false // search for buttons with ui-layout-button class and auto-bind them
309 , zIndex: null // the PANE zIndex - resizers and masks will be +1
310 , initPanes: true // false = DO NOT initialize the panes onLoad - will init later
311 , showErrorMessages: true // enables fatal error messages to warn developers of common errors
312 // PANE SETTINGS
313 , defaults: { // default options for 'all panes' - will be overridden by 'per-pane settings'
314 applyDemoStyles: false // NOTE: renamed from applyDefaultStyles for clarity
315 , closable: true // pane can open & close
316 , resizable: true // when open, pane can be resized
317 , slidable: true // when closed, pane can 'slide open' over other panes - closes on mouse-out
318 , initClosed: false // true = init pane as 'closed'
319 , initHidden: false // true = init pane as 'hidden' - no resizer-bar/spacing
320 // SELECTORS
321 //, paneSelector: "" // MUST be pane-specific - jQuery selector for pane
322 , contentSelector: ".ui-layout-content" // INNER div/element to auto-size so only it scrolls, not the entire pane!
323 , contentIgnoreSelector: ".ui-layout-ignore" // element(s) to 'ignore' when measuring 'content'
324 , findNestedContent: false // true = $P.find(contentSelector), false = $P.children(contentSelector)
325 // GENERIC ROOT-CLASSES - for auto-generated classNames
326 , paneClass: "ui-layout-pane" // border-Pane - default: 'ui-layout-pane'
327 , resizerClass: "ui-layout-resizer" // Resizer Bar - default: 'ui-layout-resizer'
328 , togglerClass: "ui-layout-toggler" // Toggler Button - default: 'ui-layout-toggler'
329 , buttonClass: "ui-layout-button" // CUSTOM Buttons - default: 'ui-layout-button-toggle/-open/-close/-pin'
330 // ELEMENT SIZE & SPACING
331 //, size: 100 // MUST be pane-specific -initial size of pane
332 , minSize: 0 // when manually resizing a pane
333 , maxSize: 0 // ditto, 0 = no limit
334 , spacing_open: 6 // space between pane and adjacent panes - when pane is 'open'
335 , spacing_closed: 6 // ditto - when pane is 'closed'
336 , togglerLength_open: 50 // Length = WIDTH of toggler button on north/south sides - HEIGHT on east/west sides
337 , togglerLength_closed: 50 // 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden'
338 , togglerAlign_open: "center" // top/left, bottom/right, center, OR...
339 , togglerAlign_closed: "center" // 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right
340 , togglerTip_open: lang.Close // Toggler tool-tip (title)
341 , togglerTip_closed: lang.Open // ditto
342 , togglerContent_open: "" // text or HTML to put INSIDE the toggler
343 , togglerContent_closed: "" // ditto
344 // RESIZING OPTIONS
345 , resizerDblClickToggle: true //
346 , autoResize: true // IF size is 'auto' or a percentage, then recalc 'pixel size' whenever the layout resizes
347 , autoReopen: true // IF a pane was auto-closed due to noRoom, reopen it when there is room? False = leave it closed
348 , resizerDragOpacity: 1 // option for ui.draggable
349 //, resizerCursor: "" // MUST be pane-specific - cursor when over resizer-bar
350 , maskIframesOnResize: true // true = all iframes OR = iframe-selector(s) - adds masking-div during resizing/dragging
351 , resizeNestedLayout: true // true = trigger nested.resizeAll() when a 'pane' of this layout is the 'container' for another
352 , resizeWhileDragging: false // true = LIVE Resizing as resizer is dragged
353 , resizeContentWhileDragging: false // true = re-measure header/footer heights as resizer is dragged
354 // TIPS & MESSAGES - also see lang object
355 , noRoomToOpenTip: lang.noRoomToOpenTip
356 , resizerTip: lang.Resize // Resizer tool-tip (title)
357 , sliderTip: lang.Slide // resizer-bar triggers 'sliding' when pane is closed
358 , sliderCursor: "pointer" // cursor when resizer-bar will trigger 'sliding'
359 , slideTrigger_open: "click" // click, dblclick, mouseenter
360 , slideTrigger_close: "mouseleave"// click, mouseleave
361 , slideDelay_open: 300 // applies only for mouseenter event - 0 = instant open
362 , slideDelay_close: 300 // applies only for mouseleave event (300ms is the minimum!)
363 , hideTogglerOnSlide: false // when pane is slid-open, should the toggler show?
364 , preventQuickSlideClose: $.layout.browser.webkit // Chrome triggers slideClosed as it is opening
365 , preventPrematureSlideClose: false
366 // HOT-KEYS & MISC
367 , showOverflowOnHover: false // will bind allowOverflow() utility to pane.onMouseOver
368 , enableCursorHotkey: true // enabled 'cursor' hotkeys
369 //, customHotkey: "" // MUST be pane-specific - EITHER a charCode OR a character
370 , customHotkeyModifier: "SHIFT" // either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT'
371 // PANE ANIMATION
372 // NOTE: fxSss_open & fxSss_close options (eg: fxName_open) are auto-generated if not passed
373 , fxName: "slide" // ('none' or blank), slide, drop, scale
374 , fxSpeed: null // slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration
375 , fxSettings: {} // can be passed, eg: { easing: "easeOutBounce", duration: 1500 }
376 , fxOpacityFix: true // tries to fix opacity in IE to restore anti-aliasing after animation
377 // CALLBACKS
378 , triggerEventsOnLoad: false // true = trigger onopen OR onclose callbacks when layout initializes
379 , triggerEventsWhileDragging: true // true = trigger onresize callback REPEATEDLY if resizeWhileDragging==true
380 , onshow_start: null // CALLBACK when pane STARTS to Show - BEFORE onopen/onhide_start
381 , onshow_end: null // CALLBACK when pane ENDS being Shown - AFTER onopen/onhide_end
382 , onhide_start: null // CALLBACK when pane STARTS to Close - BEFORE onclose_start
383 , onhide_end: null // CALLBACK when pane ENDS being Closed - AFTER onclose_end
384 , onopen_start: null // CALLBACK when pane STARTS to Open
385 , onopen_end: null // CALLBACK when pane ENDS being Opened
386 , onclose_start: null // CALLBACK when pane STARTS to Close
387 , onclose_end: null // CALLBACK when pane ENDS being Closed
388 , onresize_start: null // CALLBACK when pane STARTS being Resized ***FOR ANY REASON***
389 , onresize_end: null // CALLBACK when pane ENDS being Resized ***FOR ANY REASON***
390 , onsizecontent_start: null // CALLBACK when sizing of content-element STARTS
391 , onsizecontent_end: null // CALLBACK when sizing of content-element ENDS
392 , onswap_start: null // CALLBACK when pane STARTS to Swap
393 , onswap_end: null // CALLBACK when pane ENDS being Swapped
394 , ondrag_start: null // CALLBACK when pane STARTS being ***MANUALLY*** Resized
395 , ondrag_end: null // CALLBACK when pane ENDS being ***MANUALLY*** Resized
396 }
397 , north: {
398 paneSelector: ".ui-layout-north"
399 , size: "auto" // eg: "auto", "30%", 200
400 , resizerCursor: "n-resize" // custom = url(myCursor.cur)
401 , customHotkey: "" // EITHER a charCode OR a character
402 }
403 , south: {
404 paneSelector: ".ui-layout-south"
405 , size: "auto"
406 , resizerCursor: "s-resize"
407 , customHotkey: ""
408 }
409 , east: {
410 paneSelector: ".ui-layout-east"
411 , size: 200
412 , resizerCursor: "e-resize"
413 , customHotkey: ""
414 }
415 , west: {
416 paneSelector: ".ui-layout-west"
417 , size: 200
418 , resizerCursor: "w-resize"
419 , customHotkey: ""
420 }
421 , center: {
422 paneSelector: ".ui-layout-center"
423 , minWidth: 0
424 , minHeight: 0
425 }
426
427 // STATE MANAGMENT
428 , useStateCookie: false // Enable cookie-based state-management - can fine-tune with cookie.autoLoad/autoSave
429 , cookie: {
430 name: "" // If not specified, will use Layout.name, else just "Layout"
431 , autoSave: true // Save a state cookie when page exits?
432 , autoLoad: true // Load the state cookie when Layout inits?
433 // Cookie Options
434 , domain: ""
435 , path: ""
436 , expires: "" // 'days' to keep cookie - leave blank for 'session cookie'
437 , secure: false
438 // List of options to save in the cookie - must be pane-specific
439 , keys: "north.size,south.size,east.size,west.size,"+
440 "north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+
441 "north.isHidden,south.isHidden,east.isHidden,west.isHidden"
442 }
443 }
444
445
446 // PREDEFINED EFFECTS / DEFAULTS
447, effects = { // LIST *PREDEFINED EFFECTS* HERE, even if effect has no settings
448 slide: {
449 all: { duration: "fast" } // eg: duration: 1000, easing: "easeOutBounce"
450 , north: { direction: "up" }
451 , south: { direction: "down" }
452 , east: { direction: "right"}
453 , west: { direction: "left" }
454 }
455 , drop: {
456 all: { duration: "slow" } // eg: duration: 1000, easing: "easeOutQuint"
457 , north: { direction: "up" }
458 , south: { direction: "down" }
459 , east: { direction: "right"}
460 , west: { direction: "left" }
461 }
462 , scale: {
463 all: { duration: "fast" }
464 }
465 }
466
467
468 // DYNAMIC DATA - IS READ-ONLY EXTERNALLY!
469, state = {
470 // generate unique ID to use for event.namespace so can unbind only events added by 'this layout'
471 id: "layout"+ new Date().getTime() // code uses alias: sID
472 , initialized: false
473 , container: {} // init all keys
474 , north: {}
475 , south: {}
476 , east: {}
477 , west: {}
478 , center: {}
479 , cookie: {} // State Managment data storage
480 }
481
482
483 // INTERNAL CONFIG DATA - DO NOT CHANGE THIS!
484, _c = {
485 allPanes: "north,south,west,east,center"
486 , borderPanes: "north,south,west,east"
487 , altSide: {
488 north: "south"
489 , south: "north"
490 , east: "west"
491 , west: "east"
492 }
493 // CSS used in multiple places
494 , hidden: { visibility: "hidden" }
495 , visible: { visibility: "visible" }
496 // layout element settings
497 , zIndex: { // set z-index values here
498 pane_normal: 1 // normal z-index for panes
499 , resizer_normal: 2 // normal z-index for resizer-bars
500 , iframe_mask: 2 // overlay div used to mask pane(s) during resizing
501 , pane_sliding: 100 // applied to *BOTH* the pane and its resizer when a pane is 'slid open'
502 , pane_animate: 1000 // applied to the pane when being animated - not applied to the resizer
503 , resizer_drag: 10000 // applied to the CLONED resizer-bar when being 'dragged'
504 }
505 , resizers: {
506 cssReq: {
507 position: "absolute"
508 , padding: 0
509 , margin: 0
510 , fontSize: "1px"
511 , textAlign: "left" // to counter-act "center" alignment!
512 , overflow: "hidden" // prevent toggler-button from overflowing
513 // SEE c.zIndex.resizer_normal
514 }
515 , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
516 background: "#DDD"
517 , border: "none"
518 }
519 }
520 , togglers: {
521 cssReq: {
522 position: "absolute"
523 , display: "block"
524 , padding: 0
525 , margin: 0
526 , overflow: "hidden"
527 , textAlign: "center"
528 , fontSize: "1px"
529 , cursor: "pointer"
530 , zIndex: 1
531 }
532 , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
533 background: "#AAA"
534 }
535 }
536 , content: {
537 cssReq: {
538 position: "relative" /* contain floated or positioned elements */
539 }
540 , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
541 overflow: "auto"
542 , padding: "10px"
543 }
544 , cssDemoPane: { // DEMO CSS - REMOVE scrolling from 'pane' when it has a content-div
545 overflow: "hidden"
546 , padding: 0
547 }
548 }
549 , panes: { // defaults for ALL panes - overridden by 'per-pane settings' below
550 cssReq: {
551 position: "absolute"
552 , margin: 0
553 // SEE c.zIndex.pane_normal
554 }
555 , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
556 padding: "10px"
557 , background: "#FFF"
558 , border: "1px solid #BBB"
559 , overflow: "auto"
560 }
561 }
562 , north: {
563 side: "Top"
564 , sizeType: "Height"
565 , dir: "horz"
566 , cssReq: {
567 top: 0
568 , bottom: "auto"
569 , left: 0
570 , right: 0
571 , width: "auto"
572 // height: DYNAMIC
573 }
574 , pins: [] // array of 'pin buttons' to be auto-updated on open/close (classNames)
575 }
576 , south: {
577 side: "Bottom"
578 , sizeType: "Height"
579 , dir: "horz"
580 , cssReq: {
581 top: "auto"
582 , bottom: 0
583 , left: 0
584 , right: 0
585 , width: "auto"
586 // height: DYNAMIC
587 }
588 , pins: []
589 }
590 , east: {
591 side: "Right"
592 , sizeType: "Width"
593 , dir: "vert"
594 , cssReq: {
595 left: "auto"
596 , right: 0
597 , top: "auto" // DYNAMIC
598 , bottom: "auto" // DYNAMIC
599 , height: "auto"
600 // width: DYNAMIC
601 }
602 , pins: []
603 }
604 , west: {
605 side: "Left"
606 , sizeType: "Width"
607 , dir: "vert"
608 , cssReq: {
609 left: 0
610 , right: "auto"
611 , top: "auto" // DYNAMIC
612 , bottom: "auto" // DYNAMIC
613 , height: "auto"
614 // width: DYNAMIC
615 }
616 , pins: []
617 }
618 , center: {
619 dir: "center"
620 , cssReq: {
621 left: "auto" // DYNAMIC
622 , right: "auto" // DYNAMIC
623 , top: "auto" // DYNAMIC
624 , bottom: "auto" // DYNAMIC
625 , height: "auto"
626 , width: "auto"
627 }
628 }
629 }
630
631
632/*
633 * ###########################
634 * INTERNAL HELPER FUNCTIONS
635 * ###########################
636 */
637
638 /**
639 * Manages all internal timers
640 */
641, timer = {
642 data: {}
643 , set: function (s, fn, ms) { timer.clear(s); timer.data[s] = setTimeout(fn, ms); }
644 , clear: function (s) { var t=timer.data; if (t[s]) {clearTimeout(t[s]); delete t[s];} }
645 }
646
647 /**
648 * Returns true if passed param is EITHER a simple string OR a 'string object' - otherwise returns false
649 */
650, isStr = function (o) {
651 try { return typeof o == "string"
652 || (typeof o == "object" && o.constructor.toString().match(/string/i) !== null); }
653 catch (e) { return false; }
654 }
655
656 /**
657 * Returns a simple string if passed EITHER a simple string OR a 'string object',
658 * else returns the original object
659 */
660, str = function (o) { // trim converts 'String object' to a simple string
661 return isStr(o) ? $.trim(o) : o == undefined || o == null ? "" : o;
662 }
663
664 /**
665 * min / max
666 *
667 * Aliases for Math methods to simplify coding
668 */
669, min = function (x,y) { return Math.min(x,y); }
670, max = function (x,y) { return Math.max(x,y); }
671
672 /**
673 * Processes the options passed in and transforms them into the format used by layout()
674 * Missing keys are added, and converts the data if passed in 'flat-format' (no sub-keys)
675 * In flat-format, pane-specific-settings are prefixed like: north__optName (2-underscores)
676 * To update effects, options MUST use nested-keys format, with an effects key ???
677 *
678 * @see initOptions()
679 * @param {Object} d Data/options passed by user - may be a single level or nested levels
680 * @return {Object} Creates a data struture that perfectly matches 'options', ready to be imported
681 */
682, _transformData = function (d) {
683 var a, json = { cookie:{}, defaults:{fxSettings:{}}, north:{fxSettings:{}}, south:{fxSettings:{}}, east:{fxSettings:{}}, west:{fxSettings:{}}, center:{fxSettings:{}} };
684 d = d || {};
685 if (d.effects || d.cookie || d.defaults || d.north || d.south || d.west || d.east || d.center)
686 json = $.extend( true, json, d ); // already in json format - add to base keys
687 else
688 // convert 'flat' to 'nest-keys' format - also handles 'empty' user-options
689 $.each( d, function (key,val) {
690 a = key.split("__");
691 if (!a[1] || json[a[0]]) // check for invalid keys
692 json[ a[1] ? a[0] : "defaults" ][ a[1] ? a[1] : a[0] ] = val;
693 });
694 return json;
695 }
696
697 /**
698 * Set an INTERNAL callback to avoid simultaneous animation
699 * Runs only if needed and only if all callbacks are not 'already set'
700 * Called by open() and close() when isLayoutBusy=true
701 *
702 * @param {string} action Either 'open' or 'close'
703 * @param {string} pane A valid border-pane name, eg 'west'
704 * @param {boolean=} param Extra param for callback (optional)
705 */
706, _queue = function (action, pane, param) {
707 var tried = [];
708
709 // if isLayoutBusy, then some pane must be 'moving'
710 $.each(_c.borderPanes.split(","), function (i, p) {
711 if (_c[p].isMoving) {
712 bindCallback(p); // TRY to bind a callback
713 return false; // BREAK
714 }
715 });
716
717 // if pane does NOT have a callback, then add one, else follow the callback chain...
718 function bindCallback (p) {
719 var c = _c[p];
720 if (!c.doCallback) {
721 c.doCallback = true;
722 c.callback = action +","+ pane +","+ (param ? 1 : 0);
723 }
724 else { // try to 'chain' this callback
725 tried.push(p);
726 var cbPane = c.callback.split(",")[1]; // 2nd param of callback is 'pane'
727 // ensure callback target NOT 'itself' and NOT 'target pane' and NOT already tried (avoid loop)
728 if (cbPane != pane && !$.inArray(cbPane, tried) >= 0)
729 bindCallback(cbPane); // RECURSE
730 }
731 }
732 }
733
734 /**
735 * RUN the INTERNAL callback for this pane - if one exists
736 *
737 * @param {string} pane A valid border-pane name, eg 'west'
738 */
739, _dequeue = function (pane) {
740 var c = _c[pane];
741
742 // RESET flow-control flags
743 _c.isLayoutBusy = false;
744 delete c.isMoving;
745 if (!c.doCallback || !c.callback) return;
746
747 c.doCallback = false; // RESET logic flag
748
749 // EXECUTE the callback
750 var
751 cb = c.callback.split(",")
752 , param = (cb[2] > 0 ? true : false)
753 ;
754 if (cb[0] == "open")
755 open( cb[1], param );
756 else if (cb[0] == "close")
757 close( cb[1], param );
758
759 if (!c.doCallback) c.callback = null; // RESET - unless callback above enabled it again!
760 }
761
762 /**
763 * Executes a Callback function after a trigger event, like resize, open or close
764 *
765 * @param {?string} pane This is passed only so we can pass the 'pane object' to the callback
766 * @param {(string|function())} v_fn Accepts a function name, OR a comma-delimited array: [0]=function name, [1]=argument
767 */
768, _execCallback = function (pane, v_fn) {
769 if (!v_fn) return;
770 var fn;
771 try {
772 if (typeof v_fn == "function")
773 fn = v_fn;
774 else if (!isStr(v_fn))
775 return;
776 else if (v_fn.match(/,/)) {
777 // function name cannot contain a comma, so must be a function name AND a 'name' parameter
778 var args = v_fn.split(",");
779 fn = eval(args[0]);
780 if (typeof fn=="function" && args.length > 1)
781 return fn(args[1]); // pass the argument parsed from 'list'
782 }
783 else // just the name of an external function?
784 fn = eval(v_fn);
785
786 if (typeof fn=="function") {
787 if (pane && $Ps[pane])
788 // pass data: pane-name, pane-element, pane-state, pane-options, and layout-name
789 return fn( pane, $Ps[pane], state[pane], options[pane], options.name );
790 else // must be a layout/container callback - pass suitable info
791 return fn( Instance, state, options, options.name );
792 }
793 }
794 catch (ex) {}
795 }
796
797 /**
798 * cure iframe display issues in IE & other browsers
799 */
800, _fixIframe = function (pane) {
801 if ($.layout.browser.mozilla) return; // skip FireFox - it auto-refreshes iframes onShow
802 var $P = $Ps[pane];
803 // if the 'pane' is an iframe, do it
804 if (state[pane].tagName == "IFRAME")
805 $P.css(_c.hidden).css(_c.visible);
806 else // ditto for any iframes INSIDE the pane
807 $P.find('IFRAME').css(_c.hidden).css(_c.visible);
808 }
809
810 /**
811 * cssW / cssH / cssSize / cssMinDims
812 *
813 * Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype
814 *
815 * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
816 * @param {(string|!Object)} el Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object
817 * @param {number=} outerWidth (optional) Can pass a width, allowing calculations BEFORE element is resized
818 * @return {number} Returns the innerWidth of el by subtracting padding and borders
819 */
820, cssW = function (el, outerWidth) {
821 var str = isStr(el)
822 , $E = str ? $Ps[el] : $(el)
823 ;
824 if (!$E.length) return 0;
825 if (isNaN(outerWidth)) // not specified
826 outerWidth = str ? getPaneSize(el) : $E.outerWidth();
827 return $.layout.cssWidth($E, outerWidth);
828 }
829
830 /**
831 * @param {(string|!Object)} el Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object
832 * @param {number=} outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized
833 * @return {number} Returns the innerHeight el by subtracting padding and borders
834 */
835, cssH = function (el, outerHeight) {
836 var str = isStr(el)
837 , $E = str ? $Ps[el] : $(el)
838 ;
839 if (!$E.length) return 0;
840 if (isNaN(outerHeight)) // not specified
841 outerHeight = str ? getPaneSize(el) : $E.outerHeight();
842 return $.layout.cssHeight($E, outerHeight);
843 }
844
845 /**
846 * @param {string} pane Can accept ONLY a 'pane' (east, west, etc)
847 * @param {number=} outerSize (optional) Can pass a width, allowing calculations BEFORE element is resized
848 * @return {number} Returns the innerHeight/Width of el by subtracting padding and borders
849 */
850, cssSize = function (pane, outerSize) {
851 if (_c[pane].dir=="horz") // pane = north or south
852 return cssH(pane, outerSize);
853 else // pane = east or west
854 return cssW(pane, outerSize);
855 }
856
857 /**
858 * @param {string} pane Can accept ONLY a 'pane' (east, west, etc)
859 * @return {Object} Returns hash of minWidth & minHeight
860 */
861, cssMinDims = function (pane) {
862 // minWidth/Height means CSS width/height = 1px
863 var
864 dir = _c[pane].dir
865 , d = {
866 minWidth: 1001 - cssW(pane, 1000)
867 , minHeight: 1001 - cssH(pane, 1000)
868 }
869 ;
870 if (dir == "horz") d.minSize = d.minHeight;
871 if (dir == "vert") d.minSize = d.minWidth;
872 return d;
873 }
874
875 // TODO: see if these methods can be made more useful...
876 // TODO: *maybe* return cssW/H from these so caller can use this info
877
878 /**
879 * @param {(string|!Object)} el
880 * @param {number=} outerWidth
881 * @param {boolean=} autoHide
882 */
883, setOuterWidth = function (el, outerWidth, autoHide) {
884 var $E = el, w;
885 if (isStr(el)) $E = $Ps[el]; // west
886 else if (!el.jquery) $E = $(el);
887 w = cssW($E, outerWidth);
888 $E.css({ width: w });
889 if (w > 0) {
890 if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) {
891 $E.show().data('autoHidden', false);
892 if (!$.layout.browser.mozilla) // FireFox refreshes iframes - IE does not
893 // make hidden, then visible to 'refresh' display after animation
894 $E.css(_c.hidden).css(_c.visible);
895 }
896 }
897 else if (autoHide && !$E.data('autoHidden'))
898 $E.hide().data('autoHidden', true);
899 }
900
901 /**
902 * @param {(string|!Object)} el
903 * @param {number=} outerHeight
904 * @param {boolean=} autoHide
905 */
906, setOuterHeight = function (el, outerHeight, autoHide) {
907 var $E = el, h;
908 if (isStr(el)) $E = $Ps[el]; // west
909 else if (!el.jquery) $E = $(el);
910 h = cssH($E, outerHeight);
911 $E.css({ height: h, visibility: "visible" }); // may have been 'hidden' by sizeContent
912 if (h > 0 && $E.innerWidth() > 0) {
913 if (autoHide && $E.data('autoHidden')) {
914 $E.show().data('autoHidden', false);
915 if (!$.layout.browser.mozilla) // FireFox refreshes iframes - IE does not
916 $E.css(_c.hidden).css(_c.visible);
917 }
918 }
919 else if (autoHide && !$E.data('autoHidden'))
920 $E.hide().data('autoHidden', true);
921 }
922
923 /**
924 * @param {(string|!Object)} el
925 * @param {number=} outerSize
926 * @param {boolean=} autoHide
927 */
928, setOuterSize = function (el, outerSize, autoHide) {
929 if (_c[pane].dir=="horz") // pane = north or south
930 setOuterHeight(el, outerSize, autoHide);
931 else // pane = east or west
932 setOuterWidth(el, outerSize, autoHide);
933 }
934
935
936 /**
937 * Converts any 'size' params to a pixel/integer size, if not already
938 * If 'auto' or a decimal/percentage is passed as 'size', a pixel-size is calculated
939 *
940 /**
941 * @param {string} pane
942 * @param {(string|number)=} size
943 * @param {string=} dir
944 * @return {number}
945 */
946, _parseSize = function (pane, size, dir) {
947 if (!dir) dir = _c[pane].dir;
948
949 if (isStr(size) && size.match(/%/))
950 size = parseInt(size, 10) / 100; // convert % to decimal
951
952 if (size === 0)
953 return 0;
954 else if (size >= 1)
955 return parseInt(size, 10);
956 else if (size > 0) { // percentage, eg: .25
957 var o = options, avail;
958 if (dir=="horz") // north or south or center.minHeight
959 avail = sC.innerHeight - ($Ps.north ? o.north.spacing_open : 0) - ($Ps.south ? o.south.spacing_open : 0);
960 else if (dir=="vert") // east or west or center.minWidth
961 avail = sC.innerWidth - ($Ps.west ? o.west.spacing_open : 0) - ($Ps.east ? o.east.spacing_open : 0);
962 return Math.floor(avail * size);
963 }
964 else if (pane=="center")
965 return 0;
966 else { // size < 0 || size=='auto' || size==Missing || size==Invalid
967 // auto-size the pane
968 var
969 $P = $Ps[pane]
970 , dim = (dir == "horz" ? "height" : "width")
971 , vis = $.layout.showInvisibly($P) // show pane invisibly if hidden
972 , s = $P.css(dim); // SAVE current size
973 ;
974 $P.css(dim, "auto");
975 size = (dim == "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE
976 $P.css(dim, s).css(vis); // RESET size & visibility
977 return size;
978 }
979 }
980
981 /**
982 * Calculates current 'size' (outer-width or outer-height) of a border-pane - optionally with 'pane-spacing' added
983 *
984 * @param {(string|!Object)} pane
985 * @param {boolean=} inclSpace
986 * @return {number} Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser
987 */
988, getPaneSize = function (pane, inclSpace) {
989 var
990 $P = $Ps[pane]
991 , o = options[pane]
992 , s = state[pane]
993 , oSp = (inclSpace ? o.spacing_open : 0)
994 , cSp = (inclSpace ? o.spacing_closed : 0)
995 ;
996 if (!$P || s.isHidden)
997 return 0;
998 else if (s.isClosed || (s.isSliding && inclSpace))
999 return cSp;
1000 else if (_c[pane].dir == "horz")
1001 return $P.outerHeight() + oSp;
1002 else // dir == "vert"
1003 return $P.outerWidth() + oSp;
1004 }
1005
1006 /**
1007 * Calculate min/max pane dimensions and limits for resizing
1008 *
1009 * @param {string} pane
1010 * @param {boolean=} slide
1011 */
1012, setSizeLimits = function (pane, slide) {
1013 if (!isInitialized()) return;
1014 var
1015 o = options[pane]
1016 , s = state[pane]
1017 , c = _c[pane]
1018 , dir = c.dir
1019 , side = c.side.toLowerCase()
1020 , type = c.sizeType.toLowerCase()
1021 , isSliding = (slide != undefined ? slide : s.isSliding) // only open() passes 'slide' param
1022 , $P = $Ps[pane]
1023 , paneSpacing = o.spacing_open
1024 // measure the pane on the *opposite side* from this pane
1025 , altPane = _c.altSide[pane]
1026 , altS = state[altPane]
1027 , $altP = $Ps[altPane]
1028 , altPaneSize = (!$altP || altS.isVisible===false || altS.isSliding ? 0 : (dir=="horz" ? $altP.outerHeight() : $altP.outerWidth()))
1029 , altPaneSpacing = ((!$altP || altS.isHidden ? 0 : options[altPane][ altS.isClosed !== false ? "spacing_closed" : "spacing_open" ]) || 0)
1030 // limitSize prevents this pane from 'overlapping' opposite pane
1031 , containerSize = (dir=="horz" ? sC.innerHeight : sC.innerWidth)
1032 , minCenterDims = cssMinDims("center")
1033 , minCenterSize = dir=="horz" ? max(options.center.minHeight, minCenterDims.minHeight) : max(options.center.minWidth, minCenterDims.minWidth)
1034 // if pane is 'sliding', then ignore center and alt-pane sizes - because 'overlays' them
1035 , limitSize = (containerSize - paneSpacing - (isSliding ? 0 : (_parseSize("center", minCenterSize, dir) + altPaneSize + altPaneSpacing)))
1036 , minSize = s.minSize = max( _parseSize(pane, o.minSize), cssMinDims(pane).minSize )
1037 , maxSize = s.maxSize = min( (o.maxSize ? _parseSize(pane, o.maxSize) : 100000), limitSize )
1038 , r = s.resizerPosition = {} // used to set resizing limits
1039 , top = sC.insetTop
1040 , left = sC.insetLeft
1041 , W = sC.innerWidth
1042 , H = sC.innerHeight
1043 , rW = o.spacing_open // subtract resizer-width to get top/left position for south/east
1044 ;
1045 switch (pane) {
1046 case "north": r.min = top + minSize;
1047 r.max = top + maxSize;
1048 break;
1049 case "west": r.min = left + minSize;
1050 r.max = left + maxSize;
1051 break;
1052 case "south": r.min = top + H - maxSize - rW;
1053 r.max = top + H - minSize - rW;
1054 break;
1055 case "east": r.min = left + W - maxSize - rW;
1056 r.max = left + W - minSize - rW;
1057 break;
1058 };
1059 }
1060
1061 /**
1062 * Returns data for setting the size/position of center pane. Also used to set Height for east/west panes
1063 *
1064 * @return JSON Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height
1065 */
1066, calcNewCenterPaneDims = function () {
1067 var d = {
1068 top: getPaneSize("north", true) // true = include 'spacing' value for pane
1069 , bottom: getPaneSize("south", true)
1070 , left: getPaneSize("west", true)
1071 , right: getPaneSize("east", true)
1072 , width: 0
1073 , height: 0
1074 };
1075
1076 // NOTE: sC = state.container
1077 // calc center-pane outer dimensions
1078 d.width = sC.innerWidth - d.left - d.right; // outerWidth
1079 d.height = sC.innerHeight - d.bottom - d.top; // outerHeight
1080 // add the 'container border/padding' to get final positions relative to the container
1081 d.top += sC.insetTop;
1082 d.bottom += sC.insetBottom;
1083 d.left += sC.insetLeft;
1084 d.right += sC.insetRight;
1085
1086 return d;
1087 }
1088
1089
1090 /**
1091 * Returns data for setting size of an element (container or a pane).
1092 *
1093 * @see _create(), onWindowResize() for container, plus others for pane
1094 * @return JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
1095 */
1096, elDims = function ($E) { return $.layout.getElementDimensions($E); }
1097
1098, elCSS = function ($E, list) { return $.layout.getElementCSS($E, list); }
1099
1100
1101 /**
1102 * @param {!Object} el
1103 * @param {boolean=} allStates
1104 */
1105, getHoverClasses = function (el, allStates) {
1106 var
1107 $El = $(el)
1108 , type = $El.data("layoutRole")
1109 , pane = $El.data("layoutEdge")
1110 , o = options[pane]
1111 , root = o[type +"Class"]
1112 , _pane = "-"+ pane // eg: "-west"
1113 , _open = "-open"
1114 , _closed = "-closed"
1115 , _slide = "-sliding"
1116 , _hover = "-hover " // NOTE the trailing space
1117 , _state = $El.hasClass(root+_closed) ? _closed : _open
1118 , _alt = _state == _closed ? _open : _closed
1119 , classes = (root+_hover) + (root+_pane+_hover) + (root+_state+_hover) + (root+_pane+_state+_hover)
1120 ;
1121 if (allStates) // when 'removing' classes, also remove alternate-state classes
1122 classes += (root+_alt+_hover) + (root+_pane+_alt+_hover);
1123
1124 if (type=="resizer" && $El.hasClass(root+_slide))
1125 classes += (root+_slide+_hover) + (root+_pane+_slide+_hover);
1126
1127 return $.trim(classes);
1128 }
1129, addHover = function (evt, el) {
1130 var $E = $(el || this);
1131 if (evt && $E.data("layoutRole") == "toggler")
1132 evt.stopPropagation(); // prevent triggering 'slide' on Resizer-bar
1133 $E.addClass( getHoverClasses($E) );
1134 }
1135, removeHover = function (evt, el) {
1136 var $E = $(el || this);
1137 $E.removeClass( getHoverClasses($E, true) );
1138 }
1139
1140, onResizerEnter = function (evt) {
1141 $('body').disableSelection();
1142 addHover(evt, this);
1143 }
1144, onResizerLeave = function (evt, el) {
1145 var
1146 e = el || this // el is only passed when called by the timer
1147 , pane = $(e).data("layoutEdge")
1148 , name = pane +"ResizerLeave"
1149 ;
1150 timer.clear(pane+"_openSlider"); // cancel slideOpen timer, if set
1151 timer.clear(name); // cancel enableSelection timer - may re/set below
1152 if (!el) { // 1st call - mouseleave event
1153 removeHover(evt, this); // do this on initial call
1154 // this method calls itself on a timer because it needs to allow
1155 // enough time for dragging to kick-in and set the isResizing flag
1156 // dragging has a 100ms delay set, so this delay must be higher
1157 timer.set(name, function(){ onResizerLeave(evt, e); }, 200);
1158 }
1159 // if user is resizing, then dragStop will enableSelection() when done
1160 else if (!state[pane].isResizing) // 2nd call - by timer
1161 $('body').enableSelection();
1162 }
1163
1164/*
1165 * ###########################
1166 * INITIALIZATION METHODS
1167 * ###########################
1168 */
1169
1170 /**
1171 * Initialize the layout - called automatically whenever an instance of layout is created
1172 *
1173 * @see none - triggered onInit
1174 * @return mixed true = fully initialized | false = panes not initialized (yet) | 'cancel' = abort
1175 */
1176, _create = function () {
1177 // initialize config/options
1178 initOptions();
1179 var o = options;
1180
1181 $.layout.browser.boxModel = $.support.boxModel;
1182
1183 // update options with saved state, if option enabled
1184 if (o.useStateCookie && o.cookie.autoLoad)
1185 loadCookie(); // Update options from state-cookie
1186
1187 // TEMP state so isInitialized returns true during init process
1188 state.creatingLayout = true;
1189
1190 // options & state have been initialized, so now run beforeLoad callback
1191 // onload will CANCEL layout creation if it returns false
1192 if (false === _execCallback(null, o.onload_start))
1193 return 'cancel';
1194
1195 // initialize the container element
1196 _initContainer();
1197
1198 // bind hotkey function - keyDown - if required
1199 initHotkeys();
1200
1201 // search for and bind custom-buttons
1202 if (o.autoBindCustomButtons) initButtons();
1203
1204 // bind window.onunload
1205 $(window).bind("unload."+ sID, unload);
1206
1207 // if layout elements are hidden, then layout WILL NOT complete initialization!
1208 // initLayoutElements will set initialized=true and run the onload callback IF successful
1209 if (o.initPanes) _initLayoutElements();
1210
1211 delete state.creatingLayout;
1212
1213 return state.initialized;
1214 }
1215
1216 /**
1217 * Initialize the layout IF not already
1218 *
1219 * @see All methods in Instance run this test
1220 * @return boolean true = layoutElements have been initialized | false = panes are not initialized (yet)
1221 */
1222, isInitialized = function () {
1223 if (state.initialized || state.creatingLayout) return true; // already initialized
1224 else return _initLayoutElements(); // try to init panes NOW
1225 }
1226
1227 /**
1228 * Initialize the layout - called automatically whenever an instance of layout is created
1229 *
1230 * @see _create() & isInitialized
1231 * @return An object pointer to the instance created
1232 */
1233, _initLayoutElements = function () {
1234 // initialize config/options
1235 var o = options;
1236
1237 // CANNOT init panes inside a hidden container!
1238 if (!$N.is(":visible"))
1239 return false;
1240 // a center pane is required, so make sure it exists
1241 if (!getPane('center').length) {
1242 if (o.showErrorMessages) alert( lang.errCenterPaneMissing );
1243 return false;
1244 }
1245
1246 // TEMP state so isInitialized returns true during init process
1247 state.creatingLayout = true;
1248
1249 // update Container dims
1250 $.extend(sC, elDims( $N ));
1251
1252 // initialize all layout elements
1253 initPanes(); // size & position panes - calls initHandles() - which calls initResizable()
1254 sizeContent(); // AFTER panes & handles have been initialized, size 'content' divs
1255
1256 if (o.scrollToBookmarkOnLoad) {
1257 var l = self.location;
1258 if (l.hash) l.replace( l.hash ); // scrollTo Bookmark
1259 }
1260
1261 // bind resizeAll() for 'this layout instance' to window.resize event
1262 if (o.resizeWithWindow && !$N.data("layoutRole")) // skip if 'nested' inside a pane
1263 $(window).bind("resize."+ sID, windowResize);
1264
1265 delete state.creatingLayout;
1266 state.initialized = true;
1267
1268 _execCallback(null, o.onload_end || o.onload);
1269
1270 return true; // elements initialized successfully
1271 }
1272
1273
1274, windowResize = function () {
1275 var delay = Number(options.resizeWithWindowDelay);
1276 if (delay < 10) delay = 100; // MUST have a delay!
1277 // resizing uses a delay-loop because the resize event fires repeatly - except in FF, but delay anyway
1278 timer.clear("winResize"); // if already running
1279 timer.set("winResize", function(){
1280 timer.clear("winResize");
1281 timer.clear("winResizeRepeater");
1282 var dims = elDims( $N );
1283 // only trigger resizeAll() if container has changed size
1284 if (dims.innerWidth !== sC.innerWidth || dims.innerHeight !== sC.innerHeight)
1285 resizeAll();
1286 }, delay);
1287 // ALSO set fixed-delay timer, if not already running
1288 if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater();
1289 }
1290
1291, setWindowResizeRepeater = function () {
1292 var delay = Number(options.resizeWithWindowMaxDelay);
1293 if (delay > 0)
1294 timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay);
1295 }
1296
1297, unload = function () {
1298 var o = options;
1299 state.cookie = getState(); // save state in case onunload has custom state-management
1300 _execCallback(null, o.onunload_start);
1301 if (o.useStateCookie && o.cookie.autoSave) saveCookie();
1302 _execCallback(null, o.onunload_end || o.onunload);
1303 }
1304
1305 /**
1306 * Validate and initialize container CSS and events
1307 *
1308 * @see _create()
1309 */
1310, _initContainer = function () {
1311 var
1312 tag = sC.tagName = $N[0].tagName
1313 , o = options
1314 , fullPage= (tag == "BODY")
1315 , props = "overflow,position,margin,padding,border"
1316 , CSS = {}
1317 , hid = "hidden" // used A LOT!
1318 , isVis = $N.is(":visible")
1319 ;
1320 // sC -> state.container
1321 sC.selector = $N.selector.split(".slice")[0];
1322 sC.ref = tag +"/"+ sC.selector; // used in messages
1323
1324 $N .data("layout", Instance)
1325 .data("layoutContainer", sID) // unique identifier for internal use
1326 .addClass(o.containerClass)
1327 ;
1328
1329 // SAVE original container CSS for use in destroy()
1330 if (!$N.data("layoutCSS")) {
1331 // handle props like overflow different for BODY & HTML - has 'system default' values
1332 if (fullPage) {
1333 CSS = $.extend( elCSS($N, props), {
1334 height: $N.css("height")
1335 , overflow: $N.css("overflow")
1336 , overflowX: $N.css("overflowX")
1337 , overflowY: $N.css("overflowY")
1338 });
1339 // ALSO SAVE <HTML> CSS
1340 var $H = $("html");
1341 $H.data("layoutCSS", {
1342 height: "auto" // FF would return a fixed px-size!
1343 , overflow: $H.css("overflow")
1344 , overflowX: $H.css("overflowX")
1345 , overflowY: $H.css("overflowY")
1346 });
1347 }
1348 else // handle props normally for non-body elements
1349 CSS = elCSS($N, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY");
1350
1351 $N.data("layoutCSS", CSS);
1352 }
1353
1354 try { // format html/body if this is a full page layout
1355 if (fullPage) {
1356 $("html").css({
1357 height: "100%"
1358 , overflow: hid
1359 , overflowX: hid
1360 , overflowY: hid
1361 });
1362 $("body").css({
1363 position: "relative"
1364 , height: "100%"
1365 , overflow: hid
1366 , overflowX: hid
1367 , overflowY: hid
1368 , margin: 0
1369 , padding: 0 // TODO: test whether body-padding could be handled?
1370 , border: "none" // a body-border creates problems because it cannot be measured!
1371 });
1372
1373 // set current layout-container dimensions
1374 $.extend(sC, elDims( $N ));
1375 }
1376 else { // set required CSS for overflow and position
1377 // ENSURE container will not 'scroll'
1378 CSS = { overflow: hid, overflowX: hid, overflowY: hid }
1379 var
1380 p = $N.css("position")
1381 , h = $N.css("height")
1382 ;
1383 // if this is a NESTED layout, then container/outer-pane ALREADY has position and height
1384 if (!$N.data("layoutRole")) {
1385 if (!p || !p.match(/fixed|absolute|relative/))
1386 CSS.position = "relative"; // container MUST have a 'position'
1387 /*
1388 if (!h || h=="auto")
1389 CSS.height = "100%"; // container MUST have a 'height'
1390 */
1391 }
1392 $N.css( CSS );
1393
1394 // set current layout-container dimensions
1395 if (isVis) {
1396 $.extend(sC, elDims( $N ));
1397 if (o.showErrorMessages && sC.innerHeight < 2)
1398 alert( lang.errContainerHeight.replace(/CONTAINER/, sC.ref) );
1399 }
1400 }
1401 } catch (ex) {}
1402 }
1403
1404 /**
1405 * Bind layout hotkeys - if options enabled
1406 *
1407 * @see _create() and addPane()
1408 * @param {string=} panes The edge(s) to process, blank = all
1409 */
1410, initHotkeys = function (panes) {
1411 if (!panes || panes == "all") panes = _c.borderPanes;
1412 // bind keyDown to capture hotkeys, if option enabled for ANY pane
1413 $.each(panes.split(","), function (i, pane) {
1414 var o = options[pane];
1415 if (o.enableCursorHotkey || o.customHotkey) {
1416 $(document).bind("keydown."+ sID, keyDown); // only need to bind this ONCE
1417 return false; // BREAK - binding was done
1418 }
1419 });
1420 }
1421
1422 /**
1423 * Build final OPTIONS data
1424 *
1425 * @see _create()
1426 */
1427, initOptions = function () {
1428 // simplify logic by making sure passed 'opts' var has basic keys
1429 opts = _transformData( opts );
1430
1431 // TODO: create a compatibility add-on for new UI widget that will transform old option syntax
1432 var newOpts = {
1433 applyDefaultStyles: "applyDemoStyles"
1434 };
1435 renameOpts(opts.defaults);
1436 $.each(_c.allPanes.split(","), function (i, pane) {
1437 renameOpts(opts[pane]);
1438 });
1439
1440 // update default effects, if case user passed key
1441 if (opts.effects) {
1442 $.extend( effects, opts.effects );
1443 delete opts.effects;
1444 }
1445 $.extend( options.cookie, opts.cookie );
1446
1447 // see if any 'global options' were specified
1448 var globals = "name,containerClass,zIndex,scrollToBookmarkOnLoad,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"+
1449 "onresizeall,onresizeall_start,onresizeall_end,onload,onload_start,onload_end,onunload,onunload_start,onunload_end,autoBindCustomButtons,useStateCookie";
1450 $.each(globals.split(","), function (i, key) {
1451 if (opts[key] !== undefined)
1452 options[key] = opts[key];
1453 else if (opts.defaults[key] !== undefined) {
1454 options[key] = opts.defaults[key];
1455 delete opts.defaults[key];
1456 }
1457 });
1458
1459 // remove any 'defaults' that MUST be set 'per-pane'
1460 $.each("paneSelector,resizerCursor,customHotkey".split(","),
1461 function (i, key) { delete opts.defaults[key]; } // is OK if key does not exist
1462 );
1463
1464 // now update options.defaults
1465 $.extend( true, options.defaults, opts.defaults );
1466
1467 // merge config for 'center-pane' - border-panes handled in the loop below
1468 _c.center = $.extend( true, {}, _c.panes, _c.center );
1469 // update config.zIndex values if zIndex option specified
1470 var z = options.zIndex;
1471 if (z === 0 || z > 0) {
1472 _c.zIndex.pane_normal = z;
1473 _c.zIndex.resizer_normal = z+1;
1474 _c.zIndex.iframe_mask = z+1;
1475 }
1476
1477 // merge options for 'center-pane' - border-panes handled in the loop below
1478 $.extend( options.center, opts.center );
1479 // Most 'default options' do not apply to 'center', so add only those that DO
1480 var o_Center = $.extend( true, {}, options.defaults, opts.defaults, options.center ); // TEMP data
1481 var optionsCenter = ("paneClass,contentSelector,applyDemoStyles,triggerEventsOnLoad,showOverflowOnHover,"
1482 + "onresize,onresize_start,onresize_end,resizeNestedLayout,resizeContentWhileDragging,"
1483 + "onsizecontent,onsizecontent_start,onsizecontent_end").split(",");
1484 $.each(optionsCenter,
1485 function (i, key) { options.center[key] = o_Center[key]; }
1486 );
1487
1488 var o, defs = options.defaults;
1489
1490 // create a COMPLETE set of options for EACH border-pane
1491 $.each(_c.borderPanes.split(","), function (i, pane) {
1492
1493 // apply 'pane-defaults' to CONFIG.[PANE]
1494 _c[pane] = $.extend( true, {}, _c.panes, _c[pane] );
1495
1496 // apply 'pane-defaults' + user-options to OPTIONS.PANE
1497 o = options[pane] = $.extend( true, {}, options.defaults, options[pane], opts.defaults, opts[pane] );
1498
1499 // make sure we have base-classes
1500 if (!o.paneClass) o.paneClass = "ui-layout-pane";
1501 if (!o.resizerClass) o.resizerClass = "ui-layout-resizer";
1502 if (!o.togglerClass) o.togglerClass = "ui-layout-toggler";
1503
1504 // create FINAL fx options for each pane, ie: options.PANE.fxName/fxSpeed/fxSettings[_open|_close]
1505 $.each(["_open","_close",""], function (i,n) {
1506 var
1507 sName = "fxName"+n
1508 , sSpeed = "fxSpeed"+n
1509 , sSettings = "fxSettings"+n
1510 ;
1511 // recalculate fxName according to specificity rules
1512 o[sName] =
1513 opts[pane][sName] // opts.west.fxName_open
1514 || opts[pane].fxName // opts.west.fxName
1515 || opts.defaults[sName] // opts.defaults.fxName_open
1516 || opts.defaults.fxName // opts.defaults.fxName
1517 || o[sName] // options.west.fxName_open
1518 || o.fxName // options.west.fxName
1519 || defs[sName] // options.defaults.fxName_open
1520 || defs.fxName // options.defaults.fxName
1521 || "none"
1522 ;
1523 // validate fxName to be sure is a valid effect
1524 var fxName = o[sName];
1525 if (fxName == "none" || !$.effects || !$.effects[fxName] || (!effects[fxName] && !o[sSettings] && !o.fxSettings))
1526 fxName = o[sName] = "none"; // effect not loaded, OR undefined FX AND fxSettings not passed
1527 // set vars for effects subkeys to simplify logic
1528 var
1529 fx = effects[fxName] || {} // effects.slide
1530 , fx_all = fx.all || {} // effects.slide.all
1531 , fx_pane = fx[pane] || {} // effects.slide.west
1532 ;
1533 // RECREATE the fxSettings[_open|_close] keys using specificity rules
1534 o[sSettings] = $.extend(
1535 {}
1536 , fx_all // effects.slide.all
1537 , fx_pane // effects.slide.west
1538 , defs.fxSettings || {} // options.defaults.fxSettings
1539 , defs[sSettings] || {} // options.defaults.fxSettings_open
1540 , o.fxSettings // options.west.fxSettings
1541 , o[sSettings] // options.west.fxSettings_open
1542 , opts.defaults.fxSettings // opts.defaults.fxSettings
1543 , opts.defaults[sSettings] || {} // opts.defaults.fxSettings_open
1544 , opts[pane].fxSettings // opts.west.fxSettings
1545 , opts[pane][sSettings] || {} // opts.west.fxSettings_open
1546 );
1547 // recalculate fxSpeed according to specificity rules
1548 o[sSpeed] =
1549 opts[pane][sSpeed] // opts.west.fxSpeed_open
1550 || opts[pane].fxSpeed // opts.west.fxSpeed (pane-default)
1551 || opts.defaults[sSpeed] // opts.defaults.fxSpeed_open
1552 || opts.defaults.fxSpeed // opts.defaults.fxSpeed
1553 || o[sSpeed] // options.west.fxSpeed_open
1554 || o[sSettings].duration // options.west.fxSettings_open.duration
1555 || o.fxSpeed // options.west.fxSpeed
1556 || o.fxSettings.duration // options.west.fxSettings.duration
1557 || defs.fxSpeed // options.defaults.fxSpeed
1558 || defs.fxSettings.duration// options.defaults.fxSettings.duration
1559 || fx_pane.duration // effects.slide.west.duration
1560 || fx_all.duration // effects.slide.all.duration
1561 || "normal" // DEFAULT
1562 ;
1563 });
1564
1565 });
1566
1567 function renameOpts (O) {
1568 for (var key in newOpts) {
1569 if (O[key] != undefined) {
1570 O[newOpts[key]] = O[key];
1571 delete O[key];
1572 }
1573 }
1574 }
1575 }
1576
1577 /**
1578 * Initialize module objects, styling, size and position for all panes
1579 *
1580 * @see _create()
1581 * @param {string} pane The pane to process
1582 */
1583, getPane = function (pane) {
1584 var sel = options[pane].paneSelector
1585 if (sel.substr(0,1)==="#") // ID selector
1586 // NOTE: elements selected 'by ID' DO NOT have to be 'children'
1587 return $N.find(sel).eq(0);
1588 else { // class or other selector
1589 var $P = $N.children(sel).eq(0);
1590 // look for the pane nested inside a 'form' element
1591 return $P.length ? $P : $N.children("form:first").children(sel).eq(0);
1592 }
1593 }
1594, initPanes = function () {
1595 // NOTE: do north & south FIRST so we can measure their height - do center LAST
1596 $.each(_c.allPanes.split(","), function (idx, pane) {
1597 addPane( pane, true );
1598 });
1599
1600 // init the pane-handles NOW in case we have to hide or close the pane below
1601 initHandles();
1602
1603 // now that all panes have been initialized and initially-sized,
1604 // make sure there is really enough space available for each pane
1605 $.each(_c.borderPanes.split(","), function (i, pane) {
1606 if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN
1607 setSizeLimits(pane);
1608 makePaneFit(pane); // pane may be Closed, Hidden or Resized by makePaneFit()
1609 }
1610 });
1611 // size center-pane AGAIN in case we 'closed' a border-pane in loop above
1612 sizeMidPanes("center");
1613
1614 // Chrome fires callback BEFORE it completes resizing, so add a delay before handling children
1615 setTimeout(function(){
1616 $.each(_c.allPanes.split(","), function (i, pane) {
1617 var o = options[pane];
1618 if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN
1619 // trigger onResize callbacks for all panes with triggerEventsOnLoad = true
1620 if (o.triggerEventsOnLoad)
1621 _execCallback(pane, o.onresize_end || o.onresize);
1622 resizeNestedLayout(pane);
1623 }
1624 });
1625 }, 50 ); // 50ms delay is enough
1626
1627 if (options.showErrorMessages && $N.innerHeight() < 2)
1628 alert( lang.errContainerHeight.replace(/CONTAINER/, sC.ref) );
1629 }
1630
1631 /**
1632 * Remove a pane from the layout - subroutine of destroy()
1633 *
1634 * @see initPanes()
1635 * @param {string} pane The pane to process
1636 */
1637, addPane = function (pane, force) {
1638 if (!force && !isInitialized()) return;
1639 var
1640 o = options[pane]
1641 , s = state[pane]
1642 , c = _c[pane]
1643 , fx = s.fx
1644 , dir = c.dir
1645 , spacing = o.spacing_open || 0
1646 , isCenter = (pane == "center")
1647 , CSS = {}
1648 , $P = $Ps[pane]
1649 , size, minSize, maxSize
1650 ;
1651
1652 // if pane-pointer already exists, remove the old one first
1653 if ($P)
1654 removePane( pane );
1655 else
1656 $Cs[pane] = false; // init
1657
1658 $P = $Ps[pane] = getPane(pane);
1659 if (!$P.length) {
1660 $Ps[pane] = false; // logic
1661 return;
1662 }
1663
1664 // SAVE original Pane CSS
1665 if (!$P.data("layoutCSS")) {
1666 var props = "position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border";
1667 $P.data("layoutCSS", elCSS($P, props));
1668 }
1669
1670 // add basic classes & attributes
1671 $P
1672 .data("parentLayout", Instance)
1673 .data("layoutRole", "pane")
1674 .data("layoutEdge", pane)
1675 .css(c.cssReq).css("zIndex", _c.zIndex.pane_normal)
1676 .css(o.applyDemoStyles ? c.cssDemo : {}) // demo styles
1677 .addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector'
1678 .bind("mouseenter."+ sID, addHover )
1679 .bind("mouseleave."+ sID, removeHover )
1680 ;
1681
1682 // see if this pane has a 'scrolling-content element'
1683 initContent(pane, false); // false = do NOT sizeContent() - called later
1684
1685 if (!isCenter) {
1686 // call _parseSize AFTER applying pane classes & styles - but before making visible (if hidden)
1687 // if o.size is auto or not valid, then MEASURE the pane and use that as its 'size'
1688 size = s.size = _parseSize(pane, o.size);
1689 minSize = _parseSize(pane,o.minSize) || 1;
1690 maxSize = _parseSize(pane,o.maxSize) || 100000;
1691 if (size > 0) size = max(min(size, maxSize), minSize);
1692
1693 // state for border-panes
1694 s.isClosed = false; // true = pane is closed
1695 s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes
1696 s.isResizing= false; // true = pane is in process of being resized
1697 s.isHidden = false; // true = pane is hidden - no spacing, resizer or toggler is visible!
1698 }
1699 // state common to ALL panes
1700 s.tagName = $P[0].tagName;
1701 s.edge = pane // useful if pane is (or about to be) 'swapped' - easy find out where it is (or is going)
1702 s.noRoom = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically
1703 s.isVisible = true; // false = pane is invisible - closed OR hidden - simplify logic
1704
1705 // set css-position to account for container borders & padding
1706 switch (pane) {
1707 case "north": CSS.top = sC.insetTop;
1708 CSS.left = sC.insetLeft;
1709 CSS.right = sC.insetRight;
1710 break;
1711 case "south": CSS.bottom = sC.insetBottom;
1712 CSS.left = sC.insetLeft;
1713 CSS.right = sC.insetRight;
1714 break;
1715 case "west": CSS.left = sC.insetLeft; // top, bottom & height set by sizeMidPanes()
1716 break;
1717 case "east": CSS.right = sC.insetRight; // ditto
1718 break;
1719 case "center": // top, left, width & height set by sizeMidPanes()
1720 }
1721
1722 if (dir == "horz") // north or south pane
1723 CSS.height = max(1, cssH(pane, size));
1724 else if (dir == "vert") // east or west pane
1725 CSS.width = max(1, cssW(pane, size));
1726 //else if (isCenter) {}
1727
1728 $P.css(CSS); // apply size -- top, bottom & height will be set by sizeMidPanes
1729 if (dir != "horz") sizeMidPanes(pane, true); // true = skipCallback
1730
1731 // close or hide the pane if specified in settings
1732 if (o.initClosed && o.closable && !o.initHidden)
1733 close(pane, true, true); // true, true = force, noAnimation
1734 else if (o.initHidden || o.initClosed)
1735 hide(pane); // will be completely invisible - no resizer or spacing
1736 else if (!s.noRoom)
1737 // make the pane visible - in case was initially hidden
1738 $P.css("display","block");
1739 // ELSE setAsOpen() - called later by initHandles()
1740
1741 // RESET visibility now - pane will appear IF display:block
1742 $P.css("visibility","visible");
1743
1744 // check option for auto-handling of pop-ups & drop-downs
1745 if (o.showOverflowOnHover)
1746 $P.hover( allowOverflow, resetOverflow );
1747
1748 // if adding a pane AFTER initialization, then...
1749 if (state.initialized) {
1750 initHandles( pane );
1751 initHotkeys( pane );
1752 resizeAll(); // will sizeContent if pane is visible
1753 if (s.isVisible) { // pane is OPEN
1754 if (o.triggerEventsOnLoad)
1755 _execCallback(pane, o.onresize_end || o.onresize);
1756 resizeNestedLayout(pane);
1757 }
1758 }
1759 }
1760
1761 /**
1762 * Initialize module objects, styling, size and position for all resize bars and toggler buttons
1763 *
1764 * @see _create()
1765 * @param {string=} panes The edge(s) to process, blank = all
1766 */
1767, initHandles = function (panes) {
1768 if (!panes || panes == "all") panes = _c.borderPanes;
1769
1770 // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
1771 $.each(panes.split(","), function (i, pane) {
1772 var $P = $Ps[pane];
1773 $Rs[pane] = false; // INIT
1774 $Ts[pane] = false;
1775 if (!$P) return; // pane does not exist - skip
1776
1777 var
1778 o = options[pane]
1779 , s = state[pane]
1780 , c = _c[pane]
1781 , rClass = o.resizerClass
1782 , tClass = o.togglerClass
1783 , side = c.side.toLowerCase()
1784 , spacing = (s.isVisible ? o.spacing_open : o.spacing_closed)
1785 , _pane = "-"+ pane // used for classNames
1786 , _state = (s.isVisible ? "-open" : "-closed") // used for classNames
1787 // INIT RESIZER BAR
1788 , $R = $Rs[pane] = $("<div></div>")
1789 // INIT TOGGLER BUTTON
1790 , $T = (o.closable ? $Ts[pane] = $("<div></div>") : false)
1791 ;
1792
1793 //if (s.isVisible && o.resizable) ... handled by initResizable
1794 if (!s.isVisible && o.slidable)
1795 $R.attr("title", o.sliderTip).css("cursor", o.sliderCursor);
1796
1797 $R
1798 // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer"
1799 .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-resizer" : ""))
1800 .data("parentLayout", Instance)
1801 .data("layoutRole", "resizer")
1802 .data("layoutEdge", pane)
1803 .css(_c.resizers.cssReq).css("zIndex", _c.zIndex.resizer_normal)
1804 .css(o.applyDemoStyles ? _c.resizers.cssDemo : {}) // add demo styles
1805 .addClass(rClass +" "+ rClass+_pane)
1806 .appendTo($N) // append DIV to container
1807 ;
1808
1809 if ($T) {
1810 $T
1811 // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "#paneLeft-toggler"
1812 .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-toggler" : ""))
1813 .data("parentLayout", Instance)
1814 .data("layoutRole", "toggler")
1815 .data("layoutEdge", pane)
1816 .css(_c.togglers.cssReq) // add base/required styles
1817 .css(o.applyDemoStyles ? _c.togglers.cssDemo : {}) // add demo styles
1818 .addClass(tClass +" "+ tClass+_pane)
1819 .appendTo($R) // append SPAN to resizer DIV
1820 ;
1821 // ADD INNER-SPANS TO TOGGLER
1822 if (o.togglerContent_open) // ui-layout-open
1823 $("<span>"+ o.togglerContent_open +"</span>")
1824 .data("layoutRole", "togglerContent")
1825 .data("layoutEdge", pane)
1826 .addClass("content content-open")
1827 .css("display","none")
1828 .appendTo( $T )
1829 //.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-open instead!
1830 ;
1831 if (o.togglerContent_closed) // ui-layout-closed
1832 $("<span>"+ o.togglerContent_closed +"</span>")
1833 .data("layoutRole", "togglerContent")
1834 .data("layoutEdge", pane)
1835 .addClass("content content-closed")
1836 .css("display","none")
1837 .appendTo( $T )
1838 //.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-closed instead!
1839 ;
1840 // ADD TOGGLER.click/.hover
1841 enableClosable(pane);
1842 }
1843
1844 // add Draggable events
1845 initResizable(pane);
1846
1847 // ADD CLASSNAMES & SLIDE-BINDINGS - eg: class="resizer resizer-west resizer-open"
1848 if (s.isVisible)
1849 setAsOpen(pane); // onOpen will be called, but NOT onResize
1850 else {
1851 setAsClosed(pane); // onClose will be called
1852 bindStartSlidingEvent(pane, true); // will enable events IF option is set
1853 }
1854
1855 });
1856
1857 // SET ALL HANDLE DIMENSIONS
1858 sizeHandles("all");
1859 }
1860
1861
1862 /**
1863 * Initialize scrolling ui-layout-content div - if exists
1864 *
1865 * @see initPane() - or externally after an Ajax injection
1866 * @param {string} pane The pane to process
1867 * @param {boolean=} resize Size content after init, default = true
1868 */
1869, initContent = function (pane, resize) {
1870 if (!isInitialized()) return;
1871 var
1872 o = options[pane]
1873 , sel = o.contentSelector
1874 , $P = $Ps[pane]
1875 , $C
1876 ;
1877 if (sel) $C = $Cs[pane] = (o.findNestedContent)
1878 ? $P.find(sel).eq(0) // match 1-element only
1879 : $P.children(sel).eq(0)
1880 ;
1881 if ($C && $C.length) {
1882 // SAVE original Pane CSS
1883 if (!$C.data("layoutCSS"))
1884 $C.data("layoutCSS", elCSS($C, "height"));
1885 $C.css( _c.content.cssReq );
1886 if (o.applyDemoStyles) {
1887 $C.css( _c.content.cssDemo ); // add padding & overflow: auto to content-div
1888 $P.css( _c.content.cssDemoPane ); // REMOVE padding/scrolling from pane
1889 }
1890 state[pane].content = {}; // init content state
1891 if (resize !== false) sizeContent(pane);
1892 // sizeContent() is called AFTER init of all elements
1893 }
1894 else
1895 $Cs[pane] = false;
1896 }
1897
1898
1899 /**
1900 * Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons
1901 *
1902 * @see _create()
1903 */
1904, initButtons = function () {
1905 var pre = "ui-layout-button-", name;
1906 $.each("toggle,open,close,pin,toggle-slide,open-slide".split(","), function (i, action) {
1907 $.each(_c.borderPanes.split(","), function (ii, pane) {
1908 $("."+pre+action+"-"+pane).each(function(){
1909 // if button was previously 'bound', data.layoutName was set, but is blank if layout has no 'name'
1910 name = $(this).data("layoutName") || $(this).attr("layoutName");
1911 if (name == undefined || name == options.name)
1912 bindButton(this, action, pane);
1913 });
1914 });
1915 });
1916 }
1917
1918 /**
1919 * Add resize-bars to all panes that specify it in options
1920 * -dependancy: $.fn.resizable - will skip if not found
1921 *
1922 * @see _create()
1923 * @param {string=} panes The edge(s) to process, blank = all
1924 */
1925, initResizable = function (panes) {
1926 var
1927 draggingAvailable = (typeof $.fn.draggable == "function")
1928 , $Frames, side // set in start()
1929 ;
1930 if (!panes || panes == "all") panes = _c.borderPanes;
1931
1932 $.each(panes.split(","), function (idx, pane) {
1933 var
1934 o = options[pane]
1935 , s = state[pane]
1936 , c = _c[pane]
1937 , side = (c.dir=="horz" ? "top" : "left")
1938 , r, live // set in start because may change
1939 ;
1940 if (!draggingAvailable || !$Ps[pane] || !o.resizable) {
1941 o.resizable = false;
1942 return true; // skip to next
1943 }
1944
1945 var
1946 $P = $Ps[pane]
1947 , $R = $Rs[pane]
1948 , base = o.resizerClass
1949 // 'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process
1950 , resizerClass = base+"-drag" // resizer-drag
1951 , resizerPaneClass = base+"-"+pane+"-drag" // resizer-north-drag
1952 // 'helper' class is applied to the CLONED resizer-bar while it is being dragged
1953 , helperClass = base+"-dragging" // resizer-dragging
1954 , helperPaneClass = base+"-"+pane+"-dragging" // resizer-north-dragging
1955 , helperLimitClass = base+"-dragging-limit" // resizer-drag
1956 , helperPaneLimitClass = base+"-"+pane+"-dragging-limit" // resizer-north-drag
1957 , helperClassesSet = false // logic var
1958 ;
1959
1960 if (!s.isClosed)
1961 $R
1962 .attr("title", o.resizerTip)
1963 .css("cursor", o.resizerCursor) // n-resize, s-resize, etc
1964 ;
1965
1966 $R.bind("mouseenter."+ sID, onResizerEnter)
1967 .bind("mouseleave."+ sID, onResizerLeave);
1968
1969 $R.draggable({
1970 containment: $N[0] // limit resizing to layout container
1971 , axis: (c.dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis
1972 , delay: 0
1973 , distance: 1
1974 // basic format for helper - style it using class: .ui-draggable-dragging
1975 , helper: "clone"
1976 , opacity: o.resizerDragOpacity
1977 , addClasses: false // avoid ui-state-disabled class when disabled
1978 //, iframeFix: o.draggableIframeFix // TODO: consider using when bug is fixed
1979 , zIndex: _c.zIndex.resizer_drag
1980
1981 , start: function (e, ui) {
1982 // REFRESH options & state pointers in case we used swapPanes
1983 o = options[pane];
1984 s = state[pane];
1985 // re-read options
1986 live = o.resizeWhileDragging;
1987
1988 // ondrag_start callback - will CANCEL hide if returns false
1989 // TODO: dragging CANNOT be cancelled like this, so see if there is a way?
1990 if (false === _execCallback(pane, o.ondrag_start)) return false;
1991
1992 _c.isLayoutBusy = true; // used by sizePane() logic during a liveResize
1993 s.isResizing = true; // prevent pane from closing while resizing
1994 timer.clear(pane+"_closeSlider"); // just in case already triggered
1995
1996 // SET RESIZER LIMITS - used in drag()
1997 setSizeLimits(pane); // update pane/resizer state
1998 r = s.resizerPosition;
1999
2000 $R.addClass( resizerClass +" "+ resizerPaneClass ); // add drag classes
2001 helperClassesSet = false; // reset logic var - see drag()
2002
2003 // MASK PANES WITH IFRAMES OR OTHER TROUBLESOME ELEMENTS
2004 $Frames = $(o.maskIframesOnResize === true ? "iframe" : o.maskIframesOnResize).filter(":visible");
2005 var id, i=0; // ID incrementer - used when 'resizing' masks during dynamic resizing
2006 $Frames.each(function() {
2007 id = "ui-layout-mask-"+ (++i);
2008 $(this).data("layoutMaskID", id); // tag iframe with corresponding maskID
2009 $('<div id="'+ id +'" class="ui-layout-mask ui-layout-mask-'+ pane +'"/>')
2010 .css({
2011 background: "#fff"
2012 , opacity: "0.001"
2013 , zIndex: _c.zIndex.iframe_mask
2014 , position: "absolute"
2015 , width: this.offsetWidth+"px"
2016 , height: this.offsetHeight+"px"
2017 })
2018 .css($(this).position()) // top & left -- changed from offset()
2019 .appendTo(this.parentNode) // put mask-div INSIDE pane to avoid zIndex issues
2020 ;
2021 });
2022
2023 // DISABLE TEXT SELECTION (probably already done by resizer.mouseOver)
2024 $('body').disableSelection();
2025 }
2026
2027 , drag: function (e, ui) {
2028 if (!helperClassesSet) { // can only add classes after clone has been added to the DOM
2029 //$(".ui-draggable-dragging")
2030 ui.helper
2031 .addClass( helperClass +" "+ helperPaneClass ) // add helper classes
2032 .css({ right: "auto", bottom: "auto" }) // fix dir="rtl" issue
2033 .children().css("visibility","hidden") // hide toggler inside dragged resizer-bar
2034 ;
2035 helperClassesSet = true;
2036 // draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane!
2037 if (s.isSliding) $Ps[pane].css("zIndex", _c.zIndex.pane_sliding);
2038 }
2039 // CONTAIN RESIZER-BAR TO RESIZING LIMITS
2040 var limit = 0;
2041 if (ui.position[side] < r.min) {
2042 ui.position[side] = r.min;
2043 limit = -1;
2044 }
2045 else if (ui.position[side] > r.max) {
2046 ui.position[side] = r.max;
2047 limit = 1;
2048 }
2049 // ADD/REMOVE dragging-limit CLASS
2050 if (limit) {
2051 ui.helper.addClass( helperLimitClass +" "+ helperPaneLimitClass ); // at dragging-limit
2052 window.defaultStatus = "Panel has reached its " +
2053 ((limit>0 && pane.match(/north|west/)) || (limit<0 && pane.match(/south|east/)) ? "maximum" : "minimum") +" size";
2054 }
2055 else {
2056 ui.helper.removeClass( helperLimitClass +" "+ helperPaneLimitClass ); // not at dragging-limit
2057 window.defaultStatus = "";
2058 }
2059 // DYNAMICALLY RESIZE PANES IF OPTION ENABLED
2060 if (live) resizePanes(e, ui, pane);
2061 }
2062
2063 , stop: function (e, ui) {
2064 $('body').enableSelection(); // RE-ENABLE TEXT SELECTION
2065 window.defaultStatus = ""; // clear 'resizing limit' message from statusbar
2066 $R.removeClass( resizerClass +" "+ resizerPaneClass ); // remove drag classes from Resizer
2067 s.isResizing = false;
2068 _c.isLayoutBusy = false; // set BEFORE resizePanes so other logic can pick it up
2069 resizePanes(e, ui, pane, true); // true = resizingDone
2070 }
2071
2072 });
2073
2074 /**
2075 * resizePanes
2076 *
2077 * Sub-routine called from stop() and optionally drag()
2078 *
2079 * @param {!Object} evt
2080 * @param {!Object} ui
2081 * @param {string} pane
2082 * @param {boolean=} resizingDone
2083 */
2084 var resizePanes = function (evt, ui, pane, resizingDone) {
2085 var
2086 dragPos = ui.position
2087 , c = _c[pane]
2088 , resizerPos, newSize
2089 , i = 0 // ID incrementer
2090 ;
2091 switch (pane) {
2092 case "north": resizerPos = dragPos.top; break;
2093 case "west": resizerPos = dragPos.left; break;
2094 case "south": resizerPos = sC.offsetHeight - dragPos.top - o.spacing_open; break;
2095 case "east": resizerPos = sC.offsetWidth - dragPos.left - o.spacing_open; break;
2096 };
2097
2098 if (resizingDone) {
2099 // Remove OR Resize MASK(S) created in drag.start
2100 $("div.ui-layout-mask").each(function() { this.parentNode.removeChild(this); });
2101 //$("div.ui-layout-mask").remove(); // TODO: Is this less efficient?
2102
2103 // ondrag_start callback - will CANCEL hide if returns false
2104 if (false === _execCallback(pane, o.ondrag_end || o.ondrag)) return false;
2105 }
2106 else
2107 $Frames.each(function() {
2108 $("#"+ $(this).data("layoutMaskID")) // get corresponding mask by ID
2109 .css($(this).position()) // update top & left
2110 .css({ // update width & height
2111 width: this.offsetWidth +"px"
2112 , height: this.offsetHeight+"px"
2113 })
2114 ;
2115 });
2116
2117 // remove container margin from resizer position to get the pane size
2118 newSize = resizerPos - sC["inset"+ c.side];
2119 manualSizePane(pane, newSize);
2120 }
2121 });
2122 }
2123
2124
2125 /**
2126 * Destroy this layout and reset all elements
2127 */
2128, destroy = function () {
2129 // UNBIND layout events and remove global object
2130 $(window).unbind("."+ sID);
2131 $(document).unbind("."+ sID);
2132
2133 // loop all panes to remove layout classes, attributes and bindings
2134 $.each(_c.allPanes.split(","), function (i, pane) {
2135 removePane( pane, false, true ); // true = skipResize
2136 });
2137
2138 // reset layout-container
2139 $N .removeData("layout")
2140 .removeData("layoutContainer")
2141 .removeClass(options.containerClass)
2142 ;
2143
2144 // do NOT reset container CSS if is a 'pane' in an outer-layout - ie, THIS layout is 'nested'
2145 if (!$N.data("layoutEdge") && $N.data("layoutCSS")) // RESET CSS
2146 $N.css( $N.data("layoutCSS") ).removeData("layoutCSS");
2147
2148 // for full-page layouts, also reset the <HTML> CSS
2149 if (sC.tagName == "BODY" && ($N = $("html")).data("layoutCSS")) // RESET <HTML> CSS
2150 $N.css( $N.data("layoutCSS") ).removeData("layoutCSS");
2151
2152 // trigger state-management and onunload callback
2153 unload();
2154 }
2155
2156 /**
2157 * Remove a pane from the layout - subroutine of destroy()
2158 *
2159 * @see destroy()
2160 * @param {string} pane The pane to process
2161 * @param {boolean=} remove Remove the DOM element? default = false
2162 * @param {boolean=} skipResize Skip calling resizeAll()? default = false
2163 */
2164, removePane = function (pane, remove, skipResize) {
2165 if (!isInitialized()) return;
2166 if (!$Ps[pane]) return; // NO SUCH PANE
2167 var
2168 $P = $Ps[pane]
2169 , $C = $Cs[pane]
2170 , $R = $Rs[pane]
2171 , $T = $Ts[pane]
2172 // create list of ALL pane-classes that need to be removed
2173 , _open = "-open"
2174 , _sliding= "-sliding"
2175 , _closed = "-closed"
2176 , root = options[pane].paneClass // default="ui-layout-pane"
2177 , pRoot = root +"-"+ pane // eg: "ui-layout-pane-west"
2178 , classes = [ root, root+_open, root+_closed, root+_sliding, // generic classes
2179 pRoot, pRoot+_open, pRoot+_closed, pRoot+_sliding ] // pane-specific classes
2180 ;
2181 $.merge(classes, getHoverClasses($P, true)); // ADD hover-classes
2182
2183 if (!$P || !$P.length) {
2184 } // pane has already been deleted!
2185 else if (remove && !$P.data("layoutContainer") && (!$C || !$C.length || !$C.data("layoutContainer")))
2186 $P.remove();
2187 else {
2188 $P .removeClass( classes.join(" ") ) // remove ALL pane-classes
2189 .removeData("layoutParent")
2190 .removeData("layoutRole")
2191 .removeData("layoutEdge")
2192 .removeData("autoHidden") // in case set
2193 .unbind("."+ sID) // remove ALL Layout events
2194 // TODO: remove these extra unbind commands when jQuery is fixed
2195 //.unbind("mouseenter"+ sID)
2196 //.unbind("mouseleave"+ sID)
2197 ;
2198 // do NOT reset CSS if this pane is STILL the container of a nested layout!
2199 // the nested layout will reset its 'container' when/if it is destroyed
2200 if (!$P.data("layoutContainer"))
2201 $P.css( $P.data("layoutCSS") ).removeData("layoutCSS");
2202 // DITTO for the Content elem
2203 if ($C && $C.length && !$C.data("layoutContainer"))
2204 $C.css( $C.data("layoutCSS") ).removeData("layoutCSS");
2205 }
2206
2207 // REMOVE pane resizer and toggler elements
2208 if ($T && $T.length) $T.remove();
2209 if ($R && $R.length) $R.remove();
2210
2211 // CLEAR all pointers and data
2212 $Ps[pane] = $Cs[pane] = $Rs[pane] = $Ts[pane] = false;
2213
2214 // skip resize & state-clear when called from destroy()
2215 if (!skipResize) {
2216 resizeAll();
2217 state[pane] = {};
2218 }
2219 }
2220
2221
2222/*
2223 * ###########################
2224 * ACTION METHODS
2225 * ###########################
2226 */
2227
2228 /**
2229 * Completely 'hides' a pane, including its spacing - as if it does not exist
2230 * The pane is not actually 'removed' from the source, so can use 'show' to un-hide it
2231 *
2232 * @param {string} pane The pane being hidden, ie: north, south, east, or west
2233 * @param {boolean=} noAnimation
2234 */
2235, hide = function (pane, noAnimation) {
2236 if (!isInitialized()) return;
2237 var
2238 o = options[pane]
2239 , s = state[pane]
2240 , $P = $Ps[pane]
2241 , $R = $Rs[pane]
2242 ;
2243 if (!$P || s.isHidden) return; // pane does not exist OR is already hidden
2244
2245 // onhide_start callback - will CANCEL hide if returns false
2246 if (state.initialized && false === _execCallback(pane, o.onhide_start)) return;
2247
2248 s.isSliding = false; // just in case
2249
2250 // now hide the elements
2251 if ($R) $R.hide(); // hide resizer-bar
2252 if (!state.initialized || s.isClosed) {
2253 s.isClosed = true; // to trigger open-animation on show()
2254 s.isHidden = true;
2255 s.isVisible = false;
2256 $P.hide(); // no animation when loading page
2257 sizeMidPanes(_c[pane].dir == "horz" ? "all" : "center");
2258 if (state.initialized || o.triggerEventsOnLoad)
2259 _execCallback(pane, o.onhide_end || o.onhide);
2260 }
2261 else {
2262 s.isHiding = true; // used by onclose
2263 close(pane, false, noAnimation); // adjust all panes to fit
2264 }
2265 }
2266
2267 /**
2268 * Show a hidden pane - show as 'closed' by default unless openPane = true
2269 *
2270 * @param {string} pane The pane being opened, ie: north, south, east, or west
2271 * @param {boolean=} openPane
2272 * @param {boolean=} noAnimation
2273 * @param {boolean=} noAlert
2274 */
2275, show = function (pane, openPane, noAnimation, noAlert) {
2276 if (!isInitialized()) return;
2277 var
2278 o = options[pane]
2279 , s = state[pane]
2280 , $P = $Ps[pane]
2281 , $R = $Rs[pane]
2282 ;
2283 if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden
2284
2285 // onshow_start callback - will CANCEL show if returns false
2286 if (false === _execCallback(pane, o.onshow_start)) return;
2287
2288 s.isSliding = false; // just in case
2289 s.isShowing = true; // used by onopen/onclose
2290 //s.isHidden = false; - will be set by open/close - if not cancelled
2291
2292 // now show the elements
2293 //if ($R) $R.show(); - will be shown by open/close
2294 if (openPane === false)
2295 close(pane, true); // true = force
2296 else
2297 open(pane, false, noAnimation, noAlert); // adjust all panes to fit
2298 }
2299
2300
2301 /**
2302 * Toggles a pane open/closed by calling either open or close
2303 *
2304 * @param {string} pane The pane being toggled, ie: north, south, east, or west
2305 * @param {boolean=} slide
2306 */
2307, toggle = function (pane, slide) {
2308 if (!isInitialized()) return;
2309 if (!isStr(pane)) {
2310 pane.stopImmediatePropagation(); // pane = event
2311 pane = $(this).data("layoutEdge"); // bound to $R.dblclick
2312 }
2313 var s = state[str(pane)];
2314 if (s.isHidden)
2315 show(pane); // will call 'open' after unhiding it
2316 else if (s.isClosed)
2317 open(pane, !!slide);
2318 else
2319 close(pane);
2320 }
2321
2322
2323 /**
2324 * Utility method used during init or other auto-processes
2325 *
2326 * @param {string} pane The pane being closed
2327 * @param {boolean=} setHandles
2328 */
2329, _closePane = function (pane, setHandles) {
2330 var
2331 $P = $Ps[pane]
2332 , s = state[pane]
2333 ;
2334 $P.hide();
2335 s.isClosed = true;
2336 s.isVisible = false;
2337 // UNUSED: if (setHandles) setAsClosed(pane, true); // true = force
2338 }
2339
2340 /**
2341 * Close the specified pane (animation optional), and resize all other panes as needed
2342 *
2343 * @param {string} pane The pane being closed, ie: north, south, east, or west
2344 * @param {boolean=} force
2345 * @param {boolean=} noAnimation
2346 * @param {boolean=} skipCallback
2347 */
2348, close = function (pane, force, noAnimation, skipCallback) {
2349 if (!state.initialized && $Ps[pane]) {
2350 _closePane(pane); // INIT pane as closed
2351 return;
2352 }
2353 if (!isInitialized()) return;
2354 var
2355 $P = $Ps[pane]
2356 , $R = $Rs[pane]
2357 , $T = $Ts[pane]
2358 , o = options[pane]
2359 , s = state[pane]
2360 , doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none")
2361 // transfer logic vars to temp vars
2362 , isShowing = s.isShowing
2363 , isHiding = s.isHiding
2364 , wasSliding = s.isSliding
2365 ;
2366 // now clear the logic vars
2367 delete s.isShowing;
2368 delete s.isHiding;
2369
2370 if (!$P || (!o.closable && !isShowing && !isHiding)) return; // invalid request // (!o.resizable && !o.closable) ???
2371 else if (!force && s.isClosed && !isShowing) return; // already closed
2372
2373 if (_c.isLayoutBusy) { // layout is 'busy' - probably with an animation
2374 _queue("close", pane, force); // set a callback for this action, if possible
2375 return; // ABORT
2376 }
2377
2378 // onclose_start callback - will CANCEL hide if returns false
2379 // SKIP if just 'showing' a hidden pane as 'closed'
2380 if (!isShowing && false === _execCallback(pane, o.onclose_start)) return;
2381
2382 // SET flow-control flags
2383 _c[pane].isMoving = true;
2384 _c.isLayoutBusy = true;
2385
2386 s.isClosed = true;
2387 s.isVisible = false;
2388 // update isHidden BEFORE sizing panes
2389 if (isHiding) s.isHidden = true;
2390 else if (isShowing) s.isHidden = false;
2391
2392 if (s.isSliding) // pane is being closed, so UNBIND trigger events
2393 bindStopSlidingEvents(pane, false); // will set isSliding=false
2394 else // resize panes adjacent to this one
2395 sizeMidPanes(_c[pane].dir == "horz" ? "all" : "center", false); // false = NOT skipCallback
2396
2397 // if this pane has a resizer bar, move it NOW - before animation
2398 setAsClosed(pane);
2399
2400 // CLOSE THE PANE
2401 if (doFX) { // animate the close
2402 lockPaneForFX(pane, true); // need to set left/top so animation will work
2403 $P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () {
2404 lockPaneForFX(pane, false); // undo
2405 close_2();
2406 });
2407 }
2408 else { // hide the pane without animation
2409 $P.hide();
2410 close_2();
2411 };
2412
2413 // SUBROUTINE
2414 function close_2 () {
2415 if (s.isClosed) { // make sure pane was not 'reopened' before animation finished!
2416
2417 bindStartSlidingEvent(pane, true); // will enable if o.slidable = true
2418
2419 // if opposite-pane was autoClosed, see if it can be autoOpened now
2420 var altPane = _c.altSide[pane];
2421 if (state[ altPane ].noRoom) {
2422 setSizeLimits( altPane );
2423 makePaneFit( altPane );
2424 }
2425
2426 if (!skipCallback && (state.initialized || o.triggerEventsOnLoad)) {
2427 // onclose callback - UNLESS just 'showing' a hidden pane as 'closed'
2428 if (!isShowing) _execCallback(pane, o.onclose_end || o.onclose);
2429 // onhide OR onshow callback
2430 if (isShowing) _execCallback(pane, o.onshow_end || o.onshow);
2431 if (isHiding) _execCallback(pane, o.onhide_end || o.onhide);
2432 }
2433 }
2434 // execute internal flow-control callback
2435 _dequeue(pane);
2436 }
2437 }
2438
2439 /**
2440 * @param {string} pane The pane just closed, ie: north, south, east, or west
2441 */
2442, setAsClosed = function (pane) {
2443 var
2444 $P = $Ps[pane]
2445 , $R = $Rs[pane]
2446 , $T = $Ts[pane]
2447 , o = options[pane]
2448 , s = state[pane]
2449 , side = _c[pane].side.toLowerCase()
2450 , inset = "inset"+ _c[pane].side
2451 , rClass = o.resizerClass
2452 , tClass = o.togglerClass
2453 , _pane = "-"+ pane // used for classNames
2454 , _open = "-open"
2455 , _sliding= "-sliding"
2456 , _closed = "-closed"
2457 ;
2458 $R
2459 .css(side, sC[inset]) // move the resizer
2460 .removeClass( rClass+_open +" "+ rClass+_pane+_open )
2461 .removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
2462 .addClass( rClass+_closed +" "+ rClass+_pane+_closed )
2463 .unbind("dblclick."+ sID)
2464 ;
2465 // DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent?
2466 if (o.resizable && typeof $.fn.draggable == "function")
2467 $R
2468 .draggable("disable")
2469 .removeClass("ui-state-disabled") // do NOT apply disabled styling - not suitable here
2470 .css("cursor", "default")
2471 .attr("title","")
2472 ;
2473
2474 // if pane has a toggler button, adjust that too
2475 if ($T) {
2476 $T
2477 .removeClass( tClass+_open +" "+ tClass+_pane+_open )
2478 .addClass( tClass+_closed +" "+ tClass+_pane+_closed )
2479 .attr("title", o.togglerTip_closed) // may be blank
2480 ;
2481 // toggler-content - if exists
2482 $T.children(".content-open").hide();
2483 $T.children(".content-closed").css("display","block");
2484 }
2485
2486 // sync any 'pin buttons'
2487 syncPinBtns(pane, false);
2488
2489 if (state.initialized) {
2490 // resize 'length' and position togglers for adjacent panes
2491 sizeHandles("all");
2492 }
2493 }
2494
2495 /**
2496 * Open the specified pane (animation optional), and resize all other panes as needed
2497 *
2498 * @param {string} pane The pane being opened, ie: north, south, east, or west
2499 * @param {boolean=} slide
2500 * @param {boolean=} noAnimation
2501 * @param {boolean=} noAlert
2502 */
2503, open = function (pane, slide, noAnimation, noAlert) {
2504 if (!isInitialized()) return;
2505 var
2506 $P = $Ps[pane]
2507 , $R = $Rs[pane]
2508 , $T = $Ts[pane]
2509 , o = options[pane]
2510 , s = state[pane]
2511 , doFX = !noAnimation && s.isClosed && (o.fxName_open != "none")
2512 // transfer logic var to temp var
2513 , isShowing = s.isShowing
2514 ;
2515 // now clear the logic var
2516 delete s.isShowing;
2517
2518 if (!$P || (!o.resizable && !o.closable && !isShowing)) return; // invalid request
2519 else if (s.isVisible && !s.isSliding) return; // already open
2520
2521 // pane can ALSO be unhidden by just calling show(), so handle this scenario
2522 if (s.isHidden && !isShowing) {
2523 show(pane, true);
2524 return;
2525 }
2526
2527 if (_c.isLayoutBusy) { // layout is 'busy' - probably with an animation
2528 _queue("open", pane, slide); // set a callback for this action, if possible
2529 return; // ABORT
2530 }
2531
2532 setSizeLimits(pane, slide); // update pane-state
2533
2534 // onopen_start callback - will CANCEL hide if returns false
2535 if (false === _execCallback(pane, o.onopen_start)) return;
2536
2537 // make sure there is enough space available to open the pane
2538 setSizeLimits(pane, slide); // update pane-state
2539 if (s.minSize > s.maxSize) { // INSUFFICIENT ROOM FOR PANE TO OPEN!
2540 syncPinBtns(pane, false); // make sure pin-buttons are reset
2541 if (!noAlert && o.noRoomToOpenTip)
2542 alert(o.noRoomToOpenTip);
2543 return; // ABORT
2544 }
2545
2546 // SET flow-control flags
2547 _c[pane].isMoving = true;
2548 _c.isLayoutBusy = true;
2549
2550 if (slide) // START Sliding - will set isSliding=true
2551 bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
2552 else if (s.isSliding) // PIN PANE (stop sliding) - open pane 'normally' instead
2553 bindStopSlidingEvents(pane, false); // UNBIND trigger events - will set isSliding=false
2554 else if (o.slidable)
2555 bindStartSlidingEvent(pane, false); // UNBIND trigger events
2556
2557 s.noRoom = false; // will be reset by makePaneFit if 'noRoom'
2558 makePaneFit(pane);
2559
2560 s.isVisible = true;
2561 s.isClosed = false;
2562 // update isHidden BEFORE sizing panes - WHY??? Old?
2563 if (isShowing) s.isHidden = false;
2564
2565 if (doFX) { // ANIMATE
2566 lockPaneForFX(pane, true); // need to set left/top so animation will work
2567 $P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() {
2568 lockPaneForFX(pane, false); // undo
2569 open_2(); // continue
2570 });
2571 }
2572 else {// no animation
2573 $P.show(); // just show pane and...
2574 open_2(); // continue
2575 };
2576
2577 // SUBROUTINE
2578 function open_2 () {
2579 if (s.isVisible) { // make sure pane was not closed or hidden before animation finished!
2580
2581 // cure iframe display issues
2582 _fixIframe(pane);
2583
2584 // NOTE: if isSliding, then other panes are NOT 'resized'
2585 if (!s.isSliding) // resize all panes adjacent to this one
2586 sizeMidPanes(_c[pane].dir=="vert" ? "center" : "all", false); // false = NOT skipCallback
2587
2588 // set classes, position handles and execute callbacks...
2589 setAsOpen(pane);
2590 }
2591
2592 // internal flow-control callback
2593 _dequeue(pane);
2594 };
2595
2596 }
2597
2598 /**
2599 * @param {string} pane The pane just opened, ie: north, south, east, or west
2600 * @param {boolean=} skipCallback
2601 */
2602, setAsOpen = function (pane, skipCallback) {
2603 var
2604 $P = $Ps[pane]
2605 , $R = $Rs[pane]
2606 , $T = $Ts[pane]
2607 , o = options[pane]
2608 , s = state[pane]
2609 , side = _c[pane].side.toLowerCase()
2610 , inset = "inset"+ _c[pane].side
2611 , rClass = o.resizerClass
2612 , tClass = o.togglerClass
2613 , _pane = "-"+ pane // used for classNames
2614 , _open = "-open"
2615 , _closed = "-closed"
2616 , _sliding= "-sliding"
2617 ;
2618 $R
2619 .css(side, sC[inset] + getPaneSize(pane)) // move the resizer
2620 .removeClass( rClass+_closed +" "+ rClass+_pane+_closed )
2621 .addClass( rClass+_open +" "+ rClass+_pane+_open )
2622 ;
2623 if (s.isSliding)
2624 $R.addClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
2625 else // in case 'was sliding'
2626 $R.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
2627
2628 if (o.resizerDblClickToggle)
2629 $R.bind("dblclick", toggle );
2630 removeHover( 0, $R ); // remove hover classes
2631 if (o.resizable && typeof $.fn.draggable == "function")
2632 $R
2633 .draggable("enable")
2634 .css("cursor", o.resizerCursor)
2635 .attr("title", o.resizerTip)
2636 ;
2637 else if (!s.isSliding)
2638 $R.css("cursor", "default"); // n-resize, s-resize, etc
2639
2640 // if pane also has a toggler button, adjust that too
2641 if ($T) {
2642 $T
2643 .removeClass( tClass+_closed +" "+ tClass+_pane+_closed )
2644 .addClass( tClass+_open +" "+ tClass+_pane+_open )
2645 .attr("title", o.togglerTip_open) // may be blank
2646 ;
2647 removeHover( 0, $T ); // remove hover classes
2648 // toggler-content - if exists
2649 $T.children(".content-closed").hide();
2650 $T.children(".content-open").css("display","block");
2651 }
2652
2653 // sync any 'pin buttons'
2654 syncPinBtns(pane, !s.isSliding);
2655
2656 // update pane-state dimensions - BEFORE resizing content
2657 $.extend(s, elDims($P));
2658
2659 if (state.initialized) {
2660 // resize resizer & toggler sizes for all panes
2661 sizeHandles("all");
2662 // resize content every time pane opens - to be sure
2663 sizeContent(pane, true); // true = remeasure headers/footers, even if 'isLayoutBusy'
2664 }
2665
2666 if (!skipCallback && (state.initialized || o.triggerEventsOnLoad) && $P.is(":visible")) {
2667 // onopen callback
2668 _execCallback(pane, o.onopen_end || o.onopen);
2669 // onshow callback - TODO: should this be here?
2670 if (s.isShowing) _execCallback(pane, o.onshow_end || o.onshow);
2671 // ALSO call onresize because layout-size *may* have changed while pane was closed
2672 if (state.initialized) {
2673 _execCallback(pane, o.onresize_end || o.onresize);
2674 resizeNestedLayout(pane);
2675 }
2676 }
2677 }
2678
2679
2680 /**
2681 * slideOpen / slideClose / slideToggle
2682 *
2683 * Pass-though methods for sliding
2684 */
2685, slideOpen = function (evt_or_pane) {
2686 if (!isInitialized()) return;
2687 var
2688 evt = isStr(evt_or_pane) ? null : evt_or_pane
2689 , pane = evt ? $(this).data("layoutEdge") : evt_or_pane
2690 , s = state[pane]
2691 , delay = options[pane].slideDelay_open
2692 ;
2693 // prevent event from triggering on NEW resizer binding created below
2694 if (evt) evt.stopImmediatePropagation();
2695
2696 if (s.isClosed && evt && evt.type == "mouseenter" && delay > 0)
2697 // trigger = mouseenter - use a delay
2698 timer.set(pane+"_openSlider", open_NOW, delay);
2699 else
2700 open_NOW(); // will unbind events if is already open
2701
2702 /**
2703 * SUBROUTINE for timed open
2704 */
2705 function open_NOW (evt) {
2706 if (!s.isClosed) // skip if no longer closed!
2707 bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
2708 else if (!_c[pane].isMoving)
2709 open(pane, true); // true = slide - open() will handle binding
2710 };
2711 }
2712
2713, slideClose = function (evt_or_pane) {
2714 if (!isInitialized()) return;
2715 var
2716 evt = isStr(evt_or_pane) ? null : evt_or_pane
2717 , pane = evt ? $(this).data("layoutEdge") : evt_or_pane
2718 , o = options[pane]
2719 , s = state[pane]
2720 , delay = _c[pane].isMoving ? 1000 : 300 // MINIMUM delay - option may override
2721 ;
2722
2723 if (s.isClosed || s.isResizing)
2724 return; // skip if already closed OR in process of resizing
2725 else if (o.slideTrigger_close == "click")
2726 close_NOW(); // close immediately onClick
2727 else if (o.preventQuickSlideClose && _c.isLayoutBusy)
2728 return; // handle Chrome quick-close on slide-open
2729 else if (o.preventPrematureSlideClose && evt && $.layout.isMouseOverElem(evt, $Ps[pane]))
2730 return; // handle incorrect mouseleave trigger, like when over a SELECT-list in IE
2731 else if (evt) // trigger = mouseleave - use a delay
2732 // 1 sec delay if 'opening', else .3 sec
2733 timer.set(pane+"_closeSlider", close_NOW, max(o.slideDelay_close, delay));
2734 else // called programically
2735 close_NOW();
2736
2737 /**
2738 * SUBROUTINE for timed close
2739 */
2740 function close_NOW () {
2741 if (s.isClosed) // skip 'close' if already closed!
2742 bindStopSlidingEvents(pane, false); // UNBIND trigger events - TODO: is this needed here?
2743 else if (!_c[pane].isMoving)
2744 close(pane); // close will handle unbinding
2745 };
2746 }
2747
2748, slideToggle = function (pane) { toggle(pane, true); }
2749
2750
2751 /**
2752 * Must set left/top on East/South panes so animation will work properly
2753 *
2754 * @param {string} pane The pane to lock, 'east' or 'south' - any other is ignored!
2755 * @param {boolean} doLock true = set left/top, false = remove
2756 */
2757, lockPaneForFX = function (pane, doLock) {
2758 var $P = $Ps[pane];
2759 if (doLock) {
2760 $P.css({ zIndex: _c.zIndex.pane_animate }); // overlay all elements during animation
2761 if (pane=="south")
2762 $P.css({ top: sC.insetTop + sC.innerHeight - $P.outerHeight() });
2763 else if (pane=="east")
2764 $P.css({ left: sC.insetLeft + sC.innerWidth - $P.outerWidth() });
2765 }
2766 else { // animation DONE - RESET CSS
2767 // TODO: see if this can be deleted. It causes a quick-close when sliding in Chrome
2768 $P.css({ zIndex: (state[pane].isSliding ? _c.zIndex.pane_sliding : _c.zIndex.pane_normal) });
2769 if (pane=="south")
2770 $P.css({ top: "auto" });
2771 else if (pane=="east")
2772 $P.css({ left: "auto" });
2773 // fix anti-aliasing in IE - only needed for animations that change opacity
2774 var o = options[pane];
2775 if ($.layout.browser.msie && o.fxOpacityFix && o.fxName_open != "slide" && $P.css("filter") && $P.css("opacity") == 1)
2776 $P[0].style.removeAttribute('filter');
2777 }
2778 }
2779
2780
2781 /**
2782 * Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger
2783 *
2784 * @see open(), close()
2785 * @param {string} pane The pane to enable/disable, 'north', 'south', etc.
2786 * @param {boolean} enable Enable or Disable sliding?
2787 */
2788, bindStartSlidingEvent = function (pane, enable) {
2789 var
2790 o = options[pane]
2791 , $P = $Ps[pane]
2792 , $R = $Rs[pane]
2793 , trigger = o.slideTrigger_open.toLowerCase()
2794 ;
2795 if (!$R || (enable && !o.slidable)) return;
2796
2797 // make sure we have a valid event
2798 if (trigger.match(/mouseover/))
2799 trigger = o.slideTrigger_open = "mouseenter";
2800 else if (!trigger.match(/click|dblclick|mouseenter/))
2801 trigger = o.slideTrigger_open = "click";
2802
2803 $R
2804 // add or remove trigger event
2805 [enable ? "bind" : "unbind"](trigger +'.'+ sID, slideOpen)
2806 // set the appropriate cursor & title/tip
2807 .css("cursor", enable ? o.sliderCursor : "default")
2808 .attr("title", enable ? o.sliderTip : "")
2809 ;
2810 }
2811
2812 /**
2813 * Add or remove 'mouseleave' events to 'slide close' when pane is 'sliding' open or closed
2814 * Also increases zIndex when pane is sliding open
2815 * See bindStartSlidingEvent for code to control 'slide open'
2816 *
2817 * @see slideOpen(), slideClose()
2818 * @param {string} pane The pane to process, 'north', 'south', etc.
2819 * @param {boolean} enable Enable or Disable events?
2820 */
2821, bindStopSlidingEvents = function (pane, enable) {
2822 var
2823 o = options[pane]
2824 , s = state[pane]
2825 , z = _c.zIndex
2826 , trigger = o.slideTrigger_close.toLowerCase()
2827 , action = (enable ? "bind" : "unbind")
2828 , $P = $Ps[pane]
2829 , $R = $Rs[pane]
2830 ;
2831 s.isSliding = enable; // logic
2832 timer.clear(pane+"_closeSlider"); // just in case
2833
2834 // remove 'slideOpen' trigger event from resizer
2835 // ALSO will raise the zIndex of the pane & resizer
2836 if (enable) bindStartSlidingEvent(pane, false);
2837
2838 // RE/SET zIndex - increases when pane is sliding-open, resets to normal when not
2839 $P.css("zIndex", enable ? z.pane_sliding : z.pane_normal);
2840 $R.css("zIndex", enable ? z.pane_sliding : z.resizer_normal);
2841
2842 // make sure we have a valid event
2843 if (!trigger.match(/click|mouseleave/))
2844 trigger = o.slideTrigger_close = "mouseleave"; // also catches 'mouseout'
2845
2846 // add/remove slide triggers
2847 $R[action](trigger, slideClose); // base event on resize
2848 // need extra events for mouseleave
2849 if (trigger == "mouseleave") {
2850 // also close on pane.mouseleave
2851 $P[action]("mouseleave."+ sID, slideClose);
2852 // cancel timer when mouse moves between 'pane' and 'resizer'
2853 $R[action]("mouseenter."+ sID, cancelMouseOut);
2854 $P[action]("mouseenter."+ sID, cancelMouseOut);
2855 }
2856
2857 if (!enable)
2858 timer.clear(pane+"_closeSlider");
2859 else if (trigger == "click" && !o.resizable) {
2860 // IF pane is not resizable (which already has a cursor and tip)
2861 // then set the a cursor & title/tip on resizer when sliding
2862 $R.css("cursor", enable ? o.sliderCursor : "default");
2863 $R.attr("title", enable ? o.togglerTip_open : ""); // use Toggler-tip, eg: "Close Pane"
2864 }
2865
2866 // SUBROUTINE for mouseleave timer clearing
2867 function cancelMouseOut (evt) {
2868 timer.clear(pane+"_closeSlider");
2869 evt.stopPropagation();
2870 }
2871 }
2872
2873
2874 /**
2875 * Hides/closes a pane if there is insufficient room - reverses this when there is room again
2876 * MUST have already called setSizeLimits() before calling this method
2877 *
2878 * @param {string} pane The pane being resized
2879 * @param {boolean=} isOpening Called from onOpen?
2880 * @param {boolean=} skipCallback Should the onresize callback be run?
2881 * @param {boolean=} force
2882 */
2883, makePaneFit = function (pane, isOpening, skipCallback, force) {
2884 var
2885 o = options[pane]
2886 , s = state[pane]
2887 , c = _c[pane]
2888 , $P = $Ps[pane]
2889 , $R = $Rs[pane]
2890 , isSidePane = c.dir=="vert"
2891 , hasRoom = false
2892 ;
2893
2894 // special handling for center & east/west panes
2895 if (pane == "center" || (isSidePane && s.noVerticalRoom)) {
2896 // see if there is enough room to display the pane
2897 // ERROR: hasRoom = s.minHeight <= s.maxHeight && (isSidePane || s.minWidth <= s.maxWidth);
2898 hasRoom = (s.maxHeight > 0);
2899 if (hasRoom && s.noRoom) { // previously hidden due to noRoom, so show now
2900 $P.show();
2901 if ($R) $R.show();
2902 s.isVisible = true;
2903 s.noRoom = false;
2904 if (isSidePane) s.noVerticalRoom = false;
2905 _fixIframe(pane);
2906 }
2907 else if (!hasRoom && !s.noRoom) { // not currently hidden, so hide now
2908 $P.hide();
2909 if ($R) $R.hide();
2910 s.isVisible = false;
2911 s.noRoom = true;
2912 }
2913 }
2914
2915 // see if there is enough room to fit the border-pane
2916 if (pane == "center") {
2917 // ignore center in this block
2918 }
2919 else if (s.minSize <= s.maxSize) { // pane CAN fit
2920 hasRoom = true;
2921 if (s.size > s.maxSize) // pane is too big - shrink it
2922 sizePane(pane, s.maxSize, skipCallback, force);
2923 else if (s.size < s.minSize) // pane is too small - enlarge it
2924 sizePane(pane, s.minSize, skipCallback, force);
2925 else if ($R && $P.is(":visible")) {
2926 // make sure resizer-bar is positioned correctly
2927 // handles situation where nested layout was 'hidden' when initialized
2928 var
2929 side = c.side.toLowerCase()
2930 , pos = s.size + sC["inset"+ c.side]
2931 ;
2932 if ($.layout.cssNum($R, side) != pos) $R.css( side, pos );
2933 }
2934
2935 // if was previously hidden due to noRoom, then RESET because NOW there is room
2936 if (s.noRoom) {
2937 // s.noRoom state will be set by open or show
2938 if (s.wasOpen && o.closable) {
2939 if (o.autoReopen)
2940 open(pane, false, true, true); // true = noAnimation, true = noAlert
2941 else // leave the pane closed, so just update state
2942 s.noRoom = false;
2943 }
2944 else
2945 show(pane, s.wasOpen, true, true); // true = noAnimation, true = noAlert
2946 }
2947 }
2948 else { // !hasRoom - pane CANNOT fit
2949 if (!s.noRoom) { // pane not set as noRoom yet, so hide or close it now...
2950 s.noRoom = true; // update state
2951 s.wasOpen = !s.isClosed && !s.isSliding;
2952 if (s.isClosed){} // SKIP
2953 else if (o.closable) // 'close' if possible
2954 close(pane, true, true); // true = force, true = noAnimation
2955 else // 'hide' pane if cannot just be closed
2956 hide(pane, true); // true = noAnimation
2957 }
2958 }
2959 }
2960
2961
2962 /**
2963 * sizePane / manualSizePane
2964 * sizePane is called only by internal methods whenever a pane needs to be resized
2965 * manualSizePane is an exposed flow-through method allowing extra code when pane is 'manually resized'
2966 *
2967 * @param {string} pane The pane being resized
2968 * @param {number} size The *desired* new size for this pane - will be validated
2969 * @param {boolean=} skipCallback Should the onresize callback be run?
2970 */
2971, manualSizePane = function (pane, size, skipCallback) {
2972 if (!isInitialized()) return;
2973 // ANY call to sizePane will disabled autoResize
2974 var
2975 o = options[pane]
2976 // if resizing callbacks have been delayed and resizing is now DONE, force resizing to complete...
2977 , forceResize = o.resizeWhileDragging && !_c.isLayoutBusy // && !o.triggerEventsWhileDragging
2978 ;
2979 o.autoResize = false;
2980 // flow-through...
2981 sizePane(pane, size, skipCallback, forceResize);
2982 }
2983
2984 /**
2985 * @param {string} pane The pane being resized
2986 * @param {number} size The *desired* new size for this pane - will be validated
2987 * @param {boolean=} skipCallback Should the onresize callback be run?
2988 * @param {boolean=} force Force resizing even if does not seem necessary
2989 */
2990, sizePane = function (pane, size, skipCallback, force) {
2991 if (!isInitialized()) return;
2992 var
2993 o = options[pane]
2994 , s = state[pane]
2995 , $P = $Ps[pane]
2996 , $R = $Rs[pane]
2997 , side = _c[pane].side.toLowerCase()
2998 , dimName = _c[pane].sizeType.toLowerCase()
2999 , inset = "inset"+ _c[pane].side
3000 , skipResizeWhileDragging = _c.isLayoutBusy && !o.triggerEventsWhileDragging
3001 , oldSize
3002 ;
3003 // calculate 'current' min/max sizes
3004 setSizeLimits(pane); // update pane-state
3005 oldSize = s.size;
3006
3007 size = _parseSize(pane, size); // handle percentages & auto
3008 size = max(size, _parseSize(pane, o.minSize));
3009 size = min(size, s.maxSize);
3010 if (size < s.minSize) { // not enough room for pane!
3011 makePaneFit(pane, false, skipCallback); // will hide or close pane
3012 return;
3013 }
3014
3015 // IF newSize is same as oldSize, then nothing to do - abort
3016 if (!force && size == oldSize) return;
3017
3018 // onresize_start callback CANNOT cancel resizing because this would break the layout!
3019 if (!skipCallback && state.initialized && s.isVisible)
3020 _execCallback(pane, o.onresize_start);
3021
3022 // resize the pane, and make sure its visible
3023 $P.css( dimName, max(1, cssSize(pane, size)) );
3024
3025/*
3026var
3027 edge = _c[pane].sizeType.toLowerCase()
3028, test = [{
3029 target: size
3030 , attempt: size
3031 , actual: edge=='width' ? $P.outerWidth() : $P.outerHeight()
3032 }]
3033, lastTest = test[0]
3034, thisTest = {}
3035;
3036while (lastTest.actual != size) {
3037 test.push( {} );
3038 thisTest = test[ test.length - 1 ];
3039
3040 if (lastTest.actual > size)
3041 thisTest.attempt = Math.max(1, lastTest.attempt - (lastTest.actual - size));
3042 else // lastTest.actual < size
3043 thisTest.attempt = Math.max(1, lastTest.attempt + (size - lastTest.actual));
3044
3045 $P.css( edge, cssSize(pane, thisTest.attempt) );
3046
3047 thisTest.actual = edge=='width' ? $P.outerWidth() : $P.outerHeight()
3048
3049 // after 3 tries, is as close as its gonna get!
3050 if (test.length == 3) break;
3051 else lastTest = thisTest;
3052}
3053debugData( test, pane );
3054*/
3055
3056 // update pane-state dimensions
3057 s.size = size;
3058 $.extend(s, elDims($P));
3059
3060 // reposition the resizer-bar
3061 if ($R && $P.is(":visible")) $R.css( side, size + sC[inset] );
3062
3063 sizeContent(pane);
3064
3065 if (!skipCallback && !skipResizeWhileDragging && state.initialized && s.isVisible) {
3066 _execCallback(pane, o.onresize_end || o.onresize);
3067 resizeNestedLayout(pane);
3068 }
3069
3070 // resize all the adjacent panes, and adjust their toggler buttons
3071 // when skipCallback passed, it means the controlling method will handle 'other panes'
3072 if (!skipCallback) {
3073 // also no callback if live-resize is in progress and NOT triggerEventsWhileDragging
3074 if (!s.isSliding) sizeMidPanes(_c[pane].dir=="horz" ? "all" : "center", skipResizeWhileDragging, force);
3075 sizeHandles("all");
3076 }
3077
3078 // if opposite-pane was autoClosed, see if it can be autoOpened now
3079 var altPane = _c.altSide[pane];
3080 if (size < oldSize && state[ altPane ].noRoom) {
3081 setSizeLimits( altPane );
3082 makePaneFit( altPane, false, skipCallback );
3083 }
3084 }
3085
3086 /**
3087 * @see initPanes(), sizePane(), resizeAll(), open(), close(), hide()
3088 * @param {string} panes The pane(s) being resized, comma-delmited string
3089 * @param {boolean=} skipCallback Should the onresize callback be run?
3090 * @param {boolean=} force
3091 */
3092, sizeMidPanes = function (panes, skipCallback, force) {
3093 if (!panes || panes == "all") panes = "east,west,center";
3094
3095 $.each(panes.split(","), function (i, pane) {
3096 if (!$Ps[pane]) return; // NO PANE - skip
3097 var
3098 o = options[pane]
3099 , s = state[pane]
3100 , $P = $Ps[pane]
3101 , $R = $Rs[pane]
3102 , isCenter= (pane=="center")
3103 , hasRoom = true
3104 , CSS = {}
3105 , newCenter = calcNewCenterPaneDims()
3106 ;
3107 // update pane-state dimensions
3108 $.extend(s, elDims($P));
3109
3110 if (pane == "center") {
3111 if (!force && s.isVisible && newCenter.width == s.outerWidth && newCenter.height == s.outerHeight)
3112 return true; // SKIP - pane already the correct size
3113 // set state for makePaneFit() logic
3114 $.extend(s, cssMinDims(pane), {
3115 maxWidth: newCenter.width
3116 , maxHeight: newCenter.height
3117 });
3118 CSS = newCenter;
3119 // convert OUTER width/height to CSS width/height
3120 CSS.width = cssW(pane, CSS.width);
3121 CSS.height = cssH(pane, CSS.height);
3122 hasRoom = CSS.width > 0 && CSS.height > 0;
3123 // during layout init, try to shrink east/west panes to make room for center
3124 if (!hasRoom && !state.initialized && o.minWidth > 0) {
3125 var
3126 reqPx = o.minWidth - s.outerWidth
3127 , minE = options.east.minSize || 0
3128 , minW = options.west.minSize || 0
3129 , sizeE = state.east.size
3130 , sizeW = state.west.size
3131 , newE = sizeE
3132 , newW = sizeW
3133 ;
3134 if (reqPx > 0 && state.east.isVisible && sizeE > minE) {
3135 newE = max( sizeE-minE, sizeE-reqPx );
3136 reqPx -= sizeE-newE;
3137 }
3138 if (reqPx > 0 && state.west.isVisible && sizeW > minW) {
3139 newW = max( sizeW-minW, sizeW-reqPx );
3140 reqPx -= sizeW-newW;
3141 }
3142 // IF we found enough extra space, then resize the border panes as calculated
3143 if (reqPx == 0) {
3144 if (sizeE != minE)
3145 sizePane('east', newE, true); // true = skipCallback - initPanes will handle when done
3146 if (sizeW != minW)
3147 sizePane('west', newW, true);
3148 // now start over!
3149 sizeMidPanes('center', skipCallback, force);
3150 return; // abort this loop
3151 }
3152 }
3153 }
3154 else { // for east and west, set only the height, which is same as center height
3155 // set state.min/maxWidth/Height for makePaneFit() logic
3156 if (s.isVisible && !s.noVerticalRoom)
3157 $.extend(s, elDims($P), cssMinDims(pane))
3158 if (!force && !s.noVerticalRoom && newCenter.height == s.outerHeight)
3159 return true; // SKIP - pane already the correct size
3160 // east/west have same top, bottom & height as center
3161 CSS.top = newCenter.top;
3162 CSS.bottom = newCenter.bottom;
3163 CSS.height = cssH(pane, newCenter.height);
3164 s.maxHeight = max(0, CSS.height);
3165 hasRoom = (s.maxHeight > 0);
3166 if (!hasRoom) s.noVerticalRoom = true; // makePaneFit() logic
3167 }
3168
3169 if (hasRoom) {
3170 // resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
3171 if (!skipCallback && state.initialized)
3172 _execCallback(pane, o.onresize_start);
3173
3174 $P.css(CSS); // apply the CSS to pane
3175 if (s.noRoom && !s.isClosed && !s.isHidden)
3176 makePaneFit(pane); // will re-open/show auto-closed/hidden pane
3177 if (s.isVisible) {
3178 $.extend(s, elDims($P)); // update pane dimensions
3179 if (state.initialized) sizeContent(pane); // also resize the contents, if exists
3180 }
3181 }
3182 else if (!s.noRoom && s.isVisible) // no room for pane
3183 makePaneFit(pane); // will hide or close pane
3184
3185 if (!s.isVisible)
3186 return true; // DONE - next pane
3187
3188 /*
3189 * Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes
3190 * Normally these panes have only 'left' & 'right' positions so pane auto-sizes
3191 * ALSO required when pane is an IFRAME because will NOT default to 'full width'
3192 */
3193 if (pane == "center") { // finished processing midPanes
3194 var b = $.layout.browser;
3195 var fix = b.isIE6 || (b.msie && !b.boxModel);
3196 if ($Ps.north && (fix || state.north.tagName=="IFRAME"))
3197 $Ps.north.css("width", cssW($Ps.north, sC.innerWidth));
3198 if ($Ps.south && (fix || state.south.tagName=="IFRAME"))
3199 $Ps.south.css("width", cssW($Ps.south, sC.innerWidth));
3200 }
3201
3202 // resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
3203 if (!skipCallback && state.initialized) {
3204 _execCallback(pane, o.onresize_end || o.onresize);
3205 resizeNestedLayout(pane);
3206 }
3207 });
3208 }
3209
3210
3211 /**
3212 * @see window.onresize(), callbacks or custom code
3213 */
3214, resizeAll = function () {
3215 if (!state.initialized) {
3216 _initLayoutElements();
3217 return; // no need to resize since we just initialized!
3218 }
3219 var oldW = sC.innerWidth
3220 , oldH = sC.innerHeight
3221 ;
3222 // cannot size layout when 'container' is hidden or collapsed
3223 if (!$N.is(":visible:") ) return;
3224 $.extend( state.container, elDims( $N ) ); // UPDATE container dimensions
3225 if (!sC.outerHeight) return;
3226
3227 // onresizeall_start will CANCEL resizing if returns false
3228 // state.container has already been set, so user can access this info for calcuations
3229 if (false === _execCallback(null, options.onresizeall_start)) return false;
3230
3231 var // see if container is now 'smaller' than before
3232 shrunkH = (sC.innerHeight < oldH)
3233 , shrunkW = (sC.innerWidth < oldW)
3234 , $P, o, s, dir
3235 ;
3236 // NOTE special order for sizing: S-N-E-W
3237 $.each(["south","north","east","west"], function (i, pane) {
3238 if (!$Ps[pane]) return; // no pane - SKIP
3239 s = state[pane];
3240 o = options[pane];
3241 dir = _c[pane].dir;
3242
3243 if (o.autoResize && s.size != o.size) // resize pane to original size set in options
3244 sizePane(pane, o.size, true, true); // true=skipCallback, true=forceResize
3245 else {
3246 setSizeLimits(pane);
3247 makePaneFit(pane, false, true, true); // true=skipCallback, true=forceResize
3248 }
3249 });
3250
3251 sizeMidPanes("all", true, true); // true=skipCallback, true=forceResize
3252 sizeHandles("all"); // reposition the toggler elements
3253
3254 // trigger all individual pane callbacks AFTER layout has finished resizing
3255 o = options; // reuse alias
3256 $.each(_c.allPanes.split(","), function (i, pane) {
3257 $P = $Ps[pane];
3258 if (!$P) return; // SKIP
3259 if (state[pane].isVisible) { // undefined for non-existent panes
3260 _execCallback(pane, o[pane].onresize_end || o[pane].onresize); // callback - if exists
3261 resizeNestedLayout(pane);
3262 }
3263 });
3264
3265 _execCallback(null, o.onresizeall_end || o.onresizeall); // onresizeall callback, if exists
3266 }
3267
3268
3269 /**
3270 * Whenever a pane resizes or opens that has a nested layout, trigger resizeAll
3271 *
3272 * @param {string} pane The pane just resized or opened
3273 */
3274, resizeNestedLayout = function (pane) {
3275 var
3276 $P = $Ps[pane]
3277 , $C = $Cs[pane]
3278 , d = "layoutContainer"
3279 ;
3280 if (options[pane].resizeNestedLayout) {
3281 if ($P.data( d ))
3282 $P.layout().resizeAll();
3283 else if ($C && $C.data( d ))
3284 $C.layout().resizeAll();
3285 }
3286 }
3287
3288
3289 /**
3290 * IF pane has a content-div, then resize all elements inside pane to fit pane-height
3291 *
3292 * @param {string=} panes The pane(s) being resized
3293 * @param {boolean=} remeasure Should the content (header/footer) be remeasured?
3294 */
3295, sizeContent = function (panes, remeasure) {
3296 if (!isInitialized()) return;
3297 if (!panes || panes == "all") panes = _c.allPanes;
3298 $.each(panes.split(","), function (idx, pane) {
3299 var
3300 $P = $Ps[pane]
3301 , $C = $Cs[pane]
3302 , o = options[pane]
3303 , s = state[pane]
3304 , m = s.content // m = measurements
3305 ;
3306 if (!$P || !$C || !$P.is(":visible")) return true; // NOT VISIBLE - skip
3307
3308 // onsizecontent_start will CANCEL resizing if returns false
3309 if (false === _execCallback(null, o.onsizecontent_start)) return;
3310
3311 // skip re-measuring offsets if live-resizing
3312 if (!_c.isLayoutBusy || m.top == undefined || remeasure || o.resizeContentWhileDragging) {
3313 _measure();
3314 // if any footers are below pane-bottom, they may not measure correctly,
3315 // so allow pane overflow and re-measure
3316 if (m.hiddenFooters > 0 && $P.css("overflow") == "hidden") {
3317 $P.css("overflow", "visible");
3318 _measure(); // remeasure while overflowing
3319 $P.css("overflow", "hidden");
3320 }
3321 }
3322 // NOTE: spaceAbove/Below *includes* the pane paddingTop/Bottom, but not pane.borders
3323 var newH = s.innerHeight - (m.spaceAbove - s.css.paddingTop) - (m.spaceBelow - s.css.paddingBottom);
3324 if (!$C.is(":visible") || m.height != newH) {
3325 // size the Content element to fit new pane-size - will autoHide if not enough room
3326 setOuterHeight($C, newH, true); // true=autoHide
3327 m.height = newH; // save new height
3328 };
3329
3330 if (state.initialized) {
3331 _execCallback(pane, o.onsizecontent_end || o.onsizecontent);
3332 resizeNestedLayout(pane);
3333 }
3334
3335
3336 function _below ($E) {
3337 return max(s.css.paddingBottom, (parseInt($E.css("marginBottom"), 10) || 0));
3338 };
3339
3340 function _measure () {
3341 var
3342 ignore = options[pane].contentIgnoreSelector
3343 , $Fs = $C.nextAll().not(ignore || ':lt(0)') // not :lt(0) = ALL
3344 , $Fs_vis = $Fs.filter(':visible')
3345 , $F = $Fs_vis.filter(':last')
3346 ;
3347 m = {
3348 top: $C[0].offsetTop
3349 , height: $C.outerHeight()
3350 , numFooters: $Fs.length
3351 , hiddenFooters: $Fs.length - $Fs_vis.length
3352 , spaceBelow: 0 // correct if no content footer ($E)
3353 }
3354 m.spaceAbove = m.top; // just for state - not used in calc
3355 m.bottom = m.top + m.height;
3356 if ($F.length)
3357 //spaceBelow = (LastFooter.top + LastFooter.height) [footerBottom] - Content.bottom + max(LastFooter.marginBottom, pane.paddingBotom)
3358 m.spaceBelow = ($F[0].offsetTop + $F.outerHeight()) - m.bottom + _below($F);
3359 else // no footer - check marginBottom on Content element itself
3360 m.spaceBelow = _below($C);
3361 };
3362 });
3363 }
3364
3365
3366 /**
3367 * Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary
3368 *
3369 * @see initHandles(), open(), close(), resizeAll()
3370 * @param {string=} panes The pane(s) being resized
3371 */
3372, sizeHandles = function (panes) {
3373 if (!panes || panes == "all") panes = _c.borderPanes;
3374
3375 $.each(panes.split(","), function (i, pane) {
3376 var
3377 o = options[pane]
3378 , s = state[pane]
3379 , $P = $Ps[pane]
3380 , $R = $Rs[pane]
3381 , $T = $Ts[pane]
3382 , $TC
3383 ;
3384 if (!$P || !$R) return;
3385
3386 var
3387 dir = _c[pane].dir
3388 , _state = (s.isClosed ? "_closed" : "_open")
3389 , spacing = o["spacing"+ _state]
3390 , togAlign = o["togglerAlign"+ _state]
3391 , togLen = o["togglerLength"+ _state]
3392 , paneLen
3393 , offset
3394 , CSS = {}
3395 ;
3396
3397 if (spacing == 0) {
3398 $R.hide();
3399 return;
3400 }
3401 else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason
3402 $R.show(); // in case was previously hidden
3403
3404 // Resizer Bar is ALWAYS same width/height of pane it is attached to
3405 if (dir == "horz") { // north/south
3406 paneLen = $P.outerWidth(); // s.outerWidth ||
3407 s.resizerLength = paneLen;
3408 $R.css({
3409 width: max(1, cssW($R, paneLen)) // account for borders & padding
3410 , height: max(0, cssH($R, spacing)) // ditto
3411 , left: $.layout.cssNum($P, "left")
3412 });
3413 }
3414 else { // east/west
3415 paneLen = $P.outerHeight(); // s.outerHeight ||
3416 s.resizerLength = paneLen;
3417 $R.css({
3418 height: max(1, cssH($R, paneLen)) // account for borders & padding
3419 , width: max(0, cssW($R, spacing)) // ditto
3420 , top: sC.insetTop + getPaneSize("north", true) // TODO: what if no North pane?
3421 //, top: $.layout.cssNum($Ps["center"], "top")
3422 });
3423 }
3424
3425 // remove hover classes
3426 removeHover( o, $R );
3427
3428 if ($T) {
3429 if (togLen == 0 || (s.isSliding && o.hideTogglerOnSlide)) {
3430 $T.hide(); // always HIDE the toggler when 'sliding'
3431 return;
3432 }
3433 else
3434 $T.show(); // in case was previously hidden
3435
3436 if (!(togLen > 0) || togLen == "100%" || togLen > paneLen) {
3437 togLen = paneLen;
3438 offset = 0;
3439 }
3440 else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed
3441 if (isStr(togAlign)) {
3442 switch (togAlign) {
3443 case "top":
3444 case "left": offset = 0;
3445 break;
3446 case "bottom":
3447 case "right": offset = paneLen - togLen;
3448 break;
3449 case "middle":
3450 case "center":
3451 default: offset = Math.floor((paneLen - togLen) / 2); // 'default' catches typos
3452 }
3453 }
3454 else { // togAlign = number
3455 var x = parseInt(togAlign, 10); //
3456 if (togAlign >= 0) offset = x;
3457 else offset = paneLen - togLen + x; // NOTE: x is negative!
3458 }
3459 }
3460
3461 if (dir == "horz") { // north/south
3462 var width = cssW($T, togLen);
3463 $T.css({
3464 width: max(0, width) // account for borders & padding
3465 , height: max(1, cssH($T, spacing)) // ditto
3466 , left: offset // TODO: VERIFY that toggler positions correctly for ALL values
3467 , top: 0
3468 });
3469 // CENTER the toggler content SPAN
3470 $T.children(".content").each(function(){
3471 $TC = $(this);
3472 $TC.css("marginLeft", Math.floor((width-$TC.outerWidth())/2)); // could be negative
3473 });
3474 }
3475 else { // east/west
3476 var height = cssH($T, togLen);
3477 $T.css({
3478 height: max(0, height) // account for borders & padding
3479 , width: max(1, cssW($T, spacing)) // ditto
3480 , top: offset // POSITION the toggler
3481 , left: 0
3482 });
3483 // CENTER the toggler content SPAN
3484 $T.children(".content").each(function(){
3485 $TC = $(this);
3486 $TC.css("marginTop", Math.floor((height-$TC.outerHeight())/2)); // could be negative
3487 });
3488 }
3489
3490 // remove ALL hover classes
3491 removeHover( 0, $T );
3492 }
3493
3494 // DONE measuring and sizing this resizer/toggler, so can be 'hidden' now
3495 if (!state.initialized && (o.initHidden || s.noRoom)) {
3496 $R.hide();
3497 if ($T) $T.hide();
3498 }
3499 });
3500 }
3501
3502
3503, enableClosable = function (pane) {
3504 if (!isInitialized()) return;
3505 var $T = $Ts[pane], o = options[pane];
3506 if (!$T) return;
3507 o.closable = true;
3508 $T .bind("click."+ sID, function(evt){ evt.stopPropagation(); toggle(pane); })
3509 .bind("mouseenter."+ sID, addHover)
3510 .bind("mouseleave."+ sID, removeHover)
3511 .css("visibility", "visible")
3512 .css("cursor", "pointer")
3513 .attr("title", state[pane].isClosed ? o.togglerTip_closed : o.togglerTip_open) // may be blank
3514 .show()
3515 ;
3516 }
3517
3518, disableClosable = function (pane, hide) {
3519 if (!isInitialized()) return;
3520 var $T = $Ts[pane];
3521 if (!$T) return;
3522 options[pane].closable = false;
3523 // is closable is disable, then pane MUST be open!
3524 if (state[pane].isClosed) open(pane, false, true);
3525 $T .unbind("."+ sID)
3526 .css("visibility", hide ? "hidden" : "visible") // instead of hide(), which creates logic issues
3527 .css("cursor", "default")
3528 .attr("title", "")
3529 ;
3530 }
3531
3532
3533, enableSlidable = function (pane) {
3534 if (!isInitialized()) return;
3535 var $R = $Rs[pane], o = options[pane];
3536 if (!$R || !$R.data('draggable')) return;
3537 options[pane].slidable = true;
3538 if (s.isClosed)
3539 bindStartSlidingEvent(pane, true);
3540 }
3541
3542, disableSlidable = function (pane) {
3543 if (!isInitialized()) return;
3544 var $R = $Rs[pane];
3545 if (!$R) return;
3546 options[pane].slidable = false;
3547 if (state[pane].isSliding)
3548 close(pane, false, true);
3549 else {
3550 bindStartSlidingEvent(pane, false);
3551 $R .css("cursor", "default")
3552 .attr("title", "")
3553 ;
3554 removeHover(null, $R[0]); // in case currently hovered
3555 }
3556 }
3557
3558
3559, enableResizable = function (pane) {
3560 if (!isInitialized()) return;
3561 var $R = $Rs[pane], o = options[pane];
3562 if (!$R || !$R.data('draggable')) return;
3563 o.resizable = true;
3564 $R .draggable("enable")
3565 .bind("mouseenter."+ sID, onResizerEnter)
3566 .bind("mouseleave."+ sID, onResizerLeave)
3567 ;
3568 if (!state[pane].isClosed)
3569 $R .css("cursor", o.resizerCursor)
3570 .attr("title", o.resizerTip)
3571 ;
3572 }
3573
3574, disableResizable = function (pane) {
3575 if (!isInitialized()) return;
3576 var $R = $Rs[pane];
3577 if (!$R || !$R.data('draggable')) return;
3578 options[pane].resizable = false;
3579 $R .draggable("disable")
3580 .unbind("."+ sID)
3581 .css("cursor", "default")
3582 .attr("title", "")
3583 ;
3584 removeHover(null, $R[0]); // in case currently hovered
3585 }
3586
3587
3588 /**
3589 * Move a pane from source-side (eg, west) to target-side (eg, east)
3590 * If pane exists on target-side, move that to source-side, ie, 'swap' the panes
3591 *
3592 * @param {string} pane1 The pane/edge being swapped
3593 * @param {string} pane2 ditto
3594 */
3595, swapPanes = function (pane1, pane2) {
3596 if (!isInitialized()) return;
3597 // change state.edge NOW so callbacks can know where pane is headed...
3598 state[pane1].edge = pane2;
3599 state[pane2].edge = pane1;
3600 // run these even if NOT state.initialized
3601 var cancelled = false;
3602 if (false === _execCallback(pane1, options[pane1].onswap_start)) cancelled = true;
3603 if (!cancelled && false === _execCallback(pane2, options[pane2].onswap_start)) cancelled = true;
3604 if (cancelled) {
3605 state[pane1].edge = pane1; // reset
3606 state[pane2].edge = pane2;
3607 return;
3608 }
3609
3610 var
3611 oPane1 = copy( pane1 )
3612 , oPane2 = copy( pane2 )
3613 , sizes = {}
3614 ;
3615 sizes[pane1] = oPane1 ? oPane1.state.size : 0;
3616 sizes[pane2] = oPane2 ? oPane2.state.size : 0;
3617
3618 // clear pointers & state
3619 $Ps[pane1] = false;
3620 $Ps[pane2] = false;
3621 state[pane1] = {};
3622 state[pane2] = {};
3623
3624 // ALWAYS remove the resizer & toggler elements
3625 if ($Ts[pane1]) $Ts[pane1].remove();
3626 if ($Ts[pane2]) $Ts[pane2].remove();
3627 if ($Rs[pane1]) $Rs[pane1].remove();
3628 if ($Rs[pane2]) $Rs[pane2].remove();
3629 $Rs[pane1] = $Rs[pane2] = $Ts[pane1] = $Ts[pane2] = false;
3630
3631 // transfer element pointers and data to NEW Layout keys
3632 move( oPane1, pane2 );
3633 move( oPane2, pane1 );
3634
3635 // cleanup objects
3636 oPane1 = oPane2 = sizes = null;
3637
3638 // make panes 'visible' again
3639 if ($Ps[pane1]) $Ps[pane1].css(_c.visible);
3640 if ($Ps[pane2]) $Ps[pane2].css(_c.visible);
3641
3642 // fix any size discrepancies caused by swap
3643 resizeAll();
3644
3645 // run these even if NOT state.initialized
3646 _execCallback(pane1, options[pane1].onswap_end || options[pane1].onswap);
3647 _execCallback(pane2, options[pane2].onswap_end || options[pane2].onswap);
3648
3649 return;
3650
3651 function copy (n) { // n = pane
3652 var
3653 $P = $Ps[n]
3654 , $C = $Cs[n]
3655 ;
3656 return !$P ? false : {
3657 pane: n
3658 , P: $P ? $P[0] : false
3659 , C: $C ? $C[0] : false
3660 , state: $.extend({}, state[n])
3661 , options: $.extend({}, options[n])
3662 }
3663 };
3664
3665 function move (oPane, pane) {
3666 if (!oPane) return;
3667 var
3668 P = oPane.P
3669 , C = oPane.C
3670 , oldPane = oPane.pane
3671 , c = _c[pane]
3672 , side = c.side.toLowerCase()
3673 , inset = "inset"+ c.side
3674 // save pane-options that should be retained
3675 , s = $.extend({}, state[pane])
3676 , o = options[pane]
3677 // RETAIN side-specific FX Settings - more below
3678 , fx = { resizerCursor: o.resizerCursor }
3679 , re, size, pos
3680 ;
3681 $.each("fxName,fxSpeed,fxSettings".split(","), function (i, k) {
3682 fx[k] = o[k];
3683 fx[k +"_open"] = o[k +"_open"];
3684 fx[k +"_close"] = o[k +"_close"];
3685 });
3686
3687 // update object pointers and attributes
3688 $Ps[pane] = $(P)
3689 .data("layoutEdge", pane)
3690 .css(_c.hidden)
3691 .css(c.cssReq)
3692 ;
3693 $Cs[pane] = C ? $(C) : false;
3694
3695 // set options and state
3696 options[pane] = $.extend({}, oPane.options, fx);
3697 state[pane] = $.extend({}, oPane.state);
3698
3699 // change classNames on the pane, eg: ui-layout-pane-east ==> ui-layout-pane-west
3700 re = new RegExp(o.paneClass +"-"+ oldPane, "g");
3701 P.className = P.className.replace(re, o.paneClass +"-"+ pane);
3702
3703 // ALWAYS regenerate the resizer & toggler elements
3704 initHandles(pane); // create the required resizer & toggler
3705
3706 // if moving to different orientation, then keep 'target' pane size
3707 if (c.dir != _c[oldPane].dir) {
3708 size = sizes[pane] || 0;
3709 setSizeLimits(pane); // update pane-state
3710 size = max(size, state[pane].minSize);
3711 // use manualSizePane to disable autoResize - not useful after panes are swapped
3712 manualSizePane(pane, size, true); // true = skipCallback
3713 }
3714 else // move the resizer here
3715 $Rs[pane].css(side, sC[inset] + (state[pane].isVisible ? getPaneSize(pane) : 0));
3716
3717
3718 // ADD CLASSNAMES & SLIDE-BINDINGS
3719 if (oPane.state.isVisible && !s.isVisible)
3720 setAsOpen(pane, true); // true = skipCallback
3721 else {
3722 setAsClosed(pane);
3723 bindStartSlidingEvent(pane, true); // will enable events IF option is set
3724 }
3725
3726 // DESTROY the object
3727 oPane = null;
3728 };
3729 }
3730
3731
3732; // END var DECLARATIONS
3733
3734 /**
3735 * Capture keys when enableCursorHotkey - toggle pane if hotkey pressed
3736 *
3737 * @see document.keydown()
3738 */
3739 function keyDown (evt) {
3740 if (!evt) return true;
3741 var code = evt.keyCode;
3742 if (code < 33) return true; // ignore special keys: ENTER, TAB, etc
3743
3744 var
3745 PANE = {
3746 38: "north" // Up Cursor - $.ui.keyCode.UP
3747 , 40: "south" // Down Cursor - $.ui.keyCode.DOWN
3748 , 37: "west" // Left Cursor - $.ui.keyCode.LEFT
3749 , 39: "east" // Right Cursor - $.ui.keyCode.RIGHT
3750 }
3751 , ALT = evt.altKey // no worky!
3752 , SHIFT = evt.shiftKey
3753 , CTRL = evt.ctrlKey
3754 , CURSOR = (CTRL && code >= 37 && code <= 40)
3755 , o, k, m, pane
3756 ;
3757
3758 if (CURSOR && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey
3759 pane = PANE[code];
3760 else if (CTRL || SHIFT) // check to see if this matches a custom-hotkey
3761 $.each(_c.borderPanes.split(","), function (i, p) { // loop each pane to check its hotkey
3762 o = options[p];
3763 k = o.customHotkey;
3764 m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT"
3765 if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches
3766 if (k && code == (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches
3767 pane = p;
3768 return false; // BREAK
3769 }
3770 }
3771 });
3772
3773 // validate pane
3774 if (!pane || !$Ps[pane] || !options[pane].closable || state[pane].isHidden)
3775 return true;
3776
3777 toggle(pane);
3778
3779 evt.stopPropagation();
3780 evt.returnValue = false; // CANCEL key
3781 return false;
3782 };
3783
3784
3785/*
3786 * ######################################
3787 * UTILITY METHODS
3788 * called externally or by initButtons
3789 * ######################################
3790 */
3791
3792 /**
3793 * Change/reset a pane overflow setting & zIndex to allow popups/drop-downs to work
3794 *
3795 * @param {Object=} el (optional) Can also be 'bound' to a click, mouseOver, or other event
3796 */
3797 function allowOverflow (el) {
3798 if (!isInitialized()) return;
3799 if (this && this.tagName) el = this; // BOUND to element
3800 var $P;
3801 if (isStr(el))
3802 $P = $Ps[el];
3803 else if ($(el).data("layoutRole"))
3804 $P = $(el);
3805 else
3806 $(el).parents().each(function(){
3807 if ($(this).data("layoutRole")) {
3808 $P = $(this);
3809 return false; // BREAK
3810 }
3811 });
3812 if (!$P || !$P.length) return; // INVALID
3813
3814 var
3815 pane = $P.data("layoutEdge")
3816 , s = state[pane]
3817 ;
3818
3819 // if pane is already raised, then reset it before doing it again!
3820 // this would happen if allowOverflow is attached to BOTH the pane and an element
3821 if (s.cssSaved)
3822 resetOverflow(pane); // reset previous CSS before continuing
3823
3824 // if pane is raised by sliding or resizing, or its closed, then abort
3825 if (s.isSliding || s.isResizing || s.isClosed) {
3826 s.cssSaved = false;
3827 return;
3828 }
3829
3830 var
3831 newCSS = { zIndex: (_c.zIndex.pane_normal + 2) }
3832 , curCSS = {}
3833 , of = $P.css("overflow")
3834 , ofX = $P.css("overflowX")
3835 , ofY = $P.css("overflowY")
3836 ;
3837 // determine which, if any, overflow settings need to be changed
3838 if (of != "visible") {
3839 curCSS.overflow = of;
3840 newCSS.overflow = "visible";
3841 }
3842 if (ofX && !ofX.match(/visible|auto/)) {
3843 curCSS.overflowX = ofX;
3844 newCSS.overflowX = "visible";
3845 }
3846 if (ofY && !ofY.match(/visible|auto/)) {
3847 curCSS.overflowY = ofX;
3848 newCSS.overflowY = "visible";
3849 }
3850
3851 // save the current overflow settings - even if blank!
3852 s.cssSaved = curCSS;
3853
3854 // apply new CSS to raise zIndex and, if necessary, make overflow 'visible'
3855 $P.css( newCSS );
3856
3857 // make sure the zIndex of all other panes is normal
3858 $.each(_c.allPanes.split(","), function(i, p) {
3859 if (p != pane) resetOverflow(p);
3860 });
3861
3862 };
3863
3864 function resetOverflow (el) {
3865 if (!isInitialized()) return;
3866 if (this && this.tagName) el = this; // BOUND to element
3867 var $P;
3868 if (isStr(el))
3869 $P = $Ps[el];
3870 else if ($(el).data("layoutRole"))
3871 $P = $(el);
3872 else
3873 $(el).parents().each(function(){
3874 if ($(this).data("layoutRole")) {
3875 $P = $(this);
3876 return false; // BREAK
3877 }
3878 });
3879 if (!$P || !$P.length) return; // INVALID
3880
3881 var
3882 pane = $P.data("layoutEdge")
3883 , s = state[pane]
3884 , CSS = s.cssSaved || {}
3885 ;
3886 // reset the zIndex
3887 if (!s.isSliding && !s.isResizing)
3888 $P.css("zIndex", _c.zIndex.pane_normal);
3889
3890 // reset Overflow - if necessary
3891 $P.css( CSS );
3892
3893 // clear var
3894 s.cssSaved = false;
3895 };
3896
3897
3898 /**
3899 * Helper function to validate params received by addButton utilities
3900 *
3901 * Two classes are added to the element, based on the buttonClass...
3902 * The type of button is appended to create the 2nd className:
3903 * - ui-layout-button-pin
3904 * - ui-layout-pane-button-toggle
3905 * - ui-layout-pane-button-open
3906 * - ui-layout-pane-button-close
3907 *
3908 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
3909 * @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
3910 * @return {Array.<Object>} If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null
3911 */
3912 function getBtn (selector, pane, action) {
3913 var $E = $(selector)
3914 , err = options.showErrorMessages;
3915 if (!$E.length) { // element not found
3916 if (err) alert(lang.errButton + lang.selector +": "+ selector);
3917 }
3918 else if (_c.borderPanes.indexOf(pane) == -1) // invalid 'pane' sepecified
3919 if (err) alert(lang.errButton + lang.pane +": "+ pane);
3920 else { // VALID
3921 var btn = options[pane].buttonClass +"-"+ action;
3922 $E
3923 .addClass( btn +" "+ btn +"-"+ pane )
3924 .data("layoutName", options.name) // add layout identifier - even if blank!
3925 ;
3926 return $E;
3927 }
3928 return null; // INVALID
3929 };
3930
3931
3932 /**
3933 * NEW syntax for binding layout-buttons - will eventually replace addToggleBtn, addOpenBtn, etc.
3934 *
3935 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
3936 * @param {string} action
3937 * @param {string} pane
3938 */
3939 function bindButton (selector, action, pane) {
3940 switch (action.toLowerCase()) {
3941 case "toggle": addToggleBtn(selector, pane); break;
3942 case "open": addOpenBtn(selector, pane); break;
3943 case "close": addCloseBtn(selector, pane); break;
3944 case "pin": addPinBtn(selector, pane); break;
3945 case "toggle-slide": addToggleBtn(selector, pane, true); break;
3946 case "open-slide": addOpenBtn(selector, pane, true); break;
3947 }
3948 };
3949
3950 /**
3951 * Add a custom Toggler button for a pane
3952 *
3953 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
3954 * @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
3955 * @param {boolean=} slide true = slide-open, false = pin-open
3956 */
3957 function addToggleBtn (selector, pane, slide) {
3958 var $E = getBtn(selector, pane, "toggle");
3959 if ($E)
3960 $E.click(function (evt) {
3961 toggle(pane, !!slide);
3962 evt.stopPropagation();
3963 });
3964 };
3965
3966 /**
3967 * Add a custom Open button for a pane
3968 *
3969 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
3970 * @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
3971 * @param {boolean=} slide true = slide-open, false = pin-open
3972 */
3973 function addOpenBtn (selector, pane, slide) {
3974 var $E = getBtn(selector, pane, "open");
3975 if ($E)
3976 $E
3977 .attr("title", lang.Open)
3978 .click(function (evt) {
3979 open(pane, !!slide);
3980 evt.stopPropagation();
3981 })
3982 ;
3983 };
3984
3985 /**
3986 * Add a custom Close button for a pane
3987 *
3988 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
3989 * @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
3990 */
3991 function addCloseBtn (selector, pane) {
3992 var $E = getBtn(selector, pane, "close");
3993 if ($E)
3994 $E
3995 .attr("title", lang.Close)
3996 .click(function (evt) {
3997 close(pane);
3998 evt.stopPropagation();
3999 })
4000 ;
4001 };
4002
4003 /**
4004 * addPinBtn
4005 *
4006 * Add a custom Pin button for a pane
4007 *
4008 * Four classes are added to the element, based on the paneClass for the associated pane...
4009 * Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin:
4010 * - ui-layout-pane-pin
4011 * - ui-layout-pane-west-pin
4012 * - ui-layout-pane-pin-up
4013 * - ui-layout-pane-west-pin-up
4014 *
4015 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
4016 * @param {string} pane Name of the pane the pin is for: 'north', 'south', etc.
4017 */
4018 function addPinBtn (selector, pane) {
4019 var $E = getBtn(selector, pane, "pin");
4020 if ($E) {
4021 var s = state[pane];
4022 $E.click(function (evt) {
4023 setPinState($(this), pane, (s.isSliding || s.isClosed));
4024 if (s.isSliding || s.isClosed) open( pane ); // change from sliding to open
4025 else close( pane ); // slide-closed
4026 evt.stopPropagation();
4027 });
4028 // add up/down pin attributes and classes
4029 setPinState($E, pane, (!s.isClosed && !s.isSliding));
4030 // add this pin to the pane data so we can 'sync it' automatically
4031 // PANE.pins key is an array so we can store multiple pins for each pane
4032 _c[pane].pins.push( selector ); // just save the selector string
4033 }
4034 };
4035
4036 /**
4037 * INTERNAL function to sync 'pin buttons' when pane is opened or closed
4038 * Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
4039 *
4040 * @see open(), close()
4041 * @param {string} pane These are the params returned to callbacks by layout()
4042 * @param {boolean} doPin True means set the pin 'down', False means 'up'
4043 */
4044 function syncPinBtns (pane, doPin) {
4045 $.each(_c[pane].pins, function (i, selector) {
4046 setPinState($(selector), pane, doPin);
4047 });
4048 };
4049
4050 /**
4051 * Change the class of the pin button to make it look 'up' or 'down'
4052 *
4053 * @see addPinBtn(), syncPinBtns()
4054 * @param {Array.<Object>} $Pin The pin-span element in a jQuery wrapper
4055 * @param {string} pane These are the params returned to callbacks by layout()
4056 * @param {boolean} doPin true = set the pin 'down', false = set it 'up'
4057 */
4058 function setPinState ($Pin, pane, doPin) {
4059 var updown = $Pin.attr("pin");
4060 if (updown && doPin == (updown=="down")) return; // already in correct state
4061 var
4062 pin = options[pane].buttonClass +"-pin"
4063 , side = pin +"-"+ pane
4064 , UP = pin +"-up "+ side +"-up"
4065 , DN = pin +"-down "+side +"-down"
4066 ;
4067 $Pin
4068 .attr("pin", doPin ? "down" : "up") // logic
4069 .attr("title", doPin ? lang.Unpin : lang.Pin)
4070 .removeClass( doPin ? UP : DN )
4071 .addClass( doPin ? DN : UP )
4072 ;
4073 };
4074
4075
4076 /*
4077 * LAYOUT STATE MANAGEMENT
4078 *
4079 * @example .layout({ cookie: { name: "myLayout", keys: "west.isClosed,east.isClosed" } })
4080 * @example .layout({ cookie__name: "myLayout", cookie__keys: "west.isClosed,east.isClosed" })
4081 * @example myLayout.getState( "west.isClosed,north.size,south.isHidden" );
4082 * @example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} );
4083 * @example myLayout.deleteCookie();
4084 * @example myLayout.loadCookie();
4085 * @example var hSaved = myLayout.state.cookie;
4086 */
4087
4088 function isCookiesEnabled () {
4089 // TODO: is the cookieEnabled property common enough to be useful???
4090 return (navigator.cookieEnabled != 0);
4091 };
4092
4093 /**
4094 * Read & return data from the cookie - as JSON
4095 *
4096 * @param {Object=} opts
4097 */
4098 function getCookie (opts) {
4099 var
4100 o = $.extend( {}, options.cookie, opts || {} )
4101 , name = o.name || options.name || "Layout"
4102 , c = document.cookie
4103 , cs = c ? c.split(';') : []
4104 , pair // loop var
4105 ;
4106 for (var i=0, n=cs.length; i < n; i++) {
4107 pair = $.trim(cs[i]).split('='); // name=value pair
4108 if (pair[0] == name) // found the layout cookie
4109 // convert cookie string back to a hash
4110 return decodeJSON( decodeURIComponent(pair[1]) );
4111 }
4112 return "";
4113 };
4114
4115 /**
4116 * Get the current layout state and save it to a cookie
4117 *
4118 * @param {(string|Array)=} keys
4119 * @param {Object=} opts
4120 */
4121 function saveCookie (keys, opts) {
4122 var
4123 o = $.extend( {}, options.cookie, opts || {} )
4124 , name = o.name || options.name || "Layout"
4125 , params = ''
4126 , date = ''
4127 , clear = false
4128 ;
4129 if (o.expires.toUTCString)
4130 date = o.expires;
4131 else if (typeof o.expires == 'number') {
4132 date = new Date();
4133 if (o.expires > 0)
4134 date.setDate(date.getDate() + o.expires);
4135 else {
4136 date.setYear(1970);
4137 clear = true;
4138 }
4139 }
4140 if (date) params += ';expires='+ date.toUTCString();
4141 if (o.path) params += ';path='+ o.path;
4142 if (o.domain) params += ';domain='+ o.domain;
4143 if (o.secure) params += ';secure';
4144
4145 if (clear) {
4146 state.cookie = {}; // clear data
4147 document.cookie = name +'='+ params; // expire the cookie
4148 }
4149 else {
4150 state.cookie = getState(keys || o.keys); // read current panes-state
4151 document.cookie = name +'='+ encodeURIComponent( encodeJSON(state.cookie) ) + params; // write cookie
4152 }
4153
4154 return $.extend({}, state.cookie); // return COPY of state.cookie
4155 };
4156
4157 /**
4158 * Remove the state cookie
4159 */
4160 function deleteCookie () {
4161 saveCookie('', { expires: -1 });
4162 };
4163
4164 /**
4165 * Get data from the cookie and USE IT to loadState
4166 *
4167 * @param {Object=} opts
4168 */
4169 function loadCookie (opts) {
4170 var o = getCookie(opts); // READ the cookie
4171 if (o) {
4172 state.cookie = $.extend({}, o); // SET state.cookie
4173 loadState(o); // LOAD the retrieved state
4174 }
4175 return o;
4176 };
4177
4178 /**
4179 * Update layout options from the cookie, if one exists
4180 *
4181 * @param {Object=} opts
4182 * @param {boolean=} animate
4183 */
4184 function loadState (opts, animate) {
4185 opts = _transformData(opts);
4186 $.extend( true, options, opts ); // update layout options
4187 // if layout has already been initialized, then UPDATE layout state
4188 if (state.initialized) {
4189 var pane, o, s, h, c, a = !animate;
4190 $.each(_c.allPanes.split(","), function (idx, pane) {
4191 o = opts[ pane ];
4192 if (typeof o != 'object') return; // no key, continue
4193 s = o.size;
4194 c = o.initClosed;
4195 h = o.initHidden;
4196 if (s > 0 || s=="auto") sizePane(pane, s);
4197 if (h === true) hide(pane, a);
4198 else if (c === false) open(pane, false, a );
4199 else if (c === true) close(pane, false, a);
4200 else if (h === false) show(pane, false, a);
4201 });
4202 }
4203 };
4204
4205 /**
4206 * Get the *current layout state* and return it as a hash
4207 *
4208 * @param {(string|Array)=} keys
4209 */
4210 function getState (keys) {
4211 var
4212 data = {}
4213 , alt = { isClosed: 'initClosed', isHidden: 'initHidden' }
4214 , pair, pane, key, val
4215 ;
4216 if (!keys) keys = options.cookie.keys; // if called by user
4217 if ($.isArray(keys)) keys = keys.join(",");
4218 // convert keys to an array and change delimiters from '__' to '.'
4219 keys = keys.replace(/__/g, ".").split(',');
4220 // loop keys and create a data hash
4221 for (var i=0,n=keys.length; i < n; i++) {
4222 pair = keys[i].split(".");
4223 pane = pair[0];
4224 key = pair[1];
4225 if (_c.allPanes.indexOf(pane) < 0) continue; // bad pane!
4226 val = state[ pane ][ key ];
4227 if (val == undefined) continue;
4228 if (key=="isClosed" && state[pane]["isSliding"])
4229 val = true; // if sliding, then *really* isClosed
4230 ( data[pane] || (data[pane]={}) )[ alt[key] ? alt[key] : key ] = val;
4231 }
4232 return data;
4233 };
4234
4235 /**
4236 * Stringify a JSON hash so can save in a cookie or db-field
4237 */
4238 function encodeJSON (JSON) {
4239 return parse( JSON );
4240 function parse (h) {
4241 var D=[], i=0, k, v, t; // k = key, v = value
4242 for (k in h) {
4243 v = h[k];
4244 t = typeof v;
4245 if (t == 'string') // STRING - add quotes
4246 v = '"'+ v +'"';
4247 else if (t == 'object') // SUB-KEY - recurse into it
4248 v = parse(v);
4249 D[i++] = '"'+ k +'":'+ v;
4250 }
4251 return "{"+ D.join(",") +"}";
4252 };
4253 };
4254
4255 /**
4256 * Convert stringified JSON back to a hash object
4257 */
4258 function decodeJSON (str) {
4259 try { return window["eval"]("("+ str +")") || {}; }
4260 catch (e) { return {}; }
4261 };
4262
4263
4264/*
4265 * #####################
4266 * CREATE/RETURN LAYOUT
4267 * #####################
4268 */
4269
4270 // validate that container exists
4271 var $N = $(this).eq(0); // FIRST matching Container element
4272 if (!$N.length) {
4273 if (options.showErrorMessages)
4274 alert( lang.errContainerMissing );
4275 return null;
4276 };
4277
4278 // Users retreive Instance of a layout with: $N.layout() OR $N.data("layout")
4279 // return the Instance-pointer if layout has already been initialized
4280 if ($N.data("layoutContainer") && $N.data("layout"))
4281 return $N.data("layout"); // cached pointer
4282
4283 // init global vars
4284 var
4285 $Ps = {} // Panes x5 - set in initPanes()
4286 , $Cs = {} // Content x5 - set in initPanes()
4287 , $Rs = {} // Resizers x4 - set in initHandles()
4288 , $Ts = {} // Togglers x4 - set in initHandles()
4289 // aliases for code brevity
4290 , sC = state.container // alias for easy access to 'container dimensions'
4291 , sID = state.id // alias for unique layout ID/namespace - eg: "layout435"
4292 ;
4293
4294 // create Instance object to expose data & option Properties, and primary action Methods
4295 var Instance = {
4296 options: options // property - options hash
4297 , state: state // property - dimensions hash
4298 , container: $N // property - object pointers for layout container
4299 , panes: $Ps // property - object pointers for ALL Panes: panes.north, panes.center
4300 , contents: $Cs // property - object pointers for ALL Content: content.north, content.center
4301 , resizers: $Rs // property - object pointers for ALL Resizers, eg: resizers.north
4302 , togglers: $Ts // property - object pointers for ALL Togglers, eg: togglers.north
4303 , toggle: toggle // method - pass a 'pane' ("north", "west", etc)
4304 , hide: hide // method - ditto
4305 , show: show // method - ditto
4306 , open: open // method - ditto
4307 , close: close // method - ditto
4308 , slideOpen: slideOpen // method - ditto
4309 , slideClose: slideClose // method - ditto
4310 , slideToggle: slideToggle // method - ditto
4311 , initContent: initContent // method - ditto
4312 , sizeContent: sizeContent // method - ditto
4313 , sizePane: manualSizePane // method - pass a 'pane' AND an 'outer-size' in pixels or percent, or 'auto'
4314 , swapPanes: swapPanes // method - pass TWO 'panes' - will swap them
4315 , resizeAll: resizeAll // method - no parameters
4316 , initPanes: isInitialized // method - no parameters
4317 , destroy: destroy // method - no parameters
4318 , addPane: addPane // method - pass a 'pane'
4319 , removePane: removePane // method - pass a 'pane' to remove from layout, add 'true' to delete the pane-elem
4320 , setSizeLimits: setSizeLimits // method - pass a 'pane' - update state min/max data
4321 , bindButton: bindButton // utility - pass element selector, 'action' and 'pane' (E, "toggle", "west")
4322 , addToggleBtn: addToggleBtn // utility - pass element selector and 'pane' (E, "west")
4323 , addOpenBtn: addOpenBtn // utility - ditto
4324 , addCloseBtn: addCloseBtn // utility - ditto
4325 , addPinBtn: addPinBtn // utility - ditto
4326 , allowOverflow: allowOverflow // utility - pass calling element (this)
4327 , resetOverflow: resetOverflow // utility - ditto
4328 , encodeJSON: encodeJSON // method - pass a JSON object
4329 , decodeJSON: decodeJSON // method - pass a string of encoded JSON
4330 , getState: getState // method - returns hash of current layout-state
4331 , getCookie: getCookie // method - update options from cookie - returns hash of cookie data
4332 , saveCookie: saveCookie // method - optionally pass keys-list and cookie-options (hash)
4333 , deleteCookie: deleteCookie // method
4334 , loadCookie: loadCookie // method - update options from cookie - returns hash of cookie data
4335 , loadState: loadState // method - pass a hash of state to use to update options
4336 , cssWidth: cssW // utility - pass element and target outerWidth
4337 , cssHeight: cssH // utility - ditto
4338 , enableClosable: enableClosable
4339 , disableClosable: disableClosable
4340 , enableSlidable: enableSlidable
4341 , disableSlidable: disableSlidable
4342 , enableResizable: enableResizable
4343 , disableResizable: disableResizable
4344 };
4345
4346 // create the border layout NOW
4347 if (_create() === 'cancel') // onload_start callback returned false to CANCEL layout creation
4348 return null;
4349 else // true OR false -- if layout-elements did NOT init (hidden or do not exist), can auto-init later
4350 return Instance; // return the Instance object
4351
4352}
4353})( jQuery );
Note: See TracBrowser for help on using the repository browser.