source: sandbox/newExpressoMail/prototype/plugins/layout/jquery.layout.js @ 7209

Revision 7209, 81.0 KB checked in by gustavo, 12 years ago (diff)

Ticket #0000 - Criado novo modulo para o desenvolvimento do novo ExpressoMail?

  • Property svn:executable set to *
Line 
1/*
2 * jquery.layout 1.2.0
3 *
4 * Copyright (c) 2008
5 *   Fabrizio Balliano (http://www.fabrizioballiano.net)
6 *   Kevin Dalman (http://allpro.net)
7 *
8 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
9 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
10 *
11 * $Date: 2008-12-27 02:17:22 +0100 (sab, 27 dic 2008) $
12 * $Rev: 203 $
13 *
14 * NOTE: For best code readability, view this with a fixed-space font and tabs equal to 4-chars
15 */
16(function($) {
17
18$.fn.layout = function (opts) {
19
20/*
21 * ###########################
22 *   WIDGET CONFIG & OPTIONS
23 * ###########################
24 */
25
26        // DEFAULTS for options
27        var
28                prefix = "ui-layout-" // prefix for ALL selectors and classNames
29        ,       defaults = { // misc default values
30                        paneClass:                              prefix+"pane"           // ui-layout-pane
31                ,       resizerClass:                   prefix+"resizer"        // ui-layout-resizer
32                ,       togglerClass:                   prefix+"toggler"        // ui-layout-toggler
33                ,       togglerInnerClass:              prefix+""                       // ui-layout-open / ui-layout-closed
34                ,       buttonClass:                    prefix+"button"         // ui-layout-button
35                ,       contentSelector:                "."+prefix+"content"// ui-layout-content
36                ,       contentIgnoreSelector:  "."+prefix+"ignore"     // ui-layout-mask
37                }
38        ;
39
40        // DEFAULT PANEL OPTIONS - CHANGE IF DESIRED
41        var options = {
42                name:                                           ""                      // FUTURE REFERENCE - not used right now
43        ,       scrollToBookmarkOnLoad:         true            // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark)
44        ,       defaults: { // default options for 'all panes' - will be overridden by 'per-pane settings'
45                        applyDefaultStyles:     false           // apply basic styles directly to resizers & buttons? If not, then stylesheet must handle it
46                ,       closable:                               true            // pane can open & close
47                ,       resizable:                              true            // when open, pane can be resized
48                ,       slidable:                               true            // when closed, pane can 'slide' open over other panes - closes on mouse-out
49                //,     paneSelector:                   [ ]                     // MUST be pane-specific!
50                ,       contentSelector:                defaults.contentSelector        // INNER div/element to auto-size so only it scrolls, not the entire pane!
51                ,       contentIgnoreSelector:  defaults.contentIgnoreSelector  // elem(s) to 'ignore' when measuring 'content'
52                ,       paneClass:                              defaults.paneClass              // border-Pane - default: 'ui-layout-pane'
53                ,       resizerClass:                   defaults.resizerClass   // Resizer Bar          - default: 'ui-layout-resizer'
54                ,       togglerClass:                   defaults.togglerClass   // Toggler Button       - default: 'ui-layout-toggler'
55                ,       buttonClass:                    defaults.buttonClass    // CUSTOM Buttons       - default: 'ui-layout-button-toggle/-open/-close/-pin'
56                ,       resizerDragOpacity:             1                       // option for ui.draggable
57                //,     resizerCursor:                  ""                      // MUST be pane-specific - cursor when over resizer-bar
58                ,       maskIframesOnResize:    true            // true = all iframes OR = iframe-selector(s) - adds masking-div during resizing/dragging
59                //,     size:                                   100                     // inital size of pane - defaults are set 'per pane'
60                ,       minSize:                                0                       // when manually resizing a pane
61                ,       maxSize:                                0                       // ditto, 0 = no limit
62                ,       spacing_open:                   6                       // space between pane and adjacent panes - when pane is 'open'
63                ,       spacing_closed:                 6                       // ditto - when pane is 'closed'
64                ,       togglerLength_open:             50                      // Length = WIDTH of toggler button on north/south edges - HEIGHT on east/west edges
65                ,       togglerLength_closed:   50                      // 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden'
66                ,       togglerAlign_open:              "center"        // top/left, bottom/right, center, OR...
67                ,       togglerAlign_closed:    "center"        // 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right
68                ,       togglerTip_open:                "Close"         // Toggler tool-tip (title)
69                ,       togglerTip_closed:              "Open"          // ditto
70                ,       resizerTip:                             "Resize"        // Resizer tool-tip (title)
71                ,       sliderTip:                              "Slide Open" // resizer-bar triggers 'sliding' when pane is closed
72                ,       sliderCursor:                   "pointer"       // cursor when resizer-bar will trigger 'sliding'
73                ,       slideTrigger_open:              "click"         // click, dblclick, mouseover
74                ,       slideTrigger_close:             "mouseout"      // click, mouseout
75                ,       hideTogglerOnSlide:             false           // when pane is slid-open, should the toggler show?
76                ,       togglerContent_open:    ""                      // text or HTML to put INSIDE the toggler
77                ,       togglerContent_closed:  ""                      // ditto
78                ,       showOverflowOnHover:    false           // will bind allowOverflow() utility to pane.onMouseOver
79                ,       enableCursorHotkey:             true            // enabled 'cursor' hotkeys
80                //,     customHotkey:                   ""                      // MUST be pane-specific - EITHER a charCode OR a character
81                ,       customHotkeyModifier:   "SHIFT"         // either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT'
82                //      NOTE: fxSss_open & fxSss_close options (eg: fxName_open) are auto-generated if not passed
83                ,       fxName:                                 "slide"         // ('none' or blank), slide, drop, scale
84                ,       fxSpeed:                                null            // slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration
85                ,       fxSettings:                             {}                      // can be passed, eg: { easing: "easeOutBounce", duration: 1500 }
86                ,       initClosed:                             false           // true = init pane as 'closed'
87                ,       initHidden:                     false           // true = init pane as 'hidden' - no resizer or spacing
88               
89                /*      callback options do not have to be set - listed here for reference only
90                ,       onshow_start:                   ""                      // CALLBACK when pane STARTS to Show    - BEFORE onopen/onhide_start
91                ,       onshow_end:                             ""                      // CALLBACK when pane ENDS being Shown  - AFTER  onopen/onhide_end
92                ,       onhide_start:                   ""                      // CALLBACK when pane STARTS to Close   - BEFORE onclose_start
93                ,       onhide_end:                             ""                      // CALLBACK when pane ENDS being Closed - AFTER  onclose_end
94                ,       onopen_start:                   ""                      // CALLBACK when pane STARTS to Open
95                ,       onopen_end:                             ""                      // CALLBACK when pane ENDS being Opened
96                ,       onclose_start:                  ""                      // CALLBACK when pane STARTS to Close
97                ,       onclose_end:                    ""                      // CALLBACK when pane ENDS being Closed
98                ,       onresize_start:                 ""                      // CALLBACK when pane STARTS to be ***MANUALLY*** Resized
99                ,       onresize_end:                   ""                      // CALLBACK when pane ENDS being Resized ***FOR ANY REASON***
100                */
101                }
102        ,       north: {
103                        paneSelector:                   "."+prefix+"north" // default = .ui-layout-north
104                ,       size:                                   "auto"
105                ,       resizerCursor:                  "n-resize"
106                }
107        ,       south: {
108                        paneSelector:                   "."+prefix+"south" // default = .ui-layout-south
109                ,       size:                                   "auto"
110                ,       resizerCursor:                  "s-resize"
111                }
112        ,       east: {
113                        paneSelector:                   "."+prefix+"east" // default = .ui-layout-east
114                ,       size:                                   200
115                ,       resizerCursor:                  "e-resize"
116                }
117        ,       west: {
118                        paneSelector:                   "."+prefix+"west" // default = .ui-layout-west
119                ,       size:                                   200
120                ,       resizerCursor:                  "w-resize"
121                }
122        ,       center: {
123                        paneSelector:                   "."+prefix+"center" // default = .ui-layout-center
124                }
125
126        };
127
128
129        var effects = { // LIST *PREDEFINED EFFECTS* HERE, even if effect has no settings
130                slide:  {
131                        all:    { duration:  "fast"     } // eg: duration: 1000, easing: "easeOutBounce"
132                ,       north:  { direction: "up"       }
133                ,       south:  { direction: "down"     }
134                ,       east:   { direction: "right"}
135                ,       west:   { direction: "left"     }
136                }
137        ,       drop:   {
138                        all:    { duration:  "slow"     } // eg: duration: 1000, easing: "easeOutQuint"
139                ,       north:  { direction: "up"       }
140                ,       south:  { direction: "down"     }
141                ,       east:   { direction: "right"}
142                ,       west:   { direction: "left"     }
143                }
144        ,       scale:  {
145                        all:    { duration:  "fast"     }
146                }
147        };
148
149
150        // STATIC, INTERNAL CONFIG - DO NOT CHANGE THIS!
151        var config = {
152                allPanes:               "north,south,east,west,center"
153        ,       borderPanes:    "north,south,east,west"
154        ,       zIndex: { // set z-index values here
155                        resizer_normal: 1               // normal z-index for resizer-bars
156                ,       pane_normal:    2               // normal z-index for panes
157                ,       mask:                   4               // overlay div used to mask pane(s) during resizing
158                ,       sliding:                100             // applied to both the pane and its resizer when a pane is 'slid open'
159                ,       resizing:               10000   // applied to the CLONED resizer-bar when being 'dragged'
160                ,       animation:              10000   // applied to the pane when being animated - not applied to the resizer
161                }
162        ,       resizers: {
163                        cssReq: {
164                                position:       "absolute"
165                        ,       padding:        0
166                        ,       margin:         0
167                        ,       fontSize:       "1px"
168                        ,       textAlign:      "left" // to counter-act "center" alignment!
169                        ,       overflow:       "hidden" // keep toggler button from overflowing
170                        ,       zIndex:         1
171                        }
172                ,       cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true
173                                background: "#DDD"
174                        ,       border:         "none"
175                        }
176                }
177        ,       togglers: {
178                        cssReq: {
179                                position:       "absolute"
180                        ,       display:        "block"
181                        ,       padding:        0
182                        ,       margin:         0
183                        ,       overflow:       "hidden"
184                        ,       textAlign:      "center"
185                        ,       fontSize:       "1px"
186                        ,       cursor:         "pointer"
187                        ,       zIndex:         1
188                        }
189                ,       cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true
190                                background: "#AAA"
191                        }
192                }
193        ,       content: {
194                        cssReq: {
195                                overflow:       "auto"
196                        }
197                ,       cssDef: {}
198                }
199        ,       defaults: { // defaults for ALL panes - overridden by 'per-pane settings' below
200                        cssReq: {
201                                position:       "absolute"
202                        ,       margin:         0
203                        ,       zIndex:         2
204                        }
205                ,       cssDef: {
206                                padding:        "10px"
207                        ,       background:     "#FFF"
208                        ,       border:         "1px solid #BBB"
209                        ,       overflow:       "auto"
210                        }
211                }
212        ,       north: {
213                        edge:                   "top"
214                ,       sizeType:               "height"
215                ,       dir:                    "horz"
216                ,       cssReq: {
217                                top:            0
218                        ,       bottom:         "auto"
219                        ,       left:           0
220                        ,       right:          0
221                        ,       width:          "auto"
222                        //      height:         DYNAMIC
223                        }
224                }
225        ,       south: {
226                        edge:                   "bottom"
227                ,       sizeType:               "height"
228                ,       dir:                    "horz"
229                ,       cssReq: {
230                                top:            "auto"
231                        ,       bottom:         0
232                        ,       left:           0
233                        ,       right:          0
234                        ,       width:          "auto"
235                        //      height:         DYNAMIC
236                        }
237                }
238        ,       east: {
239                        edge:                   "right"
240                ,       sizeType:               "width"
241                ,       dir:                    "vert"
242                ,       cssReq: {
243                                left:           "auto"
244                        ,       right:          0
245                        ,       top:            "auto" // DYNAMIC
246                        ,       bottom:         "auto" // DYNAMIC
247                        ,       height:         "auto"
248                        //      width:          DYNAMIC
249                        }
250                }
251        ,       west: {
252                        edge:                   "left"
253                ,       sizeType:               "width"
254                ,       dir:                    "vert"
255                ,       cssReq: {
256                                left:           0
257                        ,       right:          "auto"
258                        ,       top:            "auto" // DYNAMIC
259                        ,       bottom:         "auto" // DYNAMIC
260                        ,       height:         "auto"
261                        //      width:          DYNAMIC
262                        }
263                }
264        ,       center: {
265                        dir:                    "center"
266                ,       cssReq: {
267                                left:           "auto" // DYNAMIC
268                        ,       right:          "auto" // DYNAMIC
269                        ,       top:            "auto" // DYNAMIC
270                        ,       bottom:         "auto" // DYNAMIC
271                        ,       height:         "auto"
272                        ,       width:          "auto"
273                        }
274                }
275        };
276
277
278        // DYNAMIC DATA
279        var state = {
280                // generate random 'ID#' to identify layout - used to create global namespace for timers
281                id:                     Math.floor(Math.random() * 10000)
282        ,       container:      {}
283        ,       north:          {}
284        ,       south:          {}
285        ,       east:           {}
286        ,       west:           {}
287        ,       center:         {}
288        };
289
290
291        var
292                altEdge = {
293                        top:    "bottom"
294                ,       bottom: "top"
295                ,       left:   "right"
296                ,       right:  "left"
297                }
298        ,       altSide = {
299                        north:  "south"
300                ,       south:  "north"
301                ,       east:   "west"
302                ,       west:   "east"
303                }
304        ;
305
306
307/*
308 * ###########################
309 *  INTERNAL HELPER FUNCTIONS
310 * ###########################
311 */
312
313        /**
314         * isStr
315         *
316         * Returns true if passed param is EITHER a simple string OR a 'string object' - otherwise returns false
317         */
318        var isStr = function (o) {
319                if (typeof o == "string")
320                        return true;
321                else if (typeof o == "object") {
322                        try {
323                                var match = o.constructor.toString().match(/string/i);
324                                return (match !== null);
325                        } catch (e) {}
326                }
327                return false;
328        };
329
330        /**
331         * str
332         *
333         * Returns a simple string if the passed param is EITHER a simple string OR a 'string object',
334         *  else returns the original object
335         */
336        var str = function (o) {
337                if (typeof o == "string" || isStr(o)) return $.trim(o); // trim converts 'String object' to a simple string
338                else return o;
339        };
340
341        /**
342         * min / max
343         *
344         * Alias for Math.min/.max to simplify coding
345         */
346        var min = function (x,y) { return Math.min(x,y); };
347        var max = function (x,y) { return Math.max(x,y); };
348
349        /**
350         * transformData
351         *
352         * Processes the options passed in and transforms them into the format used by layout()
353         * Missing keys are added, and converts the data if passed in 'flat-format' (no sub-keys)
354         * In flat-format, pane-specific-settings are prefixed like: north__optName  (2-underscores)
355         * To update effects, options MUST use nested-keys format, with an effects key
356         *
357         * @callers  initOptions()
358         * @params  JSON  d  Data/options passed by user - may be a single level or nested levels
359         * @returns JSON  Creates a data struture that perfectly matches 'options', ready to be imported
360         */
361        var transformData = function (d) {
362                var json = { defaults:{fxSettings:{}}, north:{fxSettings:{}}, south:{fxSettings:{}}, east:{fxSettings:{}}, west:{fxSettings:{}}, center:{fxSettings:{}} };
363                d = d || {};
364                if (d.effects || d.defaults || d.north || d.south || d.west || d.east || d.center)
365                        json = $.extend( json, d ); // already in json format - add to base keys
366                else
367                        // convert 'flat' to 'nest-keys' format - also handles 'empty' user-options
368                        $.each( d, function (key,val) {
369                                a = key.split("__");
370                                json[ a[1] ? a[0] : "defaults" ][ a[1] ? a[1] : a[0] ] = val;
371                        });
372                return json;
373        };
374
375        /**
376         * setFlowCallback
377         *
378         * Set an INTERNAL callback to avoid simultaneous animation
379         * Runs only if needed and only if all callbacks are not 'already set'!
380         *
381         * @param String   action  Either 'open' or 'close'
382         * @pane  String   pane    A valid border-pane name, eg 'west'
383         * @pane  Boolean  param   Extra param for callback (optional)
384         */
385        var setFlowCallback = function (action, pane, param) {
386                var
387                        cb = action +","+ pane +","+ (param ? 1 : 0)
388                ,       cP, cbPane
389                ;
390                $.each(c.borderPanes.split(","), function (i,p) {
391                        if (c[p].isMoving) {
392                                bindCallback(p); // TRY to bind a callback
393                                return false; // BREAK
394                        }
395                });
396
397                function bindCallback (p, test) {
398                        cP = c[p];
399                        if (!cP.doCallback) {
400                                cP.doCallback = true;
401                                cP.callback = cb;
402                        }
403                        else { // try to 'chain' this callback
404                                cpPane = cP.callback.split(",")[1]; // 2nd param is 'pane'
405                                if (cpPane != p && cpPane != pane) // callback target NOT 'itself' and NOT 'this pane'
406                                        bindCallback (cpPane, true); // RECURSE
407                        }
408                }
409        };
410
411        /**
412         * execFlowCallback
413         *
414         * RUN the INTERNAL callback for this pane - if one exists
415         *
416         * @param String   action  Either 'open' or 'close'
417         * @pane  String   pane    A valid border-pane name, eg 'west'
418         * @pane  Boolean  param   Extra param for callback (optional)
419         */
420        var execFlowCallback = function (pane) {
421                var cP = c[pane];
422
423                // RESET flow-control flaGs
424                c.isLayoutBusy = false;
425                delete cP.isMoving;
426                if (!cP.doCallback || !cP.callback) return;
427
428                cP.doCallback = false; // RESET logic flag
429
430                // EXECUTE the callback
431                var
432                        cb = cP.callback.split(",")
433                ,       param = (cb[2] > 0 ? true : false)
434                ;
435                if (cb[0] == "open")
436                        open( cb[1], param  );
437                else if (cb[0] == "close")
438                        close( cb[1], param );
439
440                if (!cP.doCallback) cP.callback = null; // RESET - unless callback above enabled it again!
441        };
442
443        /**
444         * execUserCallback
445         *
446         * Executes a Callback function after a trigger event, like resize, open or close
447         *
448         * @param String  pane   This is passed only so we can pass the 'pane object' to the callback
449         * @param String  v_fn  Accepts a function name, OR a comma-delimited array: [0]=function name, [1]=argument
450         */
451        var execUserCallback = function (pane, v_fn) {
452                if (!v_fn) return;
453                var fn;
454                try {
455                        if (typeof v_fn == "function")
456                                fn = v_fn;     
457                        else if (typeof v_fn != "string")
458                                return;
459                        else if (v_fn.indexOf(",") > 0) {
460                                // function name cannot contain a comma, so must be a function name AND a 'name' parameter
461                                var
462                                        args = v_fn.split(",")
463                                ,       fn = eval(args[0])
464                                ;
465                                if (typeof fn=="function" && args.length > 1)
466                                        return fn(args[1]); // pass the argument parsed from 'list'
467                        }
468                        else // just the name of an external function?
469                                fn = eval(v_fn);
470
471                        if (typeof fn=="function")
472                                // pass data: pane-name, pane-element, pane-state, pane-options, and layout-name
473                                return fn( pane, $Ps[pane], $.extend({},state[pane]), $.extend({},options[pane]), options.name );
474                }
475                catch (ex) {}
476        };
477
478        /**
479         * cssNum
480         *
481         * Returns the 'current CSS value' for an element - returns 0 if property does not exist
482         *
483         * @callers  Called by many methods
484         * @param jQuery  $Elem  Must pass a jQuery object - first element is processed
485         * @param String  property  The name of the CSS property, eg: top, width, etc.
486         * @returns Variant  Usually is used to get an integer value for position (top, left) or size (height, width)
487         */
488        var cssNum = function ($E, prop) {
489                var
490                        val = 0
491                ,       hidden = false
492                ,       visibility = ""
493                ;
494                if (!$.browser.msie) { // IE CAN read dimensions of 'hidden' elements - FF CANNOT
495                        if ($.curCSS($E[0], "display", true) == "none") {
496                                hidden = true;
497                                visibility = $.curCSS($E[0], "visibility", true); // SAVE current setting
498                                $E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so we can measure it
499                        }
500                }
501
502                val = parseInt($.curCSS($E[0], prop, true), 10) || 0;
503
504                if (hidden) { // WAS hidden, so put back the way it was
505                        $E.css({ display: "none" });
506                        if (visibility && visibility != "hidden")
507                                $E.css({ visibility: visibility }); // reset 'visibility'
508                }
509
510                return val;
511        };
512
513        /**
514         * cssW / cssH / cssSize
515         *
516         * Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype
517         *
518         * @callers  initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
519         * @param Variant  elem  Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object
520         * @param Integer  outerWidth/outerHeight  (optional) Can pass a width, allowing calculations BEFORE element is resized
521         * @returns Integer  Returns the innerHeight of the elem by subtracting padding and borders
522         *
523         * @TODO  May need to add additional logic to handle more browser/doctype variations?
524         */
525        var cssW = function (e, outerWidth) {
526                var $E;
527                if (isStr(e)) {
528                        e = str(e);
529                        $E = $Ps[e];
530                }
531                else
532                        $E = $(e);
533
534                // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
535                if (outerWidth <= 0)
536                        return 0;
537                else if (!(outerWidth>0))
538                        outerWidth = isStr(e) ? getPaneSize(e) : $E.outerWidth();
539
540                if (!$.boxModel)
541                        return outerWidth;
542
543                else // strip border and padding size from outerWidth to get CSS Width
544                        return outerWidth
545                                - cssNum($E, "paddingLeft")             
546                                - cssNum($E, "paddingRight")
547                                - ($.curCSS($E[0], "borderLeftStyle", true) == "none" ? 0 : cssNum($E, "borderLeftWidth"))
548                                - ($.curCSS($E[0], "borderRightStyle", true) == "none" ? 0 : cssNum($E, "borderRightWidth"))
549                        ;
550        };
551        var cssH = function (e, outerHeight) {
552                var $E;
553                if (isStr(e)) {
554                        e = str(e);
555                        $E = $Ps[e];
556                }
557                else
558                        $E = $(e);
559
560                // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
561                if (outerHeight <= 0)
562                        return 0;
563                else if (!(outerHeight>0))
564                        outerHeight = (isStr(e)) ? getPaneSize(e) : $E.outerHeight();
565
566                if (!$.boxModel)
567                        return outerHeight;
568
569                else // strip border and padding size from outerHeight to get CSS Height
570                        return outerHeight
571                                - cssNum($E, "paddingTop")
572                                - cssNum($E, "paddingBottom")
573                                - ($.curCSS($E[0], "borderTopStyle", true) == "none" ? 0 : cssNum($E, "borderTopWidth"))
574                                - ($.curCSS($E[0], "borderBottomStyle", true) == "none" ? 0 : cssNum($E, "borderBottomWidth"))
575                        ;
576        };
577        var cssSize = function (pane, outerSize) {
578                if (c[pane].dir=="horz") // pane = north or south
579                        return cssH(pane, outerSize);
580                else // pane = east or west
581                        return cssW(pane, outerSize);
582        };
583
584        /**
585         * getPaneSize
586         *
587         * Calculates the current 'size' (width or height) of a border-pane - optionally with 'pane spacing' added
588         *
589         * @returns Integer  Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser
590         */
591        var getPaneSize = function (pane, inclSpace) {
592                var
593                        $P      = $Ps[pane]
594                ,       o       = options[pane]
595                ,       s       = state[pane]
596                ,       oSp     = (inclSpace ? o.spacing_open : 0)
597                ,       cSp     = (inclSpace ? o.spacing_closed : 0)
598                ;
599                if (!$P || s.isHidden)
600                        return 0;
601                else if (s.isClosed || (s.isSliding && inclSpace))
602                        return cSp;
603                else if (c[pane].dir == "horz")
604                        return $P.outerHeight() + oSp;
605                else // dir == "vert"
606                        return $P.outerWidth() + oSp;
607        };
608
609        var setPaneMinMaxSizes = function (pane) {
610                var
611                        d                               = cDims
612                ,       edge                    = c[pane].edge
613                ,       dir                             = c[pane].dir
614                ,       o                               = options[pane]
615                ,       s                               = state[pane]
616                ,       $P                              = $Ps[pane]
617                ,       $altPane                = $Ps[ altSide[pane] ]
618                ,       paneSpacing             = o.spacing_open
619                ,       altPaneSpacing  = options[ altSide[pane] ].spacing_open
620                ,       altPaneSize             = (!$altPane ? 0 : (dir=="horz" ? $altPane.outerHeight() : $altPane.outerWidth()))
621                ,       containerSize   = (dir=="horz" ? d.innerHeight : d.innerWidth)
622                //      limitSize prevents this pane from 'overlapping' opposite pane - even if opposite pane is currently closed
623                ,       limitSize               = containerSize - paneSpacing - altPaneSize - altPaneSpacing
624                ,       minSize                 = s.minSize || 0
625                ,       maxSize                 = Math.min(s.maxSize || 9999, limitSize)
626                ,       minPos, maxPos  // used to set resizing limits
627                ;
628                switch (pane) {
629                        case "north":   minPos = d.offsetTop + minSize;
630                                                        maxPos = d.offsetTop + maxSize;
631                                                        break;
632                        case "west":    minPos = d.offsetLeft + minSize;
633                                                        maxPos = d.offsetLeft + maxSize;
634                                                        break;
635                        case "south":   minPos = d.offsetTop + d.innerHeight - maxSize;
636                                                        maxPos = d.offsetTop + d.innerHeight - minSize;
637                                                        break;
638                        case "east":    minPos = d.offsetLeft + d.innerWidth - maxSize;
639                                                        maxPos = d.offsetLeft + d.innerWidth - minSize;
640                                                        break;
641                }
642                // save data to pane-state
643                $.extend(s, { minSize: minSize, maxSize: maxSize, minPosition: minPos, maxPosition: maxPos });
644        };
645
646        /**
647         * getPaneDims
648         *
649         * Returns data for setting the size/position of center pane. Date is also used to set Height for east/west panes
650         *
651         * @returns JSON  Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height
652         */
653        var getPaneDims = function () {
654                var d = {
655                        top:    getPaneSize("north", true) // true = include 'spacing' value for p
656                ,       bottom: getPaneSize("south", true)
657                ,       left:   getPaneSize("west", true)
658                ,       right:  getPaneSize("east", true)
659                ,       width:  0
660                ,       height: 0
661                };
662
663                with (d) {
664                        width   = cDims.innerWidth - left - right;
665                        height  = cDims.innerHeight - bottom - top;
666                        // now add the 'container border/padding' to get final positions - relative to the container
667                        top             += cDims.top;
668                        bottom  += cDims.bottom;
669                        left    += cDims.left;
670                        right   += cDims.right;
671                }
672
673                return d;
674        };
675
676
677        /**
678         * getElemDims
679         *
680         * Returns data for setting size of an element (container or a pane).
681         *
682         * @callers  create(), onWindowResize() for container, plus others for pane
683         * @returns JSON  Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
684         */
685        var getElemDims = function ($E) {
686                var
687                        d = {} // dimensions hash
688                ,       e, b, p // edge, border, padding
689                ;
690
691                $.each("Left,Right,Top,Bottom".split(","), function () {
692                        e = str(this);
693                        b = d["border" +e] = cssNum($E, "border"+e+"Width");
694                        p = d["padding"+e] = cssNum($E, "padding"+e);
695                        d["offset" +e] = b + p; // total offset of content from outer edge
696                        // if BOX MODEL, then 'position' = PADDING (ignore borderWidth)
697                        if ($E == $Container)
698                                d[e.toLowerCase()] = ($.boxModel ? p : 0);
699                });
700
701                d.innerWidth  = d.outerWidth  = $E.outerWidth();
702                d.innerHeight = d.outerHeight = $E.outerHeight();
703                if ($.boxModel) {
704                        d.innerWidth  -= (d.offsetLeft + d.offsetRight);
705                        d.innerHeight -= (d.offsetTop  + d.offsetBottom);
706                }
707
708                return d;
709        };
710
711
712        var setTimer = function (pane, action, fn, ms) {
713                var
714                        Layout = window.layout = window.layout || {}
715                ,       Timers = Layout.timers = Layout.timers || {}
716                ,       name = "layout_"+ state.id +"_"+ pane +"_"+ action // UNIQUE NAME for every layout-pane-action
717                ;
718                if (Timers[name]) return; // timer already set!
719                else Timers[name] = setTimeout(fn, ms);
720        };
721
722        var clearTimer = function (pane, action) {
723                var
724                        Layout = window.layout = window.layout || {}
725                ,       Timers = Layout.timers = Layout.timers || {}
726                ,       name = "layout_"+ state.id +"_"+ pane +"_"+ action // UNIQUE NAME for every layout-pane-action
727                ;
728                if (Timers[name]) {
729                        clearTimeout( Timers[name] );
730                        delete Timers[name];
731                        return true;
732                }
733                else
734                        return false;
735        };
736
737
738/*
739 * ###########################
740 *   INITIALIZATION METHODS
741 * ###########################
742 */
743
744        /**
745         * create
746         *
747         * Initialize the layout - called automatically whenever an instance of layout is created
748         *
749         * @callers  NEVER explicity called
750         * @returns  An object pointer to the instance created
751         */
752        var create = function () {
753                // initialize config/options
754                initOptions();
755
756                // initialize all objects
757                initContainer();        // set CSS as needed and init state.container dimensions
758                initPanes();            // size & position all panes
759                initHandles();          // create and position all resize bars & togglers buttons
760                initResizable();        // activate resizing on all panes where resizable=true
761                sizeContent("all");     // AFTER panes & handles have been initialized, size 'content' divs
762
763                if (options.scrollToBookmarkOnLoad)
764                        with (self.location) if (hash) replace( hash ); // scrollTo Bookmark
765
766                // bind hotkey function - keyDown - if required
767                initHotkeys();
768
769                // bind resizeAll() for 'this layout instance' to window.resize event
770                $(window).resize(function () {
771                        var timerID = "timerLayout_"+state.id;
772                        if (window[timerID]) clearTimeout(window[timerID]);
773                        window[timerID] = null;
774                        if (true || $.browser.msie) // use a delay for IE because the resize event fires repeatly
775                                window[timerID] = setTimeout(resizeAll, 100);
776                        else // most other browsers have a built-in delay before firing the resize event
777                                resizeAll(); // resize all layout elements NOW!
778                });
779        };
780
781        /**
782         * initContainer
783         *
784         * Validate and initialize container CSS and events
785         *
786         * @callers  create()
787         */
788        var initContainer = function () {
789                try { // format html/body if this is a full page layout
790                        if ($Container[0].tagName == "BODY") {
791                                $("html").css({
792                                        height:         "100%"
793                                ,       overflow:       "hidden"
794                                });
795                                $("body").css({
796                                        position:       "relative"
797                                ,       height:         "100%"
798                                ,       overflow:       "hidden"
799                                ,       margin:         0
800                                ,       padding:        0               // TODO: test whether body-padding could be handled?
801                                ,       border:         "none"  // a body-border creates problems because it cannot be measured!
802                                });
803                        }
804                        else { // set required CSS - overflow and position
805                                var
806                                        CSS     = { overflow: "hidden" } // make sure container will not 'scroll'
807                                ,       p       = $Container.css("position")
808                                ,       h       = $Container.css("height")
809                                ;
810                                // if this is a NESTED layout, then outer-pane ALREADY has position and height
811                                if (!$Container.hasClass("ui-layout-pane")) {
812                                        if (!p || "fixed,absolute,relative".indexOf(p) < 0)
813                                                CSS.position = "relative"; // container MUST have a 'position'
814                                        if (!h || h=="auto")
815                                                CSS.height = "100%"; // container MUST have a 'height'
816                                }
817                                $Container.css( CSS );
818                        }
819                } catch (ex) {}
820
821                // get layout-container dimensions (updated when necessary)
822                cDims = state.container = getElemDims( $Container ); // update data-pointer too
823        };
824
825        /**
826         * initHotkeys
827         *
828         * Bind layout hotkeys - if options enabled
829         *
830         * @callers  create()
831         */
832        var initHotkeys = function () {
833                // bind keyDown to capture hotkeys, if option enabled for ANY pane
834                $.each(c.borderPanes.split(","), function (i,pane) {
835                        var o = options[pane];
836                        if (o.enableCursorHotkey || o.customHotkey) {
837                                $(document).keydown( keyDown ); // only need to bind this ONCE
838                                return false; // BREAK - binding was done
839                        }
840                });
841        };
842
843        /**
844         * initOptions
845         *
846         * Build final CONFIG and OPTIONS data
847         *
848         * @callers  create()
849         */
850        var initOptions = function () {
851                // simplify logic by making sure passed 'opts' var has basic keys
852                opts = transformData( opts );
853
854                // update default effects, if case user passed key
855                if (opts.effects) {
856                        $.extend( effects, opts.effects );
857                        delete opts.effects;
858                }
859
860                // see if any 'global options' were specified
861                $.each("name,scrollToBookmarkOnLoad".split(","), function (idx,key) {
862                        if (opts[key] !== undefined)
863                                options[key] = opts[key];
864                        else if (opts.defaults[key] !== undefined) {
865                                options[key] = opts.defaults[key];
866                                delete opts.defaults[key];
867                        }
868                });
869
870                // remove any 'defaults' that MUST be set 'per-pane'
871                $.each("paneSelector,resizerCursor,customHotkey".split(","),
872                        function (idx,key) { delete opts.defaults[key]; } // is OK if key does not exist
873                );
874
875                // now update options.defaults
876                $.extend( options.defaults, opts.defaults );
877                // make sure required sub-keys exist
878                //if (typeof options.defaults.fxSettings != "object") options.defaults.fxSettings = {};
879
880                // merge all config & options for the 'center' pane
881                c.center = $.extend( true, {}, c.defaults, c.center );
882                $.extend( options.center, opts.center );
883                // Most 'default options' do not apply to 'center', so add only those that DO
884                var o_Center = $.extend( true, {}, options.defaults, opts.defaults, options.center ); // TEMP data
885                $.each("paneClass,contentSelector,contentIgnoreSelector,applyDefaultStyles,showOverflowOnHover".split(","),
886                        function (idx,key) { options.center[key] = o_Center[key]; }
887                );
888
889                var defs = options.defaults;
890
891                // create a COMPLETE set of options for EACH border-pane
892                $.each(c.borderPanes.split(","), function(i,pane) {
893                        // apply 'pane-defaults' to CONFIG.PANE
894                        c[pane] = $.extend( true, {}, c.defaults, c[pane] );
895                        // apply 'pane-defaults' +  user-options to OPTIONS.PANE
896                        o = options[pane] = $.extend( true, {}, options.defaults, options[pane], opts.defaults, opts[pane] );
897
898                        // make sure we have base-classes
899                        if (!o.paneClass)               o.paneClass             = defaults.paneClass;
900                        if (!o.resizerClass)    o.resizerClass  = defaults.resizerClass;
901                        if (!o.togglerClass)    o.togglerClass  = defaults.togglerClass;
902
903                        // create FINAL fx options for each pane, ie: options.PANE.fxName/fxSpeed/fxSettings[_open|_close]
904                        $.each(["_open","_close",""], function (i,n) {
905                                var
906                                        sName           = "fxName"+n
907                                ,       sSpeed          = "fxSpeed"+n
908                                ,       sSettings       = "fxSettings"+n
909                                ;
910                                // recalculate fxName according to specificity rules
911                                o[sName] =
912                                        opts[pane][sName]               // opts.west.fxName_open
913                                ||      opts[pane].fxName               // opts.west.fxName
914                                ||      opts.defaults[sName]    // opts.defaults.fxName_open
915                                ||      opts.defaults.fxName    // opts.defaults.fxName
916                                ||      o[sName]                                // options.west.fxName_open
917                                ||      o.fxName                                // options.west.fxName
918                                ||      defs[sName]                             // options.defaults.fxName_open
919                                ||      defs.fxName                             // options.defaults.fxName
920                                ||      "none"
921                                ;
922                                // validate fxName to be sure is a valid effect
923                                var fxName = o[sName];
924                                if (fxName == "none" || !$.effects || !$.effects[fxName] || (!effects[fxName] && !o[sSettings] && !o.fxSettings))
925                                        fxName = o[sName] = "none"; // effect not loaded, OR undefined FX AND fxSettings not passed
926                                // set vars for effects subkeys to simplify logic
927                                var
928                                        fx = effects[fxName]    || {} // effects.slide
929                                ,       fx_all  = fx.all                || {} // effects.slide.all
930                                ,       fx_pane = fx[pane]              || {} // effects.slide.west
931                                ;
932                                // RECREATE the fxSettings[_open|_close] keys using specificity rules
933                                o[sSettings] = $.extend(
934                                        {}
935                                ,       fx_all                                          // effects.slide.all
936                                ,       fx_pane                                         // effects.slide.west
937                                ,       defs.fxSettings || {}           // options.defaults.fxSettings
938                                ,       defs[sSettings] || {}           // options.defaults.fxSettings_open
939                                ,       o.fxSettings                            // options.west.fxSettings
940                                ,       o[sSettings]                            // options.west.fxSettings_open
941                                ,       opts.defaults.fxSettings        // opts.defaults.fxSettings
942                                ,       opts.defaults[sSettings] || {} // opts.defaults.fxSettings_open
943                                ,       opts[pane].fxSettings           // opts.west.fxSettings
944                                ,       opts[pane][sSettings] || {}     // opts.west.fxSettings_open
945                                );
946                                // recalculate fxSpeed according to specificity rules
947                                o[sSpeed] =
948                                        opts[pane][sSpeed]              // opts.west.fxSpeed_open
949                                ||      opts[pane].fxSpeed              // opts.west.fxSpeed (pane-default)
950                                ||      opts.defaults[sSpeed]   // opts.defaults.fxSpeed_open
951                                ||      opts.defaults.fxSpeed   // opts.defaults.fxSpeed
952                                ||      o[sSpeed]                               // options.west.fxSpeed_open
953                                ||      o[sSettings].duration   // options.west.fxSettings_open.duration
954                                ||      o.fxSpeed                               // options.west.fxSpeed
955                                ||      o.fxSettings.duration   // options.west.fxSettings.duration
956                                ||      defs.fxSpeed                    // options.defaults.fxSpeed
957                                ||      defs.fxSettings.duration// options.defaults.fxSettings.duration
958                                ||      fx_pane.duration                // effects.slide.west.duration
959                                ||      fx_all.duration                 // effects.slide.all.duration
960                                ||      "normal"                                // DEFAULT
961                                ;
962                                // DEBUG: if (pane=="east") debugData( $.extend({}, {speed: o[sSpeed], fxSettings_duration: o[sSettings].duration}, o[sSettings]), pane+"."+sName+" = "+fxName );
963                        });
964                });
965        };
966
967        /**
968         * initPanes
969         *
970         * Initialize module objects, styling, size and position for all panes
971         *
972         * @callers  create()
973         */
974        var initPanes = function () {
975                // NOTE: do north & south FIRST so we can measure their height - do center LAST
976                $.each(c.allPanes.split(","), function() {
977                        var
978                                pane    = str(this)
979                        ,       o               = options[pane]
980                        ,       s               = state[pane]
981                        ,       fx              = s.fx
982                        ,       dir             = c[pane].dir
983                        //      if o.size is not > 0, then we will use MEASURE the pane and use that as it's 'size'
984                        ,       size    = o.size=="auto" || isNaN(o.size) ? 0 : o.size
985                        ,       minSize = o.minSize || 1
986                        ,       maxSize = o.maxSize || 9999
987                        ,       spacing = o.spacing_open || 0
988                        ,       sel             = o.paneSelector
989                        ,       isIE6   = ($.browser.msie && $.browser.version < 7)
990                        ,       CSS             = {}
991                        ,       $P, $C
992                        ;
993                        $Cs[pane] = false; // init
994
995                        if (sel.substr(0,1)==="#") // ID selector
996                                // NOTE: elements selected 'by ID' DO NOT have to be 'children'
997                                $P = $Ps[pane] = $Container.find(sel+":first");
998                        else { // class or other selector
999                                $P = $Ps[pane] = $Container.children(sel+":first");
1000                                // look for the pane nested inside a 'form' element
1001                                if (!$P.length) $P = $Ps[pane] = $Container.children("form:first").children(sel+":first");
1002                        }
1003
1004                        if (!$P.length) {
1005                                $Ps[pane] = false; // logic
1006                                return true; // SKIP to next
1007                        }
1008
1009                        // add basic classes & attributes
1010                        $P
1011                                .attr("pane", pane) // add pane-identifier
1012                                .addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector'
1013                        ;
1014
1015                        // init pane-logic vars, etc.
1016                        if (pane != "center") {
1017                                s.isClosed  = false; // true = pane is closed
1018                                s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes
1019                                s.isResizing= false; // true = pane is in process of being resized
1020                                s.isHidden      = false; // true = pane is hidden - no spacing, resizer or toggler is visible!
1021                                s.noRoom        = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically
1022                                // create special keys for internal use
1023                                c[pane].pins = [];   // used to track and sync 'pin-buttons' for border-panes
1024                        }
1025
1026                        CSS = $.extend({ visibility: "visible", display: "block" }, c.defaults.cssReq, c[pane].cssReq );
1027                        if (o.applyDefaultStyles) $.extend( CSS, c.defaults.cssDef, c[pane].cssDef ); // cosmetic defaults
1028                        $P.css(CSS); // add base-css BEFORE 'measuring' to calc size & position
1029                        CSS = {};       // reset var
1030
1031                        // set css-position to account for container borders & padding
1032                        switch (pane) {
1033                                case "north":   CSS.top         = cDims.top;
1034                                                                CSS.left        = cDims.left;
1035                                                                CSS.right       = cDims.right;
1036                                                                break;
1037                                case "south":   CSS.bottom      = cDims.bottom;
1038                                                                CSS.left        = cDims.left;
1039                                                                CSS.right       = cDims.right;
1040                                                                break;
1041                                case "west":    CSS.left        = cDims.left; // top, bottom & height set by sizeMidPanes()
1042                                                                break;
1043                                case "east":    CSS.right       = cDims.right; // ditto
1044                                                                break;
1045                                case "center":  // top, left, width & height set by sizeMidPanes()
1046                        }
1047
1048                        if (dir == "horz") { // north or south pane
1049                                if (size === 0 || size == "auto") {
1050                                        $P.css({ height: "auto" });
1051                                        size = $P.outerHeight();
1052                                }
1053                                size = max(size, minSize);
1054                                size = min(size, maxSize);
1055                                size = min(size, cDims.innerHeight - spacing);
1056                                CSS.height = max(1, cssH(pane, size));
1057                                s.size = size; // update state
1058                                // make sure minSize is sufficient to avoid errors
1059                                s.maxSize = maxSize; // init value
1060                                s.minSize = max(minSize, size - CSS.height + 1); // = pane.outerHeight when css.height = 1px
1061                                // handle IE6
1062                                //if (isIE6) CSS.width = cssW($P, cDims.innerWidth);
1063                                $P.css(CSS); // apply size & position
1064                        }
1065                        else if (dir == "vert") { // east or west pane
1066                                if (size === 0 || size == "auto") {
1067                                        $P.css({ width: "auto", float: "left" }); // float = FORCE pane to auto-size
1068                                        size = $P.outerWidth();
1069                                        $P.css({ float: "none" }); // RESET
1070                                }
1071                                size = max(size, minSize);
1072                                size = min(size, maxSize);
1073                                size = min(size, cDims.innerWidth - spacing);
1074                                CSS.width = max(1, cssW(pane, size));
1075                                s.size = size; // update state
1076                                s.maxSize = maxSize; // init value
1077                                // make sure minSize is sufficient to avoid errors
1078                                s.minSize = max(minSize, size - CSS.width + 1); // = pane.outerWidth when css.width = 1px
1079                                $P.css(CSS); // apply size - top, bottom & height set by sizeMidPanes
1080                                sizeMidPanes(pane, null, true); // true = onInit
1081                        }
1082                        else if (pane == "center") {
1083                                $P.css(CSS); // top, left, width & height set by sizeMidPanes...
1084                                sizeMidPanes("center", null, true); // true = onInit
1085                        }
1086
1087                        // close or hide the pane if specified in settings
1088                        if (o.initClosed && o.closable) {
1089                                $P.hide().addClass("closed");
1090                                s.isClosed = true;
1091                        }
1092                        else if (o.initHidden || o.initClosed) {
1093                                hide(pane, true); // will be completely invisible - no resizer or spacing
1094                                s.isHidden = true;
1095                        }
1096                        else
1097                                $P.addClass("open");
1098
1099                        // check option for auto-handling of pop-ups & drop-downs
1100                        if (o.showOverflowOnHover)
1101                                $P.hover( allowOverflow, resetOverflow );
1102
1103                        /*
1104                         *      see if this pane has a 'content element' that we need to auto-size
1105                         */
1106                        if (o.contentSelector) {
1107                                $C = $Cs[pane] = $P.children(o.contentSelector+":first"); // match 1-element only
1108                                if (!$C.length) {
1109                                        $Cs[pane] = false;
1110                                        return true; // SKIP to next
1111                                }
1112                                $C.css( c.content.cssReq );
1113                                if (o.applyDefaultStyles) $C.css( c.content.cssDef ); // cosmetic defaults
1114                                // NO PANE-SCROLLING when there is a content-div
1115                                $P.css({ overflow: "hidden" });
1116                        }
1117                });
1118        };
1119
1120        /**
1121         * initHandles
1122         *
1123         * Initialize module objects, styling, size and position for all resize bars and toggler buttons
1124         *
1125         * @callers  create()
1126         */
1127        var initHandles = function () {
1128                // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
1129                $.each(c.borderPanes.split(","), function() {
1130                        var
1131                                pane    = str(this)
1132                        ,       o               = options[pane]
1133                        ,       s               = state[pane]
1134                        ,       rClass  = o.resizerClass
1135                        ,       tClass  = o.togglerClass
1136                        ,       $P              = $Ps[pane]
1137                        ;
1138                        $Rs[pane] = false; // INIT
1139                        $Ts[pane] = false;
1140
1141                        if (!$P || (!o.closable && !o.resizable)) return; // pane does not exist - skip
1142
1143                        var
1144                                edge    = c[pane].edge
1145                        ,       isOpen  = $P.is(":visible")
1146                        ,       spacing = (isOpen ? o.spacing_open : o.spacing_closed)
1147                        ,       _pane   = "-"+ pane // used for classNames
1148                        ,       _state  = (isOpen ? "-open" : "-closed") // used for classNames
1149                        ,       $R, $T
1150                        ;
1151                        // INIT RESIZER BAR
1152                        $R = $Rs[pane] = $("<span></span>");
1153       
1154                        if (isOpen && o.resizable)
1155                                ; // this is handled by initResizable
1156                        else if (!isOpen && o.slidable)
1157                                $R.attr("title", o.sliderTip).css("cursor", o.sliderCursor);
1158       
1159                        $R
1160                                // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer"
1161                                .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-resizer" : ""))
1162                                .attr("resizer", pane) // so we can read this from the resizer
1163                                .css(c.resizers.cssReq) // add base/required styles
1164                                // POSITION of resizer bar - allow for container border & padding
1165                                .css(edge, cDims[edge] + getPaneSize(pane))
1166                                // ADD CLASSNAMES - eg: class="resizer resizer-west resizer-open"
1167                                .addClass( rClass +" "+ rClass+_pane +" "+ rClass+_state +" "+ rClass+_pane+_state )
1168                                .appendTo($Container) // append DIV to container
1169                        ;
1170                         // ADD VISUAL STYLES
1171                        if (o.applyDefaultStyles)
1172                                $R.css(c.resizers.cssDef);
1173
1174                        if (o.closable) {
1175                                // INIT COLLAPSER BUTTON
1176                                $T = $Ts[pane] = $("<div></div>");
1177                                $T
1178                                        // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-toggler"
1179                                        .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-toggler" : ""))
1180                                        .css(c.togglers.cssReq) // add base/required styles
1181                                        .attr("title", (isOpen ? o.togglerTip_open : o.togglerTip_closed))
1182                                        .click(function(evt){ toggle(pane); evt.stopPropagation(); })
1183                                        .mouseover(function(evt){ evt.stopPropagation(); }) // prevent resizer event
1184                                        // ADD CLASSNAMES - eg: class="toggler toggler-west toggler-west-open"
1185                                        .addClass( tClass +" "+ tClass+_pane +" "+ tClass+_state +" "+ tClass+_pane+_state )
1186                                        .appendTo($R) // append SPAN to resizer DIV
1187                                ;
1188
1189                                // ADD INNER-SPANS TO TOGGLER
1190                                if (o.togglerContent_open) // ui-layout-open
1191                                        $("<span>"+ o.togglerContent_open +"</span>")
1192                                                .addClass("content content-open")
1193                                                .css("display", s.isClosed ? "none" : "block")
1194                                                .appendTo( $T )
1195                                        ;
1196                                if (o.togglerContent_closed) // ui-layout-closed
1197                                        $("<span>"+ o.togglerContent_closed +"</span>")
1198                                                .addClass("content content-closed")
1199                                                .css("display", s.isClosed ? "block" : "none")
1200                                                .appendTo( $T )
1201                                        ;
1202
1203                                 // ADD BASIC VISUAL STYLES
1204                                if (o.applyDefaultStyles)
1205                                        $T.css(c.togglers.cssDef);
1206
1207                                if (!isOpen) bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true
1208                        }
1209
1210                });
1211
1212                // SET ALL HANDLE SIZES & LENGTHS
1213                sizeHandles("all", true); // true = onInit
1214        };
1215
1216        /**
1217         * initResizable
1218         *
1219         * Add resize-bars to all panes that specify it in options
1220         *
1221         * @dependancies  $.fn.resizable - will abort if not found
1222         * @callers  create()
1223         */
1224        var initResizable = function () {
1225                var
1226                        draggingAvailable = (typeof $.fn.draggable == "function")
1227                ,       minPosition, maxPosition, edge // set in start()
1228                ;
1229
1230                $.each(c.borderPanes.split(","), function() {
1231                        var
1232                                pane    = str(this)
1233                        ,       o               = options[pane]
1234                        ,       s               = state[pane]
1235                        ;
1236                        if (!draggingAvailable || !$Ps[pane] || !o.resizable) {
1237                                o.resizable = false;
1238                                return true; // skip to next
1239                        }
1240
1241                        var
1242                                rClass                          = o.resizerClass
1243                        //      'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process
1244                        ,       dragClass                       = rClass+"-drag"                        // resizer-drag
1245                        ,       dragPaneClass           = rClass+"-"+pane+"-drag"       // resizer-north-drag
1246                        //      'dragging' class is applied to the CLONED resizer-bar while it is being dragged
1247                        ,       draggingClass           = rClass+"-dragging"            // resizer-dragging
1248                        ,       draggingPaneClass       = rClass+"-"+pane+"-dragging" // resizer-north-dragging
1249                        ,       draggingClassSet        = false                                         // logic var
1250                        ,       $P                                      = $Ps[pane]
1251                        ,       $R                                      = $Rs[pane]
1252                        ;
1253
1254                        if (!s.isClosed)
1255                                $R
1256                                        .attr("title", o.resizerTip)
1257                                        .css("cursor", o.resizerCursor) // n-resize, s-resize, etc
1258                                ;
1259
1260                        $R.draggable({
1261                                containment:    $Container[0] // limit resizing to layout container
1262                        ,       axis:                   (c[pane].dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis
1263                        ,       delay:                  200
1264                        ,       distance:               1
1265                        //      basic format for helper - style it using class: .ui-draggable-dragging
1266                        ,       helper:                 "clone"
1267                        ,       opacity:                o.resizerDragOpacity
1268                        //,     iframeFix:              o.draggableIframeFix // TODO: consider using when bug is fixed
1269                        ,       zIndex:                 c.zIndex.resizing
1270
1271                        ,       start: function (e, ui) {
1272                                        // onresize_start callback - will CANCEL hide if returns false
1273                                        // TODO: CONFIRM that dragging can be cancelled like this???
1274                                        if (false === execUserCallback(pane, o.onresize_start)) return false;
1275
1276                                        s.isResizing = true; // prevent pane from closing while resizing
1277                                        clearTimer(pane, "closeSlider"); // just in case already triggered
1278
1279                                        $R.addClass( dragClass +" "+ dragPaneClass ); // add drag classes
1280                                        draggingClassSet = false; // reset logic var - see drag()
1281
1282                                        // SET RESIZING LIMITS - used in drag()
1283                                        var resizerWidth = (pane=="east" || pane=="south" ? o.spacing_open : 0);
1284                                        setPaneMinMaxSizes(pane); // update pane-state
1285                                        s.minPosition -= resizerWidth;
1286                                        s.maxPosition -= resizerWidth;
1287                                        edge = (c[pane].dir=="horz" ? "top" : "left");
1288
1289                                        // MASK PANES WITH IFRAMES OR OTHER TROUBLESOME ELEMENTS
1290                                        $(o.maskIframesOnResize === true ? "iframe" : o.maskIframesOnResize).each(function() {                                 
1291                                                $('<div class="ui-layout-mask"/>')
1292                                                        .css({
1293                                                                background:     "#fff"
1294                                                        ,       opacity:        "0.001"
1295                                                        ,       zIndex:         9
1296                                                        ,       position:       "absolute"
1297                                                        ,       width:          this.offsetWidth+"px"
1298                                                        ,       height:         this.offsetHeight+"px"
1299                                                        })
1300                                                        .css($(this).offset()) // top & left
1301                                                        .appendTo(this.parentNode) // put div INSIDE pane to avoid zIndex issues
1302                                                ;
1303                                        });
1304                                }
1305
1306                        ,       drag: function (e, ui) {
1307                                        if (!draggingClassSet) { // can only add classes after clone has been added to the DOM
1308                                                $(".ui-draggable-dragging")
1309                                                        .addClass( draggingClass +" "+ draggingPaneClass ) // add dragging classes
1310                                                        .children().css("visibility","hidden") // hide toggler inside dragged resizer-bar
1311                                                ;
1312                                                draggingClassSet = true;
1313                                                // draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane!
1314                                                if (s.isSliding) $Ps[pane].css("zIndex", c.zIndex.sliding);
1315                                        }
1316                                        // CONTAIN RESIZER-BAR TO RESIZING LIMITS
1317                                        if              (ui.position[edge] < s.minPosition) ui.position[edge] = s.minPosition;
1318                                        else if (ui.position[edge] > s.maxPosition) ui.position[edge] = s.maxPosition;
1319                                }
1320
1321                        ,       stop: function (e, ui) {
1322                                        var
1323                                                dragPos = ui.position
1324                                        ,       resizerPos
1325                                        ,       newSize
1326                                        ;
1327                                        $R.removeClass( dragClass +" "+ dragPaneClass ); // remove drag classes
1328       
1329                                        switch (pane) {
1330                                                case "north":   resizerPos = dragPos.top; break;
1331                                                case "west":    resizerPos = dragPos.left; break;
1332                                                case "south":   resizerPos = cDims.outerHeight - dragPos.top - $R.outerHeight(); break;
1333                                                case "east":    resizerPos = cDims.outerWidth - dragPos.left - $R.outerWidth(); break;
1334                                        }
1335                                        // remove container margin from resizer position to get the pane size
1336                                        newSize = resizerPos - cDims[ c[pane].edge ];
1337
1338                                        sizePane(pane, newSize);
1339
1340                                        // UN-MASK PANES MASKED IN drag.start
1341                                        $("div.ui-layout-mask").remove(); // Remove iframe masks       
1342
1343                                        s.isResizing = false;
1344                                }
1345
1346                        });
1347                });
1348        };
1349
1350
1351
1352/*
1353 * ###########################
1354 *       ACTION METHODS
1355 * ###########################
1356 */
1357
1358        /**
1359         * hide / show
1360         *
1361         * Completely 'hides' a pane, including its spacing - as if it does not exist
1362         * The pane is not actually 'removed' from the source, so can use 'show' to un-hide it
1363         *
1364         * @param String  pane   The pane being hidden, ie: north, south, east, or west
1365         */
1366        var hide = function (pane, onInit) {
1367                var
1368                        o       = options[pane]
1369                ,       s       = state[pane]
1370                ,       $P      = $Ps[pane]
1371                ,       $R      = $Rs[pane]
1372                ;
1373                if (!$P || s.isHidden) return; // pane does not exist OR is already hidden
1374
1375                // onhide_start callback - will CANCEL hide if returns false
1376                if (false === execUserCallback(pane, o.onhide_start)) return;
1377
1378                s.isSliding = false; // just in case
1379
1380                // now hide the elements
1381                if ($R) $R.hide(); // hide resizer-bar
1382                if (onInit || s.isClosed) {
1383                        s.isClosed = true; // to trigger open-animation on show()
1384                        s.isHidden  = true;
1385                        $P.hide(); // no animation when loading page
1386                        sizeMidPanes(c[pane].dir == "horz" ? "all" : "center");
1387                        execUserCallback(pane, o.onhide_end || o.onhide);
1388                }
1389                else {
1390                        s.isHiding = true; // used by onclose
1391                        close(pane, false); // adjust all panes to fit
1392                        //s.isHidden  = true; - will be set by close - if not cancelled
1393                }
1394        };
1395
1396        var show = function (pane, openPane) {
1397                var
1398                        o       = options[pane]
1399                ,       s       = state[pane]
1400                ,       $P      = $Ps[pane]
1401                ,       $R      = $Rs[pane]
1402                ;
1403                if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden
1404
1405                // onhide_start callback - will CANCEL hide if returns false
1406                if (false === execUserCallback(pane, o.onshow_start)) return;
1407
1408                s.isSliding = false; // just in case
1409                s.isShowing = true; // used by onopen/onclose
1410                //s.isHidden  = false; - will be set by open/close - if not cancelled
1411
1412                // now show the elements
1413                if ($R && o.spacing_open > 0) $R.show();
1414                if (openPane === false)
1415                        close(pane, true); // true = force
1416                else
1417                        open(pane); // adjust all panes to fit
1418        };
1419
1420
1421        /**
1422         * toggle
1423         *
1424         * Toggles a pane open/closed by calling either open or close
1425         *
1426         * @param String  pane   The pane being toggled, ie: north, south, east, or west
1427         */
1428        var toggle = function (pane) {
1429                var s = state[pane];
1430                if (s.isHidden)
1431                        show(pane); // will call 'open' after unhiding it
1432                else if (s.isClosed)
1433                        open(pane);
1434                else
1435                        close(pane);
1436        };
1437
1438        /**
1439         * close
1440         *
1441         * Close the specified pane (animation optional), and resize all other panes as needed
1442         *
1443         * @param String  pane   The pane being closed, ie: north, south, east, or west
1444         */
1445        var close = function (pane, force, noAnimation) {
1446                var
1447                        $P              = $Ps[pane]
1448                ,       $R              = $Rs[pane]
1449                ,       $T              = $Ts[pane]
1450                ,       o               = options[pane]
1451                ,       s               = state[pane]
1452                ,       doFX    = !noAnimation && !s.isClosed && (o.fxName_close != "none")
1453                ,       edge    = c[pane].edge
1454                ,       rClass  = o.resizerClass
1455                ,       tClass  = o.togglerClass
1456                ,       _pane   = "-"+ pane // used for classNames
1457                ,       _open   = "-open"
1458                ,       _sliding= "-sliding"
1459                ,       _closed = "-closed"
1460                //      transfer logic vars to temp vars
1461                ,       isShowing = s.isShowing
1462                ,       isHiding = s.isHiding
1463                ;
1464                // now clear the logic vars
1465                delete s.isShowing;
1466                delete s.isHiding;
1467
1468                if (!$P || (!o.resizable && !o.closable)) return; // invalid request
1469                else if (!force && s.isClosed && !isShowing) return; // already closed
1470
1471                if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation
1472                        setFlowCallback("close", pane, force); // set a callback for this action, if possible
1473                        return; // ABORT
1474                }
1475
1476                // onclose_start callback - will CANCEL hide if returns false
1477                // SKIP if just 'showing' a hidden pane as 'closed'
1478                if (!isShowing && false === execUserCallback(pane, o.onclose_start)) return;
1479
1480                // SET flow-control flags
1481                c[pane].isMoving = true;
1482                c.isLayoutBusy = true;
1483
1484                s.isClosed = true;
1485                // update isHidden BEFORE sizing panes
1486                if (isHiding) s.isHidden = true;
1487                else if (isShowing) s.isHidden = false;
1488
1489                // sync any 'pin buttons'
1490                syncPinBtns(pane, false);
1491
1492                // resize panes adjacent to this one
1493                if (!s.isSliding) sizeMidPanes(c[pane].dir == "horz" ? "all" : "center");
1494
1495                // if this pane has a resizer bar, move it now
1496                if ($R) {
1497                        $R
1498                                .css(edge, cDims[edge]) // move the resizer bar
1499                                .removeClass( rClass+_open +" "+ rClass+_pane+_open )
1500                                .removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
1501                                .addClass( rClass+_closed +" "+ rClass+_pane+_closed )
1502                        ;
1503                        // DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent
1504                        if (o.resizable)
1505                                $R
1506                                        .draggable("disable")
1507                                        .css("cursor", "default")
1508                                        .attr("title","")
1509                                ;
1510                        // if pane has a toggler button, adjust that too
1511                        if ($T) {
1512                                $T
1513                                        .removeClass( tClass+_open +" "+ tClass+_pane+_open )
1514                                        .addClass( tClass+_closed +" "+ tClass+_pane+_closed )
1515                                        .attr("title", o.togglerTip_closed) // may be blank
1516                                ;
1517                        }
1518                        sizeHandles(); // resize 'length' and position togglers for adjacent panes
1519                }
1520
1521                // ANIMATE 'CLOSE' - if no animation, then was ALREADY shown above
1522                if (doFX) {
1523                        lockPaneForFX(pane, true); // need to set left/top so animation will work
1524                        $P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () {
1525                                lockPaneForFX(pane, false); // undo
1526                                if (!s.isClosed) return; // pane was opened before animation finished!
1527                                close_2();
1528                        });
1529                }
1530                else {
1531                        $P.hide(); // just hide pane NOW
1532                        close_2();
1533                }
1534
1535                // SUBROUTINE
1536                function close_2 () {
1537                        bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true
1538
1539                        // onclose callback - UNLESS just 'showing' a hidden pane as 'closed'
1540                        if (!isShowing) execUserCallback(pane, o.onclose_end || o.onclose);
1541                        // onhide OR onshow callback
1542                        if (isShowing)  execUserCallback(pane, o.onshow_end || o.onshow);
1543                        if (isHiding)   execUserCallback(pane, o.onhide_end || o.onhide);
1544
1545                        // internal flow-control callback
1546                        execFlowCallback(pane);
1547                }
1548        };
1549
1550        /**
1551         * open
1552         *
1553         * Open the specified pane (animation optional), and resize all other panes as needed
1554         *
1555         * @param String  pane   The pane being opened, ie: north, south, east, or west
1556         */
1557        var open = function (pane, slide, noAnimation) {
1558                var
1559                        $P              = $Ps[pane]
1560                ,       $R              = $Rs[pane]
1561                ,       $T              = $Ts[pane]
1562                ,       o               = options[pane]
1563                ,       s               = state[pane]
1564                ,       doFX    = !noAnimation && s.isClosed && (o.fxName_open != "none")
1565                ,       edge    = c[pane].edge
1566                ,       rClass  = o.resizerClass
1567                ,       tClass  = o.togglerClass
1568                ,       _pane   = "-"+ pane // used for classNames
1569                ,       _open   = "-open"
1570                ,       _closed = "-closed"
1571                ,       _sliding= "-sliding"
1572                //      transfer logic var to temp var
1573                ,       isShowing = s.isShowing
1574                ;
1575                // now clear the logic var
1576                delete s.isShowing;
1577
1578                if (!$P || (!o.resizable && !o.closable)) return; // invalid request
1579                else if (!s.isClosed && !s.isSliding) return; // already open
1580
1581                // pane can ALSO be unhidden by just calling show(), so handle this scenario
1582                if (s.isHidden && !isShowing) {
1583                        show(pane, true);
1584                        return;
1585                }
1586
1587                if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation
1588                        setFlowCallback("open", pane, slide); // set a callback for this action, if possible
1589                        return; // ABORT
1590                }
1591
1592                // onopen_start callback - will CANCEL hide if returns false
1593                if (false === execUserCallback(pane, o.onopen_start)) return;
1594
1595                // SET flow-control flags
1596                c[pane].isMoving = true;
1597                c.isLayoutBusy = true;
1598
1599                // 'PIN PANE' - stop sliding
1600                if (s.isSliding && !slide) // !slide = 'open pane normally' - NOT sliding
1601                        bindStopSlidingEvents(pane, false); // will set isSliding=false
1602
1603                s.isClosed = false;
1604                // update isHidden BEFORE sizing panes
1605                if (isShowing) s.isHidden = false;
1606
1607                // Container size may have changed - shrink the pane if now 'too big'
1608                setPaneMinMaxSizes(pane); // update pane-state
1609                if (s.size > s.maxSize) // pane is too big! resize it before opening
1610                        $P.css( c[pane].sizeType, max(1, cssSize(pane, s.maxSize)) );
1611
1612                bindStartSlidingEvent(pane, false); // remove trigger event from resizer-bar
1613
1614                if (doFX) { // ANIMATE
1615                        lockPaneForFX(pane, true); // need to set left/top so animation will work
1616                        $P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() {
1617                                lockPaneForFX(pane, false); // undo
1618                                if (s.isClosed) return; // pane was closed before animation finished!
1619                                open_2(); // continue
1620                        });
1621                }
1622                else {// no animation
1623                        $P.show();      // just show pane and...
1624                        open_2();       // continue
1625                }
1626
1627                // SUBROUTINE
1628                function open_2 () {
1629                        // NOTE: if isSliding, then other panes are NOT 'resized'
1630                        if (!s.isSliding) // resize all panes adjacent to this one
1631                                sizeMidPanes(c[pane].dir=="vert" ? "center" : "all");
1632
1633                        // if this pane has a toggler, move it now
1634                        if ($R) {
1635                                $R
1636                                        .css(edge, cDims[edge] + getPaneSize(pane)) // move the toggler
1637                                        .removeClass( rClass+_closed +" "+ rClass+_pane+_closed )
1638                                        .addClass( rClass+_open +" "+ rClass+_pane+_open )
1639                                        .addClass( !s.isSliding ? "" : rClass+_sliding +" "+ rClass+_pane+_sliding )
1640                                ;
1641                                if (o.resizable)
1642                                        $R
1643                                                .draggable("enable")
1644                                                .css("cursor", o.resizerCursor)
1645                                                .attr("title", o.resizerTip)
1646                                        ;
1647                                else
1648                                        $R.css("cursor", "default"); // n-resize, s-resize, etc
1649                                // if pane also has a toggler button, adjust that too
1650                                if ($T) {
1651                                        $T
1652                                                .removeClass( tClass+_closed +" "+ tClass+_pane+_closed )
1653                                                .addClass( tClass+_open +" "+ tClass+_pane+_open )
1654                                                .attr("title", o.togglerTip_open) // may be blank
1655                                        ;
1656                                }
1657                                sizeHandles("all"); // resize resizer & toggler sizes for all panes
1658                        }
1659
1660                        // resize content every time pane opens - to be sure
1661                        sizeContent(pane);
1662
1663                        // sync any 'pin buttons'
1664                        syncPinBtns(pane, !s.isSliding);
1665
1666                        // onopen callback
1667                        execUserCallback(pane, o.onopen_end || o.onopen);
1668
1669                        // onshow callback
1670                        if (isShowing) execUserCallback(pane, o.onshow_end || o.onshow);
1671
1672                        // internal flow-control callback
1673                        execFlowCallback(pane);
1674                }
1675        };
1676       
1677
1678        /**
1679         * lockPaneForFX
1680         *
1681         * Must set left/top on East/South panes so animation will work properly
1682         *
1683         * @param String  pane  The pane to lock, 'east' or 'south' - any other is ignored!
1684         * @param Boolean  doLock  true = set left/top, false = remove
1685         */
1686        var lockPaneForFX = function (pane, doLock) {
1687                var $P = $Ps[pane];
1688                if (doLock) {
1689                        $P.css({ zIndex: c.zIndex.animation }); // overlay all elements during animation
1690                        if (pane=="south")
1691                                $P.css({ top: cDims.top + cDims.innerHeight - $P.outerHeight() });
1692                        else if (pane=="east")
1693                                $P.css({ left: cDims.left + cDims.innerWidth - $P.outerWidth() });
1694                }
1695                else {
1696                        if (!state[pane].isSliding) $P.css({ zIndex: c.zIndex.pane_normal });
1697                        if (pane=="south")
1698                                $P.css({ top: "auto" });
1699                        else if (pane=="east")
1700                                $P.css({ left: "auto" });
1701                }
1702        };
1703
1704
1705        /**
1706         * bindStartSlidingEvent
1707         *
1708         * Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger
1709         *
1710         * @callers  open(), close()
1711         * @param String  pane  The pane to enable/disable, 'north', 'south', etc.
1712         * @param Boolean  enable  Enable or Disable sliding?
1713         */
1714        var bindStartSlidingEvent = function (pane, enable) {
1715                var
1716                        o               = options[pane]
1717                ,       $R              = $Rs[pane]
1718                ,       trigger = o.slideTrigger_open
1719                ;
1720                if (!$R || !o.slidable) return;
1721                // make sure we have a valid event
1722                if (trigger != "click" && trigger != "dblclick" && trigger != "mouseover") trigger = "click";
1723                $R
1724                        // add or remove trigger event
1725                        [enable ? "bind" : "unbind"](trigger, slideOpen)
1726                        // set the appropriate cursor & title/tip
1727                        .css("cursor", (enable ? o.sliderCursor: "default"))
1728                        .attr("title", (enable ? o.sliderTip : ""))
1729                ;
1730        };
1731
1732        /**
1733         * bindStopSlidingEvents
1734         *
1735         * Add or remove 'mouseout' events to 'slide close' when pane is 'sliding' open or closed
1736         * Also increases zIndex when pane is sliding open
1737         * See bindStartSlidingEvent for code to control 'slide open'
1738         *
1739         * @callers  slideOpen(), slideClosed()
1740         * @param String  pane  The pane to process, 'north', 'south', etc.
1741         * @param Boolean  isOpen  Is pane open or closed?
1742         */
1743        var bindStopSlidingEvents = function (pane, enable) {
1744                var
1745                        o               = options[pane]
1746                ,       s               = state[pane]
1747                ,       trigger = o.slideTrigger_close
1748                ,       action  = (enable ? "bind" : "unbind") // can't make 'unbind' work! - see disabled code below
1749                ,       $P              = $Ps[pane]
1750                ,       $R              = $Rs[pane]
1751                ;
1752
1753                s.isSliding = enable; // logic
1754                clearTimer(pane, "closeSlider"); // just in case
1755
1756                // raise z-index when sliding
1757                $P.css({ zIndex: (enable ? c.zIndex.sliding : c.zIndex.pane_normal) });
1758                $R.css({ zIndex: (enable ? c.zIndex.sliding : c.zIndex.resizer_normal) });
1759
1760                // make sure we have a valid event
1761                if (trigger != "click" && trigger != "mouseout") trigger = "mouseout";
1762
1763                // when trigger is 'mouseout', must cancel timer when mouse moves between 'pane' and 'resizer'
1764                if (enable) { // BIND trigger events
1765                        $P.bind(trigger, slideClosed );
1766                        $R.bind(trigger, slideClosed );
1767                        if (trigger = "mouseout") {
1768                                $P.bind("mouseover", cancelMouseOut );
1769                                $R.bind("mouseover", cancelMouseOut );
1770                        }
1771                }
1772                else { // UNBIND trigger events
1773                        // TODO: why does unbind of a 'single function' not work reliably?
1774                        //$P[action](trigger, slideClosed );
1775                        $P.unbind(trigger);
1776                        $R.unbind(trigger);
1777                        if (trigger = "mouseout") {
1778                                //$P[action]("mouseover", cancelMouseOut );
1779                                $P.unbind("mouseover");
1780                                $R.unbind("mouseover");
1781                                clearTimer(pane, "closeSlider");
1782                        }
1783                }
1784
1785                // SUBROUTINE for mouseout timer clearing
1786                function cancelMouseOut (evt) {
1787                        clearTimer(pane, "closeSlider");
1788                        evt.stopPropagation();
1789                }
1790        };
1791
1792        var slideOpen = function () {
1793                var pane = $(this).attr("resizer"); // attr added by initHandles
1794                if (state[pane].isClosed) { // skip if already open!
1795                        bindStopSlidingEvents(pane, true); // pane is opening, so BIND trigger events to close it
1796                        open(pane, true); // true = slide - ie, called from here!
1797                }
1798        };
1799
1800        var slideClosed = function () {
1801                var
1802                        $E = $(this)
1803                ,       pane = $E.attr("pane") || $E.attr("resizer")
1804                ,       o = options[pane]
1805                ,       s = state[pane]
1806                ;
1807                if (s.isClosed || s.isResizing)
1808                        return; // skip if already closed OR in process of resizing
1809                else if (o.slideTrigger_close == "click")
1810                        close_NOW(); // close immediately onClick
1811                else // trigger = mouseout - use a delay
1812                        setTimer(pane, "closeSlider", close_NOW, 300); // .3 sec delay
1813
1814                // SUBROUTINE for timed close
1815                function close_NOW () {
1816                        bindStopSlidingEvents(pane, false); // pane is being closed, so UNBIND trigger events
1817                        if (!s.isClosed) close(pane); // skip if already closed!
1818                }
1819        };
1820
1821
1822        /**
1823         * sizePane
1824         *
1825         * @callers  initResizable.stop()
1826         * @param String  pane   The pane being resized - usually west or east, but potentially north or south
1827         * @param Integer  newSize  The new size for this pane - will be validated
1828         */
1829        var sizePane = function (pane, size) {
1830                // TODO: accept "auto" as size, and size-to-fit pane content
1831                var
1832                        edge    = c[pane].edge
1833                ,       dir             = c[pane].dir
1834                ,       o               = options[pane]
1835                ,       s               = state[pane]
1836                ,       $P              = $Ps[pane]
1837                ,       $R              = $Rs[pane]
1838                ;
1839                // calculate 'current' min/max sizes
1840                setPaneMinMaxSizes(pane); // update pane-state
1841                // compare/update calculated min/max to user-options
1842                s.minSize = max(s.minSize, o.minSize);
1843                if (o.maxSize > 0) s.maxSize = min(s.maxSize, o.maxSize);
1844                // validate passed size
1845                size = max(size, s.minSize);
1846                size = min(size, s.maxSize);
1847                s.size = size; // update state
1848
1849                // move the resizer bar and resize the pane
1850                $R.css( edge, size + cDims[edge] );
1851                $P.css( c[pane].sizeType, max(1, cssSize(pane, size)) );
1852
1853                // resize all the adjacent panes, and adjust their toggler buttons
1854                if (!s.isSliding) sizeMidPanes(dir=="horz" ? "all" : "center");
1855                sizeHandles();
1856                sizeContent(pane);
1857                execUserCallback(pane, o.onresize_end || o.onresize);
1858        };
1859
1860        /**
1861         * sizeMidPanes
1862         *
1863         * @callers  create(), open(), close(), onWindowResize()
1864         */
1865        var sizeMidPanes = function (panes, overrideDims, onInit) {
1866                if (!panes || panes == "all") panes = "east,west,center";
1867
1868                var d = getPaneDims();
1869                if (overrideDims) $.extend( d, overrideDims );
1870
1871                $.each(panes.split(","), function() {
1872                        if (!$Ps[this]) return; // NO PANE - skip
1873                        var
1874                                pane    = str(this)
1875                        ,       o               = options[pane]
1876                        ,       s               = state[pane]
1877                        ,       $P              = $Ps[pane]
1878                        ,       $R              = $Rs[pane]
1879                        ,       hasRoom = true
1880                        ,       CSS             = {}
1881                        ;
1882
1883                        if (pane == "center") {
1884                                d = getPaneDims(); // REFRESH Dims because may have just 'unhidden' East or West pane after a 'resize'
1885                                CSS = $.extend( {}, d ); // COPY ALL of the paneDims
1886                                CSS.width  = max(1, cssW(pane, CSS.width));
1887                                CSS.height = max(1, cssH(pane, CSS.height));
1888                                hasRoom = (CSS.width > 1 && CSS.height > 1);
1889                                /*
1890                                 * Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes
1891                                 * Normally these panes have only 'left' & 'right' positions so pane auto-sizes
1892                                 */
1893                                if ($.browser.msie && (!$.boxModel || $.browser.version < 7)) {
1894                                        if ($Ps.north) $Ps.north.css({ width: cssW($Ps.north, cDims.innerWidth) });
1895                                        if ($Ps.south) $Ps.south.css({ width: cssW($Ps.south, cDims.innerWidth) });
1896                                }
1897                        }
1898                        else { // for east and west, set only the height
1899                                CSS.top = d.top;
1900                                CSS.bottom = d.bottom;
1901                                CSS.height = max(1, cssH(pane, d.height));
1902                                hasRoom = (CSS.height > 1);
1903                        }
1904
1905                        if (hasRoom) {
1906                                $P.css(CSS);
1907                                if (s.noRoom) {
1908                                        s.noRoom = false;
1909                                        if (s.isHidden) return;
1910                                        else show(pane, !s.isClosed);
1911                                        /* OLD CODE - keep until sure line above works right!
1912                                        if (!s.isClosed) $P.show(); // in case was previously hidden due to NOT hasRoom
1913                                        if ($R) $R.show();
1914                                        */
1915                                }
1916                                if (!onInit) {
1917                                        sizeContent(pane);
1918                                        execUserCallback(pane, o.onresize_end || o.onresize);
1919                                }
1920                        }
1921                        else if (!s.noRoom) { // no room for pane, so just hide it (if not already)
1922                                s.noRoom = true; // update state
1923                                if (s.isHidden) return;
1924                                if (onInit) { // skip onhide callback and other logic onLoad
1925                                        $P.hide();
1926                                        if ($R) $R.hide();
1927                                }
1928                                else hide(pane);
1929                        }
1930                });
1931        };
1932
1933
1934        var sizeContent = function (panes) {
1935                if (!panes || panes == "all") panes = c.allPanes;
1936
1937                $.each(panes.split(","), function() {
1938                        if (!$Cs[this]) return; // NO CONTENT - skip
1939                        var
1940                                pane    = str(this)
1941                        ,       ignore  = options[pane].contentIgnoreSelector
1942                        ,       $P              = $Ps[pane]
1943                        ,       $C              = $Cs[pane]
1944                        ,       e_C             = $C[0]         // DOM element
1945                        ,       height  = cssH($P);     // init to pane.innerHeight
1946                        ;
1947                        $P.children().each(function() {
1948                                if (this == e_C) return; // Content elem - skip
1949                                var $E = $(this);
1950                                if (!ignore || !$E.is(ignore))
1951                                        height -= $E.outerHeight();
1952                        });
1953                        if (height > 0)
1954                                height = cssH($C, height);
1955                        if (height < 1)
1956                                $C.hide(); // no room for content!
1957                        else
1958                                $C.css({ height: height }).show();
1959                });
1960        };
1961
1962
1963        /**
1964         * sizeHandles
1965         *
1966         * Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary
1967         *
1968         * @callers  initHandles(), open(), close(), resizeAll()
1969         */
1970        var sizeHandles = function (panes, onInit) {
1971                if (!panes || panes == "all") panes = c.borderPanes;
1972
1973                $.each(panes.split(","), function() {
1974                        var
1975                                pane    = str(this)
1976                        ,       o               = options[pane]
1977                        ,       s               = state[pane]
1978                        ,       $P              = $Ps[pane]
1979                        ,       $R              = $Rs[pane]
1980                        ,       $T              = $Ts[pane]
1981                        ;
1982                        if (!$P || !$R || (!o.resizable && !o.closable)) return; // skip
1983
1984                        var
1985                                dir                     = c[pane].dir
1986                        ,       _state          = (s.isClosed ? "_closed" : "_open")
1987                        ,       spacing         = o["spacing"+ _state]
1988                        ,       togAlign        = o["togglerAlign"+ _state]
1989                        ,       togLen          = o["togglerLength"+ _state]
1990                        ,       paneLen
1991                        ,       offset
1992                        ,       CSS = {}
1993                        ;
1994                        if (spacing == 0) {
1995                                $R.hide();
1996                                return;
1997                        }
1998                        else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason
1999                                $R.show(); // in case was previously hidden
2000
2001                        // Resizer Bar is ALWAYS same width/height of pane it is attached to
2002                        if (dir == "horz") { // north/south
2003                                paneLen = $P.outerWidth();
2004                                $R.css({
2005                                        width:  max(1, cssW($R, paneLen)) // account for borders & padding
2006                                ,       height: max(1, cssH($R, spacing)) // ditto
2007                                ,       left:   cssNum($P, "left")
2008                                });
2009                        }
2010                        else { // east/west
2011                                paneLen = $P.outerHeight();
2012                                $R.css({
2013                                        height: max(1, cssH($R, paneLen)) // account for borders & padding
2014                                ,       width:  max(1, cssW($R, spacing)) // ditto
2015                                ,       top:    cDims.top + getPaneSize("north", true)
2016                                //,     top:    cssNum($Ps["center"], "top")
2017                                });
2018                               
2019                        }
2020
2021                        if ($T) {
2022                                if (togLen == 0 || (s.isSliding && o.hideTogglerOnSlide)) {
2023                                        $T.hide(); // always HIDE the toggler when 'sliding'
2024                                        return;
2025                                }
2026                                else
2027                                        $T.show(); // in case was previously hidden
2028
2029                                if (!(togLen > 0) || togLen == "100%" || togLen > paneLen) {
2030                                        togLen = paneLen;
2031                                        offset = 0;
2032                                }
2033                                else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed
2034                                        if (typeof togAlign == "string") {
2035                                                switch (togAlign) {
2036                                                        case "top":
2037                                                        case "left":    offset = 0;
2038                                                                                        break;
2039                                                        case "bottom":
2040                                                        case "right":   offset = paneLen - togLen;
2041                                                                                        break;
2042                                                        case "middle":
2043                                                        case "center":
2044                                                        default:                offset = Math.floor((paneLen - togLen) / 2); // 'default' catches typos
2045                                                }
2046                                        }
2047                                        else { // togAlign = number
2048                                                var x = parseInt(togAlign); //
2049                                                if (togAlign >= 0) offset = x;
2050                                                else offset = paneLen - togLen + x; // NOTE: x is negative!
2051                                        }
2052                                }
2053
2054                                var
2055                                        $TC_o = (o.togglerContent_open   ? $T.children(".content-open") : false)
2056                                ,       $TC_c = (o.togglerContent_closed ? $T.children(".content-closed")   : false)
2057                                ,       $TC   = (s.isClosed ? $TC_c : $TC_o)
2058                                ;
2059                                if ($TC_o) $TC_o.css("display", s.isClosed ? "none" : "block");
2060                                if ($TC_c) $TC_c.css("display", s.isClosed ? "block" : "none");
2061
2062                                if (dir == "horz") { // north/south
2063                                        var width = cssW($T, togLen);
2064                                        $T.css({
2065                                                width:  max(0, width)  // account for borders & padding
2066                                        ,       height: max(1, cssH($T, spacing)) // ditto
2067                                        ,       left:   offset // TODO: VERIFY that toggler  positions correctly for ALL values
2068                                        });
2069                                        if ($TC) // CENTER the toggler content SPAN
2070                                                $TC.css("marginLeft", Math.floor((width-$TC.outerWidth())/2)); // could be negative
2071                                }
2072                                else { // east/west
2073                                        var height = cssH($T, togLen);
2074                                        $T.css({
2075                                                height: max(0, height)  // account for borders & padding
2076                                        ,       width:  max(1, cssW($T, spacing)) // ditto
2077                                        ,       top:    offset // POSITION the toggler
2078                                        });
2079                                        if ($TC) // CENTER the toggler content SPAN
2080                                                $TC.css("marginTop", Math.floor((height-$TC.outerHeight())/2)); // could be negative
2081                                }
2082
2083
2084                        }
2085
2086                        // DONE measuring and sizing this resizer/toggler, so can be 'hidden' now
2087                        if (onInit && o.initHidden) {
2088                                $R.hide();
2089                                if ($T) $T.hide();
2090                        }
2091                });
2092        };
2093
2094
2095        /**
2096         * resizeAll
2097         *
2098         * @callers  window.onresize(), callbacks or custom code
2099         */
2100        var resizeAll = function () {
2101                var
2102                        oldW    = cDims.innerWidth
2103                ,       oldH    = cDims.innerHeight
2104                ;
2105                cDims = state.container = getElemDims($Container); // UPDATE container dimensions
2106
2107                var
2108                        checkH  = (cDims.innerHeight < oldH)
2109                ,       checkW  = (cDims.innerWidth < oldW)
2110                ,       s, dir
2111                ;
2112
2113                if (checkH || checkW)
2114                        // NOTE special order for sizing: S-N-E-W
2115                        $.each(["south","north","east","west"], function(i,pane) {
2116                                s = state[pane];
2117                                dir = c[pane].dir;
2118                                if (!s.isClosed && ((checkH && dir=="horz") || (checkW && dir=="vert"))) {
2119                                        setPaneMinMaxSizes(pane); // update pane-state
2120                                        // shrink pane if 'too big' to fit
2121                                        if (s.size > s.maxSize)
2122                                                sizePane(pane, s.maxSize);
2123                                }
2124                        });
2125
2126                sizeMidPanes("all");
2127                sizeHandles("all"); // reposition the toggler elements
2128        };
2129
2130
2131        /**
2132         * keyDown
2133         *
2134         * Capture keys when enableCursorHotkey - toggle pane if hotkey pressed
2135         *
2136         * @callers  document.keydown()
2137         */
2138        function keyDown (evt) {
2139                if (!evt) return true;
2140                var code = evt.keyCode;
2141                if (code < 33) return true; // ignore special keys: ENTER, TAB, etc
2142
2143                var
2144                        PANE = {
2145                                38: "north" // Up Cursor
2146                        ,       40: "south" // Down Cursor
2147                        ,       37: "west"  // Left Cursor
2148                        ,       39: "east"  // Right Cursor
2149                        }
2150                ,       isCursorKey = (code >= 37 && code <= 40)
2151                ,       ALT = evt.altKey // no worky!
2152                ,       SHIFT = evt.shiftKey
2153                ,       CTRL = evt.ctrlKey
2154                ,       pane = false
2155                ,       s, o, k, m, el
2156                ;
2157
2158                if (!CTRL && !SHIFT)
2159                        return true; // no modifier key - abort
2160                else if (isCursorKey && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey
2161                        pane = PANE[code];
2162                else // check to see if this matches a custom-hotkey
2163                        $.each(c.borderPanes.split(","), function(i,p) { // loop each pane to check its hotkey
2164                                o = options[p];
2165                                k = o.customHotkey;
2166                                m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT"
2167                                if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches
2168                                        if (k && code == (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches
2169                                                pane = p;
2170                                                return false; // BREAK
2171                                        }
2172                                }
2173                        });
2174
2175                if (!pane) return true; // no hotkey - abort
2176
2177                // validate pane
2178                o = options[pane]; // get pane options
2179                s = state[pane]; // get pane options
2180                if (!o.enableCursorHotkey || s.isHidden || !$Ps[pane]) return true;
2181
2182                // see if user is in a 'form field' because may be 'selecting text'!
2183                el = evt.target || evt.srcElement;
2184                if (el && SHIFT && isCursorKey && (el.tagName=="TEXTAREA" || (el.tagName=="INPUT" && (code==37 || code==39))))
2185                        return true; // allow text-selection
2186
2187                // SYNTAX NOTES
2188                // use "returnValue=false" to abort keystroke but NOT abort function - can run another command afterwards
2189                // use "return false" to abort keystroke AND abort function
2190                toggle(pane);
2191                evt.stopPropagation();
2192                evt.returnValue = false; // CANCEL key
2193                return false;
2194        };
2195
2196
2197/*
2198 * ###########################
2199 *     UTILITY METHODS
2200 *   called externally only
2201 * ###########################
2202 */
2203
2204        function allowOverflow (elem) {
2205                if (this && this.tagName) elem = this; // BOUND to element
2206                var $P;
2207                if (typeof elem=="string")
2208                        $P = $Ps[elem];
2209                else {
2210                        if ($(elem).attr("pane")) $P = $(elem);
2211                        else $P = $(elem).parents("div[pane]:first");
2212                }
2213                if (!$P.length) return; // INVALID
2214
2215                var
2216                        pane    = $P.attr("pane")
2217                ,       s               = state[pane]
2218                ;
2219
2220                // if pane is already raised, then reset it before doing it again!
2221                // this would happen if allowOverflow is attached to BOTH the pane and an element
2222                if (s.cssSaved)
2223                        resetOverflow(pane); // reset previous CSS before continuing
2224
2225                // if pane is raised by sliding or resizing, or it's closed, then abort
2226                if (s.isSliding || s.isResizing || s.isClosed) {
2227                        s.cssSaved = false;
2228                        return;
2229                }
2230
2231                var
2232                        newCSS  = { zIndex: (c.zIndex.pane_normal + 1) }
2233                ,       curCSS  = {}
2234                ,       of              = $P.css("overflow")
2235                ,       ofX             = $P.css("overflowX")
2236                ,       ofY             = $P.css("overflowY")
2237                ;
2238                // determine which, if any, overflow settings need to be changed
2239                if (of != "visible") {
2240                        curCSS.overflow = of;
2241                        newCSS.overflow = "visible";
2242                }
2243                if (ofX && ofX != "visible" && ofX != "auto") {
2244                        curCSS.overflowX = ofX;
2245                        newCSS.overflowX = "visible";
2246                }
2247                if (ofY && ofY != "visible" && ofY != "auto") {
2248                        curCSS.overflowY = ofX;
2249                        newCSS.overflowY = "visible";
2250                }
2251
2252                // save the current overflow settings - even if blank!
2253                s.cssSaved = curCSS;
2254
2255                // apply new CSS to raise zIndex and, if necessary, make overflow 'visible'
2256                $P.css( newCSS );
2257
2258                // make sure the zIndex of all other panes is normal
2259                $.each(c.allPanes.split(","), function(i, p) {
2260                        if (p != pane) resetOverflow(p);
2261                });
2262
2263        };
2264
2265        function resetOverflow (elem) {
2266                if (this && this.tagName) elem = this; // BOUND to element
2267                var $P;
2268                if (typeof elem=="string")
2269                        $P = $Ps[elem];
2270                else {
2271                        if ($(elem).hasClass("ui-layout-pane")) $P = $(elem);
2272                        else $P = $(elem).parents("div[pane]:first");
2273                }
2274                if (!$P.length) return; // INVALID
2275
2276                var
2277                        pane    = $P.attr("pane")
2278                ,       s               = state[pane]
2279                ,       CSS             = s.cssSaved || {}
2280                ;
2281                // reset the zIndex
2282                if (!s.isSliding && !s.isResizing)
2283                        $P.css("zIndex", c.zIndex.pane_normal);
2284
2285                // reset Overflow - if necessary
2286                $P.css( CSS );
2287
2288                // clear var
2289                s.cssSaved = false;
2290        };
2291
2292
2293        /**
2294        * getBtn
2295        *
2296        * Helper function to validate params received by addButton utilities
2297        *
2298        * @param String   selector      jQuery selector for button, eg: ".ui-layout-north .toggle-button"
2299        * @param String   pane          Name of the pane the button is for: 'north', 'south', etc.
2300        * @returns  If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise 'false'
2301        */
2302        function getBtn(selector, pane, action) {
2303                var
2304                        $E = $(selector)
2305                ,       err = "Error Adding Button \n\nInvalid "
2306                ;
2307                if (!$E.length) // element not found
2308                        alert(err+"selector: "+ selector);
2309                else if (c.borderPanes.indexOf(pane) == -1) // invalid 'pane' sepecified
2310                        alert(err+"pane: "+ pane);
2311                else { // VALID
2312                        var btn = options[pane].buttonClass +"-"+ action;
2313                        $E.addClass( btn +" "+ btn +"-"+ pane );
2314                        return $E;
2315                }
2316                return false;  // INVALID
2317        };
2318
2319
2320        /**
2321        * addToggleBtn
2322        *
2323        * Add a custom Toggler button for a pane
2324        *
2325        * @param String   selector      jQuery selector for button, eg: ".ui-layout-north .toggle-button"
2326        * @param String   pane          Name of the pane the button is for: 'north', 'south', etc.
2327        */
2328        function addToggleBtn (selector, pane) {
2329                var $E = getBtn(selector, pane, "toggle");
2330                if ($E)
2331                        $E
2332                                .attr("title", state[pane].isClosed ? "Open" : "Close")
2333                                .click(function (evt) {
2334                                        toggle(pane);
2335                                        evt.stopPropagation();
2336                                })
2337                        ;
2338        };
2339
2340        /**
2341        * addOpenBtn
2342        *
2343        * Add a custom Open button for a pane
2344        *
2345        * @param String   selector      jQuery selector for button, eg: ".ui-layout-north .open-button"
2346        * @param String   pane          Name of the pane the button is for: 'north', 'south', etc.
2347        */
2348        function addOpenBtn (selector, pane) {
2349                var $E = getBtn(selector, pane, "open");
2350                if ($E)
2351                        $E
2352                                .attr("title", "Open")
2353                                .click(function (evt) {
2354                                        open(pane);
2355                                        evt.stopPropagation();
2356                                })
2357                        ;
2358        };
2359
2360        /**
2361        * addCloseBtn
2362        *
2363        * Add a custom Close button for a pane
2364        *
2365        * @param String   selector      jQuery selector for button, eg: ".ui-layout-north .close-button"
2366        * @param String   pane          Name of the pane the button is for: 'north', 'south', etc.
2367        */
2368        function addCloseBtn (selector, pane) {
2369                var $E = getBtn(selector, pane, "close");
2370                if ($E)
2371                        $E
2372                                .attr("title", "Close")
2373                                .click(function (evt) {
2374                                        close(pane);
2375                                        evt.stopPropagation();
2376                                })
2377                        ;
2378        };
2379
2380        /**
2381        * addPinBtn
2382        *
2383        * Add a custom Pin button for a pane
2384        *
2385        * Four classes are added to the element, based on the paneClass for the associated pane...
2386        * Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin:
2387        *  - ui-layout-pane-pin
2388        *  - ui-layout-pane-west-pin
2389        *  - ui-layout-pane-pin-up
2390        *  - ui-layout-pane-west-pin-up
2391        *
2392        * @param String   selector      jQuery selector for button, eg: ".ui-layout-north .ui-layout-pin"
2393        * @param String   pane          Name of the pane the pin is for: 'north', 'south', etc.
2394        */
2395        function addPinBtn (selector, pane) {
2396                var $E = getBtn(selector, pane, "pin");
2397                if ($E) {
2398                        var s = state[pane];
2399                        $E.click(function (evt) {
2400                                setPinState($(this), pane, (s.isSliding || s.isClosed));
2401                                if (s.isSliding || s.isClosed) open( pane ); // change from sliding to open
2402                                else close( pane ); // slide-closed
2403                                evt.stopPropagation();
2404                        });
2405                        // add up/down pin attributes and classes
2406                        setPinState ($E, pane, (!s.isClosed && !s.isSliding));
2407                        // add this pin to the pane data so we can 'sync it' automatically
2408                        // PANE.pins key is an array so we can store multiple pins for each pane
2409                        c[pane].pins.push( selector ); // just save the selector string
2410                }
2411        };
2412
2413        /**
2414        * syncPinBtns
2415        *
2416        * INTERNAL function to sync 'pin buttons' when pane is opened or closed
2417        * Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
2418        *
2419        * @callers  open(), close()
2420        * @params  pane   These are the params returned to callbacks by layout()
2421        * @params  doPin  True means set the pin 'down', False means 'up'
2422        */
2423        function syncPinBtns (pane, doPin) {
2424                $.each(c[pane].pins, function (i, selector) {
2425                        setPinState($(selector), pane, doPin);
2426                });
2427        };
2428
2429        /**
2430        * setPinState
2431        *
2432        * Change the class of the pin button to make it look 'up' or 'down'
2433        *
2434        * @callers  addPinBtn(), syncPinBtns()
2435        * @param Element  $Pin          The pin-span element in a jQuery wrapper
2436        * @param Boolean  doPin         True = set the pin 'down', False = set it 'up'
2437        * @param String   pinClass      The root classname for pins - will add '-up' or '-down' suffix
2438        */
2439        function setPinState ($Pin, pane, doPin) {
2440                var updown = $Pin.attr("pin");
2441                if (updown && doPin == (updown=="down")) return; // already in correct state
2442                var
2443                        root    = options[pane].buttonClass
2444                ,       class1  = root +"-pin"
2445                ,       class2  = class1 +"-"+ pane
2446                ,       UP1             = class1 + "-up"
2447                ,       UP2             = class2 + "-up"
2448                ,       DN1             = class1 + "-down"
2449                ,       DN2             = class2 + "-down"
2450                ;
2451                $Pin
2452                        .attr("pin", doPin ? "down" : "up") // logic
2453                        .attr("title", doPin ? "Un-Pin" : "Pin")
2454                        .removeClass( doPin ? UP1 : DN1 )
2455                        .removeClass( doPin ? UP2 : DN2 )
2456                        .addClass( doPin ? DN1 : UP1 )
2457                        .addClass( doPin ? DN2 : UP2 )
2458                ;
2459        };
2460
2461
2462/*
2463 * ###########################
2464 * CREATE/RETURN BORDER-LAYOUT
2465 * ###########################
2466 */
2467
2468        // init global vars
2469        var
2470                $Container = $(this).css({ overflow: "hidden" }) // Container elem
2471        ,       $Ps             = {} // Panes x4        - set in initPanes()
2472        ,       $Cs             = {} // Content x4      - set in initPanes()
2473        ,       $Rs             = {} // Resizers x4     - set in initHandles()
2474        ,       $Ts             = {} // Togglers x4     - set in initHandles()
2475        //      object aliases
2476        ,       c               = config // alias for config hash
2477        ,       cDims   = state.container // alias for easy access to 'container dimensions'
2478        ;
2479
2480        // create the border layout NOW
2481        create();
2482
2483        // return object pointers to expose data & option Properties, and primary action Methods
2484        return {
2485                options:                options                 // property - options hash
2486        ,       state:                  state                   // property - dimensions hash
2487        ,       panes:                  $Ps                             // property - object pointers for ALL panes: panes.north, panes.center
2488        ,       toggle:                 toggle                  // method - pass a 'pane' ("north", "west", etc)
2489        ,       open:                   open                    // method - ditto
2490        ,       close:                  close                   // method - ditto
2491        ,       hide:                   hide                    // method - ditto
2492        ,       show:                   show                    // method - ditto
2493        ,       resizeContent:  sizeContent             // method - ditto
2494        ,       sizePane:               sizePane                // method - pass a 'pane' AND a 'size' in pixels
2495        ,       resizeAll:              resizeAll               // method - no parameters
2496        ,       addToggleBtn:   addToggleBtn    // utility - pass element selector and 'pane'
2497        ,       addOpenBtn:             addOpenBtn              // utility - ditto
2498        ,       addCloseBtn:    addCloseBtn             // utility - ditto
2499        ,       addPinBtn:              addPinBtn               // utility - ditto
2500        ,       allowOverflow:  allowOverflow   // utility - pass calling element
2501        ,       resetOverflow:  resetOverflow   // utility - ditto
2502        ,       cssWidth:               cssW
2503        ,       cssHeight:              cssH
2504        };
2505
2506}
2507})( jQuery );
Note: See TracBrowser for help on using the repository browser.