source: SMC/trunk/SMC/src/web/scripts/js/jquery/jquery.layout-1.3.0.rc29.12.js @ 6846

Last change on this file since 6846 was 6846, checked in by mateusz.zoltak@oeaw.ac.at, 9 years ago

Bunch of fixes making it easy to deploy a working instance:

  • JS libraries names in the repo now agree with URLs in src/web/index.html
  • sample data added - now data list and locations agree with the list set up in src/web/scripts/config.js
  • src/web/get.php generates allow-origin headers so you can query it with AJAX
  • static pages (examples, userdoc) added (copied from the clarin instance)
  • few fixes in smc.graph.js (most notably it can again read old graph jsons in which nodes in links are denoted by index and not by id)

(Probably) the last SVN commit, we are switching to git

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