ultimix
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 );