jsgem-jquery-layout 1.2.0.pre1

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