1/** 2 * @preserve 3 * jquery.layout 1.4.3 4 * $Date: 2014-08-30 08:00:00 (Sat, 30 Aug 2014) $ 5 * $Rev: 1.0403 $ 6 * 7 * Copyright (c) 2014 Kevin Dalman (http://jquery-dev.com) 8 * Based on work by Fabrizio Balliano (http://www.fabrizioballiano.net) 9 * 10 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) 11 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. 12 * 13 * SEE: http://layout.jquery-dev.com/LICENSE.txt 14 * 15 * Changelog: http://layout.jquery-dev.com/changelog.cfm 16 * 17 * Docs: http://layout.jquery-dev.com/documentation.html 18 * Tips: http://layout.jquery-dev.com/tips.html 19 * Help: http://groups.google.com/group/jquery-ui-layout 20 */ 21 22/* JavaDoc Info: http://code.google.com/closure/compiler/docs/js-for-compiler.html 23 * {!Object} non-nullable type (never NULL) 24 * {?string} nullable type (sometimes NULL) - default for {Object} 25 * {number=} optional parameter 26 * {*} ALL types 27 */ 28/* TODO for jQ 2.0 29 * change .andSelf() to .addBack() 30 * check $.fn.disableSelection - this is in jQuery UI 1.9.x 31 */ 32 33// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars 34 35;(function ($) { 36 37// alias Math methods - used a lot! 38var min = Math.min 39, max = Math.max 40, round = Math.floor 41 42, isStr = function (v) { return $.type(v) === "string"; } 43 44 /** 45 * @param {!Object} Instance 46 * @param {Array.<string>} a_fn 47 */ 48, runPluginCallbacks = function (Instance, a_fn) { 49 if ($.isArray(a_fn)) 50 for (var i=0, c=a_fn.length; i<c; i++) { 51 var fn = a_fn[i]; 52 try { 53 if (isStr(fn)) // 'name' of a function 54 fn = eval(fn); 55 if ($.isFunction(fn)) 56 g(fn)( Instance ); 57 } catch (ex) {} 58 } 59 function g (f) { return f; }; // compiler hack 60 } 61; 62 63/* 64 * GENERIC $.layout METHODS - used by all layouts 65 */ 66$.layout = { 67 68 version: "1.4.3" 69, revision: 1.0403 // eg: 1.4.1 final = 1.0401 - major(n+).minor(nn)+patch(nn+) 70 71 // $.layout.browser REPLACES $.browser 72, browser: {} // set below 73 74 // *PREDEFINED* EFFECTS & DEFAULTS 75 // MUST list effect here - OR MUST set an fxSettings option (can be an empty hash: {}) 76, effects: { 77 78 // Pane Open/Close Animations 79 slide: { 80 all: { duration: "fast" } // eg: duration: 1000, easing: "easeOutBounce" 81 , north: { direction: "up" } 82 , south: { direction: "down" } 83 , east: { direction: "right"} 84 , west: { direction: "left" } 85 } 86 , drop: { 87 all: { duration: "slow" } 88 , north: { direction: "up" } 89 , south: { direction: "down" } 90 , east: { direction: "right"} 91 , west: { direction: "left" } 92 } 93 , scale: { 94 all: { duration: "fast" } 95 } 96 // these are not recommended, but can be used 97 , blind: {} 98 , clip: {} 99 , explode: {} 100 , fade: {} 101 , fold: {} 102 , puff: {} 103 104 // Pane Resize Animations 105 , size: { 106 all: { easing: "swing" } 107 } 108 } 109 110 // INTERNAL CONFIG DATA - DO NOT CHANGE THIS! 111, config: { 112 optionRootKeys: "effects,panes,north,south,west,east,center".split(",") 113 , allPanes: "north,south,west,east,center".split(",") 114 , borderPanes: "north,south,west,east".split(",") 115 , oppositeEdge: { 116 north: "south" 117 , south: "north" 118 , east: "west" 119 , west: "east" 120 } 121 // offscreen data 122 , offscreenCSS: { left: "-99999px", right: "auto" } // used by hide/close if useOffscreenClose=true 123 , offscreenReset: "offscreenReset" // key used for data 124 // CSS used in multiple places 125 , hidden: { visibility: "hidden" } 126 , visible: { visibility: "visible" } 127 // layout element settings 128 , resizers: { 129 cssReq: { 130 position: "absolute" 131 , padding: 0 132 , margin: 0 133 , fontSize: "1px" 134 , textAlign: "left" // to counter-act "center" alignment! 135 , overflow: "hidden" // prevent toggler-button from overflowing 136 // SEE $.layout.defaults.zIndexes.resizer_normal 137 } 138 , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true 139 background: "#DDD" 140 , border: "none" 141 } 142 } 143 , togglers: { 144 cssReq: { 145 position: "absolute" 146 , display: "block" 147 , padding: 0 148 , margin: 0 149 , overflow: "hidden" 150 , textAlign: "center" 151 , fontSize: "1px" 152 , cursor: "pointer" 153 , zIndex: 1 154 } 155 , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true 156 background: "#AAA" 157 } 158 } 159 , content: { 160 cssReq: { 161 position: "relative" /* contain floated or positioned elements */ 162 } 163 , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true 164 overflow: "auto" 165 , padding: "10px" 166 } 167 , cssDemoPane: { // DEMO CSS - REMOVE scrolling from 'pane' when it has a content-div 168 overflow: "hidden" 169 , padding: 0 170 } 171 } 172 , panes: { // defaults for ALL panes - overridden by 'per-pane settings' below 173 cssReq: { 174 position: "absolute" 175 , margin: 0 176 // $.layout.defaults.zIndexes.pane_normal 177 } 178 , cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true 179 padding: "10px" 180 , background: "#FFF" 181 , border: "1px solid #BBB" 182 , overflow: "auto" 183 } 184 } 185 , north: { 186 side: "top" 187 , sizeType: "Height" 188 , dir: "horz" 189 , cssReq: { 190 top: 0 191 , bottom: "auto" 192 , left: 0 193 , right: 0 194 , width: "auto" 195 // height: DYNAMIC 196 } 197 } 198 , south: { 199 side: "bottom" 200 , sizeType: "Height" 201 , dir: "horz" 202 , cssReq: { 203 top: "auto" 204 , bottom: 0 205 , left: 0 206 , right: 0 207 , width: "auto" 208 // height: DYNAMIC 209 } 210 } 211 , east: { 212 side: "right" 213 , sizeType: "Width" 214 , dir: "vert" 215 , cssReq: { 216 left: "auto" 217 , right: 0 218 , top: "auto" // DYNAMIC 219 , bottom: "auto" // DYNAMIC 220 , height: "auto" 221 // width: DYNAMIC 222 } 223 } 224 , west: { 225 side: "left" 226 , sizeType: "Width" 227 , dir: "vert" 228 , cssReq: { 229 left: 0 230 , right: "auto" 231 , top: "auto" // DYNAMIC 232 , bottom: "auto" // DYNAMIC 233 , height: "auto" 234 // width: DYNAMIC 235 } 236 } 237 , center: { 238 dir: "center" 239 , cssReq: { 240 left: "auto" // DYNAMIC 241 , right: "auto" // DYNAMIC 242 , top: "auto" // DYNAMIC 243 , bottom: "auto" // DYNAMIC 244 , height: "auto" 245 , width: "auto" 246 } 247 } 248 } 249 250 // CALLBACK FUNCTION NAMESPACE - used to store reusable callback functions 251, callbacks: {} 252 253, getParentPaneElem: function (el) { 254 // must pass either a container or pane element 255 var $el = $(el) 256 , layout = $el.data("layout") || $el.data("parentLayout"); 257 if (layout) { 258 var $cont = layout.container; 259 // see if this container is directly-nested inside an outer-pane 260 if ($cont.data("layoutPane")) return $cont; 261 var $pane = $cont.closest("."+ $.layout.defaults.panes.paneClass); 262 // if a pane was found, return it 263 if ($pane.data("layoutPane")) return $pane; 264 } 265 return null; 266 } 267 268, getParentPaneInstance: function (el) { 269 // must pass either a container or pane element 270 var $pane = $.layout.getParentPaneElem(el); 271 return $pane ? $pane.data("layoutPane") : null; 272 } 273 274, getParentLayoutInstance: function (el) { 275 // must pass either a container or pane element 276 var $pane = $.layout.getParentPaneElem(el); 277 return $pane ? $pane.data("parentLayout") : null; 278 } 279 280, getEventObject: function (evt) { 281 return typeof evt === "object" && evt.stopPropagation ? evt : null; 282 } 283, parsePaneName: function (evt_or_pane) { 284 var evt = $.layout.getEventObject( evt_or_pane ) 285 , pane = evt_or_pane; 286 if (evt) { 287 // ALWAYS stop propagation of events triggered in Layout! 288 evt.stopPropagation(); 289 pane = $(this).data("layoutEdge"); 290 } 291 if (pane && !/^(west|east|north|south|center)$/.test(pane)) { 292 $.layout.msg('LAYOUT ERROR - Invalid pane-name: "'+ pane +'"'); 293 pane = "error"; 294 } 295 return pane; 296 } 297 298 299 // LAYOUT-PLUGIN REGISTRATION 300 // more plugins can added beyond this default list 301, plugins: { 302 draggable: !!$.fn.draggable // resizing 303 , effects: { 304 core: !!$.effects // animimations (specific effects tested by initOptions) 305 , slide: $.effects && ($.effects.slide || ($.effects.effect && $.effects.effect.slide)) // default effect 306 } 307 } 308 309// arrays of plugin or other methods to be triggered for events in *each layout* - will be passed 'Instance' 310, onCreate: [] // runs when layout is just starting to be created - right after options are set 311, onLoad: [] // runs after layout container and global events init, but before initPanes is called 312, onReady: [] // runs after initialization *completes* - ie, after initPanes completes successfully 313, onDestroy: [] // runs after layout is destroyed 314, onUnload: [] // runs after layout is destroyed OR when page unloads 315, afterOpen: [] // runs after setAsOpen() completes 316, afterClose: [] // runs after setAsClosed() completes 317 318 /* 319 * GENERIC UTILITY METHODS 320 */ 321 322 // calculate and return the scrollbar width, as an integer 323, scrollbarWidth: function () { return window.scrollbarWidth || $.layout.getScrollbarSize('width'); } 324, scrollbarHeight: function () { return window.scrollbarHeight || $.layout.getScrollbarSize('height'); } 325, getScrollbarSize: function (dim) { 326 var $c = $('<div style="position: absolute; top: -10000px; left: -10000px; width: 100px; height: 100px; border: 0; overflow: scroll;"></div>').appendTo("body") 327 , d = { width: $c.outerWidth - $c[0].clientWidth, height: 100 - $c[0].clientHeight }; 328 $c.remove(); 329 window.scrollbarWidth = d.width; 330 window.scrollbarHeight = d.height; 331 return dim.match(/^(width|height)$/) ? d[dim] : d; 332 } 333 334 335, disableTextSelection: function () { 336 var $d = $(document) 337 , s = 'textSelectionDisabled' 338 , x = 'textSelectionInitialized' 339 ; 340 if ($.fn.disableSelection) { 341 if (!$d.data(x)) // document hasn't been initialized yet 342 $d.on('mouseup', $.layout.enableTextSelection ).data(x, true); 343 if (!$d.data(s)) 344 $d.disableSelection().data(s, true); 345 } 346 } 347, enableTextSelection: function () { 348 var $d = $(document) 349 , s = 'textSelectionDisabled'; 350 if ($.fn.enableSelection && $d.data(s)) 351 $d.enableSelection().data(s, false); 352 } 353 354 355 /** 356 * Returns hash container 'display' and 'visibility' 357 * 358 * @see $.swap() - swaps CSS, runs callback, resets CSS 359 * @param {!Object} $E jQuery element 360 * @param {boolean=} [force=false] Run even if display != none 361 * @return {!Object} Returns current style props, if applicable 362 */ 363, showInvisibly: function ($E, force) { 364 if ($E && $E.length && (force || $E.css("display") === "none")) { // only if not *already hidden* 365 var s = $E[0].style 366 // save ONLY the 'style' props because that is what we must restore 367 , CSS = { display: s.display || '', visibility: s.visibility || '' }; 368 // show element 'invisibly' so can be measured 369 $E.css({ display: "block", visibility: "hidden" }); 370 return CSS; 371 } 372 return {}; 373 } 374 375 /** 376 * Returns data for setting size of an element (container or a pane). 377 * 378 * @see _create(), onWindowResize() for container, plus others for pane 379 * @return JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc 380 */ 381, getElementDimensions: function ($E, inset) { 382 var 383 // dimensions hash - start with current data IF passed 384 d = { css: {}, inset: {} } 385 , x = d.css // CSS hash 386 , i = { bottom: 0 } // TEMP insets (bottom = complier hack) 387 , N = $.layout.cssNum 388 , R = Math.round 389 , off = $E.offset() 390 , b, p, ei // TEMP border, padding 391 ; 392 d.offsetLeft = off.left; 393 d.offsetTop = off.top; 394 395 if (!inset) inset = {}; // simplify logic below 396 397 $.each("Left,Right,Top,Bottom".split(","), function (idx, e) { // e = edge 398 b = x["border" + e] = $.layout.borderWidth($E, e); 399 p = x["padding"+ e] = $.layout.cssNum($E, "padding"+e); 400 ei = e.toLowerCase(); 401 d.inset[ei] = inset[ei] >= 0 ? inset[ei] : p; // any missing insetX value = paddingX 402 i[ei] = d.inset[ei] + b; // total offset of content from outer side 403 }); 404 405 x.width = R($E.width()); 406 x.height = R($E.height()); 407 x.top = N($E,"top",true); 408 x.bottom = N($E,"bottom",true); 409 x.left = N($E,"left",true); 410 x.right = N($E,"right",true); 411 412 d.outerWidth = R($E.outerWidth()); 413 d.outerHeight = R($E.outerHeight()); 414 // calc the TRUE inner-dimensions, even in quirks-mode! 415 d.innerWidth = max(0, d.outerWidth - i.left - i.right); 416 d.innerHeight = max(0, d.outerHeight - i.top - i.bottom); 417 // layoutWidth/Height is used in calcs for manual resizing 418 // layoutW/H only differs from innerW/H when in quirks-mode - then is like outerW/H 419 d.layoutWidth = R($E.innerWidth()); 420 d.layoutHeight = R($E.innerHeight()); 421 422 //if ($E.prop('tagName') === 'BODY') { debugData( d, $E.prop('tagName') ); } // DEBUG 423 424 //d.visible = $E.is(":visible");// && x.width > 0 && x.height > 0; 425 426 return d; 427 } 428 429, getElementStyles: function ($E, list) { 430 var 431 CSS = {} 432 , style = $E[0].style 433 , props = list.split(",") 434 , sides = "Top,Bottom,Left,Right".split(",") 435 , attrs = "Color,Style,Width".split(",") 436 , p, s, a, i, j, k 437 ; 438 for (i=0; i < props.length; i++) { 439 p = props[i]; 440 if (p.match(/(border|padding|margin)$/)) 441 for (j=0; j < 4; j++) { 442 s = sides[j]; 443 if (p === "border") 444 for (k=0; k < 3; k++) { 445 a = attrs[k]; 446 CSS[p+s+a] = style[p+s+a]; 447 } 448 else 449 CSS[p+s] = style[p+s]; 450 } 451 else 452 CSS[p] = style[p]; 453 }; 454 return CSS 455 } 456 457 /** 458 * Return the innerWidth for the current browser/doctype 459 * 460 * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles() 461 * @param {Array.<Object>} $E Must pass a jQuery object - first element is processed 462 * @param {number=} outerWidth (optional) Can pass a width, allowing calculations BEFORE element is resized 463 * @return {number} Returns the innerWidth of the elem by subtracting padding and borders 464 */ 465, cssWidth: function ($E, outerWidth) { 466 // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed 467 if (outerWidth <= 0) return 0; 468 469 var lb = $.layout.browser 470 , bs = !lb.boxModel ? "border-box" : lb.boxSizing ? $E.css("boxSizing") : "content-box" 471 , b = $.layout.borderWidth 472 , n = $.layout.cssNum 473 , W = outerWidth 474 ; 475 // strip border and/or padding from outerWidth to get CSS Width 476 if (bs !== "border-box") 477 W -= (b($E, "Left") + b($E, "Right")); 478 if (bs === "content-box") 479 W -= (n($E, "paddingLeft") + n($E, "paddingRight")); 480 return max(0,W); 481 } 482 483 /** 484 * Return the innerHeight for the current browser/doctype 485 * 486 * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles() 487 * @param {Array.<Object>} $E Must pass a jQuery object - first element is processed 488 * @param {number=} outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized 489 * @return {number} Returns the innerHeight of the elem by subtracting padding and borders 490 */ 491, cssHeight: function ($E, outerHeight) { 492 // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed 493 if (outerHeight <= 0) return 0; 494 495 var lb = $.layout.browser 496 , bs = !lb.boxModel ? "border-box" : lb.boxSizing ? $E.css("boxSizing") : "content-box" 497 , b = $.layout.borderWidth 498 , n = $.layout.cssNum 499 , H = outerHeight 500 ; 501 // strip border and/or padding from outerHeight to get CSS Height 502 if (bs !== "border-box") 503 H -= (b($E, "Top") + b($E, "Bottom")); 504 if (bs === "content-box") 505 H -= (n($E, "paddingTop") + n($E, "paddingBottom")); 506 return max(0,H); 507 } 508 509 /** 510 * Returns the 'current CSS numeric value' for a CSS property - 0 if property does not exist 511 * 512 * @see Called by many methods 513 * @param {Array.<Object>} $E Must pass a jQuery object - first element is processed 514 * @param {string} prop The name of the CSS property, eg: top, width, etc. 515 * @param {boolean=} [allowAuto=false] true = return 'auto' if that is value; false = return 0 516 * @return {(string|number)} Usually used to get an integer value for position (top, left) or size (height, width) 517 */ 518, cssNum: function ($E, prop, allowAuto) { 519 if (!$E.jquery) $E = $($E); 520 var CSS = $.layout.showInvisibly($E) 521 , p = $.css($E[0], prop, true) 522 , v = allowAuto && p=="auto" ? p : Math.round(parseFloat(p) || 0); 523 $E.css( CSS ); // RESET 524 return v; 525 } 526 527, borderWidth: function (el, side) { 528 if (el.jquery) el = el[0]; 529 var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left 530 return $.css(el, b+"Style", true) === "none" ? 0 : Math.round(parseFloat($.css(el, b+"Width", true)) || 0); 531 } 532 533 /** 534 * Mouse-tracking utility - FUTURE REFERENCE 535 * 536 * init: if (!window.mouse) { 537 * window.mouse = { x: 0, y: 0 }; 538 * $(document).mousemove( $.layout.trackMouse ); 539 * } 540 * 541 * @param {Object} evt 542 * 543, trackMouse: function (evt) { 544 window.mouse = { x: evt.clientX, y: evt.clientY }; 545 } 546 */ 547 548 /** 549 * SUBROUTINE for preventPrematureSlideClose option 550 * 551 * @param {Object} evt 552 * @param {Object=} el 553 */ 554, isMouseOverElem: function (evt, el) { 555 var 556 $E = $(el || this) 557 , d = $E.offset() 558 , T = d.top 559 , L = d.left 560 , R = L + $E.outerWidth() 561 , B = T + $E.outerHeight() 562 , x = evt.pageX // evt.clientX ? 563 , y = evt.pageY // evt.clientY ? 564 ; 565 // if X & Y are < 0, probably means is over an open SELECT 566 return ($.layout.browser.msie && x < 0 && y < 0) || ((x >= L && x <= R) && (y >= T && y <= B)); 567 } 568 569 /** 570 * Message/Logging Utility 571 * 572 * @example $.layout.msg("My message"); // log text 573 * @example $.layout.msg("My message", true); // alert text 574 * @example $.layout.msg({ foo: "bar" }, "Title"); // log hash-data, with custom title 575 * @example $.layout.msg({ foo: "bar" }, true, "Title", { sort: false }); -OR- 576 * @example $.layout.msg({ foo: "bar" }, "Title", { sort: false, display: true }); // alert hash-data 577 * 578 * @param {(Object|string)} info String message OR Hash/Array 579 * @param {(Boolean|string|Object)=} [popup=false] True means alert-box - can be skipped 580 * @param {(Object|string)=} [debugTitle=""] Title for Hash data - can be skipped 581 * @param {Object=} [debugOpts] Extra options for debug output 582 */ 583, msg: function (info, popup, debugTitle, debugOpts) { 584 if ($.isPlainObject(info) && window.debugData) { 585 if (typeof popup === "string") { 586 debugOpts = debugTitle; 587 debugTitle = popup; 588 } 589 else if (typeof debugTitle === "object") { 590 debugOpts = debugTitle; 591 debugTitle = null; 592 } 593 var t = debugTitle || "log( <object> )" 594 , o = $.extend({ sort: false, returnHTML: false, display: false }, debugOpts); 595 if (popup === true || o.display) 596 debugData( info, t, o ); 597 else if (window.console) 598 console.log(debugData( info, t, o )); 599 } 600 else if (popup) 601 alert(info); 602 else if (window.console) 603 console.log(info); 604 else { 605 var id = "#layoutLogger" 606 , $l = $(id); 607 if (!$l.length) 608 $l = createLog(); 609 $l.children("ul").append('<li style="padding: 4px 10px; margin: 0; border-top: 1px solid #CCC;">'+ info.replace(/\</g,"<").replace(/\>/g,">") +'</li>'); 610 } 611 612 function createLog () { 613 var pos = $.support.fixedPosition ? 'fixed' : 'absolute' 614 , $e = $('<div id="layoutLogger" style="position: '+ pos +'; top: 5px; z-index: 999999; max-width: 25%; overflow: hidden; border: 1px solid #000; border-radius: 5px; background: #FBFBFB; box-shadow: 0 2px 10px rgba(0,0,0,0.3);">' 615 + '<div style="font-size: 13px; font-weight: bold; padding: 5px 10px; background: #F6F6F6; border-radius: 5px 5px 0 0; cursor: move;">' 616 + '<span style="float: right; padding-left: 7px; cursor: pointer;" title="Remove Console" onclick="$(this).closest(\'#layoutLogger\').remove()">X</span>Layout console.log</div>' 617 + '<ul style="font-size: 13px; font-weight: none; list-style: none; margin: 0; padding: 0 0 2px;"></ul>' 618 + '</div>' 619 ).appendTo("body"); 620 $e.css('left', $(window).width() - $e.outerWidth() - 5) 621 if ($.ui.draggable) $e.draggable({ handle: ':first-child' }); 622 return $e; 623 }; 624 } 625 626}; 627 628 629/* 630 * $.layout.browser REPLACES removed $.browser, with extra data 631 * Parsing code here adapted from jQuery 1.8 $.browse 632 */ 633(function(){ 634 var u = navigator.userAgent.toLowerCase() 635 , m = /(chrome)[ \/]([\w.]+)/.exec( u ) 636 || /(webkit)[ \/]([\w.]+)/.exec( u ) 637 || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( u ) 638 || /(msie) ([\w.]+)/.exec( u ) 639 || u.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( u ) 640 || [] 641 , b = m[1] || "" 642 , v = m[2] || 0 643 , ie = b === "msie" 644 , cm = document.compatMode 645 , $s = $.support 646 , bs = $s.boxSizing !== undefined ? $s.boxSizing : $s.boxSizingReliable 647 , bm = !ie || !cm || cm === "CSS1Compat" || $s.boxModel || false 648 , lb = $.layout.browser = { 649 version: v 650 , safari: b === "webkit" // webkit (NOT chrome) = safari 651 , webkit: b === "chrome" // chrome = webkit 652 , msie: ie 653 , isIE6: ie && v == 6 654 // ONLY IE reverts to old box-model - Note that compatMode was deprecated as of IE8 655 , boxModel: bm 656 , boxSizing: !!(typeof bs === "function" ? bs() : bs) 657 }; 658 ; 659 if (b) lb[b] = true; // set CURRENT browser 660 /* OLD versions of jQuery only set $.support.boxModel after page is loaded 661 * so if this is IE, use support.boxModel to test for quirks-mode (ONLY IE changes boxModel) */ 662 if (!bm && !cm) $(function(){ lb.boxModel = $s.boxModel; }); 663})(); 664 665 666// DEFAULT OPTIONS 667$.layout.defaults = { 668/* 669 * LAYOUT & LAYOUT-CONTAINER OPTIONS 670 * - none of these options are applicable to individual panes 671 */ 672 name: "" // Not required, but useful for buttons and used for the state-cookie 673, containerClass: "ui-layout-container" // layout-container element 674, inset: null // custom container-inset values (override padding) 675, scrollToBookmarkOnLoad: true // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark) 676, resizeWithWindow: true // bind thisLayout.resizeAll() to the window.resize event 677, resizeWithWindowDelay: 200 // delay calling resizeAll because makes window resizing very jerky 678, resizeWithWindowMaxDelay: 0 // 0 = none - force resize every XX ms while window is being resized 679, maskPanesEarly: false // true = create pane-masks on resizer.mouseDown instead of waiting for resizer.dragstart 680, onresizeall_start: null // CALLBACK when resizeAll() STARTS - NOT pane-specific 681, onresizeall_end: null // CALLBACK when resizeAll() ENDS - NOT pane-specific 682, onload_start: null // CALLBACK when Layout inits - after options initialized, but before elements 683, onload_end: null // CALLBACK when Layout inits - after EVERYTHING has been initialized 684, onunload_start: null // CALLBACK when Layout is destroyed OR onWindowUnload 685, onunload_end: null // CALLBACK when Layout is destroyed OR onWindowUnload 686, initPanes: true // false = DO NOT initialize the panes onLoad - will init later 687, showErrorMessages: true // enables fatal error messages to warn developers of common errors 688, showDebugMessages: false // display console-and-alert debug msgs - IF this Layout version _has_ debugging code! 689// Changing this zIndex value will cause other zIndex values to automatically change 690, zIndex: null // the PANE zIndex - resizers and masks will be +1 691// DO NOT CHANGE the zIndex values below unless you clearly understand their relationships 692, zIndexes: { // set _default_ z-index values here... 693 pane_normal: 0 // normal z-index for panes 694 , content_mask: 1 // applied to overlays used to mask content INSIDE panes during resizing 695 , resizer_normal: 2 // normal z-index for resizer-bars 696 , pane_sliding: 100 // applied to *BOTH* the pane and its resizer when a pane is 'slid open' 697 , pane_animate: 1000 // applied to the pane when being animated - not applied to the resizer 698 , resizer_drag: 10000 // applied to the CLONED resizer-bar when being 'dragged' 699 } 700, errors: { 701 pane: "pane" // description of "layout pane element" - used only in error messages 702 , selector: "selector" // description of "jQuery-selector" - used only in error messages 703 , addButtonError: "Error Adding Button\nInvalid " 704 , containerMissing: "UI Layout Initialization Error\nThe specified layout-container does not exist." 705 , centerPaneMissing: "UI Layout Initialization Error\nThe center-pane element does not exist.\nThe center-pane is a required element." 706 , noContainerHeight: "UI Layout Initialization Warning\nThe layout-container \"CONTAINER\" has no height.\nTherefore the layout is 0-height and hence 'invisible'!" 707 , callbackError: "UI Layout Callback Error\nThe EVENT callback is not a valid function." 708 } 709/* 710 * PANE DEFAULT SETTINGS 711 * - settings under the 'panes' key become the default settings for *all panes* 712 * - ALL pane-options can also be set specifically for each panes, which will override these 'default values' 713 */ 714, panes: { // default options for 'all panes' - will be overridden by 'per-pane settings' 715 applyDemoStyles: false // NOTE: renamed from applyDefaultStyles for clarity 716 , closable: true // pane can open & close 717 , resizable: true // when open, pane can be resized 718 , slidable: true // when closed, pane can 'slide open' over other panes - closes on mouse-out 719 , initClosed: false // true = init pane as 'closed' 720 , initHidden: false // true = init pane as 'hidden' - no resizer-bar/spacing 721 // SELECTORS 722 //, paneSelector: "" // MUST be pane-specific - jQuery selector for pane 723 , contentSelector: ".ui-layout-content" // INNER div/element to auto-size so only it scrolls, not the entire pane! 724 , contentIgnoreSelector: ".ui-layout-ignore" // element(s) to 'ignore' when measuring 'content' 725 , findNestedContent: false // true = $P.find(contentSelector), false = $P.children(contentSelector) 726 // GENERIC ROOT-CLASSES - for auto-generated classNames 727 , paneClass: "ui-layout-pane" // Layout Pane 728 , resizerClass: "ui-layout-resizer" // Resizer Bar 729 , togglerClass: "ui-layout-toggler" // Toggler Button 730 , buttonClass: "ui-layout-button" // CUSTOM Buttons - eg: '[ui-layout-button]-toggle/-open/-close/-pin' 731 // ELEMENT SIZE & SPACING 732 //, size: 100 // MUST be pane-specific -initial size of pane 733 , minSize: 0 // when manually resizing a pane 734 , maxSize: 0 // ditto, 0 = no limit 735 , spacing_open: 6 // space between pane and adjacent panes - when pane is 'open' 736 , spacing_closed: 6 // ditto - when pane is 'closed' 737 , togglerLength_open: 50 // Length = WIDTH of toggler button on north/south sides - HEIGHT on east/west sides 738 , togglerLength_closed: 50 // 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden' 739 , togglerAlign_open: "center" // top/left, bottom/right, center, OR... 740 , togglerAlign_closed: "center" // 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right 741 , togglerContent_open: "" // text or HTML to put INSIDE the toggler 742 , togglerContent_closed: "" // ditto 743 // RESIZING OPTIONS 744 , resizerDblClickToggle: true // 745 , autoResize: true // IF size is 'auto' or a percentage, then recalc 'pixel size' whenever the layout resizes 746 , autoReopen: true // IF a pane was auto-closed due to noRoom, reopen it when there is room? False = leave it closed 747 , resizerDragOpacity: 1 // option for ui.draggable 748 //, resizerCursor: "" // MUST be pane-specific - cursor when over resizer-bar 749 , maskContents: false // true = add DIV-mask over-or-inside this pane so can 'drag' over IFRAMES 750 , maskObjects: false // true = add IFRAME-mask over-or-inside this pane to cover objects/applets - content-mask will overlay this mask 751 , maskZindex: null // will override zIndexes.content_mask if specified - not applicable to iframe-panes 752 , resizingGrid: false // grid size that the resizers will snap-to during resizing, eg: [20,20] 753 , livePaneResizing: false // true = LIVE Resizing as resizer is dragged 754 , liveContentResizing: false // true = re-measure header/footer heights as resizer is dragged 755 , liveResizingTolerance: 1 // how many px change before pane resizes, to control performance 756 // SLIDING OPTIONS 757 , sliderCursor: "pointer" // cursor when resizer-bar will trigger 'sliding' 758 , slideTrigger_open: "click" // click, dblclick, mouseenter 759 , slideTrigger_close: "mouseleave"// click, mouseleave 760 , slideDelay_open: 300 // applies only for mouseenter event - 0 = instant open 761 , slideDelay_close: 300 // applies only for mouseleave event (300ms is the minimum!) 762 , hideTogglerOnSlide: false // when pane is slid-open, should the toggler show? 763 , preventQuickSlideClose: $.layout.browser.webkit // Chrome triggers slideClosed as it is opening 764 , preventPrematureSlideClose: false // handle incorrect mouseleave trigger, like when over a SELECT-list in IE 765 // PANE-SPECIFIC TIPS & MESSAGES 766 , tips: { 767 Open: "Open" // eg: "Open Pane" 768 , Close: "Close" 769 , Resize: "Resize" 770 , Slide: "Slide Open" 771 , Pin: "Pin" 772 , Unpin: "Un-Pin" 773 , noRoomToOpen: "Not enough room to show this panel." // alert if user tries to open a pane that cannot 774 , minSizeWarning: "Panel has reached its minimum size" // displays in browser statusbar 775 , maxSizeWarning: "Panel has reached its maximum size" // ditto 776 } 777 // HOT-KEYS & MISC 778 , showOverflowOnHover: false // will bind allowOverflow() utility to pane.onMouseOver 779 , enableCursorHotkey: true // enabled 'cursor' hotkeys 780 //, customHotkey: "" // MUST be pane-specific - EITHER a charCode OR a character 781 , customHotkeyModifier: "SHIFT" // either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT' 782 // PANE ANIMATION 783 // NOTE: fxSss_open, fxSss_close & fxSss_size options (eg: fxName_open) are auto-generated if not passed 784 , fxName: "slide" // ('none' or blank), slide, drop, scale -- only relevant to 'open' & 'close', NOT 'size' 785 , fxSpeed: null // slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration 786 , fxSettings: {} // can be passed, eg: { easing: "easeOutBounce", duration: 1500 } 787 , fxOpacityFix: true // tries to fix opacity in IE to restore anti-aliasing after animation 788 , animatePaneSizing: false // true = animate resizing after dragging resizer-bar OR sizePane() is called 789 /* NOTE: Action-specific FX options are auto-generated from the options above if not specifically set: 790 fxName_open: "slide" // 'Open' pane animation 791 fnName_close: "slide" // 'Close' pane animation 792 fxName_size: "slide" // 'Size' pane animation - when animatePaneSizing = true 793 fxSpeed_open: null 794 fxSpeed_close: null 795 fxSpeed_size: null 796 fxSettings_open: {} 797 fxSettings_close: {} 798 fxSettings_size: {} 799 */ 800 // CHILD/NESTED LAYOUTS 801 , children: null // Layout-options for nested/child layout - even {} is valid as options 802 , containerSelector: '' // if child is NOT 'directly nested', a selector to find it/them (can have more than one child layout!) 803 , initChildren: true // true = child layout will be created as soon as _this_ layout completes initialization 804 , destroyChildren: true // true = destroy child-layout if this pane is destroyed 805 , resizeChildren: true // true = trigger child-layout.resizeAll() when this pane is resized 806 // EVENT TRIGGERING 807 , triggerEventsOnLoad: false // true = trigger onopen OR onclose callbacks when layout initializes 808 , triggerEventsDuringLiveResize: true // true = trigger onresize callback REPEATEDLY if livePaneResizing==true 809 // PANE CALLBACKS 810 , onshow_start: null // CALLBACK when pane STARTS to Show - BEFORE onopen/onhide_start 811 , onshow_end: null // CALLBACK when pane ENDS being Shown - AFTER onopen/onhide_end 812 , onhide_start: null // CALLBACK when pane STARTS to Close - BEFORE onclose_start 813 , onhide_end: null // CALLBACK when pane ENDS being Closed - AFTER onclose_end 814 , onopen_start: null // CALLBACK when pane STARTS to Open 815 , onopen_end: null // CALLBACK when pane ENDS being Opened 816 , onclose_start: null // CALLBACK when pane STARTS to Close 817 , onclose_end: null // CALLBACK when pane ENDS being Closed 818 , onresize_start: null // CALLBACK when pane STARTS being Resized ***FOR ANY REASON*** 819 , onresize_end: null // CALLBACK when pane ENDS being Resized ***FOR ANY REASON*** 820 , onsizecontent_start: null // CALLBACK when sizing of content-element STARTS 821 , onsizecontent_end: null // CALLBACK when sizing of content-element ENDS 822 , onswap_start: null // CALLBACK when pane STARTS to Swap 823 , onswap_end: null // CALLBACK when pane ENDS being Swapped 824 , ondrag_start: null // CALLBACK when pane STARTS being ***MANUALLY*** Resized 825 , ondrag_end: null // CALLBACK when pane ENDS being ***MANUALLY*** Resized 826 } 827/* 828 * PANE-SPECIFIC SETTINGS 829 * - options listed below MUST be specified per-pane - they CANNOT be set under 'panes' 830 * - all options under the 'panes' key can also be set specifically for any pane 831 * - most options under the 'panes' key apply only to 'border-panes' - NOT the the center-pane 832 */ 833, north: { 834 paneSelector: ".ui-layout-north" 835 , size: "auto" // eg: "auto", "30%", .30, 200 836 , resizerCursor: "n-resize" // custom = url(myCursor.cur) 837 , customHotkey: "" // EITHER a charCode (43) OR a character ("o") 838 } 839, south: { 840 paneSelector: ".ui-layout-south" 841 , size: "auto" 842 , resizerCursor: "s-resize" 843 , customHotkey: "" 844 } 845, east: { 846 paneSelector: ".ui-layout-east" 847 , size: 200 848 , resizerCursor: "e-resize" 849 , customHotkey: "" 850 } 851, west: { 852 paneSelector: ".ui-layout-west" 853 , size: 200 854 , resizerCursor: "w-resize" 855 , customHotkey: "" 856 } 857, center: { 858 paneSelector: ".ui-layout-center" 859 , minWidth: 0 860 , minHeight: 0 861 } 862}; 863 864$.layout.optionsMap = { 865 // layout/global options - NOT pane-options 866 layout: ("name,instanceKey,stateManagement,effects,inset,zIndexes,errors," 867 + "zIndex,scrollToBookmarkOnLoad,showErrorMessages,maskPanesEarly," 868 + "outset,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay," 869 + "onresizeall,onresizeall_start,onresizeall_end,onload,onload_start,onload_end,onunload,onunload_start,onunload_end").split(",") 870// borderPanes: [ ALL options that are NOT specified as 'layout' ] 871 // default.panes options that apply to the center-pane (most options apply _only_ to border-panes) 872, center: ("paneClass,contentSelector,contentIgnoreSelector,findNestedContent,applyDemoStyles,triggerEventsOnLoad," 873 + "showOverflowOnHover,maskContents,maskObjects,liveContentResizing," 874 + "containerSelector,children,initChildren,resizeChildren,destroyChildren," 875 + "onresize,onresize_start,onresize_end,onsizecontent,onsizecontent_start,onsizecontent_end").split(",") 876 // options that MUST be specifically set 'per-pane' - CANNOT set in the panes (defaults) key 877, noDefault: ("paneSelector,resizerCursor,customHotkey").split(",") 878}; 879 880/** 881 * Processes options passed in converts flat-format data into subkey (JSON) format 882 * In flat-format, subkeys are _currently_ separated with 2 underscores, like north__optName 883 * Plugins may also call this method so they can transform their own data 884 * 885 * @param {!Object} hash Data/options passed by user - may be a single level or nested levels 886 * @param {boolean=} [addKeys=false] Should the primary layout.options keys be added if they do not exist? 887 * @return {Object} Returns hash of minWidth & minHeight 888 */ 889$.layout.transformData = function (hash, addKeys) { 890 var json = addKeys ? { panes: {}, center: {} } : {} // init return object 891 , branch, optKey, keys, key, val, i, c; 892 893 if (typeof hash !== "object") return json; // no options passed 894 895 // convert all 'flat-keys' to 'sub-key' format 896 for (optKey in hash) { 897 branch = json; 898 val = hash[ optKey ]; 899 keys = optKey.split("__"); // eg: west__size or north__fxSettings__duration 900 c = keys.length - 1; 901 // convert underscore-delimited to subkeys 902 for (i=0; i <= c; i++) { 903 key = keys[i]; 904 if (i === c) { // last key = value 905 if ($.isPlainObject( val )) 906 branch[key] = $.layout.transformData( val ); // RECURSE 907 else 908 branch[key] = val; 909 } 910 else { 911 if (!branch[key]) 912 branch[key] = {}; // create the subkey 913 // recurse to sub-key for next loop - if not done 914 branch = branch[key]; 915 } 916 } 917 } 918 return json; 919}; 920 921// INTERNAL CONFIG DATA - DO NOT CHANGE THIS! 922$.layout.backwardCompatibility = { 923 // data used by renameOldOptions() 924 map: { 925 // OLD Option Name: NEW Option Name 926 applyDefaultStyles: "applyDemoStyles" 927 // CHILD/NESTED LAYOUTS 928 , childOptions: "children" 929 , initChildLayout: "initChildren" 930 , destroyChildLayout: "destroyChildren" 931 , resizeChildLayout: "resizeChildren" 932 , resizeNestedLayout: "resizeChildren" 933 // MISC Options 934 , resizeWhileDragging: "livePaneResizing" 935 , resizeContentWhileDragging: "liveContentResizing" 936 , triggerEventsWhileDragging: "triggerEventsDuringLiveResize" 937 , maskIframesOnResize: "maskContents" 938 // STATE MANAGEMENT 939 , useStateCookie: "stateManagement.enabled" 940 , "cookie.autoLoad": "stateManagement.autoLoad" 941 , "cookie.autoSave": "stateManagement.autoSave" 942 , "cookie.keys": "stateManagement.stateKeys" 943 , "cookie.name": "stateManagement.cookie.name" 944 , "cookie.domain": "stateManagement.cookie.domain" 945 , "cookie.path": "stateManagement.cookie.path" 946 , "cookie.expires": "stateManagement.cookie.expires" 947 , "cookie.secure": "stateManagement.cookie.secure" 948 // OLD Language options 949 , noRoomToOpenTip: "tips.noRoomToOpen" 950 , togglerTip_open: "tips.Close" // open = Close 951 , togglerTip_closed: "tips.Open" // closed = Open 952 , resizerTip: "tips.Resize" 953 , sliderTip: "tips.Slide" 954 } 955 956/** 957* @param {Object} opts 958*/ 959, renameOptions: function (opts) { 960 var map = $.layout.backwardCompatibility.map 961 , oldData, newData, value 962 ; 963 for (var itemPath in map) { 964 oldData = getBranch( itemPath ); 965 value = oldData.branch[ oldData.key ]; 966 if (value !== undefined) { 967 newData = getBranch( map[itemPath], true ); 968 newData.branch[ newData.key ] = value; 969 delete oldData.branch[ oldData.key ]; 970 } 971 } 972 973 /** 974 * @param {string} path 975 * @param {boolean=} [create=false] Create path if does not exist 976 */ 977 function getBranch (path, create) { 978 var a = path.split(".") // split keys into array 979 , c = a.length - 1 980 , D = { branch: opts, key: a[c] } // init branch at top & set key (last item) 981 , i = 0, k, undef; 982 for (; i<c; i++) { // skip the last key (data) 983 k = a[i]; 984 if (D.branch[ k ] == undefined) { // child-key does not exist 985 if (create) { 986 D.branch = D.branch[ k ] = {}; // create child-branch 987 } 988 else // can't go any farther 989 D.branch = {}; // branch is undefined 990 } 991 else 992 D.branch = D.branch[ k ]; // get child-branch 993 } 994 return D; 995 }; 996 } 997 998/** 999* @param {Object} opts 1000*/ 1001, renameAllOptions: function (opts) { 1002 var ren = $.layout.backwardCompatibility.renameOptions; 1003 // rename root (layout) options 1004 ren( opts ); 1005 // rename 'defaults' to 'panes' 1006 if (opts.defaults) { 1007 if (typeof opts.panes !== "object") 1008 opts.panes = {}; 1009 $.extend(true, opts.panes, opts.defaults); 1010 delete opts.defaults; 1011 } 1012 // rename options in the the options.panes key 1013 if (opts.panes) ren( opts.panes ); 1014 // rename options inside *each pane key*, eg: options.west 1015 $.each($.layout.config.allPanes, function (i, pane) { 1016 if (opts[pane]) ren( opts[pane] ); 1017 }); 1018 return opts; 1019 } 1020}; 1021 1022 1023 1024 1025/* ============================================================ 1026 * BEGIN WIDGET: $( selector ).layout( {options} ); 1027 * ============================================================ 1028 */ 1029$.fn.layout = function (opts) { 1030 var 1031 1032 // local aliases to global data 1033 browser = $.layout.browser 1034, _c = $.layout.config 1035 1036 // local aliases to utlity methods 1037, cssW = $.layout.cssWidth 1038, cssH = $.layout.cssHeight 1039, elDims = $.layout.getElementDimensions 1040, styles = $.layout.getElementStyles 1041, evtObj = $.layout.getEventObject 1042, evtPane = $.layout.parsePaneName 1043 1044/** 1045 * options - populated by initOptions() 1046 */ 1047, options = $.extend(true, {}, $.layout.defaults) 1048, effects = options.effects = $.extend(true, {}, $.layout.effects) 1049 1050/** 1051 * layout-state object 1052 */ 1053, state = { 1054 // generate unique ID to use for event.namespace so can unbind only events added by 'this layout' 1055 id: "layout"+ $.now() // code uses alias: sID 1056 , initialized: false 1057 , paneResizing: false 1058 , panesSliding: {} 1059 , container: { // list all keys referenced in code to avoid compiler error msgs 1060 innerWidth: 0 1061 , innerHeight: 0 1062 , outerWidth: 0 1063 , outerHeight: 0 1064 , layoutWidth: 0 1065 , layoutHeight: 0 1066 } 1067 , north: { childIdx: 0 } 1068 , south: { childIdx: 0 } 1069 , east: { childIdx: 0 } 1070 , west: { childIdx: 0 } 1071 , center: { childIdx: 0 } 1072 } 1073 1074/** 1075 * parent/child-layout pointers 1076 */ 1077//, hasParentLayout = false - exists ONLY inside Instance so can be set externally 1078, children = { 1079 north: null 1080 , south: null 1081 , east: null 1082 , west: null 1083 , center: null 1084 } 1085 1086/* 1087 * ########################### 1088 * INTERNAL HELPER FUNCTIONS 1089 * ########################### 1090 */ 1091 1092 /** 1093 * Manages all internal timers 1094 */ 1095, timer = { 1096 data: {} 1097 , set: function (s, fn, ms) { timer.clear(s); timer.data[s] = setTimeout(fn, ms); } 1098 , clear: function (s) { var t=timer.data; if (t[s]) {clearTimeout(t[s]); delete t[s];} } 1099 } 1100 1101 /** 1102 * Alert or console.log a message - IF option is enabled. 1103 * 1104 * @param {(string|!Object)} msg Message (or debug-data) to display 1105 * @param {boolean=} [popup=false] True by default, means 'alert', false means use console.log 1106 * @param {boolean=} [debug=false] True means is a widget debugging message 1107 */ 1108, _log = function (msg, popup, debug) { 1109 var o = options; 1110 if ((o.showErrorMessages && !debug) || (debug && o.showDebugMessages)) 1111 $.layout.msg( o.name +' / '+ msg, (popup !== false) ); 1112 return false; 1113 } 1114 1115 /** 1116 * Executes a Callback function after a trigger event, like resize, open or close 1117 * 1118 * @param {string} evtName Name of the layout callback, eg "onresize_start" 1119 * @param {(string|boolean)=} [pane=""] This is passed only so we can pass the 'pane object' to the callback 1120 * @param {(string|boolean)=} [skipBoundEvents=false] True = do not run events bound to the elements - only the callbacks set in options 1121 */ 1122, _runCallbacks = function (evtName, pane, skipBoundEvents) { 1123 var hasPane = pane && isStr(pane) 1124 , s = hasPane ? state[pane] : state 1125 , o = hasPane ? options[pane] : options 1126 , lName = options.name 1127 // names like onopen and onopen_end separate are interchangeable in options... 1128 , lng = evtName + (evtName.match(/_/) ? "" : "_end") 1129 , shrt = lng.match(/_end$/) ? lng.substr(0, lng.length - 4) : "" 1130 , fn = o[lng] || o[shrt] 1131 , retVal = "NC" // NC = No Callback 1132 , args = [] 1133 , $P = hasPane ? $Ps[pane] : 0 1134 ; 1135 if (hasPane && !$P) // a pane is specified, but does not exist! 1136 return retVal; 1137 if ( !hasPane && $.type(pane) === "boolean" ) { 1138 skipBoundEvents = pane; // allow pane param to be skipped for Layout callback 1139 pane = ""; 1140 } 1141 1142 // first trigger the callback set in the options 1143 if (fn) { 1144 try { 1145 // convert function name (string) to function object 1146 if (isStr( fn )) { 1147 if (fn.match(/,/)) { 1148 // function name cannot contain a comma, 1149 // so must be a function name AND a parameter to pass 1150 args = fn.split(",") 1151 , fn = eval(args[0]); 1152 } 1153 else // just the name of an external function? 1154 fn = eval(fn); 1155 } 1156 // execute the callback, if exists 1157 if ($.isFunction( fn )) { 1158 if (args.length) 1159 retVal = g(fn)(args[1]); // pass the argument parsed from 'list' 1160 else if ( hasPane ) 1161 // pass data: pane-name, pane-element, pane-state, pane-options, and layout-name 1162 retVal = g(fn)( pane, $Ps[pane], s, o, lName ); 1163 else // must be a layout/container callback - pass suitable info 1164 retVal = g(fn)( Instance, s, o, lName ); 1165 } 1166 } 1167 catch (ex) { 1168 _log( options.errors.callbackError.replace(/EVENT/, $.trim((pane || "") +" "+ lng)), false ); 1169 if ($.type(ex) === "string" && string.length) 1170 _log("Exception: "+ ex, false ); 1171 } 1172 } 1173 1174 // trigger additional events bound directly to the pane 1175 if (!skipBoundEvents && retVal !== false) { 1176 if ( hasPane ) { // PANE events can be bound to each pane-elements 1177 o = options[pane]; 1178 s = state[pane]; 1179 $P.triggerHandler("layoutpane"+ lng, [ pane, $P, s, o, lName ]); 1180 if (shrt) 1181 $P.triggerHandler("layoutpane"+ shrt, [ pane, $P, s, o, lName ]); 1182 } 1183 else { // LAYOUT events can be bound to the container-element 1184 $N.triggerHandler("layout"+ lng, [ Instance, s, o, lName ]); 1185 if (shrt) 1186 $N.triggerHandler("layout"+ shrt, [ Instance, s, o, lName ]); 1187 } 1188 } 1189 1190 // ALWAYS resizeChildren after an onresize_end event - even during initialization 1191 // IGNORE onsizecontent_end event because causes child-layouts to resize TWICE 1192 if (hasPane && evtName === "onresize_end") // BAD: || evtName === "onsizecontent_end" 1193 resizeChildren(pane+"", true); // compiler hack -force string 1194 1195 return retVal; 1196 1197 function g (f) { return f; }; // compiler hack 1198 } 1199 1200 1201 /** 1202 * cure iframe display issues in IE & other browsers 1203 */ 1204, _fixIframe = function (pane) { 1205 if (browser.mozilla) return; // skip FireFox - it auto-refreshes iframes onShow 1206 var $P = $Ps[pane]; 1207 // if the 'pane' is an iframe, do it 1208 if (state[pane].tagName === "IFRAME") 1209 $P.css(_c.hidden).css(_c.visible); 1210 else // ditto for any iframes INSIDE the pane 1211 $P.find('IFRAME').css(_c.hidden).css(_c.visible); 1212 } 1213 1214 /** 1215 * @param {string} pane Can accept ONLY a 'pane' (east, west, etc) 1216 * @param {number=} outerSize (optional) Can pass a width, allowing calculations BEFORE element is resized 1217 * @return {number} Returns the innerHeight/Width of el by subtracting padding and borders 1218 */ 1219, cssSize = function (pane, outerSize) { 1220 var fn = _c[pane].dir=="horz" ? cssH : cssW; 1221 return fn($Ps[pane], outerSize); 1222 } 1223 1224 /** 1225 * @param {string} pane Can accept ONLY a 'pane' (east, west, etc) 1226 * @return {Object} Returns hash of minWidth & minHeight 1227 */ 1228, cssMinDims = function (pane) { 1229 // minWidth/Height means CSS width/height = 1px 1230 var $P = $Ps[pane] 1231 , dir = _c[pane].dir 1232 , d = { 1233 minWidth: 1001 - cssW($P, 1000) 1234 , minHeight: 1001 - cssH($P, 1000) 1235 } 1236 ; 1237 if (dir === "horz") d.minSize = d.minHeight; 1238 if (dir === "vert") d.minSize = d.minWidth; 1239 return d; 1240 } 1241 1242 // TODO: see if these methods can be made more useful... 1243 // TODO: *maybe* return cssW/H from these so caller can use this info 1244 1245 /** 1246 * @param {(string|!Object)} el 1247 * @param {number=} outerWidth 1248 * @param {boolean=} [autoHide=false] 1249 */ 1250, setOuterWidth = function (el, outerWidth, autoHide) { 1251 var $E = el, w; 1252 if (isStr(el)) $E = $Ps[el]; // west 1253 else if (!el.jquery) $E = $(el); 1254 w = cssW($E, outerWidth); 1255 $E.css({ width: w }); 1256 if (w > 0) { 1257 if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) { 1258 $E.show().data('autoHidden', false); 1259 if (!browser.mozilla) // FireFox refreshes iframes - IE does not 1260 // make hidden, then visible to 'refresh' display after animation 1261 $E.css(_c.hidden).css(_c.visible); 1262 } 1263 } 1264 else if (autoHide && !$E.data('autoHidden')) 1265 $E.hide().data('autoHidden', true); 1266 } 1267 1268 /** 1269 * @param {(string|!Object)} el 1270 * @param {number=} outerHeight 1271 * @param {boolean=} [autoHide=false] 1272 */ 1273, setOuterHeight = function (el, outerHeight, autoHide) { 1274 var $E = el, h; 1275 if (isStr(el)) $E = $Ps[el]; // west 1276 else if (!el.jquery) $E = $(el); 1277 h = cssH($E, outerHeight); 1278 $E.css({ height: h, visibility: "visible" }); // may have been 'hidden' by sizeContent 1279 if (h > 0 && $E.innerWidth() > 0) { 1280 if (autoHide && $E.data('autoHidden')) { 1281 $E.show().data('autoHidden', false); 1282 if (!browser.mozilla) // FireFox refreshes iframes - IE does not 1283 $E.css(_c.hidden).css(_c.visible); 1284 } 1285 } 1286 else if (autoHide && !$E.data('autoHidden')) 1287 $E.hide().data('autoHidden', true); 1288 } 1289 1290 1291 /** 1292 * Converts any 'size' params to a pixel/integer size, if not already 1293 * If 'auto' or a decimal/percentage is passed as 'size', a pixel-size is calculated 1294 * 1295 /** 1296 * @param {string} pane 1297 * @param {(string|number)=} size 1298 * @param {string=} [dir] 1299 * @return {number} 1300 */ 1301, _parseSize = function (pane, size, dir) { 1302 if (!dir) dir = _c[pane].dir; 1303 1304 if (isStr(size) && size.match(/%/)) 1305 size = (size === '100%') ? -1 : parseInt(size, 10) / 100; // convert % to decimal 1306 1307 if (size === 0) 1308 return 0; 1309 else if (size >= 1) 1310 return parseInt(size, 10); 1311 1312 var o = options, avail = 0; 1313 if (dir=="horz") // north or south or center.minHeight 1314 avail = sC.innerHeight - ($Ps.north ? o.north.spacing_open : 0) - ($Ps.south ? o.south.spacing_open : 0); 1315 else if (dir=="vert") // east or west or center.minWidth 1316 avail = sC.innerWidth - ($Ps.west ? o.west.spacing_open : 0) - ($Ps.east ? o.east.spacing_open : 0); 1317 1318 if (size === -1) // -1 == 100% 1319 return avail; 1320 else if (size > 0) // percentage, eg: .25 1321 return round(avail * size); 1322 else if (pane=="center") 1323 return 0; 1324 else { // size < 0 || size=='auto' || size==Missing || size==Invalid 1325 // auto-size the pane 1326 var dim = (dir === "horz" ? "height" : "width") 1327 , $P = $Ps[pane] 1328 , $C = dim === 'height' ? $Cs[pane] : false 1329 , vis = $.layout.showInvisibly($P) // show pane invisibly if hidden 1330 , szP = $P.css(dim) // SAVE current pane size 1331 , szC = $C ? $C.css(dim) : 0 // SAVE current content size 1332 ; 1333 $P.css(dim, "auto"); 1334 if ($C) $C.css(dim, "auto"); 1335 size = (dim === "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE 1336 $P.css(dim, szP).css(vis); // RESET size & visibility 1337 if ($C) $C.css(dim, szC); 1338 return size; 1339 } 1340 } 1341 1342 /** 1343 * Calculates current 'size' (outer-width or outer-height) of a border-pane - optionally with 'pane-spacing' added 1344 * 1345 * @param {(string|!Object)} pane 1346 * @param {boolean=} [inclSpace=false] 1347 * @return {number} Returns EITHER Width for east/west panes OR Height for north/south panes 1348 */ 1349, getPaneSize = function (pane, inclSpace) { 1350 var 1351 $P = $Ps[pane] 1352 , o = options[pane] 1353 , s = state[pane] 1354 , oSp = (inclSpace ? o.spacing_open : 0) 1355 , cSp = (inclSpace ? o.spacing_closed : 0) 1356 ; 1357 if (!$P || s.isHidden) 1358 return 0; 1359 else if (s.isClosed || (s.isSliding && inclSpace)) 1360 return cSp; 1361 else if (_c[pane].dir === "horz") 1362 return $P.outerHeight() + oSp; 1363 else // dir === "vert" 1364 return $P.outerWidth() + oSp; 1365 } 1366 1367 /** 1368 * Calculate min/max pane dimensions and limits for resizing 1369 * 1370 * @param {string} pane 1371 * @param {boolean=} [slide=false] 1372 */ 1373, setSizeLimits = function (pane, slide) { 1374 if (!isInitialized()) return; 1375 var 1376 o = options[pane] 1377 , s = state[pane] 1378 , c = _c[pane] 1379 , dir = c.dir 1380 , type = c.sizeType.toLowerCase() 1381 , isSliding = (slide != undefined ? slide : s.isSliding) // only open() passes 'slide' param 1382 , $P = $Ps[pane] 1383 , paneSpacing = o.spacing_open 1384 // measure the pane on the *opposite side* from this pane 1385 , altPane = _c.oppositeEdge[pane] 1386 , altS = state[altPane] 1387 , $altP = $Ps[altPane] 1388 , altPaneSize = (!$altP || altS.isVisible===false || altS.isSliding ? 0 : (dir=="horz" ? $altP.outerHeight() : $altP.outerWidth())) 1389 , altPaneSpacing = ((!$altP || altS.isHidden ? 0 : options[altPane][ altS.isClosed !== false ? "spacing_closed" : "spacing_open" ]) || 0) 1390 // limitSize prevents this pane from 'overlapping' opposite pane 1391 , containerSize = (dir=="horz" ? sC.innerHeight : sC.innerWidth) 1392 , minCenterDims = cssMinDims("center") 1393 , minCenterSize = dir=="horz" ? max(options.center.minHeight, minCenterDims.minHeight) : max(options.center.minWidth, minCenterDims.minWidth) 1394 // if pane is 'sliding', then ignore center and alt-pane sizes - because 'overlays' them 1395 , limitSize = (containerSize - paneSpacing - (isSliding ? 0 : (_parseSize("center", minCenterSize, dir) + altPaneSize + altPaneSpacing))) 1396 , minSize = s.minSize = max( _parseSize(pane, o.minSize), cssMinDims(pane).minSize ) 1397 , maxSize = s.maxSize = min( (o.maxSize ? _parseSize(pane, o.maxSize) : 100000), limitSize ) 1398 , r = s.resizerPosition = {} // used to set resizing limits 1399 , top = sC.inset.top 1400 , left = sC.inset.left 1401 , W = sC.innerWidth 1402 , H = sC.innerHeight 1403 , rW = o.spacing_open // subtract resizer-width to get top/left position for south/east 1404 ; 1405 switch (pane) { 1406 case "north": r.min = top + minSize; 1407 r.max = top + maxSize; 1408 break; 1409 case "west": r.min = left + minSize; 1410 r.max = left + maxSize; 1411 break; 1412 case "south": r.min = top + H - maxSize - rW; 1413 r.max = top + H - minSize - rW; 1414 break; 1415 case "east": r.min = left + W - maxSize - rW; 1416 r.max = left + W - minSize - rW; 1417 break; 1418 }; 1419 } 1420 1421 /** 1422 * Returns data for setting the size/position of center pane. Also used to set Height for east/west panes 1423 * 1424 * @return JSON Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height 1425 */ 1426, calcNewCenterPaneDims = function () { 1427 var d = { 1428 top: getPaneSize("north", true) // true = include 'spacing' value for pane 1429 , bottom: getPaneSize("south", true) 1430 , left: getPaneSize("west", true) 1431 , right: getPaneSize("east", true) 1432 , width: 0 1433 , height: 0 1434 }; 1435 1436 // NOTE: sC = state.container 1437 // calc center-pane outer dimensions 1438 d.width = sC.innerWidth - d.left - d.right; // outerWidth 1439 d.height = sC.innerHeight - d.bottom - d.top; // outerHeight 1440 // add the 'container border/padding' to get final positions relative to the container 1441 d.top += sC.inset.top; 1442 d.bottom += sC.inset.bottom; 1443 d.left += sC.inset.left; 1444 d.right += sC.inset.right; 1445 1446 return d; 1447 } 1448 1449 1450 /** 1451 * @param {!Object} el 1452 * @param {boolean=} [allStates=false] 1453 */ 1454, getHoverClasses = function (el, allStates) { 1455 var 1456 $El = $(el) 1457 , type = $El.data("layoutRole") 1458 , pane = $El.data("layoutEdge") 1459 , o = options[pane] 1460 , root = o[type +"Class"] 1461 , _pane = "-"+ pane // eg: "-west" 1462 , _open = "-open" 1463 , _closed = "-closed" 1464 , _slide = "-sliding" 1465 , _hover = "-hover " // NOTE the trailing space 1466 , _state = $El.hasClass(root+_closed) ? _closed : _open 1467 , _alt = _state === _closed ? _open : _closed 1468 , classes = (root+_hover) + (root+_pane+_hover) + (root+_state+_hover) + (root+_pane+_state+_hover) 1469 ; 1470 if (allStates) // when 'removing' classes, also remove alternate-state classes 1471 classes += (root+_alt+_hover) + (root+_pane+_alt+_hover); 1472 1473 if (type=="resizer" && $El.hasClass(root+_slide)) 1474 classes += (root+_slide+_hover) + (root+_pane+_slide+_hover); 1475 1476 return $.trim(classes); 1477 } 1478, addHover = function (evt, el) { 1479 var $E = $(el || this); 1480 if (evt && $E.data("layoutRole") === "toggler") 1481 evt.stopPropagation(); // prevent triggering 'slide' on Resizer-bar 1482 $E.addClass( getHoverClasses($E) ); 1483 } 1484, removeHover = function (evt, el) { 1485 var $E = $(el || this); 1486 $E.removeClass( getHoverClasses($E, true) ); 1487 } 1488 1489, onResizerEnter = function (evt) { // ALSO called by toggler.mouseenter 1490 var pane = $(this).data("layoutEdge") 1491 , s = state[pane] 1492 , $d = $(document) 1493 ; 1494 // ignore closed-panes and mouse moving back & forth over resizer! 1495 // also ignore if ANY pane is currently resizing 1496 if ( s.isResizing || state.paneResizing ) return; 1497 1498 if (options.maskPanesEarly) 1499 showMasks( pane, { resizing: true }); 1500 } 1501, onResizerLeave = function (evt, el) { 1502 var e = el || this // el is only passed when called by the timer 1503 , pane = $(e).data("layoutEdge") 1504 , name = pane +"ResizerLeave" 1505 , $d = $(document) 1506 ; 1507 timer.clear(pane+"_openSlider"); // cancel slideOpen timer, if set 1508 timer.clear(name); // cancel enableSelection timer - may re/set below 1509 // this method calls itself on a timer because it needs to allow 1510 // enough time for dragging to kick-in and set the isResizing flag 1511 // dragging has a 100ms delay set, so this delay must be >100 1512 if (!el) // 1st call - mouseleave event 1513 timer.set(name, function(){ onResizerLeave(evt, e); }, 200); 1514 // if user is resizing, dragStop will reset everything, so skip it here 1515 else if (options.maskPanesEarly && !state.paneResizing) // 2nd call - by timer 1516 hideMasks(); 1517 } 1518 1519/* 1520 * ########################### 1521 * INITIALIZATION METHODS 1522 * ########################### 1523 */ 1524 1525 /** 1526 * Initialize the layout - called automatically whenever an instance of layout is created 1527 * 1528 * @see none - triggered onInit 1529 * @return mixed true = fully initialized | false = panes not initialized (yet) | 'cancel' = abort 1530 */ 1531, _create = function () { 1532 // initialize config/options 1533 initOptions(); 1534 var o = options 1535 , s = state; 1536 1537 // TEMP state so isInitialized returns true during init process 1538 s.creatingLayout = true; 1539 1540 // init plugins for this layout, if there are any (eg: stateManagement) 1541 runPluginCallbacks( Instance, $.layout.onCreate ); 1542 1543 // options & state have been initialized, so now run beforeLoad callback 1544 // onload will CANCEL layout creation if it returns false 1545 if (false === _runCallbacks("onload_start")) 1546 return 'cancel'; 1547 1548 // initialize the container element 1549 _initContainer(); 1550 1551 // bind hotkey function - keyDown - if required 1552 initHotkeys(); 1553 1554 // bind window.onunload 1555 $(window).bind("unload."+ sID, unload); 1556 1557 // init plugins for this layout, if there are any (eg: customButtons) 1558 runPluginCallbacks( Instance, $.layout.onLoad ); 1559 1560 // if layout elements are hidden, then layout WILL NOT complete initialization! 1561 // initLayoutElements will set initialized=true and run the onload callback IF successful 1562 if (o.initPanes) _initLayoutElements(); 1563 1564 delete s.creatingLayout; 1565 1566 return state.initialized; 1567 } 1568 1569 /** 1570 * Initialize the layout IF not already 1571 * 1572 * @see All methods in Instance run this test 1573 * @return boolean true = layoutElements have been initialized | false = panes are not initialized (yet) 1574 */ 1575, isInitialized = function () { 1576 if (state.initialized || state.creatingLayout) return true; // already initialized 1577 else return _initLayoutElements(); // try to init panes NOW 1578 } 1579 1580 /** 1581 * Initialize the layout - called automatically whenever an instance of layout is created 1582 * 1583 * @see _create() & isInitialized 1584 * @param {boolean=} [retry=false] // indicates this is a 2nd try 1585 * @return An object pointer to the instance created 1586 */ 1587, _initLayoutElements = function (retry) { 1588 // initialize config/options 1589 var o = options; 1590 // CANNOT init panes inside a hidden container! 1591 if (!$N.is(":visible")) { 1592 // handle Chrome bug where popup window 'has no height' 1593 // if layout is BODY element, try again in 50ms 1594 // SEE: http://layout.jquery-dev.com/samples/test_popup_window.html 1595 if ( !retry && browser.webkit && $N[0].tagName === "BODY" ) 1596 setTimeout(function(){ _initLayoutElements(true); }, 50); 1597 return false; 1598 } 1599 1600 // a center pane is required, so make sure it exists 1601 if (!getPane("center").length) { 1602 return _log( o.errors.centerPaneMissing ); 1603 } 1604 1605 // TEMP state so isInitialized returns true during init process 1606 state.creatingLayout = true; 1607 1608 // update Container dims 1609 $.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT include insetX values 1610 1611 // initialize all layout elements 1612 initPanes(); // size & position panes - calls initHandles() - which calls initResizable() 1613 1614 if (o.scrollToBookmarkOnLoad) { 1615 var l = self.location; 1616 if (l.hash) l.replace( l.hash ); // scrollTo Bookmark 1617 } 1618 1619 // check to see if this layout 'nested' inside a pane 1620 if (Instance.hasParentLayout) 1621 o.resizeWithWindow = false; 1622 // bind resizeAll() for 'this layout instance' to window.resize event 1623 else if (o.resizeWithWindow) 1624 $(window).bind("resize."+ sID, windowResize); 1625 1626 delete state.creatingLayout; 1627 state.initialized = true; 1628 1629 // init plugins for this layout, if there are any 1630 runPluginCallbacks( Instance, $.layout.onReady ); 1631 1632 // now run the onload callback, if exists 1633 _runCallbacks("onload_end"); 1634 1635 return true; // elements initialized successfully 1636 } 1637 1638 /** 1639 * Initialize nested layouts for a specific pane - can optionally pass layout-options 1640 * 1641 * @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west 1642 * @param {Object=} [opts] Layout-options - if passed, will OVERRRIDE options[pane].children 1643 * @return An object pointer to the layout instance created - or null 1644 */ 1645, createChildren = function (evt_or_pane, opts) { 1646 var pane = evtPane.call(this, evt_or_pane) 1647 , $P = $Ps[pane] 1648 ; 1649 if (!$P) return; 1650 var $C = $Cs[pane] 1651 , s = state[pane] 1652 , o = options[pane] 1653 , sm = options.stateManagement || {} 1654 , cos = opts ? (o.children = opts) : o.children 1655 ; 1656 if ( $.isPlainObject( cos ) ) 1657 cos = [ cos ]; // convert a hash to a 1-elem array 1658 else if (!cos || !$.isArray( cos )) 1659 return; 1660 1661 $.each( cos, function (idx, co) { 1662 if ( !$.isPlainObject( co ) ) return; 1663 1664 // determine which element is supposed to be the 'child container' 1665 // if pane has a 'containerSelector' OR a 'content-div', use those instead of the pane 1666 var $containers = co.containerSelector ? $P.find( co.containerSelector ) : ($C || $P); 1667 1668 $containers.each(function(){ 1669 var $cont = $(this) 1670 , child = $cont.data("layout") // see if a child-layout ALREADY exists on this element 1671 ; 1672 // if no layout exists, but children are set, try to create the layout now 1673 if (!child) { 1674 // TODO: see about moving this to the stateManagement plugin, as a method 1675 // set a unique child-instance key for this layout, if not already set 1676 setInstanceKey({ container: $cont, options: co }, s ); 1677 // If THIS layout has a hash in stateManagement.autoLoad, 1678 // then see if it also contains state-data for this child-layout 1679 // If so, copy the stateData to child.options.stateManagement.autoLoad 1680 if ( sm.includeChildren && state.stateData[pane] ) { 1681 // THIS layout's state was cached when its state was loaded 1682 var paneChildren = state.stateData[pane].children || {} 1683 , childState = paneChildren[ co.instanceKey ] 1684 , co_sm = co.stateManagement || (co.stateManagement = { autoLoad: true }) 1685 ; 1686 // COPY the stateData into the autoLoad key 1687 if ( co_sm.autoLoad === true && childState ) { 1688 co_sm.autoSave = false; // disable autoSave because saving handled by parent-layout 1689 co_sm.includeChildren = true; // cascade option - FOR NOW 1690 co_sm.autoLoad = $.extend(true, {}, childState); // COPY the state-hash 1691 } 1692 } 1693 1694 // create the layout 1695 child = $cont.layout( co ); 1696 1697 // if successful, update data 1698 if (child) { 1699 // add the child and update all layout-pointers 1700 // MAY have already been done by child-layout calling parent.refreshChildren() 1701 refreshChildren( pane, child ); 1702 } 1703 } 1704 }); 1705 }); 1706 } 1707 1708, setInstanceKey = function (child, parentPaneState) { 1709 // create a named key for use in state and instance branches 1710 var $c = child.container 1711 , o = child.options 1712 , sm = o.stateManagement 1713 , key = o.instanceKey || $c.data("layoutInstanceKey") 1714 ; 1715 if (!key) key = (sm && sm.cookie ? sm.cookie.name : '') || o.name; // look for a name/key 1716 if (!key) key = "layout"+ (++parentPaneState.childIdx); // if no name/key found, generate one 1717 else key = key.replace(/[^\w-]/gi, '_').replace(/_{2,}/g, '_'); // ensure is valid as a hash key 1718 o.instanceKey = key; 1719 $c.data("layoutInstanceKey", key); // useful if layout is destroyed and then recreated 1720 return key; 1721 } 1722 1723 /** 1724 * @param {string} pane The pane being opened, ie: north, south, east, or west 1725 * @param {Object=} newChild New child-layout Instance to add to this pane 1726 */ 1727, refreshChildren = function (pane, newChild) { 1728 var $P = $Ps[pane] 1729 , pC = children[pane] 1730 , s = state[pane] 1731 , o 1732 ; 1733 // check for destroy()ed layouts and update the child pointers & arrays 1734 if ($.isPlainObject( pC )) { 1735 $.each( pC, function (key, child) { 1736 if (child.destroyed) delete pC[key] 1737 }); 1738 // if no more children, remove the children hash 1739 if ($.isEmptyObject( pC )) 1740 pC = children[pane] = null; // clear children hash 1741 } 1742 1743 // see if there is a directly-nested layout inside this pane 1744 // if there is, then there can be only ONE child-layout, so check that... 1745 if (!newChild && !pC) { 1746 newChild = $P.data("layout"); 1747 } 1748 1749 // if a newChild instance was passed, add it to children[pane] 1750 if (newChild) { 1751 // update child.state 1752 newChild.hasParentLayout = true; // set parent-flag in child 1753 // instanceKey is a key-name used in both state and children 1754 o = newChild.options; 1755 // set a unique child-instance key for this layout, if not already set 1756 setInstanceKey( newChild, s ); 1757 // add pointer to pane.children hash 1758 if (!pC) pC = children[pane] = {}; // create an empty children hash 1759 pC[ o.instanceKey ] = newChild.container.data("layout"); // add childLayout instance 1760 } 1761 1762 // ALWAYS refresh the pane.children alias, even if null 1763 Instance[pane].children = children[pane]; 1764 1765 // if newChild was NOT passed - see if there is a child layout NOW 1766 if (!newChild) { 1767 createChildren(pane); // MAY create a child and re-call this method 1768 } 1769 } 1770 1771, windowResize = function () { 1772 var o = options 1773 , delay = Number(o.resizeWithWindowDelay); 1774 if (delay < 10) delay = 100; // MUST have a delay! 1775 // resizing uses a delay-loop because the resize event fires repeatly - except in FF, but delay anyway 1776 timer.clear("winResize"); // if already running 1777 timer.set("winResize", function(){ 1778 timer.clear("winResize"); 1779 timer.clear("winResizeRepeater"); 1780 var dims = elDims( $N, o.inset ); 1781 // only trigger resizeAll() if container has changed size 1782 if (dims.innerWidth !== sC.innerWidth || dims.innerHeight !== sC.innerHeight) 1783 resizeAll(); 1784 }, delay); 1785 // ALSO set fixed-delay timer, if not already running 1786 if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater(); 1787 } 1788 1789, setWindowResizeRepeater = function () { 1790 var delay = Number(options.resizeWithWindowMaxDelay); 1791 if (delay > 0) 1792 timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay); 1793 } 1794 1795, unload = function () { 1796 var o = options; 1797 1798 _runCallbacks("onunload_start"); 1799 1800 // trigger plugin callabacks for this layout (eg: stateManagement) 1801 runPluginCallbacks( Instance, $.layout.onUnload ); 1802 1803 _runCallbacks("onunload_end"); 1804 } 1805 1806 /** 1807 * Validate and initialize container CSS and events 1808 * 1809 * @see _create() 1810 */ 1811, _initContainer = function () { 1812 var 1813 N = $N[0] 1814 , $H = $("html") 1815 , tag = sC.tagName = N.tagName 1816 , id = sC.id = N.id 1817 , cls = sC.className = N.className 1818 , o = options 1819 , name = o.name 1820 , props = "position,margin,padding,border" 1821 , css = "layoutCSS" 1822 , CSS = {} 1823 , hid = "hidden" // used A LOT! 1824 // see if this container is a 'pane' inside an outer-layout 1825 , parent = $N.data("parentLayout") // parent-layout Instance 1826 , pane = $N.data("layoutEdge") // pane-name in parent-layout 1827 , isChild = parent && pane 1828 , num = $.layout.cssNum 1829 , $parent, n 1830 ; 1831 // sC = state.container 1832 // sC.selector = $N.selector.split(".slice")[0]; 1833 sC.ref = (o.name ? o.name +' layout / ' : '') + tag + (id ? "#"+id : cls ? '.['+cls+']' : ''); // used in messages 1834 sC.isBody = (tag === "BODY"); 1835 1836 // try to find a parent-layout 1837 if (!isChild && !sC.isBody) { 1838 $parent = $N.closest("."+ $.layout.defaults.panes.paneClass); 1839 parent = $parent.data("parentLayout"); 1840 pane = $parent.data("layoutEdge"); 1841 isChild = parent && pane; 1842 } 1843 1844 $N .data({ 1845 layout: Instance 1846 , layoutContainer: sID // FLAG to indicate this is a layout-container - contains unique internal ID 1847 }) 1848 .addClass(o.containerClass) 1849 ; 1850 var layoutMethods = { 1851 destroy: '' 1852 , initPanes: '' 1853 , resizeAll: 'resizeAll' 1854 , resize: 'resizeAll' 1855 }; 1856 // loop hash and bind all methods - include layoutID namespacing 1857 for (name in layoutMethods) { 1858 $N.bind("layout"+ name.toLowerCase() +"."+ sID, Instance[ layoutMethods[name] || name ]); 1859 } 1860 1861 // if this container is another layout's 'pane', then set child/parent pointers 1862 if (isChild) { 1863 // update parent flag 1864 Instance.hasParentLayout = true; 1865 // set pointers to THIS child-layout (Instance) in parent-layout 1866 parent.refreshChildren( pane, Instance ); 1867 } 1868 1869 // SAVE original container CSS for use in destroy() 1870 if (!$N.data(css)) { 1871 // handle props like overflow different for BODY & HTML - has 'system default' values 1872 if (sC.isBody) { 1873 // SAVE <BODY> CSS 1874 $N.data(css, $.extend( styles($N, props), { 1875 height: $N.css("height") 1876 , overflow: $N.css("overflow") 1877 , overflowX: $N.css("overflowX") 1878 , overflowY: $N.css("overflowY") 1879 })); 1880 // ALSO SAVE <HTML> CSS 1881 $H.data(css, $.extend( styles($H, 'padding'), { 1882 height: "auto" // FF would return a fixed px-size! 1883 , overflow: $H.css("overflow") 1884 , overflowX: $H.css("overflowX") 1885 , overflowY: $H.css("overflowY") 1886 })); 1887 } 1888 else // handle props normally for non-body elements 1889 $N.data(css, styles($N, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY") ); 1890 } 1891 1892 try { 1893 // common container CSS 1894 CSS = { 1895 overflow: hid 1896 , overflowX: hid 1897 , overflowY: hid 1898 }; 1899 $N.css( CSS ); 1900 1901 if (o.inset && !$.isPlainObject(o.inset)) { 1902 // can specify a single number for equal outset all-around 1903 n = parseInt(o.inset, 10) || 0 1904 o.inset = { 1905 top: n 1906 , bottom: n 1907 , left: n 1908 , right: n 1909 }; 1910 } 1911 1912 // format html & body if this is a full page layout 1913 if (sC.isBody) { 1914 // if HTML has padding, use this as an outer-spacing around BODY 1915 if (!o.outset) { 1916 // use padding from parent-elem (HTML) as outset 1917 o.outset = { 1918 top: num($H, "paddingTop") 1919 , bottom: num($H, "paddingBottom") 1920 , left: num($H, "paddingLeft") 1921 , right: num($H, "paddingRight") 1922 }; 1923 } 1924 else if (!$.isPlainObject(o.outset)) { 1925 // can specify a single number for equal outset all-around 1926 n = parseInt(o.outset, 10) || 0 1927 o.outset = { 1928 top: n 1929 , bottom: n 1930 , left: n 1931 , right: n 1932 }; 1933 } 1934 // HTML 1935 $H.css( CSS ).css({ 1936 height: "100%" 1937 , border: "none" // no border or padding allowed when using height = 100% 1938 , padding: 0 // ditto 1939 , margin: 0 1940 }); 1941 // BODY 1942 if (browser.isIE6) { 1943 // IE6 CANNOT use the trick of setting absolute positioning on all 4 sides - must have 'height' 1944 $N.css({ 1945 width: "100%" 1946 , height: "100%" 1947 , border: "none" // no border or padding allowed when using height = 100% 1948 , padding: 0 // ditto 1949 , margin: 0 1950 , position: "relative" 1951 }); 1952 // convert body padding to an inset option - the border cannot be measured in IE6! 1953 if (!o.inset) o.inset = elDims( $N ).inset; 1954 } 1955 else { // use absolute positioning for BODY to allow borders & padding without overflow 1956 $N.css({ 1957 width: "auto" 1958 , height: "auto" 1959 , margin: 0 1960 , position: "absolute" // allows for border and padding on BODY 1961 }); 1962 // apply edge-positioning created above 1963 $N.css( o.outset ); 1964 } 1965 // set current layout-container dimensions 1966 $.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT include insetX values 1967 } 1968 else { 1969 // container MUST have 'position' 1970 var p = $N.css("position"); 1971 if (!p || !p.match(/(fixed|absolute|relative)/)) 1972 $N.css("position","relative"); 1973 1974 // set current layout-container dimensions 1975 if ( $N.is(":visible") ) { 1976 $.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT change insetX (padding) values 1977 if (sC.innerHeight < 1) // container has no 'height' - warn developer 1978 _log( o.errors.noContainerHeight.replace(/CONTAINER/, sC.ref) ); 1979 } 1980 } 1981 1982 // if container has min-width/height, then enable scrollbar(s) 1983 if ( num($N, "minWidth") ) $N.parent().css("overflowX","auto"); 1984 if ( num($N, "minHeight") ) $N.parent().css("overflowY","auto"); 1985 1986 } catch (ex) {} 1987 } 1988 1989 /** 1990 * Bind layout hotkeys - if options enabled 1991 * 1992 * @see _create() and addPane() 1993 * @param {string=} [panes=""] The edge(s) to process 1994 */ 1995, initHotkeys = function (panes) { 1996 panes = panes ? panes.split(",") : _c.borderPanes; 1997 // bind keyDown to capture hotkeys, if option enabled for ANY pane 1998 $.each(panes, function (i, pane) { 1999 var o = options[pane]; 2000 if (o.enableCursorHotkey || o.customHotkey) { 2001 $(document).bind("keydown."+ sID, keyDown); // only need to bind this ONCE 2002 return false; // BREAK - binding was done 2003 } 2004 }); 2005 } 2006 2007 /** 2008 * Build final OPTIONS data 2009 * 2010 * @see _create() 2011 */ 2012, initOptions = function () { 2013 var data, d, pane, key, val, i, c, o; 2014 2015 // reprocess user's layout-options to have correct options sub-key structure 2016 opts = $.layout.transformData( opts, true ); // panes = default subkey 2017 2018 // auto-rename old options for backward compatibility 2019 opts = $.layout.backwardCompatibility.renameAllOptions( opts ); 2020 2021 // if user-options has 'panes' key (pane-defaults), clean it... 2022 if (!$.isEmptyObject(opts.panes)) { 2023 // REMOVE any pane-defaults that MUST be set per-pane 2024 data = $.layout.optionsMap.noDefault; 2025 for (i=0, c=data.length; i<c; i++) { 2026 key = data[i]; 2027 delete opts.panes[key]; // OK if does not exist 2028 } 2029 // REMOVE any layout-options specified under opts.panes 2030 data = $.layout.optionsMap.layout; 2031 for (i=0, c=data.length; i<c; i++) { 2032 key = data[i]; 2033 delete opts.panes[key]; // OK if does not exist 2034 } 2035 } 2036 2037 // MOVE any NON-layout-options from opts-root to opts.panes 2038 data = $.layout.optionsMap.layout; 2039 var rootKeys = $.layout.config.optionRootKeys; 2040 for (key in opts) { 2041 val = opts[key]; 2042 if ($.inArray(key, rootKeys) < 0 && $.inArray(key, data) < 0) { 2043 if (!opts.panes[key]) 2044 opts.panes[key] = $.isPlainObject(val) ? $.extend(true, {}, val) : val; 2045 delete opts[key] 2046 } 2047 } 2048 2049 // START by updating ALL options from opts 2050 $.extend(true, options, opts); 2051 2052 // CREATE final options (and config) for EACH pane 2053 $.each(_c.allPanes, function (i, pane) { 2054 2055 // apply 'pane-defaults' to CONFIG.[PANE] 2056 _c[pane] = $.extend(true, {}, _c.panes, _c[pane]); 2057 2058 d = options.panes; 2059 o = options[pane]; 2060 2061 // center-pane uses SOME keys in defaults.panes branch 2062 if (pane === 'center') { 2063 // ONLY copy keys from opts.panes listed in: $.layout.optionsMap.center 2064 data = $.layout.optionsMap.center; // list of 'center-pane keys' 2065 for (i=0, c=data.length; i<c; i++) { // loop the list... 2066 key = data[i]; 2067 // only need to use pane-default if pane-specific value not set 2068 if (!opts.center[key] && (opts.panes[key] || !o[key])) 2069 o[key] = d[key]; // pane-default 2070 } 2071 } 2072 else { 2073 // border-panes use ALL keys in defaults.panes branch 2074 o = options[pane] = $.extend(true, {}, d, o); // re-apply pane-specific opts AFTER pane-defaults 2075 createFxOptions( pane ); 2076 // ensure all border-pane-specific base-classes exist 2077 if (!o.resizerClass) o.resizerClass = "ui-layout-resizer"; 2078 if (!o.togglerClass) o.togglerClass = "ui-layout-toggler"; 2079 } 2080 // ensure we have base pane-class (ALL panes) 2081 if (!o.paneClass) o.paneClass = "ui-layout-pane"; 2082 }); 2083 2084 // update options.zIndexes if a zIndex-option specified 2085 var zo = opts.zIndex 2086 , z = options.zIndexes; 2087 if (zo > 0) { 2088 z.pane_normal = zo; 2089 z.content_mask = max(zo+1, z.content_mask); // MIN = +1 2090 z.resizer_normal = max(zo+2, z.resizer_normal); // MIN = +2 2091 } 2092 2093 // DELETE 'panes' key now that we are done - values were copied to EACH pane 2094 delete options.panes; 2095 2096 2097 function createFxOptions ( pane ) { 2098 var o = options[pane] 2099 , d = options.panes; 2100 // ensure fxSettings key to avoid errors 2101 if (!o.fxSettings) o.fxSettings = {}; 2102 if (!d.fxSettings) d.fxSettings = {}; 2103 2104 $.each(["_open","_close","_size"], function (i,n) { 2105 var 2106 sName = "fxName"+ n 2107 , sSpeed = "fxSpeed"+ n 2108 , sSettings = "fxSettings"+ n 2109 // recalculate fxName according to specificity rules 2110 , fxName = o[sName] = 2111 o[sName] // options.west.fxName_open 2112 || d[sName] // options.panes.fxName_open 2113 || o.fxName // options.west.fxName 2114 || d.fxName // options.panes.fxName 2115 || "none" // MEANS $.layout.defaults.panes.fxName == "" || false || null || 0 2116 , fxExists = $.effects && ($.effects[fxName] || ($.effects.effect && $.effects.effect[fxName])) 2117 ; 2118 // validate fxName to ensure is valid effect - MUST have effect-config data in options.effects 2119 if (fxName === "none" || !options.effects[fxName] || !fxExists) 2120 fxName = o[sName] = "none"; // effect not loaded OR unrecognized fxName 2121 2122 // set vars for effects subkeys to simplify logic 2123 var fx = options.effects[fxName] || {} // effects.slide 2124 , fx_all = fx.all || null // effects.slide.all 2125 , fx_pane = fx[pane] || null // effects.slide.west 2126 ; 2127 // create fxSpeed[_open|_close|_size] 2128 o[sSpeed] = 2129 o[sSpeed] // options.west.fxSpeed_open 2130 || d[sSpeed] // options.west.fxSpeed_open 2131 || o.fxSpeed // options.west.fxSpeed 2132 || d.fxSpeed // options.panes.fxSpeed 2133 || null // DEFAULT - let fxSetting.duration control speed 2134 ; 2135 // create fxSettings[_open|_close|_size] 2136 o[sSettings] = $.extend( 2137 true 2138 , {} 2139 , fx_all // effects.slide.all 2140 , fx_pane // effects.slide.west 2141 , d.fxSettings // options.panes.fxSettings 2142 , o.fxSettings // options.west.fxSettings 2143 , d[sSettings] // options.panes.fxSettings_open 2144 , o[sSettings] // options.west.fxSettings_open 2145 ); 2146 }); 2147 2148 // DONE creating action-specific-settings for this pane, 2149 // so DELETE generic options - are no longer meaningful 2150 delete o.fxName; 2151 delete o.fxSpeed; 2152 delete o.fxSettings; 2153 } 2154 } 2155 2156 /** 2157 * Initialize module objects, styling, size and position for all panes 2158 * 2159 * @see _initElements() 2160 * @param {string} pane The pane to process 2161 */ 2162, getPane = function (pane) { 2163 var sel = options[pane].paneSelector 2164 if (sel.substr(0,1)==="#") // ID selector 2165 // NOTE: elements selected 'by ID' DO NOT have to be 'children' 2166 return $N.find(sel).eq(0); 2167 else { // class or other selector 2168 var $P = $N.children(sel).eq(0); 2169 // look for the pane nested inside a 'form' element 2170 return $P.length ? $P : $N.children("form:first").children(sel).eq(0); 2171 } 2172 } 2173 2174 /** 2175 * @param {Object=} evt 2176 */ 2177, initPanes = function (evt) { 2178 // stopPropagation if called by trigger("layoutinitpanes") - use evtPane utility 2179 evtPane(evt); 2180 2181 // NOTE: do north & south FIRST so we can measure their height - do center LAST 2182 $.each(_c.allPanes, function (idx, pane) { 2183 addPane( pane, true ); 2184 }); 2185 2186 // init the pane-handles NOW in case we have to hide or close the pane below 2187 initHandles(); 2188 2189 // now that all panes have been initialized and initially-sized, 2190 // make sure there is really enough space available for each pane 2191 $.each(_c.borderPanes, function (i, pane) { 2192 if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN 2193 setSizeLimits(pane); 2194 makePaneFit(pane); // pane may be Closed, Hidden or Resized by makePaneFit() 2195 } 2196 }); 2197 // size center-pane AGAIN in case we 'closed' a border-pane in loop above 2198 sizeMidPanes("center"); 2199 2200 // Chrome/Webkit sometimes fires callbacks BEFORE it completes resizing! 2201 // Before RC30.3, there was a 10ms delay here, but that caused layout 2202 // to load asynchrously, which is BAD, so try skipping delay for now 2203 2204 // process pane contents and callbacks, and init/resize child-layout if exists 2205 $.each(_c.allPanes, function (idx, pane) { 2206 afterInitPane(pane); 2207 }); 2208 } 2209 2210 /** 2211 * Add a pane to the layout - subroutine of initPanes() 2212 * 2213 * @see initPanes() 2214 * @param {string} pane The pane to process 2215 * @param {boolean=} [force=false] Size content after init 2216 */ 2217, addPane = function (pane, force) { 2218 if ( !force && !isInitialized() ) return; 2219 var 2220 o = options[pane] 2221 , s = state[pane] 2222 , c = _c[pane] 2223 , dir = c.dir 2224 , fx = s.fx 2225 , spacing = o.spacing_open || 0 2226 , isCenter = (pane === "center") 2227 , CSS = {} 2228 , $P = $Ps[pane] 2229 , size, minSize, maxSize, child 2230 ; 2231 // if pane-pointer already exists, remove the old one first 2232 if ($P) 2233 removePane( pane, false, true, false ); 2234 else 2235 $Cs[pane] = false; // init 2236 2237 $P = $Ps[pane] = getPane(pane); 2238 if (!$P.length) { 2239 $Ps[pane] = false; // logic 2240 return; 2241 } 2242 2243 // SAVE original Pane CSS 2244 if (!$P.data("layoutCSS")) { 2245 var props = "position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border"; 2246 $P.data("layoutCSS", styles($P, props)); 2247 } 2248 2249 // create alias for pane data in Instance - initHandles will add more 2250 Instance[pane] = { 2251 name: pane 2252 , pane: $Ps[pane] 2253 , content: $Cs[pane] 2254 , options: options[pane] 2255 , state: state[pane] 2256 , children: children[pane] 2257 }; 2258 2259 // add classes, attributes & events 2260 $P .data({ 2261 parentLayout: Instance // pointer to Layout Instance 2262 , layoutPane: Instance[pane] // NEW pointer to pane-alias-object 2263 , layoutEdge: pane 2264 , layoutRole: "pane" 2265 }) 2266 .css(c.cssReq).css("zIndex", options.zIndexes.pane_normal) 2267 .css(o.applyDemoStyles ? c.cssDemo : {}) // demo styles 2268 .addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector' 2269 .bind("mouseenter."+ sID, addHover ) 2270 .bind("mouseleave."+ sID, removeHover ) 2271 ; 2272 var paneMethods = { 2273 hide: '' 2274 , show: '' 2275 , toggle: '' 2276 , close: '' 2277 , open: '' 2278 , slideOpen: '' 2279 , slideClose: '' 2280 , slideToggle: '' 2281 , size: 'sizePane' 2282 , sizePane: 'sizePane' 2283 , sizeContent: '' 2284 , sizeHandles: '' 2285 , enableClosable: '' 2286 , disableClosable: '' 2287 , enableSlideable: '' 2288 , disableSlideable: '' 2289 , enableResizable: '' 2290 , disableResizable: '' 2291 , swapPanes: 'swapPanes' 2292 , swap: 'swapPanes' 2293 , move: 'swapPanes' 2294 , removePane: 'removePane' 2295 , remove: 'removePane' 2296 , createChildren: '' 2297 , resizeChildren: '' 2298 , resizeAll: 'resizeAll' 2299 , resizeLayout: 'resizeAll' 2300 } 2301 , name; 2302 // loop hash and bind all methods - include layoutID namespacing 2303 for (name in paneMethods) { 2304 $P.bind("layoutpane"+ name.toLowerCase() +"."+ sID, Instance[ paneMethods[name] || name ]); 2305 } 2306 2307 // see if this pane has a 'scrolling-content element' 2308 initContent(pane, false); // false = do NOT sizeContent() - called later 2309 2310 if (!isCenter) { 2311 // call _parseSize AFTER applying pane classes & styles - but before making visible (if hidden) 2312 // if o.size is auto or not valid, then MEASURE the pane and use that as its 'size' 2313 size = s.size = _parseSize(pane, o.size); 2314 minSize = _parseSize(pane,o.minSize) || 1; 2315 maxSize = _parseSize(pane,o.maxSize) || 100000; 2316 if (size > 0) size = max(min(size, maxSize), minSize); 2317 s.autoResize = o.autoResize; // used with percentage sizes 2318 2319 // state for border-panes 2320 s.isClosed = false; // true = pane is closed 2321 s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes 2322 s.isResizing= false; // true = pane is in process of being resized 2323 s.isHidden = false; // true = pane is hidden - no spacing, resizer or toggler is visible! 2324 2325 // array for 'pin buttons' whose classNames are auto-updated on pane-open/-close 2326 if (!s.pins) s.pins = []; 2327 } 2328 // states common to ALL panes 2329 s.tagName = $P[0].tagName; 2330 s.edge = pane; // useful if pane is (or about to be) 'swapped' - easy find out where it is (or is going) 2331 s.noRoom = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically 2332 s.isVisible = true; // false = pane is invisible - closed OR hidden - simplify logic 2333 2334 // init pane positioning 2335 setPanePosition( pane ); 2336 2337 // if pane is not visible, 2338 if (dir === "horz") // north or south pane 2339 CSS.height = cssH($P, size); 2340 else if (dir === "vert") // east or west pane 2341 CSS.width = cssW($P, size); 2342 //else if (isCenter) {} 2343 2344 $P.css(CSS); // apply size -- top, bottom & height will be set by sizeMidPanes 2345 if (dir != "horz") sizeMidPanes(pane, true); // true = skipCallback 2346 2347 // if manually adding a pane AFTER layout initialization, then... 2348 if (state.initialized) { 2349 initHandles( pane ); 2350 initHotkeys( pane ); 2351 } 2352 2353 // close or hide the pane if specified in settings 2354 if (o.initClosed && o.closable && !o.initHidden) 2355 close(pane, true, true); // true, true = force, noAnimation 2356 else if (o.initHidden || o.initClosed) 2357 hide(pane); // will be completely invisible - no resizer or spacing 2358 else if (!s.noRoom) 2359 // make the pane visible - in case was initially hidden 2360 $P.css("display","block"); 2361 // ELSE setAsOpen() - called later by initHandles() 2362 2363 // RESET visibility now - pane will appear IF display:block 2364 $P.css("visibility","visible"); 2365 2366 // check option for auto-handling of pop-ups & drop-downs 2367 if (o.showOverflowOnHover) 2368 $P.hover( allowOverflow, resetOverflow ); 2369 2370 // if manually adding a pane AFTER layout initialization, then... 2371 if (state.initialized) { 2372 afterInitPane( pane ); 2373 } 2374 } 2375 2376, afterInitPane = function (pane) { 2377 var $P = $Ps[pane] 2378 , s = state[pane] 2379 , o = options[pane] 2380 ; 2381 if (!$P) return; 2382 2383 // see if there is a directly-nested layout inside this pane 2384 if ($P.data("layout")) 2385 refreshChildren( pane, $P.data("layout") ); 2386 2387 // process pane contents and callbacks, and init/resize child-layout if exists 2388 if (s.isVisible) { // pane is OPEN 2389 if (state.initialized) // this pane was added AFTER layout was created 2390 resizeAll(); // will also sizeContent 2391 else 2392 sizeContent(pane); 2393 2394 if (o.triggerEventsOnLoad) 2395 _runCallbacks("onresize_end", pane); 2396 else // automatic if onresize called, otherwise call it specifically 2397 // resize child - IF inner-layout already exists (created before this layout) 2398 resizeChildren(pane, true); // a previously existing childLayout 2399 } 2400 2401 // init childLayouts - even if pane is not visible 2402 if (o.initChildren && o.children) 2403 createChildren(pane); 2404 } 2405 2406 /** 2407 * @param {string=} panes The pane(s) to process 2408 */ 2409, setPanePosition = function (panes) { 2410 panes = panes ? panes.split(",") : _c.borderPanes; 2411 2412 // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV 2413 $.each(panes, function (i, pane) { 2414 var $P = $Ps[pane] 2415 , $R = $Rs[pane] 2416 , o = options[pane] 2417 , s = state[pane] 2418 , side = _c[pane].side 2419 , CSS = {} 2420 ; 2421 if (!$P) return; // pane does not exist - skip 2422 2423 // set css-position to account for container borders & padding 2424 switch (pane) { 2425 case "north": CSS.top = sC.inset.top; 2426 CSS.left = sC.inset.left; 2427 CSS.right = sC.inset.right; 2428 break; 2429 case "south": CSS.bottom = sC.inset.bottom; 2430 CSS.left = sC.inset.left; 2431 CSS.right = sC.inset.right; 2432 break; 2433 case "west": CSS.left = sC.inset.left; // top, bottom & height set by sizeMidPanes() 2434 break; 2435 case "east": CSS.right = sC.inset.right; // ditto 2436 break; 2437 case "center": // top, left, width & height set by sizeMidPanes() 2438 } 2439 // apply position 2440 $P.css(CSS); 2441 2442 // update resizer position 2443 if ($R && s.isClosed) 2444 $R.css(side, sC.inset[side]); 2445 else if ($R && !s.isHidden) 2446 $R.css(side, sC.inset[side] + getPaneSize(pane)); 2447 }); 2448 } 2449 2450 /** 2451 * Initialize module objects, styling, size and position for all resize bars and toggler buttons 2452 * 2453 * @see _create() 2454 * @param {string=} [panes=""] The edge(s) to process 2455 */ 2456, initHandles = function (panes) { 2457 panes = panes ? panes.split(",") : _c.borderPanes; 2458 2459 // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV 2460 $.each(panes, function (i, pane) { 2461 var $P = $Ps[pane]; 2462 $Rs[pane] = false; // INIT 2463 $Ts[pane] = false; 2464 if (!$P) return; // pane does not exist - skip 2465 2466 var o = options[pane] 2467 , s = state[pane] 2468 , c = _c[pane] 2469 , paneId = o.paneSelector.substr(0,1) === "#" ? o.paneSelector.substr(1) : "" 2470 , rClass = o.resizerClass 2471 , tClass = o.togglerClass 2472 , spacing = (s.isVisible ? o.spacing_open : o.spacing_closed) 2473 , _pane = "-"+ pane // used for classNames 2474 , _state = (s.isVisible ? "-open" : "-closed") // used for classNames 2475 , I = Instance[pane] 2476 // INIT RESIZER BAR 2477 , $R = I.resizer = $Rs[pane] = $("<div></div>") 2478 // INIT TOGGLER BUTTON 2479 , $T = I.toggler = (o.closable ? $Ts[pane] = $("<div></div>") : false) 2480 ; 2481 2482 //if (s.isVisible && o.resizable) ... handled by initResizable 2483 if (!s.isVisible && o.slidable) 2484 $R.attr("title", o.tips.Slide).css("cursor", o.sliderCursor); 2485 2486 $R // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer" 2487 .attr("id", paneId ? paneId +"-resizer" : "" ) 2488 .data({ 2489 parentLayout: Instance 2490 , layoutPane: Instance[pane] // NEW pointer to pane-alias-object 2491 , layoutEdge: pane 2492 , layoutRole: "resizer" 2493 }) 2494 .css(_c.resizers.cssReq).css("zIndex", options.zIndexes.resizer_normal) 2495 .css(o.applyDemoStyles ? _c.resizers.cssDemo : {}) // add demo styles 2496 .addClass(rClass +" "+ rClass+_pane) 2497 .hover(addHover, removeHover) // ALWAYS add hover-classes, even if resizing is not enabled - handle with CSS instead 2498 .hover(onResizerEnter, onResizerLeave) // ALWAYS NEED resizer.mouseleave to balance toggler.mouseenter 2499 .mousedown($.layout.disableTextSelection) // prevent text-selection OUTSIDE resizer 2500 .mouseup($.layout.enableTextSelection) // not really necessary, but just in case 2501 .appendTo($N) // append DIV to container 2502 ; 2503 if ($.fn.disableSelection) 2504 $R.disableSelection(); // prevent text-selection INSIDE resizer 2505 if (o.resizerDblClickToggle) 2506 $R.bind("dblclick."+ sID, toggle ); 2507 2508 if ($T) { 2509 $T // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "#paneLeft-toggler" 2510 .attr("id", paneId ? paneId +"-toggler" : "" ) 2511 .data({ 2512 parentLayout: Instance 2513 , layoutPane: Instance[pane] // NEW pointer to pane-alias-object 2514 , layoutEdge: pane 2515 , layoutRole: "toggler" 2516 }) 2517 .css(_c.togglers.cssReq) // add base/required styles 2518 .css(o.applyDemoStyles ? _c.togglers.cssDemo : {}) // add demo styles 2519 .addClass(tClass +" "+ tClass+_pane) 2520 .hover(addHover, removeHover) // ALWAYS add hover-classes, even if toggling is not enabled - handle with CSS instead 2521 .bind("mouseenter", onResizerEnter) // NEED toggler.mouseenter because mouseenter MAY NOT fire on resizer 2522 .appendTo($R) // append SPAN to resizer DIV 2523 ; 2524 // ADD INNER-SPANS TO TOGGLER 2525 if (o.togglerContent_open) // ui-layout-open 2526 $("<span>"+ o.togglerContent_open +"</span>") 2527 .data({ 2528 layoutEdge: pane 2529 , layoutRole: "togglerContent" 2530 }) 2531 .data("layoutRole", "togglerContent") 2532 .data("layoutEdge", pane) 2533 .addClass("content content-open") 2534 .css("display","none") 2535 .appendTo( $T ) 2536 //.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-open instead! 2537 ; 2538 if (o.togglerContent_closed) // ui-layout-closed 2539 $("<span>"+ o.togglerContent_closed +"</span>") 2540 .data({ 2541 layoutEdge: pane 2542 , layoutRole: "togglerContent" 2543 }) 2544 .addClass("content content-closed") 2545 .css("display","none") 2546 .appendTo( $T ) 2547 //.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-closed instead! 2548 ; 2549 // ADD TOGGLER.click/.hover 2550 enableClosable(pane); 2551 } 2552 2553 // add Draggable events 2554 initResizable(pane); 2555 2556 // ADD CLASSNAMES & SLIDE-BINDINGS - eg: class="resizer resizer-west resizer-open" 2557 if (s.isVisible) 2558 setAsOpen(pane); // onOpen will be called, but NOT onResize 2559 else { 2560 setAsClosed(pane); // onClose will be called 2561 bindStartSlidingEvents(pane, true); // will enable events IF option is set 2562 } 2563 2564 }); 2565 2566 // SET ALL HANDLE DIMENSIONS 2567 sizeHandles(); 2568 } 2569 2570 2571 /** 2572 * Initialize scrolling ui-layout-content div - if exists 2573 * 2574 * @see initPane() - or externally after an Ajax injection 2575 * @param {string} pane The pane to process 2576 * @param {boolean=} [resize=true] Size content after init 2577 */ 2578, initContent = function (pane, resize) { 2579 if (!isInitialized()) return; 2580 var 2581 o = options[pane] 2582 , sel = o.contentSelector 2583 , I = Instance[pane] 2584 , $P = $Ps[pane] 2585 , $C 2586 ; 2587 if (sel) $C = I.content = $Cs[pane] = (o.findNestedContent) 2588 ? $P.find(sel).eq(0) // match 1-element only 2589 : $P.children(sel).eq(0) 2590 ; 2591 if ($C && $C.length) { 2592 $C.data("layoutRole", "content"); 2593 // SAVE original Content CSS 2594 if (!$C.data("layoutCSS")) 2595 $C.data("layoutCSS", styles($C, "height")); 2596 $C.css( _c.content.cssReq ); 2597 if (o.applyDemoStyles) { 2598 $C.css( _c.content.cssDemo ); // add padding & overflow: auto to content-div 2599 $P.css( _c.content.cssDemoPane ); // REMOVE padding/scrolling from pane 2600 } 2601 // ensure no vertical scrollbar on pane - will mess up measurements 2602 if ($P.css("overflowX").match(/(scroll|auto)/)) { 2603 $P.css("overflow", "hidden"); 2604 } 2605 state[pane].content = {}; // init content state 2606 if (resize !== false) sizeContent(pane); 2607 // sizeContent() is called AFTER init of all elements 2608 } 2609 else 2610 I.content = $Cs[pane] = false; 2611 } 2612 2613 2614 /** 2615 * Add resize-bars to all panes that specify it in options 2616 * -dependancy: $.fn.resizable - will skip if not found 2617 * 2618 * @see _create() 2619 * @param {string=} [panes=""] The edge(s) to process 2620 */ 2621, initResizable = function (panes) { 2622 var draggingAvailable = $.layout.plugins.draggable 2623 , side // set in start() 2624 ; 2625 panes = panes ? panes.split(",") : _c.borderPanes; 2626 2627 $.each(panes, function (idx, pane) { 2628 var o = options[pane]; 2629 if (!draggingAvailable || !$Ps[pane] || !o.resizable) { 2630 o.resizable = false; 2631 return true; // skip to next 2632 } 2633 2634 var s = state[pane] 2635 , z = options.zIndexes 2636 , c = _c[pane] 2637 , side = c.dir=="horz" ? "top" : "left" 2638 , $P = $Ps[pane] 2639 , $R = $Rs[pane] 2640 , base = o.resizerClass 2641 , lastPos = 0 // used when live-resizing 2642 , r, live // set in start because may change 2643 // 'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process 2644 , resizerClass = base+"-drag" // resizer-drag 2645 , resizerPaneClass = base+"-"+pane+"-drag" // resizer-north-drag 2646 // 'helper' class is applied to the CLONED resizer-bar while it is being dragged 2647 , helperClass = base+"-dragging" // resizer-dragging 2648 , helperPaneClass = base+"-"+pane+"-dragging" // resizer-north-dragging 2649 , helperLimitClass = base+"-dragging-limit" // resizer-drag 2650 , helperPaneLimitClass = base+"-"+pane+"-dragging-limit" // resizer-north-drag 2651 , helperClassesSet = false // logic var 2652 ; 2653 2654 if (!s.isClosed) 2655 $R.attr("title", o.tips.Resize) 2656 .css("cursor", o.resizerCursor); // n-resize, s-resize, etc 2657 2658 $R.draggable({ 2659 containment: $N[0] // limit resizing to layout container 2660 , axis: (c.dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis 2661 , delay: 0 2662 , distance: 1 2663 , grid: o.resizingGrid 2664 // basic format for helper - style it using class: .ui-draggable-dragging 2665 , helper: "clone" 2666 , opacity: o.resizerDragOpacity 2667 , addClasses: false // avoid ui-state-disabled class when disabled 2668 //, iframeFix: o.draggableIframeFix // TODO: consider using when bug is fixed 2669 , zIndex: z.resizer_drag 2670 2671 , start: function (e, ui) { 2672 // REFRESH options & state pointers in case we used swapPanes 2673 o = options[pane]; 2674 s = state[pane]; 2675 // re-read options 2676 live = o.livePaneResizing; 2677 2678 // ondrag_start callback - will CANCEL hide if returns false 2679 // TODO: dragging CANNOT be cancelled like this, so see if there is a way? 2680 if (false === _runCallbacks("ondrag_start", pane)) return false; 2681 2682 s.isResizing = true; // prevent pane from closing while resizing 2683 state.paneResizing = pane; // easy to see if ANY pane is resizing 2684 timer.clear(pane+"_closeSlider"); // just in case already triggered 2685 2686 // SET RESIZER LIMITS - used in drag() 2687 setSizeLimits(pane); // update pane/resizer state 2688 r = s.resizerPosition; 2689 lastPos = ui.position[ side ] 2690 2691 $R.addClass( resizerClass +" "+ resizerPaneClass ); // add drag classes 2692 helperClassesSet = false; // reset logic var - see drag() 2693 2694 // MASK PANES CONTAINING IFRAMES, APPLETS OR OTHER TROUBLESOME ELEMENTS 2695 showMasks( pane, { resizing: true }); 2696 } 2697 2698 , drag: function (e, ui) { 2699 if (!helperClassesSet) { // can only add classes after clone has been added to the DOM 2700 //$(".ui-draggable-dragging") 2701 ui.helper 2702 .addClass( helperClass +" "+ helperPaneClass ) // add helper classes 2703 .css({ right: "auto", bottom: "auto" }) // fix dir="rtl" issue 2704 .children().css("visibility","hidden") // hide toggler inside dragged resizer-bar 2705 ; 2706 helperClassesSet = true; 2707 // draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane! 2708 if (s.isSliding) $Ps[pane].css("zIndex", z.pane_sliding); 2709 } 2710 // CONTAIN RESIZER-BAR TO RESIZING LIMITS 2711 var limit = 0; 2712 if (ui.position[side] < r.min) { 2713 ui.position[side] = r.min; 2714 limit = -1; 2715 } 2716 else if (ui.position[side] > r.max) { 2717 ui.position[side] = r.max; 2718 limit = 1; 2719 } 2720 // ADD/REMOVE dragging-limit CLASS 2721 if (limit) { 2722 ui.helper.addClass( helperLimitClass +" "+ helperPaneLimitClass ); // at dragging-limit 2723 window.defaultStatus = (limit>0 && pane.match(/(north|west)/)) || (limit<0 && pane.match(/(south|east)/)) ? o.tips.maxSizeWarning : o.tips.minSizeWarning; 2724 } 2725 else { 2726 ui.helper.removeClass( helperLimitClass +" "+ helperPaneLimitClass ); // not at dragging-limit 2727 window.defaultStatus = ""; 2728 } 2729 // DYNAMICALLY RESIZE PANES IF OPTION ENABLED 2730 // won't trigger unless resizer has actually moved! 2731 if (live && Math.abs(ui.position[side] - lastPos) >= o.liveResizingTolerance) { 2732 lastPos = ui.position[side]; 2733 resizePanes(e, ui, pane) 2734 } 2735 } 2736 2737 , stop: function (e, ui) { 2738 $('body').enableSelection(); // RE-ENABLE TEXT SELECTION 2739 window.defaultStatus = ""; // clear 'resizing limit' message from statusbar 2740 $R.removeClass( resizerClass +" "+ resizerPaneClass ); // remove drag classes from Resizer 2741 s.isResizing = false; 2742 state.paneResizing = false; // easy to see if ANY pane is resizing 2743 resizePanes(e, ui, pane, true); // true = resizingDone 2744 } 2745 2746 }); 2747 }); 2748 2749 /** 2750 * resizePanes 2751 * 2752 * Sub-routine called from stop() - and drag() if livePaneResizing 2753 * 2754 * @param {!Object} evt 2755 * @param {!Object} ui 2756 * @param {string} pane 2757 * @param {boolean=} [resizingDone=false] 2758 */ 2759 var resizePanes = function (evt, ui, pane, resizingDone) { 2760 var dragPos = ui.position 2761 , c = _c[pane] 2762 , o = options[pane] 2763 , s = state[pane] 2764 , resizerPos 2765 ; 2766 switch (pane) { 2767 case "north": resizerPos = dragPos.top; break; 2768 case "west": resizerPos = dragPos.left; break; 2769 case "south": resizerPos = sC.layoutHeight - dragPos.top - o.spacing_open; break; 2770 case "east": resizerPos = sC.layoutWidth - dragPos.left - o.spacing_open; break; 2771 }; 2772 // remove container margin from resizer position to get the pane size 2773 var newSize = resizerPos - sC.inset[c.side]; 2774 2775 // Disable OR Resize Mask(s) created in drag.start 2776 if (!resizingDone) { 2777 // ensure we meet liveResizingTolerance criteria 2778 if (Math.abs(newSize - s.size) < o.liveResizingTolerance) 2779 return; // SKIP resize this time 2780 // resize the pane 2781 manualSizePane(pane, newSize, false, true); // true = noAnimation 2782 sizeMasks(); // resize all visible masks 2783 } 2784 else { // resizingDone 2785 // ondrag_end callback 2786 if (false !== _runCallbacks("ondrag_end", pane)) 2787 manualSizePane(pane, newSize, false, true); // true = noAnimation 2788 hideMasks(true); // true = force hiding all masks even if one is 'sliding' 2789 if (s.isSliding) // RE-SHOW 'object-masks' so objects won't show through sliding pane 2790 showMasks( pane, { resizing: true }); 2791 } 2792 }; 2793 } 2794 2795 /** 2796 * sizeMask 2797 * 2798 * Needed to overlay a DIV over an IFRAME-pane because mask CANNOT be *inside* the pane 2799 * Called when mask created, and during livePaneResizing 2800 */ 2801, sizeMask = function () { 2802 var $M = $(this) 2803 , pane = $M.data("layoutMask") // eg: "west" 2804 , s = state[pane] 2805 ; 2806 // only masks over an IFRAME-pane need manual resizing 2807 if (s.tagName == "IFRAME" && s.isVisible) // no need to mask closed/hidden panes 2808 $M.css({ 2809 top: s.offsetTop 2810 , left: s.offsetLeft 2811 , width: s.outerWidth 2812 , height: s.outerHeight 2813 }); 2814 /* ALT Method... 2815 var $P = $Ps[pane]; 2816 $M.css( $P.position() ).css({ width: $P[0].offsetWidth, height: $P[0].offsetHeight }); 2817 */ 2818 } 2819, sizeMasks = function () { 2820 $Ms.each( sizeMask ); // resize all 'visible' masks 2821 } 2822 2823 /** 2824 * @param {string} pane The pane being resized, animated or isSliding 2825 * @param {Object=} [args] (optional) Options: which masks to apply, and to which panes 2826 */ 2827, showMasks = function (pane, args) { 2828 var c = _c[pane] 2829 , panes = ["center"] 2830 , z = options.zIndexes 2831 , a = $.extend({ 2832 objectsOnly: false 2833 , animation: false 2834 , resizing: true 2835 , sliding: state[pane].isSliding 2836 }, args ) 2837 , o, s 2838 ; 2839 if (a.resizing) 2840 panes.push( pane ); 2841 if (a.sliding) 2842 panes.push( _c.oppositeEdge[pane] ); // ADD the oppositeEdge-pane 2843 2844 if (c.dir === "horz") { 2845 panes.push("west"); 2846 panes.push("east"); 2847 } 2848 2849 $.each(panes, function(i,p){ 2850 s = state[p]; 2851 o = options[p]; 2852 if (s.isVisible && ( o.maskObjects || (!a.objectsOnly && o.maskContents) )) { 2853 getMasks(p).each(function(){ 2854 sizeMask.call(this); 2855 this.style.zIndex = s.isSliding ? z.pane_sliding+1 : z.pane_normal+1 2856 this.style.display = "block"; 2857 }); 2858 } 2859 }); 2860 } 2861 2862 /** 2863 * @param {boolean=} force Hide masks even if a pane is sliding 2864 */ 2865, hideMasks = function (force) { 2866 // ensure no pane is resizing - could be a timing issue 2867 if (force || !state.paneResizing) { 2868 $Ms.hide(); // hide ALL masks 2869 } 2870 // if ANY pane is sliding, then DO NOT remove masks from panes with maskObjects enabled 2871 else if (!force && !$.isEmptyObject( state.panesSliding )) { 2872 var i = $Ms.length - 1 2873 , p, $M; 2874 for (; i >= 0; i--) { 2875 $M = $Ms.eq(i); 2876 p = $M.data("layoutMask"); 2877 if (!options[p].maskObjects) { 2878 $M.hide(); 2879 } 2880 } 2881 } 2882 } 2883 2884 /** 2885 * @param {string} pane 2886 */ 2887, getMasks = function (pane) { 2888 var $Masks = $([]) 2889 , $M, i = 0, c = $Ms.length 2890 ; 2891 for (; i<c; i++) { 2892 $M = $Ms.eq(i); 2893 if ($M.data("layoutMask") === pane) 2894 $Masks = $Masks.add( $M ); 2895 } 2896 if ($Masks.length) 2897 return $Masks; 2898 else 2899 return createMasks(pane); 2900 } 2901 2902 /** 2903 * createMasks 2904 * 2905 * Generates both DIV (ALWAYS used) and IFRAME (optional) elements as masks 2906 * An IFRAME mask is created *under* the DIV when maskObjects=true, because a DIV cannot mask an applet 2907 * 2908 * @param {string} pane 2909 */ 2910, createMasks = function (pane) { 2911 var 2912 $P = $Ps[pane] 2913 , s = state[pane] 2914 , o = options[pane] 2915 , z = options.zIndexes 2916 , isIframe, el, $M, css, i 2917 ; 2918 if (!o.maskContents && !o.maskObjects) return $([]); 2919 // if o.maskObjects=true, then loop TWICE to create BOTH kinds of mask, else only create a DIV 2920 for (i=0; i < (o.maskObjects ? 2 : 1); i++) { 2921 isIframe = o.maskObjects && i==0; 2922 el = document.createElement( isIframe ? "iframe" : "div" ); 2923 $M = $(el).data("layoutMask", pane); // add data to relate mask to pane 2924 el.className = "ui-layout-mask ui-layout-mask-"+ pane; // for user styling 2925 css = el.style; 2926 // Both DIVs and IFRAMES 2927 css.background = "#FFF"; 2928 css.position = "absolute"; 2929 css.display = "block"; 2930 if (isIframe) { // IFRAME-only props 2931 el.src = "about:blank"; 2932 el.frameborder = 0; 2933 css.border = 0; 2934 css.opacity = 0; 2935 css.filter = "Alpha(Opacity='0')"; 2936 //el.allowTransparency = true; - for IE, but breaks masking ability! 2937 } 2938 else { // DIV-only props 2939 css.opacity = 0.001; 2940 css.filter = "Alpha(Opacity='1')"; 2941 } 2942 // if pane IS an IFRAME, then must mask the pane itself 2943 if (s.tagName == "IFRAME") { 2944 // NOTE sizing done by a subroutine so can be called during live-resizing 2945 css.zIndex = z.pane_normal+1; // 1-higher than pane 2946 $N.append( el ); // append to LAYOUT CONTAINER 2947 } 2948 // otherwise put masks *inside the pane* to mask its contents 2949 else { 2950 $M.addClass("ui-layout-mask-inside-pane"); 2951 css.zIndex = o.maskZindex || z.content_mask; // usually 1, but customizable 2952 css.top = 0; 2953 css.left = 0; 2954 css.width = "100%"; 2955 css.height = "100%"; 2956 $P.append( el ); // append INSIDE pane element 2957 } 2958 // add Mask to cached array so can be resized & reused 2959 $Ms = $Ms.add( el ); 2960 } 2961 return $Ms; 2962 } 2963 2964 2965 /** 2966 * Destroy this layout and reset all elements 2967 * 2968 * @param {boolean=} [destroyChildren=false] Destory Child-Layouts first? 2969 */ 2970, destroy = function (evt_or_destroyChildren, destroyChildren) { 2971 // UNBIND layout events and remove global object 2972 $(window).unbind("."+ sID); // resize & unload 2973 $(document).unbind("."+ sID); // keyDown (hotkeys) 2974 2975 if (typeof evt_or_destroyChildren === "object") 2976 // stopPropagation if called by trigger("layoutdestroy") - use evtPane utility 2977 evtPane(evt_or_destroyChildren); 2978 else // no event, so transfer 1st param to destroyChildren param 2979 destroyChildren = evt_or_destroyChildren; 2980 2981 // need to look for parent layout BEFORE we remove the container data, else skips a level 2982 //var parentPane = Instance.hasParentLayout ? $.layout.getParentPaneInstance( $N ) : null; 2983 2984 // reset layout-container 2985 $N .clearQueue() 2986 .removeData("layout") 2987 .removeData("layoutContainer") 2988 .removeClass(options.containerClass) 2989 .unbind("."+ sID) // remove ALL Layout events 2990 ; 2991 2992 // remove all mask elements that have been created 2993 $Ms.remove(); 2994 2995 // loop all panes to remove layout classes, attributes and bindings 2996 $.each(_c.allPanes, function (i, pane) { 2997 removePane( pane, false, true, destroyChildren ); // true = skipResize 2998 }); 2999 3000 // do NOT reset container CSS if is a 'pane' (or 'content') in an outer-layout - ie, THIS layout is 'nested' 3001 var css = "layoutCSS"; 3002 if ($N.data(css) && !$N.data("layoutRole")) // RESET CSS 3003 $N.css( $N.data(css) ).removeData(css); 3004 3005 // for full-page layouts, also reset the <HTML> CSS 3006 if (sC.tagName === "BODY" && ($N = $("html")).data(css)) // RESET <HTML> CSS 3007 $N.css( $N.data(css) ).removeData(css); 3008 3009 // trigger plugins for this layout, if there are any 3010 runPluginCallbacks( Instance, $.layout.onDestroy ); 3011 3012 // trigger state-management and onunload callback 3013 unload(); 3014 3015 // clear the Instance of everything except for container & options (so could recreate) 3016 // RE-CREATE: myLayout = myLayout.container.layout( myLayout.options ); 3017 for (var n in Instance) 3018 if (!n.match(/^(container|options)$/)) delete Instance[ n ]; 3019 // add a 'destroyed' flag to make it easy to check 3020 Instance.destroyed = true; 3021 3022 // if this is a child layout, CLEAR the child-pointer in the parent 3023 /* for now the pointer REMAINS, but with only container, options and destroyed keys 3024 if (parentPane) { 3025 var layout = parentPane.pane.data("parentLayout") 3026 , key = layout.options.instanceKey || 'error'; 3027 // THIS SYNTAX MAY BE WRONG! 3028 parentPane.children[key] = layout.children[ parentPane.name ].children[key] = null; 3029 } 3030 */ 3031 3032 return Instance; // for coding convenience 3033 } 3034 3035 /** 3036 * Remove a pane from the layout - subroutine of destroy() 3037 * 3038 * @see destroy() 3039 * @param {(string|Object)} evt_or_pane The pane to process 3040 * @param {boolean=} [remove=false] Remove the DOM element? 3041 * @param {boolean=} [skipResize=false] Skip calling resizeAll()? 3042 * @param {boolean=} [destroyChild=true] Destroy Child-layouts? If not passed, obeys options setting 3043 */ 3044, removePane = function (evt_or_pane, remove, skipResize, destroyChild) { 3045 if (!isInitialized()) return; 3046 var pane = evtPane.call(this, evt_or_pane) 3047 , $P = $Ps[pane] 3048 , $C = $Cs[pane] 3049 , $R = $Rs[pane] 3050 , $T = $Ts[pane] 3051 ; 3052 // NOTE: elements can still exist even after remove() 3053 // so check for missing data(), which is cleared by removed() 3054 if ($P && $.isEmptyObject( $P.data() )) $P = false; 3055 if ($C && $.isEmptyObject( $C.data() )) $C = false; 3056 if ($R && $.isEmptyObject( $R.data() )) $R = false; 3057 if ($T && $.isEmptyObject( $T.data() )) $T = false; 3058 3059 if ($P) $P.stop(true, true); 3060 3061 var o = options[pane] 3062 , s = state[pane] 3063 , d = "layout" 3064 , css = "layoutCSS" 3065 , pC = children[pane] 3066 , hasChildren = $.isPlainObject( pC ) && !$.isEmptyObject( pC ) 3067 , destroy = destroyChild !== undefined ? destroyChild : o.destroyChildren 3068 ; 3069 // FIRST destroy the child-layout(s) 3070 if (hasChildren && destroy) { 3071 $.each( pC, function (key, child) { 3072 if (!child.destroyed) 3073 child.destroy(true);// tell child-layout to destroy ALL its child-layouts too 3074 if (child.destroyed) // destroy was successful 3075 delete pC[key]; 3076 }); 3077 // if no more children, remove the children hash 3078 if ($.isEmptyObject( pC )) { 3079 pC = children[pane] = null; // clear children hash 3080 hasChildren = false; 3081 } 3082 } 3083 3084 // Note: can't 'remove' a pane element with non-destroyed children 3085 if ($P && remove && !hasChildren) 3086 $P.remove(); // remove the pane-element and everything inside it 3087 else if ($P && $P[0]) { 3088 // create list of ALL pane-classes that need to be removed 3089 var root = o.paneClass // default="ui-layout-pane" 3090 , pRoot = root +"-"+ pane // eg: "ui-layout-pane-west" 3091 , _open = "-open" 3092 , _sliding= "-sliding" 3093 , _closed = "-closed" 3094 , classes = [ root, root+_open, root+_closed, root+_sliding, // generic classes 3095 pRoot, pRoot+_open, pRoot+_closed, pRoot+_sliding ] // pane-specific classes 3096 ; 3097 $.merge(classes, getHoverClasses($P, true)); // ADD hover-classes 3098 // remove all Layout classes from pane-element 3099 $P .removeClass( classes.join(" ") ) // remove ALL pane-classes 3100 .removeData("parentLayout") 3101 .removeData("layoutPane") 3102 .removeData("layoutRole") 3103 .removeData("layoutEdge") 3104 .removeData("autoHidden") // in case set 3105 .unbind("."+ sID) // remove ALL Layout events 3106 // TODO: remove these extra unbind commands when jQuery is fixed 3107 //.unbind("mouseenter"+ sID) 3108 //.unbind("mouseleave"+ sID) 3109 ; 3110 // do NOT reset CSS if this pane/content is STILL the container of a nested layout! 3111 // the nested layout will reset its 'container' CSS when/if it is destroyed 3112 if (hasChildren && $C) { 3113 // a content-div may not have a specific width, so give it one to contain the Layout 3114 $C.width( $C.width() ); 3115 $.each( pC, function (key, child) { 3116 child.resizeAll(); // resize the Layout 3117 }); 3118 } 3119 else if ($C) 3120 $C.css( $C.data(css) ).removeData(css).removeData("layoutRole"); 3121 // remove pane AFTER content in case there was a nested layout 3122 if (!$P.data(d)) 3123 $P.css( $P.data(css) ).removeData(css); 3124 } 3125 3126 // REMOVE pane resizer and toggler elements 3127 if ($T) $T.remove(); 3128 if ($R) $R.remove(); 3129 3130 // CLEAR all pointers and state data 3131 Instance[pane] = $Ps[pane] = $Cs[pane] = $Rs[pane] = $Ts[pane] = false; 3132 s = { removed: true }; 3133 3134 if (!skipResize) 3135 resizeAll(); 3136 } 3137 3138 3139/* 3140 * ########################### 3141 * ACTION METHODS 3142 * ########################### 3143 */ 3144 3145 /** 3146 * @param {string} pane 3147 */ 3148, _hidePane = function (pane) { 3149 var $P = $Ps[pane] 3150 , o = options[pane] 3151 , s = $P[0].style 3152 ; 3153 if (o.useOffscreenClose) { 3154 if (!$P.data(_c.offscreenReset)) 3155 $P.data(_c.offscreenReset, { left: s.left, right: s.right }); 3156 $P.css( _c.offscreenCSS ); 3157 } 3158 else 3159 $P.hide().removeData(_c.offscreenReset); 3160 } 3161 3162 /** 3163 * @param {string} pane 3164 */ 3165, _showPane = function (pane) { 3166 var $P = $Ps[pane] 3167 , o = options[pane] 3168 , off = _c.offscreenCSS 3169 , old = $P.data(_c.offscreenReset) 3170 , s = $P[0].style 3171 ; 3172 $P .show() // ALWAYS show, just in case 3173 .removeData(_c.offscreenReset); 3174 if (o.useOffscreenClose && old) { 3175 if (s.left == off.left) 3176 s.left = old.left; 3177 if (s.right == off.right) 3178 s.right = old.right; 3179 } 3180 } 3181 3182 3183 /** 3184 * Completely 'hides' a pane, including its spacing - as if it does not exist 3185 * The pane is not actually 'removed' from the source, so can use 'show' to un-hide it 3186 * 3187 * @param {(string|Object)} evt_or_pane The pane being hidden, ie: north, south, east, or west 3188 * @param {boolean=} [noAnimation=false] 3189 */ 3190, hide = function (evt_or_pane, noAnimation) { 3191 if (!isInitialized()) return; 3192 var pane = evtPane.call(this, evt_or_pane) 3193 , o = options[pane] 3194 , s = state[pane] 3195 , $P = $Ps[pane] 3196 , $R = $Rs[pane] 3197 ; 3198 if (pane === "center" || !$P || s.isHidden) return; // pane does not exist OR is already hidden 3199 3200 // onhide_start callback - will CANCEL hide if returns false 3201 if (state.initialized && false === _runCallbacks("onhide_start", pane)) return; 3202 3203 s.isSliding = false; // just in case 3204 delete state.panesSliding[pane]; 3205 3206 // now hide the elements 3207 if ($R) $R.hide(); // hide resizer-bar 3208 if (!state.initialized || s.isClosed) { 3209 s.isClosed = true; // to trigger open-animation on show() 3210 s.isHidden = true; 3211 s.isVisible = false; 3212 if (!state.initialized) 3213 _hidePane(pane); // no animation when loading page 3214 sizeMidPanes(_c[pane].dir === "horz" ? "" : "center"); 3215 if (state.initialized || o.triggerEventsOnLoad) 3216 _runCallbacks("onhide_end", pane); 3217 } 3218 else { 3219 s.isHiding = true; // used by onclose 3220 close(pane, false, noAnimation); // adjust all panes to fit 3221 } 3222 } 3223 3224 /** 3225 * Show a hidden pane - show as 'closed' by default unless openPane = true 3226 * 3227 * @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west 3228 * @param {boolean=} [openPane=false] 3229 * @param {boolean=} [noAnimation=false] 3230 * @param {boolean=} [noAlert=false] 3231 */ 3232, show = function (evt_or_pane, openPane, noAnimation, noAlert) { 3233 if (!isInitialized()) return; 3234 var pane = evtPane.call(this, evt_or_pane) 3235 , o = options[pane] 3236 , s = state[pane] 3237 , $P = $Ps[pane] 3238 , $R = $Rs[pane] 3239 ; 3240 if (pane === "center" || !$P || !s.isHidden) return; // pane does not exist OR is not hidden 3241 3242 // onshow_start callback - will CANCEL show if returns false 3243 if (false === _runCallbacks("onshow_start", pane)) return; 3244 3245 s.isShowing = true; // used by onopen/onclose 3246 //s.isHidden = false; - will be set by open/close - if not cancelled 3247 s.isSliding = false; // just in case 3248 delete state.panesSliding[pane]; 3249 3250 // now show the elements 3251 //if ($R) $R.show(); - will be shown by open/close 3252 if (openPane === false) 3253 close(pane, true); // true = force 3254 else 3255 open(pane, false, noAnimation, noAlert); // adjust all panes to fit 3256 } 3257 3258 3259 /** 3260 * Toggles a pane open/closed by calling either open or close 3261 * 3262 * @param {(string|Object)} evt_or_pane The pane being toggled, ie: north, south, east, or west 3263 * @param {boolean=} [slide=false] 3264 */ 3265, toggle = function (evt_or_pane, slide) { 3266 if (!isInitialized()) return; 3267 var evt = evtObj(evt_or_pane) 3268 , pane = evtPane.call(this, evt_or_pane) 3269 , s = state[pane] 3270 ; 3271 if (evt) // called from to $R.dblclick OR triggerPaneEvent 3272 evt.stopImmediatePropagation(); 3273 if (s.isHidden) 3274 show(pane); // will call 'open' after unhiding it 3275 else if (s.isClosed) 3276 open(pane, !!slide); 3277 else 3278 close(pane); 3279 } 3280 3281 3282 /** 3283 * Utility method used during init or other auto-processes 3284 * 3285 * @param {string} pane The pane being closed 3286 * @param {boolean=} [setHandles=false] 3287 */ 3288, _closePane = function (pane, setHandles) { 3289 var 3290 $P = $Ps[pane] 3291 , s = state[pane] 3292 ; 3293 _hidePane(pane); 3294 s.isClosed = true; 3295 s.isVisible = false; 3296 if (setHandles) setAsClosed(pane); 3297 } 3298 3299 /** 3300 * Close the specified pane (animation optional), and resize all other panes as needed 3301 * 3302 * @param {(string|Object)} evt_or_pane The pane being closed, ie: north, south, east, or west 3303 * @param {boolean=} [force=false] 3304 * @param {boolean=} [noAnimation=false] 3305 * @param {boolean=} [skipCallback=false] 3306 */ 3307, close = function (evt_or_pane, force, noAnimation, skipCallback) { 3308 var pane = evtPane.call(this, evt_or_pane); 3309 if (pane === "center") return; // validate 3310 // if pane has been initialized, but NOT the complete layout, close pane instantly 3311 if (!state.initialized && $Ps[pane]) { 3312 _closePane(pane, true); // INIT pane as closed 3313 return; 3314 } 3315 if (!isInitialized()) return; 3316 3317 var 3318 $P = $Ps[pane] 3319 , $R = $Rs[pane] 3320 , $T = $Ts[pane] 3321 , o = options[pane] 3322 , s = state[pane] 3323 , c = _c[pane] 3324 , doFX, isShowing, isHiding, wasSliding; 3325 3326 // QUEUE in case another action/animation is in progress 3327 $N.queue(function( queueNext ){ 3328 3329 if ( !$P 3330 || (!o.closable && !s.isShowing && !s.isHiding) // invalid request // (!o.resizable && !o.closable) ??? 3331 || (!force && s.isClosed && !s.isShowing) // already closed 3332 ) return queueNext(); 3333 3334 // onclose_start callback - will CANCEL hide if returns false 3335 // SKIP if just 'showing' a hidden pane as 'closed' 3336 var abort = !s.isShowing && false === _runCallbacks("onclose_start", pane); 3337 3338 // transfer logic vars to temp vars 3339 isShowing = s.isShowing; 3340 isHiding = s.isHiding; 3341 wasSliding = s.isSliding; 3342 // now clear the logic vars (REQUIRED before aborting) 3343 delete s.isShowing; 3344 delete s.isHiding; 3345 3346 if (abort) return queueNext(); 3347 3348 doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none"); 3349 s.isMoving = true; 3350 s.isClosed = true; 3351 s.isVisible = false; 3352 // update isHidden BEFORE sizing panes 3353 if (isHiding) s.isHidden = true; 3354 else if (isShowing) s.isHidden = false; 3355 3356 if (s.isSliding) // pane is being closed, so UNBIND trigger events 3357 bindStopSlidingEvents(pane, false); // will set isSliding=false 3358 else // resize panes adjacent to this one 3359 sizeMidPanes(_c[pane].dir === "horz" ? "" : "center", false); // false = NOT skipCallback 3360 3361 // if this pane has a resizer bar, move it NOW - before animation 3362 setAsClosed(pane); 3363 3364 // CLOSE THE PANE 3365 if (doFX) { // animate the close 3366 lockPaneForFX(pane, true); // need to set left/top so animation will work 3367 $P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () { 3368 lockPaneForFX(pane, false); // undo 3369 if (s.isClosed) close_2(); 3370 queueNext(); 3371 }); 3372 } 3373 else { // hide the pane without animation 3374 _hidePane(pane); 3375 close_2(); 3376 queueNext(); 3377 }; 3378 }); 3379 3380 // SUBROUTINE 3381 function close_2 () { 3382 s.isMoving = false; 3383 bindStartSlidingEvents(pane, true); // will enable if o.slidable = true 3384 3385 // if opposite-pane was autoClosed, see if it can be autoOpened now 3386 var altPane = _c.oppositeEdge[pane]; 3387 if (state[ altPane ].noRoom) { 3388 setSizeLimits( altPane ); 3389 makePaneFit( altPane ); 3390 } 3391 3392 if (!skipCallback && (state.initialized || o.triggerEventsOnLoad)) { 3393 // onclose callback - UNLESS just 'showing' a hidden pane as 'closed' 3394 if (!isShowing) _runCallbacks("onclose_end", pane); 3395 // onhide OR onshow callback 3396 if (isShowing) _runCallbacks("onshow_end", pane); 3397 if (isHiding) _runCallbacks("onhide_end", pane); 3398 } 3399 } 3400 } 3401 3402 /** 3403 * @param {string} pane The pane just closed, ie: north, south, east, or west 3404 */ 3405, setAsClosed = function (pane) { 3406 if (!$Rs[pane]) return; // handles not initialized yet! 3407 var 3408 $P = $Ps[pane] 3409 , $R = $Rs[pane] 3410 , $T = $Ts[pane] 3411 , o = options[pane] 3412 , s = state[pane] 3413 , side = _c[pane].side 3414 , rClass = o.resizerClass 3415 , tClass = o.togglerClass 3416 , _pane = "-"+ pane // used for classNames 3417 , _open = "-open" 3418 , _sliding= "-sliding" 3419 , _closed = "-closed" 3420 ; 3421 $R 3422 .css(side, sC.inset[side]) // move the resizer 3423 .removeClass( rClass+_open +" "+ rClass+_pane+_open ) 3424 .removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) 3425 .addClass( rClass+_closed +" "+ rClass+_pane+_closed ) 3426 ; 3427 // handle already-hidden panes in case called by swap() or a similar method 3428 if (s.isHidden) $R.hide(); // hide resizer-bar 3429 3430 // DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvents? 3431 if (o.resizable && $.layout.plugins.draggable) 3432 $R 3433 .draggable("disable") 3434 .removeClass("ui-state-disabled") // do NOT apply disabled styling - not suitable here 3435 .css("cursor", "default") 3436 .attr("title","") 3437 ; 3438 3439 // if pane has a toggler button, adjust that too 3440 if ($T) { 3441 $T 3442 .removeClass( tClass+_open +" "+ tClass+_pane+_open ) 3443 .addClass( tClass+_closed +" "+ tClass+_pane+_closed ) 3444 .attr("title", o.tips.Open) // may be blank 3445 ; 3446 // toggler-content - if exists 3447 $T.children(".content-open").hide(); 3448 $T.children(".content-closed").css("display","block"); 3449 } 3450 3451 // sync any 'pin buttons' 3452 syncPinBtns(pane, false); 3453 3454 if (state.initialized) { 3455 // resize 'length' and position togglers for adjacent panes 3456 sizeHandles(); 3457 } 3458 } 3459 3460 /** 3461 * Open the specified pane (animation optional), and resize all other panes as needed 3462 * 3463 * @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west 3464 * @param {boolean=} [slide=false] 3465 * @param {boolean=} [noAnimation=false] 3466 * @param {boolean=} [noAlert=false] 3467 */ 3468, open = function (evt_or_pane, slide, noAnimation, noAlert) { 3469 if (!isInitialized()) return; 3470 var pane = evtPane.call(this, evt_or_pane) 3471 , $P = $Ps[pane] 3472 , $R = $Rs[pane] 3473 , $T = $Ts[pane] 3474 , o = options[pane] 3475 , s = state[pane] 3476 , c = _c[pane] 3477 , doFX, isShowing 3478 ; 3479 if (pane === "center") return; // validate 3480 // QUEUE in case another action/animation is in progress 3481 $N.queue(function( queueNext ){ 3482 3483 if ( !$P 3484 || (!o.resizable && !o.closable && !s.isShowing) // invalid request 3485 || (s.isVisible && !s.isSliding) // already open 3486 ) return queueNext(); 3487 3488 // pane can ALSO be unhidden by just calling show(), so handle this scenario 3489 if (s.isHidden && !s.isShowing) { 3490 queueNext(); // call before show() because it needs the queue free 3491 show(pane, true); 3492 return; 3493 } 3494 3495 if (s.autoResize && s.size != o.size) // resize pane to original size set in options 3496 sizePane(pane, o.size, true, true, true); // true=skipCallback/noAnimation/forceResize 3497 else 3498 // make sure there is enough space available to open the pane 3499 setSizeLimits(pane, slide); 3500 3501 // onopen_start callback - will CANCEL open if returns false 3502 var cbReturn = _runCallbacks("onopen_start", pane); 3503 3504 if (cbReturn === "abort") 3505 return queueNext(); 3506 3507 // update pane-state again in case options were changed in onopen_start 3508 if (cbReturn !== "NC") // NC = "No Callback" 3509 setSizeLimits(pane, slide); 3510 3511 if (s.minSize > s.maxSize) { // INSUFFICIENT ROOM FOR PANE TO OPEN! 3512 syncPinBtns(pane, false); // make sure pin-buttons are reset 3513 if (!noAlert && o.tips.noRoomToOpen) 3514 alert(o.tips.noRoomToOpen); 3515 return queueNext(); // ABORT 3516 } 3517 3518 if (slide) // START Sliding - will set isSliding=true 3519 bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane 3520 else if (s.isSliding) // PIN PANE (stop sliding) - open pane 'normally' instead 3521 bindStopSlidingEvents(pane, false); // UNBIND trigger events - will set isSliding=false 3522 else if (o.slidable) 3523 bindStartSlidingEvents(pane, false); // UNBIND trigger events 3524 3525 s.noRoom = false; // will be reset by makePaneFit if 'noRoom' 3526 makePaneFit(pane); 3527 3528 // transfer logic var to temp var 3529 isShowing = s.isShowing; 3530 // now clear the logic var 3531 delete s.isShowing; 3532 3533 doFX = !noAnimation && s.isClosed && (o.fxName_open != "none"); 3534 s.isMoving = true; 3535 s.isVisible = true; 3536 s.isClosed = false; 3537 // update isHidden BEFORE sizing panes - WHY??? Old? 3538 if (isShowing) s.isHidden = false; 3539 3540 if (doFX) { // ANIMATE 3541 // mask adjacent panes with objects 3542 lockPaneForFX(pane, true); // need to set left/top so animation will work 3543 $P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() { 3544 lockPaneForFX(pane, false); // undo 3545 if (s.isVisible) open_2(); // continue 3546 queueNext(); 3547 }); 3548 } 3549 else { // no animation 3550 _showPane(pane);// just show pane and... 3551 open_2(); // continue 3552 queueNext(); 3553 }; 3554 }); 3555 3556 // SUBROUTINE 3557 function open_2 () { 3558 s.isMoving = false; 3559 3560 // cure iframe display issues 3561 _fixIframe(pane); 3562 3563 // NOTE: if isSliding, then other panes are NOT 'resized' 3564 if (!s.isSliding) { // resize all panes adjacent to this one 3565 sizeMidPanes(_c[pane].dir=="vert" ? "center" : "", false); // false = NOT skipCallback 3566 } 3567 3568 // set classes, position handles and execute callbacks... 3569 setAsOpen(pane); 3570 }; 3571 3572 } 3573 3574 /** 3575 * @param {string} pane The pane just opened, ie: north, south, east, or west 3576 * @param {boolean=} [skipCallback=false] 3577 */ 3578, setAsOpen = function (pane, skipCallback) { 3579 var 3580 $P = $Ps[pane] 3581 , $R = $Rs[pane] 3582 , $T = $Ts[pane] 3583 , o = options[pane] 3584 , s = state[pane] 3585 , side = _c[pane].side 3586 , rClass = o.resizerClass 3587 , tClass = o.togglerClass 3588 , _pane = "-"+ pane // used for classNames 3589 , _open = "-open" 3590 , _closed = "-closed" 3591 , _sliding= "-sliding" 3592 ; 3593 $R 3594 .css(side, sC.inset[side] + getPaneSize(pane)) // move the resizer 3595 .removeClass( rClass+_closed +" "+ rClass+_pane+_closed ) 3596 .addClass( rClass+_open +" "+ rClass+_pane+_open ) 3597 ; 3598 if (s.isSliding) 3599 $R.addClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) 3600 else // in case 'was sliding' 3601 $R.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) 3602 3603 removeHover( 0, $R ); // remove hover classes 3604 if (o.resizable && $.layout.plugins.draggable) 3605 $R .draggable("enable") 3606 .css("cursor", o.resizerCursor) 3607 .attr("title", o.tips.Resize); 3608 else if (!s.isSliding) 3609 $R.css("cursor", "default"); // n-resize, s-resize, etc 3610 3611 // if pane also has a toggler button, adjust that too 3612 if ($T) { 3613 $T .removeClass( tClass+_closed +" "+ tClass+_pane+_closed ) 3614 .addClass( tClass+_open +" "+ tClass+_pane+_open ) 3615 .attr("title", o.tips.Close); // may be blank 3616 removeHover( 0, $T ); // remove hover classes 3617 // toggler-content - if exists 3618 $T.children(".content-closed").hide(); 3619 $T.children(".content-open").css("display","block"); 3620 } 3621 3622 // sync any 'pin buttons' 3623 syncPinBtns(pane, !s.isSliding); 3624 3625 // update pane-state dimensions - BEFORE resizing content 3626 $.extend(s, elDims($P)); 3627 3628 if (state.initialized) { 3629 // resize resizer & toggler sizes for all panes 3630 sizeHandles(); 3631 // resize content every time pane opens - to be sure 3632 sizeContent(pane, true); // true = remeasure headers/footers, even if 'pane.isMoving' 3633 } 3634 3635 if (!skipCallback && (state.initialized || o.triggerEventsOnLoad) && $P.is(":visible")) { 3636 // onopen callback 3637 _runCallbacks("onopen_end", pane); 3638 // onshow callback - TODO: should this be here? 3639 if (s.isShowing) _runCallbacks("onshow_end", pane); 3640 3641 // ALSO call onresize because layout-size *may* have changed while pane was closed 3642 if (state.initialized) 3643 _runCallbacks("onresize_end", pane); 3644 } 3645 3646 // TODO: Somehow sizePane("north") is being called after this point??? 3647 } 3648 3649 3650 /** 3651 * slideOpen / slideClose / slideToggle 3652 * 3653 * Pass-though methods for sliding 3654 */ 3655, slideOpen = function (evt_or_pane) { 3656 if (!isInitialized()) return; 3657 var evt = evtObj(evt_or_pane) 3658 , pane = evtPane.call(this, evt_or_pane) 3659 , s = state[pane] 3660 , delay = options[pane].slideDelay_open 3661 ; 3662 if (pane === "center") return; // validate 3663 // prevent event from triggering on NEW resizer binding created below 3664 if (evt) evt.stopImmediatePropagation(); 3665 3666 if (s.isClosed && evt && evt.type === "mouseenter" && delay > 0) 3667 // trigger = mouseenter - use a delay 3668 timer.set(pane+"_openSlider", open_NOW, delay); 3669 else 3670 open_NOW(); // will unbind events if is already open 3671 3672 /** 3673 * SUBROUTINE for timed open 3674 */ 3675 function open_NOW () { 3676 if (!s.isClosed) // skip if no longer closed! 3677 bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane 3678 else if (!s.isMoving) 3679 open(pane, true); // true = slide - open() will handle binding 3680 }; 3681 } 3682 3683,