ultimix

- Main Page
- Data Structures
- Files
- Examples

- File List
- Globals

jquery.layout.min.js
Go to the documentation of this file.
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
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
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
346 var min = function (x,y) { return Math.min(x,y); };
347 var max = function (x,y) { return Math.max(x,y); };
348
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
2423 function syncPinBtns (pane, doPin) {
2424 $.each(c[pane].pins, function (i, selector) {
2425 setPinState($(selector), pane, doPin);
2426 });
2427 };
2428
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 );