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, slideClose = function (evt_or_pane) { 3684 if (!isInitialized()) return; 3685 var evt = evtObj(evt_or_pane) 3686 , pane = evtPane.call(this, evt_or_pane) 3687 , o = options[pane] 3688 , s = state[pane] 3689 , delay = s.isMoving ? 1000 : 300 // MINIMUM delay - option may override 3690 ; 3691 if (pane === "center") return; // validate 3692 if (s.isClosed || s.isResizing) 3693 return; // skip if already closed OR in process of resizing 3694 else if (o.slideTrigger_close === "click") 3695 close_NOW(); // close immediately onClick 3696 else if (o.preventQuickSlideClose && s.isMoving) 3697 return; // handle Chrome quick-close on slide-open 3698 else if (o.preventPrematureSlideClose && evt && $.layout.isMouseOverElem(evt, $Ps[pane])) 3699 return; // handle incorrect mouseleave trigger, like when over a SELECT-list in IE 3700 else if (evt) // trigger = mouseleave - use a delay 3701 // 1 sec delay if 'opening', else .3 sec 3702 timer.set(pane+"_closeSlider", close_NOW, max(o.slideDelay_close, delay)); 3703 else // called programically 3704 close_NOW(); 3705 3706 /** 3707 * SUBROUTINE for timed close 3708 */ 3709 function close_NOW () { 3710 if (s.isClosed) // skip 'close' if already closed! 3711 bindStopSlidingEvents(pane, false); // UNBIND trigger events - TODO: is this needed here? 3712 else if (!s.isMoving) 3713 close(pane); // close will handle unbinding 3714 }; 3715 } 3716 3717 /** 3718 * @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west 3719 */ 3720, slideToggle = function (evt_or_pane) { 3721 var pane = evtPane.call(this, evt_or_pane); 3722 toggle(pane, true); 3723 } 3724 3725 3726 /** 3727 * Must set left/top on East/South panes so animation will work properly 3728 * 3729 * @param {string} pane The pane to lock, 'east' or 'south' - any other is ignored! 3730 * @param {boolean} doLock true = set left/top, false = remove 3731 */ 3732, lockPaneForFX = function (pane, doLock) { 3733 var $P = $Ps[pane] 3734 , s = state[pane] 3735 , o = options[pane] 3736 , z = options.zIndexes 3737 ; 3738 if (doLock) { 3739 showMasks( pane, { animation: true, objectsOnly: true }); 3740 $P.css({ zIndex: z.pane_animate }); // overlay all elements during animation 3741 if (pane=="south") 3742 $P.css({ top: sC.inset.top + sC.innerHeight - $P.outerHeight() }); 3743 else if (pane=="east") 3744 $P.css({ left: sC.inset.left + sC.innerWidth - $P.outerWidth() }); 3745 } 3746 else { // animation DONE - RESET CSS 3747 hideMasks(); 3748 $P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) }); 3749 if (pane=="south") 3750 $P.css({ top: "auto" }); 3751 // if pane is positioned 'off-screen', then DO NOT screw with it! 3752 else if (pane=="east" && !$P.css("left").match(/\-99999/)) 3753 $P.css({ left: "auto" }); 3754 // fix anti-aliasing in IE - only needed for animations that change opacity 3755 if (browser.msie && o.fxOpacityFix && o.fxName_open != "slide" && $P.css("filter") && $P.css("opacity") == 1) 3756 $P[0].style.removeAttribute('filter'); 3757 } 3758 } 3759 3760 3761 /** 3762 * Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger 3763 * 3764 * @see open(), close() 3765 * @param {string} pane The pane to enable/disable, 'north', 'south', etc. 3766 * @param {boolean} enable Enable or Disable sliding? 3767 */ 3768, bindStartSlidingEvents = function (pane, enable) { 3769 var o = options[pane] 3770 , $P = $Ps[pane] 3771 , $R = $Rs[pane] 3772 , evtName = o.slideTrigger_open.toLowerCase() 3773 ; 3774 if (!$R || (enable && !o.slidable)) return; 3775 3776 // make sure we have a valid event 3777 if (evtName.match(/mouseover/)) 3778 evtName = o.slideTrigger_open = "mouseenter"; 3779 else if (!evtName.match(/(click|dblclick|mouseenter)/)) 3780 evtName = o.slideTrigger_open = "click"; 3781 3782 // must remove double-click-toggle when using dblclick-slide 3783 if (o.resizerDblClickToggle && evtName.match(/click/)) { 3784 $R[enable ? "unbind" : "bind"]('dblclick.'+ sID, toggle) 3785 } 3786 3787 $R 3788 // add or remove event 3789 [enable ? "bind" : "unbind"](evtName +'.'+ sID, slideOpen) 3790 // set the appropriate cursor & title/tip 3791 .css("cursor", enable ? o.sliderCursor : "default") 3792 .attr("title", enable ? o.tips.Slide : "") 3793 ; 3794 } 3795 3796 /** 3797 * Add or remove 'mouseleave' events to 'slide close' when pane is 'sliding' open or closed 3798 * Also increases zIndex when pane is sliding open 3799 * See bindStartSlidingEvents for code to control 'slide open' 3800 * 3801 * @see slideOpen(), slideClose() 3802 * @param {string} pane The pane to process, 'north', 'south', etc. 3803 * @param {boolean} enable Enable or Disable events? 3804 */ 3805, bindStopSlidingEvents = function (pane, enable) { 3806 var o = options[pane] 3807 , s = state[pane] 3808 , c = _c[pane] 3809 , z = options.zIndexes 3810 , evtName = o.slideTrigger_close.toLowerCase() 3811 , action = (enable ? "bind" : "unbind") 3812 , $P = $Ps[pane] 3813 , $R = $Rs[pane] 3814 ; 3815 timer.clear(pane+"_closeSlider"); // just in case 3816 3817 if (enable) { 3818 s.isSliding = true; 3819 state.panesSliding[pane] = true; 3820 // remove 'slideOpen' event from resizer 3821 // ALSO will raise the zIndex of the pane & resizer 3822 bindStartSlidingEvents(pane, false); 3823 } 3824 else { 3825 s.isSliding = false; 3826 delete state.panesSliding[pane]; 3827 } 3828 3829 // RE/SET zIndex - increases when pane is sliding-open, resets to normal when not 3830 $P.css("zIndex", enable ? z.pane_sliding : z.pane_normal); 3831 $R.css("zIndex", enable ? z.pane_sliding+2 : z.resizer_normal); // NOTE: mask = pane_sliding+1 3832 3833 // make sure we have a valid event 3834 if (!evtName.match(/(click|mouseleave)/)) 3835 evtName = o.slideTrigger_close = "mouseleave"; // also catches 'mouseout' 3836 3837 // add/remove slide triggers 3838 $R[action](evtName, slideClose); // base event on resize 3839 // need extra events for mouseleave 3840 if (evtName === "mouseleave") { 3841 // also close on pane.mouseleave 3842 $P[action]("mouseleave."+ sID, slideClose); 3843 // cancel timer when mouse moves between 'pane' and 'resizer' 3844 $R[action]("mouseenter."+ sID, cancelMouseOut); 3845 $P[action]("mouseenter."+ sID, cancelMouseOut); 3846 } 3847 3848 if (!enable) 3849 timer.clear(pane+"_closeSlider"); 3850 else if (evtName === "click" && !o.resizable) { 3851 // IF pane is not resizable (which already has a cursor and tip) 3852 // then set the a cursor & title/tip on resizer when sliding 3853 $R.css("cursor", enable ? o.sliderCursor : "default"); 3854 $R.attr("title", enable ? o.tips.Close : ""); // use Toggler-tip, eg: "Close Pane" 3855 } 3856 3857 // SUBROUTINE for mouseleave timer clearing 3858 function cancelMouseOut (evt) { 3859 timer.clear(pane+"_closeSlider"); 3860 evt.stopPropagation(); 3861 } 3862 } 3863 3864 3865 /** 3866 * Hides/closes a pane if there is insufficient room - reverses this when there is room again 3867 * MUST have already called setSizeLimits() before calling this method 3868 * 3869 * @param {string} pane The pane being resized 3870 * @param {boolean=} [isOpening=false] Called from onOpen? 3871 * @param {boolean=} [skipCallback=false] Should the onresize callback be run? 3872 * @param {boolean=} [force=false] 3873 */ 3874, makePaneFit = function (pane, isOpening, skipCallback, force) { 3875 var o = options[pane] 3876 , s = state[pane] 3877 , c = _c[pane] 3878 , $P = $Ps[pane] 3879 , $R = $Rs[pane] 3880 , isSidePane = c.dir==="vert" 3881 , hasRoom = false 3882 ; 3883 // special handling for center & east/west panes 3884 if (pane === "center" || (isSidePane && s.noVerticalRoom)) { 3885 // see if there is enough room to display the pane 3886 // ERROR: hasRoom = s.minHeight <= s.maxHeight && (isSidePane || s.minWidth <= s.maxWidth); 3887 hasRoom = (s.maxHeight >= 0); 3888 if (hasRoom && s.noRoom) { // previously hidden due to noRoom, so show now 3889 _showPane(pane); 3890 if ($R) $R.show(); 3891 s.isVisible = true; 3892 s.noRoom = false; 3893 if (isSidePane) s.noVerticalRoom = false; 3894 _fixIframe(pane); 3895 } 3896 else if (!hasRoom && !s.noRoom) { // not currently hidden, so hide now 3897 _hidePane(pane); 3898 if ($R) $R.hide(); 3899 s.isVisible = false; 3900 s.noRoom = true; 3901 } 3902 } 3903 3904 // see if there is enough room to fit the border-pane 3905 if (pane === "center") { 3906 // ignore center in this block 3907 } 3908 else if (s.minSize <= s.maxSize) { // pane CAN fit 3909 hasRoom = true; 3910 if (s.size > s.maxSize) // pane is too big - shrink it 3911 sizePane(pane, s.maxSize, skipCallback, true, force); // true = noAnimation 3912 else if (s.size < s.minSize) // pane is too small - enlarge it 3913 sizePane(pane, s.minSize, skipCallback, true, force); // true = noAnimation 3914 // need s.isVisible because new pseudoClose method keeps pane visible, but off-screen 3915 else if ($R && s.isVisible && $P.is(":visible")) { 3916 // make sure resizer-bar is positioned correctly 3917 // handles situation where nested layout was 'hidden' when initialized 3918 var pos = s.size + sC.inset[c.side]; 3919 if ($.layout.cssNum( $R, c.side ) != pos) $R.css( c.side, pos ); 3920 } 3921 3922 // if was previously hidden due to noRoom, then RESET because NOW there is room 3923 if (s.noRoom) { 3924 // s.noRoom state will be set by open or show 3925 if (s.wasOpen && o.closable) { 3926 if (o.autoReopen) 3927 open(pane, false, true, true); // true = noAnimation, true = noAlert 3928 else // leave the pane closed, so just update state 3929 s.noRoom = false; 3930 } 3931 else 3932 show(pane, s.wasOpen, true, true); // true = noAnimation, true = noAlert 3933 } 3934 } 3935 else { // !hasRoom - pane CANNOT fit 3936 if (!s.noRoom) { // pane not set as noRoom yet, so hide or close it now... 3937 s.noRoom = true; // update state 3938 s.wasOpen = !s.isClosed && !s.isSliding; 3939 if (s.isClosed){} // SKIP 3940 else if (o.closable) // 'close' if possible 3941 close(pane, true, true); // true = force, true = noAnimation 3942 else // 'hide' pane if cannot just be closed 3943 hide(pane, true); // true = noAnimation 3944 } 3945 } 3946 } 3947 3948 3949 /** 3950 * manualSizePane is an exposed flow-through method allowing extra code when pane is 'manually resized' 3951 * 3952 * @param {(string|Object)} evt_or_pane The pane being resized 3953 * @param {number} size The *desired* new size for this pane - will be validated 3954 * @param {boolean=} [skipCallback=false] Should the onresize callback be run? 3955 * @param {boolean=} [noAnimation=false] 3956 * @param {boolean=} [force=false] Force resizing even if does not seem necessary 3957 */ 3958, manualSizePane = function (evt_or_pane, size, skipCallback, noAnimation, force) { 3959 if (!isInitialized()) return; 3960 var pane = evtPane.call(this, evt_or_pane) 3961 , o = options[pane] 3962 , s = state[pane] 3963 // if resizing callbacks have been delayed and resizing is now DONE, force resizing to complete... 3964 , forceResize = force || (o.livePaneResizing && !s.isResizing) 3965 ; 3966 if (pane === "center") return; // validate 3967 // ANY call to manualSizePane disables autoResize - ie, percentage sizing 3968 s.autoResize = false; 3969 // flow-through... 3970 sizePane(pane, size, skipCallback, noAnimation, forceResize); // will animate resize if option enabled 3971 } 3972 3973 /** 3974 * sizePane is called only by internal methods whenever a pane needs to be resized 3975 * 3976 * @param {(string|Object)} evt_or_pane The pane being resized 3977 * @param {number} size The *desired* new size for this pane - will be validated 3978 * @param {boolean=} [skipCallback=false] Should the onresize callback be run? 3979 * @param {boolean=} [noAnimation=false] 3980 * @param {boolean=} [force=false] Force resizing even if does not seem necessary 3981 */ 3982, sizePane = function (evt_or_pane, size, skipCallback, noAnimation, force) { 3983 if (!isInitialized()) return; 3984 var pane = evtPane.call(this, evt_or_pane) // probably NEVER called from event? 3985 , o = options[pane] 3986 , s = state[pane] 3987 , $P = $Ps[pane] 3988 , $R = $Rs[pane] 3989 , side = _c[pane].side 3990 , dimName = _c[pane].sizeType.toLowerCase() 3991 , skipResizeWhileDragging = s.isResizing && !o.triggerEventsDuringLiveResize 3992 , doFX = noAnimation !== true && o.animatePaneSizing 3993 , oldSize, newSize 3994 ; 3995 if (pane === "center") return; // validate 3996 // QUEUE in case another action/animation is in progress 3997 $N.queue(function( queueNext ){ 3998 // calculate 'current' min/max sizes 3999 setSizeLimits(pane); // update pane-state 4000 oldSize = s.size; 4001 size = _parseSize(pane, size); // handle percentages & auto 4002 size = max(size, _parseSize(pane, o.minSize)); 4003 size = min(size, s.maxSize); 4004 if (size < s.minSize) { // not enough room for pane! 4005 queueNext(); // call before makePaneFit() because it needs the queue free 4006 makePaneFit(pane, false, skipCallback); // will hide or close pane 4007 return; 4008 } 4009 4010 // IF newSize is same as oldSize, then nothing to do - abort 4011 if (!force && size === oldSize) 4012 return queueNext(); 4013 4014 s.newSize = size; 4015 4016 // onresize_start callback CANNOT cancel resizing because this would break the layout! 4017 if (!skipCallback && state.initialized && s.isVisible) 4018 _runCallbacks("onresize_start", pane); 4019 4020 // resize the pane, and make sure its visible 4021 newSize = cssSize(pane, size); 4022 4023 if (doFX && $P.is(":visible")) { // ANIMATE 4024 var fx = $.layout.effects.size[pane] || $.layout.effects.size.all 4025 , easing = o.fxSettings_size.easing || fx.easing 4026 , z = options.zIndexes 4027 , props = {}; 4028 props[ dimName ] = newSize +'px'; 4029 s.isMoving = true; 4030 // overlay all elements during animation 4031 $P.css({ zIndex: z.pane_animate }) 4032 .show().animate( props, o.fxSpeed_size, easing, function(){ 4033 // reset zIndex after animation 4034 $P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) }); 4035 s.isMoving = false; 4036 delete s.newSize; 4037 sizePane_2(); // continue 4038 queueNext(); 4039 }); 4040 } 4041 else { // no animation 4042 $P.css( dimName, newSize ); // resize pane 4043 delete s.newSize; 4044 // if pane is visible, then 4045 if ($P.is(":visible")) 4046 sizePane_2(); // continue 4047 else { 4048 // pane is NOT VISIBLE, so just update state data... 4049 // when pane is *next opened*, it will have the new size 4050 s.size = size; // update state.size 4051 //$.extend(s, elDims($P)); // update state dimensions - CANNOT do this when not visible! } 4052 } 4053 queueNext(); 4054 }; 4055 4056 }); 4057 4058 // SUBROUTINE 4059 function sizePane_2 () { 4060 /* Panes are sometimes not sized precisely in some browsers!? 4061 * This code will resize the pane up to 3 times to nudge the pane to the correct size 4062 */ 4063 var actual = dimName==='width' ? $P.outerWidth() : $P.outerHeight() 4064 , tries = [{ 4065 pane: pane 4066 , count: 1 4067 , target: size 4068 , actual: actual 4069 , correct: (size === actual) 4070 , attempt: size 4071 , cssSize: newSize 4072 }] 4073 , lastTry = tries[0] 4074 , thisTry = {} 4075 , msg = 'Inaccurate size after resizing the '+ pane +'-pane.' 4076 ; 4077 while ( !lastTry.correct ) { 4078 thisTry = { pane: pane, count: lastTry.count+1, target: size }; 4079 4080 if (lastTry.actual > size) 4081 thisTry.attempt = max(0, lastTry.attempt - (lastTry.actual - size)); 4082 else // lastTry.actual < size 4083 thisTry.attempt = max(0, lastTry.attempt + (size - lastTry.actual)); 4084 4085 thisTry.cssSize = cssSize(pane, thisTry.attempt); 4086 $P.css( dimName, thisTry.cssSize ); 4087 4088 thisTry.actual = dimName=='width' ? $P.outerWidth() : $P.outerHeight(); 4089 thisTry.correct = (size === thisTry.actual); 4090 4091 // log attempts and alert the user of this *non-fatal error* (if showDebugMessages) 4092 if ( tries.length === 1) { 4093 _log(msg, false, true); 4094 _log(lastTry, false, true); 4095 } 4096 _log(thisTry, false, true); 4097 // after 4 tries, is as close as its gonna get! 4098 if (tries.length > 3) break; 4099 4100 tries.push( thisTry ); 4101 lastTry = tries[ tries.length - 1 ]; 4102 } 4103 // END TESTING CODE 4104 4105 // update pane-state dimensions 4106 s.size = size; 4107 $.extend(s, elDims($P)); 4108 4109 if (s.isVisible && $P.is(":visible")) { 4110 // reposition the resizer-bar 4111 if ($R) $R.css( side, size + sC.inset[side] ); 4112 // resize the content-div 4113 sizeContent(pane); 4114 } 4115 4116 if (!skipCallback && !skipResizeWhileDragging && state.initialized && s.isVisible) 4117 _runCallbacks("onresize_end", pane); 4118 4119 // resize all the adjacent panes, and adjust their toggler buttons 4120 // when skipCallback passed, it means the controlling method will handle 'other panes' 4121 if (!skipCallback) { 4122 // also no callback if live-resize is in progress and NOT triggerEventsDuringLiveResize 4123 if (!s.isSliding) sizeMidPanes(_c[pane].dir=="horz" ? "" : "center", skipResizeWhileDragging, force); 4124 sizeHandles(); 4125 } 4126 4127 // if opposite-pane was autoClosed, see if it can be autoOpened now 4128 var altPane = _c.oppositeEdge[pane]; 4129 if (size < oldSize && state[ altPane ].noRoom) { 4130 setSizeLimits( altPane ); 4131 makePaneFit( altPane, false, skipCallback ); 4132 } 4133 4134 // DEBUG - ALERT user/developer so they know there was a sizing problem 4135 if (tries.length > 1) 4136 _log(msg +'\nSee the Error Console for details.', true, true); 4137 } 4138 } 4139 4140 /** 4141 * @see initPanes(), sizePane(), resizeAll(), open(), close(), hide() 4142 * @param {(Array.<string>|string)} panes The pane(s) being resized, comma-delmited string 4143 * @param {boolean=} [skipCallback=false] Should the onresize callback be run? 4144 * @param {boolean=} [force=false] 4145 */ 4146, sizeMidPanes = function (panes, skipCallback, force) { 4147 panes = (panes ? panes : "east,west,center").split(","); 4148 4149 $.each(panes, function (i, pane) { 4150 if (!$Ps[pane]) return; // NO PANE - skip 4151 var 4152 o = options[pane] 4153 , s = state[pane] 4154 , $P = $Ps[pane] 4155 , $R = $Rs[pane] 4156 , isCenter= (pane=="center") 4157 , hasRoom = true 4158 , CSS = {} 4159 // if pane is not visible, show it invisibly NOW rather than for *each call* in this script 4160 , visCSS = $.layout.showInvisibly($P) 4161 4162 , newCenter = calcNewCenterPaneDims() 4163 ; 4164 4165 // update pane-state dimensions 4166 $.extend(s, elDims($P)); 4167 4168 if (pane === "center") { 4169 if (!force && s.isVisible && newCenter.width === s.outerWidth && newCenter.height === s.outerHeight) { 4170 $P.css(visCSS); 4171 return true; // SKIP - pane already the correct size 4172 } 4173 // set state for makePaneFit() logic 4174 $.extend(s, cssMinDims(pane), { 4175 maxWidth: newCenter.width 4176 , maxHeight: newCenter.height 4177 }); 4178 CSS = newCenter; 4179 s.newWidth = CSS.width; 4180 s.newHeight = CSS.height; 4181 // convert OUTER width/height to CSS width/height 4182 CSS.width = cssW($P, CSS.width); 4183 // NEW - allow pane to extend 'below' visible area rather than hide it 4184 CSS.height = cssH($P, CSS.height); 4185 hasRoom = CSS.width >= 0 && CSS.height >= 0; // height >= 0 = ALWAYS TRUE NOW 4186 4187 // during layout init, try to shrink east/west panes to make room for center 4188 if (!state.initialized && o.minWidth > newCenter.width) { 4189 var 4190 reqPx = o.minWidth - s.outerWidth 4191 , minE = options.east.minSize || 0 4192 , minW = options.west.minSize || 0 4193 , sizeE = state.east.size 4194 , sizeW = state.west.size 4195 , newE = sizeE 4196 , newW = sizeW 4197 ; 4198 if (reqPx > 0 && state.east.isVisible && sizeE > minE) { 4199 newE = max( sizeE-minE, sizeE-reqPx ); 4200 reqPx -= sizeE-newE; 4201 } 4202 if (reqPx > 0 && state.west.isVisible && sizeW > minW) { 4203 newW = max( sizeW-minW, sizeW-reqPx ); 4204 reqPx -= sizeW-newW; 4205 } 4206 // IF we found enough extra space, then resize the border panes as calculated 4207 if (reqPx === 0) { 4208 if (sizeE && sizeE != minE) 4209 sizePane('east', newE, true, true, force); // true = skipCallback/noAnimation - initPanes will handle when done 4210 if (sizeW && sizeW != minW) 4211 sizePane('west', newW, true, true, force); // true = skipCallback/noAnimation 4212 // now start over! 4213 sizeMidPanes('center', skipCallback, force); 4214 $P.css(visCSS); 4215 return; // abort this loop 4216 } 4217 } 4218 } 4219 else { // for east and west, set only the height, which is same as center height 4220 // set state.min/maxWidth/Height for makePaneFit() logic 4221 if (s.isVisible && !s.noVerticalRoom) 4222 $.extend(s, elDims($P), cssMinDims(pane)) 4223 if (!force && !s.noVerticalRoom && newCenter.height === s.outerHeight) { 4224 $P.css(visCSS); 4225 return true; // SKIP - pane already the correct size 4226 } 4227 // east/west have same top, bottom & height as center 4228 CSS.top = newCenter.top; 4229 CSS.bottom = newCenter.bottom; 4230 s.newSize = newCenter.height 4231 // NEW - allow pane to extend 'below' visible area rather than hide it 4232 CSS.height = cssH($P, newCenter.height); 4233 s.maxHeight = CSS.height; 4234 hasRoom = (s.maxHeight >= 0); // ALWAYS TRUE NOW 4235 if (!hasRoom) s.noVerticalRoom = true; // makePaneFit() logic 4236 } 4237 4238 if (hasRoom) { 4239 // resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized 4240 if (!skipCallback && state.initialized) 4241 _runCallbacks("onresize_start", pane); 4242 4243 $P.css(CSS); // apply the CSS to pane 4244 if (pane !== "center") 4245 sizeHandles(pane); // also update resizer length 4246 if (s.noRoom && !s.isClosed && !s.isHidden) 4247 makePaneFit(pane); // will re-open/show auto-closed/hidden pane 4248 if (s.isVisible) { 4249 $.extend(s, elDims($P)); // update pane dimensions 4250 if (state.initialized) sizeContent(pane); // also resize the contents, if exists 4251 } 4252 } 4253 else if (!s.noRoom && s.isVisible) // no room for pane 4254 makePaneFit(pane); // will hide or close pane 4255 4256 // reset visibility, if necessary 4257 $P.css(visCSS); 4258 4259 delete s.newSize; 4260 delete s.newWidth; 4261 delete s.newHeight; 4262 4263 if (!s.isVisible) 4264 return true; // DONE - next pane 4265 4266 /* 4267 * Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes 4268 * Normally these panes have only 'left' & 'right' positions so pane auto-sizes 4269 * ALSO required when pane is an IFRAME because will NOT default to 'full width' 4270 * TODO: Can I use width:100% for a north/south iframe? 4271 * TODO: Sounds like a job for $P.outerWidth( sC.innerWidth ) SETTER METHOD 4272 */ 4273 if (pane === "center") { // finished processing midPanes 4274 var fix = browser.isIE6 || !browser.boxModel; 4275 if ($Ps.north && (fix || state.north.tagName=="IFRAME")) 4276 $Ps.north.css("width", cssW($Ps.north, sC.innerWidth)); 4277 if ($Ps.south && (fix || state.south.tagName=="IFRAME")) 4278 $Ps.south.css("width", cssW($Ps.south, sC.innerWidth)); 4279 } 4280 4281 // resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized 4282 if (!skipCallback && state.initialized) 4283 _runCallbacks("onresize_end", pane); 4284 }); 4285 } 4286 4287 4288 /** 4289 * @see window.onresize(), callbacks or custom code 4290 * @param {(Object|boolean)=} evt_or_refresh If 'true', then also reset pane-positioning 4291 */ 4292, resizeAll = function (evt_or_refresh) { 4293 var oldW = sC.innerWidth 4294 , oldH = sC.innerHeight 4295 ; 4296 // stopPropagation if called by trigger("layoutdestroy") - use evtPane utility 4297 evtPane(evt_or_refresh); 4298 4299 // cannot size layout when 'container' is hidden or collapsed 4300 if (!$N.is(":visible")) return; 4301 4302 if (!state.initialized) { 4303 _initLayoutElements(); 4304 return; // no need to resize since we just initialized! 4305 } 4306 4307 if (evt_or_refresh === true && $.isPlainObject(options.outset)) { 4308 // update container CSS in case outset option has changed 4309 $N.css( options.outset ); 4310 } 4311 // UPDATE container dimensions 4312 $.extend(sC, elDims( $N, options.inset )); 4313 if (!sC.outerHeight) return; 4314 4315 // if 'true' passed, refresh pane & handle positioning too 4316 if (evt_or_refresh === true) { 4317 setPanePosition(); 4318 } 4319 4320 // onresizeall_start will CANCEL resizing if returns false 4321 // state.container has already been set, so user can access this info for calcuations 4322 if (false === _runCallbacks("onresizeall_start")) return false; 4323 4324 var // see if container is now 'smaller' than before 4325 shrunkH = (sC.innerHeight < oldH) 4326 , shrunkW = (sC.innerWidth < oldW) 4327 , $P, o, s 4328 ; 4329 // NOTE special order for sizing: S-N-E-W 4330 $.each(["south","north","east","west"], function (i, pane) { 4331 if (!$Ps[pane]) return; // no pane - SKIP 4332 o = options[pane]; 4333 s = state[pane]; 4334 if (s.autoResize && s.size != o.size) // resize pane to original size set in options 4335 sizePane(pane, o.size, true, true, true); // true=skipCallback/noAnimation/forceResize 4336 else { 4337 setSizeLimits(pane); 4338 makePaneFit(pane, false, true, true); // true=skipCallback/forceResize 4339 } 4340 }); 4341 4342 sizeMidPanes("", true, true); // true=skipCallback/forceResize 4343 sizeHandles(); // reposition the toggler elements 4344 4345 // trigger all individual pane callbacks AFTER layout has finished resizing 4346 $.each(_c.allPanes, function (i, pane) { 4347 $P = $Ps[pane]; 4348 if (!$P) return; // SKIP 4349 if (state[pane].isVisible) // undefined for non-existent panes 4350 _runCallbacks("onresize_end", pane); // callback - if exists 4351 }); 4352 4353 _runCallbacks("onresizeall_end"); 4354 //_triggerLayoutEvent(pane, 'resizeall'); 4355 } 4356 4357 /** 4358 * Whenever a pane resizes or opens that has a nested layout, trigger resizeAll 4359 * 4360 * @param {(string|Object)} evt_or_pane The pane just resized or opened 4361 */ 4362, resizeChildren = function (evt_or_pane, skipRefresh) { 4363 var pane = evtPane.call(this, evt_or_pane); 4364 4365 if (!options[pane].resizeChildren) return; 4366 4367 // ensure the pane-children are up-to-date 4368 if (!skipRefresh) refreshChildren( pane ); 4369 var pC = children[pane]; 4370 if ($.isPlainObject( pC )) { 4371 // resize one or more children 4372 $.each( pC, function (key, child) { 4373 if (!child.destroyed) child.resizeAll(); 4374 }); 4375 } 4376 } 4377 4378 /** 4379 * IF pane has a content-div, then resize all elements inside pane to fit pane-height 4380 * 4381 * @param {(string|Object)} evt_or_panes The pane(s) being resized 4382 * @param {boolean=} [remeasure=false] Should the content (header/footer) be remeasured? 4383 */ 4384, sizeContent = function (evt_or_panes, remeasure) { 4385 if (!isInitialized()) return; 4386 4387 var panes = evtPane.call(this, evt_or_panes); 4388 panes = panes ? panes.split(",") : _c.allPanes; 4389 4390 $.each(panes, function (idx, pane) { 4391 var 4392 $P = $Ps[pane] 4393 , $C = $Cs[pane] 4394 , o = options[pane] 4395 , s = state[pane] 4396 , m = s.content // m = measurements 4397 ; 4398 if (!$P || !$C || !$P.is(":visible")) return true; // NOT VISIBLE - skip 4399 4400 // if content-element was REMOVED, update OR remove the pointer 4401 if (!$C.length) { 4402 initContent(pane, false); // false = do NOT sizeContent() - already there! 4403 if (!$C) return; // no replacement element found - pointer have been removed 4404 } 4405 4406 // onsizecontent_start will CANCEL resizing if returns false 4407 if (false === _runCallbacks("onsizecontent_start", pane)) return; 4408 4409 // skip re-measuring offsets if live-resizing 4410 if ((!s.isMoving && !s.isResizing) || o.liveContentResizing || remeasure || m.top == undefined) { 4411 _measure(); 4412 // if any footers are below pane-bottom, they may not measure correctly, 4413 // so allow pane overflow and re-measure 4414 if (m.hiddenFooters > 0 && $P.css("overflow") === "hidden") { 4415 $P.css("overflow", "visible"); 4416 _measure(); // remeasure while overflowing 4417 $P.css("overflow", "hidden"); 4418 } 4419 } 4420 // NOTE: spaceAbove/Below *includes* the pane paddingTop/Bottom, but not pane.borders 4421 var newH = s.innerHeight - (m.spaceAbove - s.css.paddingTop) - (m.spaceBelow - s.css.paddingBottom); 4422 4423 if (!$C.is(":visible") || m.height != newH) { 4424 // size the Content element to fit new pane-size - will autoHide if not enough room 4425 setOuterHeight($C, newH, true); // true=autoHide 4426 m.height = newH; // save new height 4427 }; 4428 4429 if (state.initialized) 4430 _runCallbacks("onsizecontent_end", pane); 4431 4432 function _below ($E) { 4433 return max(s.css.paddingBottom, (parseInt($E.css("marginBottom"), 10) || 0)); 4434 }; 4435 4436 function _measure () { 4437 var 4438 ignore = options[pane].contentIgnoreSelector 4439 , $Fs = $C.nextAll().not(".ui-layout-mask").not(ignore || ":lt(0)") // not :lt(0) = ALL 4440 , $Fs_vis = $Fs.filter(':visible') 4441 , $F = $Fs_vis.filter(':last') 4442 ; 4443 m = { 4444 top: $C[0].offsetTop 4445 , height: $C.outerHeight() 4446 , numFooters: $Fs.length 4447 , hiddenFooters: $Fs.length - $Fs_vis.length 4448 , spaceBelow: 0 // correct if no content footer ($E) 4449 } 4450 m.spaceAbove = m.top; // just for state - not used in calc 4451 m.bottom = m.top + m.height; 4452 if ($F.length) 4453 //spaceBelow = (LastFooter.top + LastFooter.height) [footerBottom] - Content.bottom + max(LastFooter.marginBottom, pane.paddingBotom) 4454 m.spaceBelow = ($F[0].offsetTop + $F.outerHeight()) - m.bottom + _below($F); 4455 else // no footer - check marginBottom on Content element itself 4456 m.spaceBelow = _below($C); 4457 }; 4458 }); 4459 } 4460 4461 4462 /** 4463 * Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary 4464 * 4465 * @see initHandles(), open(), close(), resizeAll() 4466 * @param {(string|Object)=} evt_or_panes The pane(s) being resized 4467 */ 4468, sizeHandles = function (evt_or_panes) { 4469 var panes = evtPane.call(this, evt_or_panes) 4470 panes = panes ? panes.split(",") : _c.borderPanes; 4471 4472 $.each(panes, function (i, pane) { 4473 var 4474 o = options[pane] 4475 , s = state[pane] 4476 , $P = $Ps[pane] 4477 , $R = $Rs[pane] 4478 , $T = $Ts[pane] 4479 , $TC 4480 ; 4481 if (!$P || !$R) return; 4482 4483 var 4484 dir = _c[pane].dir 4485 , _state = (s.isClosed ? "_closed" : "_open") 4486 , spacing = o["spacing"+ _state] 4487 , togAlign = o["togglerAlign"+ _state] 4488 , togLen = o["togglerLength"+ _state] 4489 , paneLen 4490 , left 4491 , offset 4492 , CSS = {} 4493 ; 4494 4495 if (spacing === 0) { 4496 $R.hide(); 4497 return; 4498 } 4499 else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason 4500 $R.show(); // in case was previously hidden 4501 4502 // Resizer Bar is ALWAYS same width/height of pane it is attached to 4503 if (dir === "horz") { // north/south 4504 //paneLen = $P.outerWidth(); // s.outerWidth || 4505 paneLen = sC.innerWidth; // handle offscreen-panes 4506 s.resizerLength = paneLen; 4507 left = $.layout.cssNum($P, "left") 4508 $R.css({ 4509 width: cssW($R, paneLen) // account for borders & padding 4510 , height: cssH($R, spacing) // ditto 4511 , left: left > -9999 ? left : sC.inset.left // handle offscreen-panes 4512 }); 4513 } 4514 else { // east/west 4515 paneLen = $P.outerHeight(); // s.outerHeight || 4516 s.resizerLength = paneLen; 4517 $R.css({ 4518 height: cssH($R, paneLen) // account for borders & padding 4519 , width: cssW($R, spacing) // ditto 4520 , top: sC.inset.top + getPaneSize("north", true) // TODO: what if no North pane? 4521 //, top: $.layout.cssNum($Ps["center"], "top") 4522 }); 4523 } 4524 4525 // remove hover classes 4526 removeHover( o, $R ); 4527 4528 if ($T) { 4529 if (togLen === 0 || (s.isSliding && o.hideTogglerOnSlide)) { 4530 $T.hide(); // always HIDE the toggler when 'sliding' 4531 return; 4532 } 4533 else 4534 $T.show(); // in case was previously hidden 4535 4536 if (!(togLen > 0) || togLen === "100%" || togLen > paneLen) { 4537 togLen = paneLen; 4538 offset = 0; 4539 } 4540 else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed 4541 if (isStr(togAlign)) { 4542 switch (togAlign) { 4543 case "top": 4544 case "left": offset = 0; 4545 break; 4546 case "bottom": 4547 case "right": offset = paneLen - togLen; 4548 break; 4549 case "middle": 4550 case "center": 4551 default: offset = round((paneLen - togLen) / 2); // 'default' catches typos 4552 } 4553 } 4554 else { // togAlign = number 4555 var x = parseInt(togAlign, 10); // 4556 if (togAlign >= 0) offset = x; 4557 else offset = paneLen - togLen + x; // NOTE: x is negative! 4558 } 4559 } 4560 4561 if (dir === "horz") { // north/south 4562 var width = cssW($T, togLen); 4563 $T.css({ 4564 width: width // account for borders & padding 4565 , height: cssH($T, spacing) // ditto 4566 , left: offset // TODO: VERIFY that toggler positions correctly for ALL values 4567 , top: 0 4568 }); 4569 // CENTER the toggler content SPAN 4570 $T.children(".content").each(function(){ 4571 $TC = $(this); 4572 $TC.css("marginLeft", round((width-$TC.outerWidth())/2)); // could be negative 4573 }); 4574 } 4575 else { // east/west 4576 var height = cssH($T, togLen); 4577 $T.css({ 4578 height: height // account for borders & padding 4579 , width: cssW($T, spacing) // ditto 4580 , top: offset // POSITION the toggler 4581 , left: 0 4582 }); 4583 // CENTER the toggler content SPAN 4584 $T.children(".content").each(function(){ 4585 $TC = $(this); 4586 $TC.css("marginTop", round((height-$TC.outerHeight())/2)); // could be negative 4587 }); 4588 } 4589 4590 // remove ALL hover classes 4591 removeHover( 0, $T ); 4592 } 4593 4594 // DONE measuring and sizing this resizer/toggler, so can be 'hidden' now 4595 if (!state.initialized && (o.initHidden || s.isHidden)) { 4596 $R.hide(); 4597 if ($T) $T.hide(); 4598 } 4599 }); 4600 } 4601 4602 4603 /** 4604 * @param {(string|Object)} evt_or_pane 4605 */ 4606, enableClosable = function (evt_or_pane) { 4607 if (!isInitialized()) return; 4608 var pane = evtPane.call(this, evt_or_pane) 4609 , $T = $Ts[pane] 4610 , o = options[pane] 4611 ; 4612 if (!$T) return; 4613 o.closable = true; 4614 $T .bind("click."+ sID, function(evt){ evt.stopPropagation(); toggle(pane); }) 4615 .css("visibility", "visible") 4616 .css("cursor", "pointer") 4617 .attr("title", state[pane].isClosed ? o.tips.Open : o.tips.Close) // may be blank 4618 .show(); 4619 } 4620 /** 4621 * @param {(string|Object)} evt_or_pane 4622 * @param {boolean=} [hide=false] 4623 */ 4624, disableClosable = function (evt_or_pane, hide) { 4625 if (!isInitialized()) return; 4626 var pane = evtPane.call(this, evt_or_pane) 4627 , $T = $Ts[pane] 4628 ; 4629 if (!$T) return; 4630 options[pane].closable = false; 4631 // is closable is disable, then pane MUST be open! 4632 if (state[pane].isClosed) open(pane, false, true); 4633 $T .unbind("."+ sID) 4634 .css("visibility", hide ? "hidden" : "visible") // instead of hide(), which creates logic issues 4635 .css("cursor", "default") 4636 .attr("title", ""); 4637 } 4638 4639 4640 /** 4641 * @param {(string|Object)} evt_or_pane 4642 */ 4643, enableSlidable = function (evt_or_pane) { 4644 if (!isInitialized()) return; 4645 var pane = evtPane.call(this, evt_or_pane) 4646 , $R = $Rs[pane] 4647 ; 4648 if (!$R || !$R.data('draggable')) return; 4649 options[pane].slidable = true; 4650 if (state[pane].isClosed) 4651 bindStartSlidingEvents(pane, true); 4652 } 4653 /** 4654 * @param {(string|Object)} evt_or_pane 4655 */ 4656, disableSlidable = function (evt_or_pane) { 4657 if (!isInitialized()) return; 4658 var pane = evtPane.call(this, evt_or_pane) 4659 , $R = $Rs[pane] 4660 ; 4661 if (!$R) return; 4662 options[pane].slidable = false; 4663 if (state[pane].isSliding) 4664 close(pane, false, true); 4665 else { 4666 bindStartSlidingEvents(pane, false); 4667 $R .css("cursor", "default") 4668 .attr("title", ""); 4669 removeHover(null, $R[0]); // in case currently hovered 4670 } 4671 } 4672 4673 4674 /** 4675 * @param {(string|Object)} evt_or_pane 4676 */ 4677, enableResizable = function (evt_or_pane) { 4678 if (!isInitialized()) return; 4679 var pane = evtPane.call(this, evt_or_pane) 4680 , $R = $Rs[pane] 4681 , o = options[pane] 4682 ; 4683 if (!$R || !$R.data('draggable')) return; 4684 o.resizable = true; 4685 $R.draggable("enable"); 4686 if (!state[pane].isClosed) 4687 $R .css("cursor", o.resizerCursor) 4688 .attr("title", o.tips.Resize); 4689 } 4690 /** 4691 * @param {(string|Object)} evt_or_pane 4692 */ 4693, disableResizable = function (evt_or_pane) { 4694 if (!isInitialized()) return; 4695 var pane = evtPane.call(this, evt_or_pane) 4696 , $R = $Rs[pane] 4697 ; 4698 if (!$R || !$R.data('draggable')) return; 4699 options[pane].resizable = false; 4700 $R .draggable("disable") 4701 .css("cursor", "default") 4702 .attr("title", ""); 4703 removeHover(null, $R[0]); // in case currently hovered 4704 } 4705 4706 4707 /** 4708 * Move a pane from source-side (eg, west) to target-side (eg, east) 4709 * If pane exists on target-side, move that to source-side, ie, 'swap' the panes 4710 * 4711 * @param {(string|Object)} evt_or_pane1 The pane/edge being swapped 4712 * @param {string} pane2 ditto 4713 */ 4714, swapPanes = function (evt_or_pane1, pane2) { 4715 if (!isInitialized()) return; 4716 var pane1 = evtPane.call(this, evt_or_pane1); 4717 // change state.edge NOW so callbacks can know where pane is headed... 4718 state[pane1].edge = pane2; 4719 state[pane2].edge = pane1; 4720 // run these even if NOT state.initialized 4721 if (false === _runCallbacks("onswap_start", pane1) 4722 || false === _runCallbacks("onswap_start", pane2) 4723 ) { 4724 state[pane1].edge = pane1; // reset 4725 state[pane2].edge = pane2; 4726 return; 4727 } 4728 4729 var 4730 oPane1 = copy( pane1 ) 4731 , oPane2 = copy( pane2 ) 4732 , sizes = {} 4733 ; 4734 sizes[pane1] = oPane1 ? oPane1.state.size : 0; 4735 sizes[pane2] = oPane2 ? oPane2.state.size : 0; 4736 4737 // clear pointers & state 4738 $Ps[pane1] = false; 4739 $Ps[pane2] = false; 4740 state[pane1] = {}; 4741 state[pane2] = {}; 4742 4743 // ALWAYS remove the resizer & toggler elements 4744 if ($Ts[pane1]) $Ts[pane1].remove(); 4745 if ($Ts[pane2]) $Ts[pane2].remove(); 4746 if ($Rs[pane1]) $Rs[pane1].remove(); 4747 if ($Rs[pane2]) $Rs[pane2].remove(); 4748 $Rs[pane1] = $Rs[pane2] = $Ts[pane1] = $Ts[pane2] = false; 4749 4750 // transfer element pointers and data to NEW Layout keys 4751 move( oPane1, pane2 ); 4752 move( oPane2, pane1 ); 4753 4754 // cleanup objects 4755 oPane1 = oPane2 = sizes = null; 4756 4757 // make panes 'visible' again 4758 if ($Ps[pane1]) $Ps[pane1].css(_c.visible); 4759 if ($Ps[pane2]) $Ps[pane2].css(_c.visible); 4760 4761 // fix any size discrepancies caused by swap 4762 resizeAll(); 4763 4764 // run these even if NOT state.initialized 4765 _runCallbacks("onswap_end", pane1); 4766 _runCallbacks("onswap_end", pane2); 4767 4768 return; 4769 4770 function copy (n) { // n = pane 4771 var 4772 $P = $Ps[n] 4773 , $C = $Cs[n] 4774 ; 4775 return !$P ? false : { 4776 pane: n 4777 , P: $P ? $P[0] : false 4778 , C: $C ? $C[0] : false 4779 , state: $.extend(true, {}, state[n]) 4780 , options: $.extend(true, {}, options[n]) 4781 } 4782 }; 4783 4784 function move (oPane, pane) { 4785 if (!oPane) return; 4786 var 4787 P = oPane.P 4788 , C = oPane.C 4789 , oldPane = oPane.pane 4790 , c = _c[pane] 4791 // save pane-options that should be retained 4792 , s = $.extend(true, {}, state[pane]) 4793 , o = options[pane] 4794 // RETAIN side-specific FX Settings - more below 4795 , fx = { resizerCursor: o.resizerCursor } 4796 , re, size, pos 4797 ; 4798 $.each("fxName,fxSpeed,fxSettings".split(","), function (i, k) { 4799 fx[k +"_open"] = o[k +"_open"]; 4800 fx[k +"_close"] = o[k +"_close"]; 4801 fx[k +"_size"] = o[k +"_size"]; 4802 }); 4803 4804 // update object pointers and attributes 4805 $Ps[pane] = $(P) 4806 .data({ 4807 layoutPane: Instance[pane] // NEW pointer to pane-alias-object 4808 , layoutEdge: pane 4809 }) 4810 .css(_c.hidden) 4811 .css(c.cssReq) 4812 ; 4813 $Cs[pane] = C ? $(C) : false; 4814 4815 // set options and state 4816 options[pane] = $.extend(true, {}, oPane.options, fx); 4817 state[pane] = $.extend(true, {}, oPane.state); 4818 4819 // change classNames on the pane, eg: ui-layout-pane-east ==> ui-layout-pane-west 4820 re = new RegExp(o.paneClass +"-"+ oldPane, "g"); 4821 P.className = P.className.replace(re, o.paneClass +"-"+ pane); 4822 4823 // ALWAYS regenerate the resizer & toggler elements 4824 initHandles(pane); // create the required resizer & toggler 4825 4826 // if moving to different orientation, then keep 'target' pane size 4827 if (c.dir != _c[oldPane].dir) { 4828 size = sizes[pane] || 0; 4829 setSizeLimits(pane); // update pane-state 4830 size = max(size, state[pane].minSize); 4831 // use manualSizePane to disable autoResize - not useful after panes are swapped 4832 manualSizePane(pane, size, true, true); // true/true = skipCallback/noAnimation 4833 } 4834 else // move the resizer here 4835 $Rs[pane].css(c.side, sC.inset[c.side] + (state[pane].isVisible ? getPaneSize(pane) : 0)); 4836 4837 4838 // ADD CLASSNAMES & SLIDE-BINDINGS 4839 if (oPane.state.isVisible && !s.isVisible) 4840 setAsOpen(pane, true); // true = skipCallback 4841 else { 4842 setAsClosed(pane); 4843 bindStartSlidingEvents(pane, true); // will enable events IF option is set 4844 } 4845 4846 // DESTROY the object 4847 oPane = null; 4848 }; 4849 } 4850 4851 4852 /** 4853 * INTERNAL method to sync pin-buttons when pane is opened or closed 4854 * Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes 4855 * 4856 * @see open(), setAsOpen(), setAsClosed() 4857 * @param {string} pane These are the params returned to callbacks by layout() 4858 * @param {boolean} doPin True means set the pin 'down', False means 'up' 4859 */ 4860, syncPinBtns = function (pane, doPin) { 4861 if ($.layout.plugins.buttons) 4862 $.each(state[pane].pins, function (i, selector) { 4863 $.layout.buttons.setPinState(Instance, $(selector), pane, doPin); 4864 }); 4865 } 4866 4867; // END var DECLARATIONS 4868 4869 /** 4870 * Capture keys when enableCursorHotkey - toggle pane if hotkey pressed 4871 * 4872 * @see document.keydown() 4873 */ 4874 function keyDown (evt) { 4875 if (!evt) return true; 4876 var code = evt.keyCode; 4877 if (code < 33) return true; // ignore special keys: ENTER, TAB, etc 4878 4879 var 4880 PANE = { 4881 38: "north" // Up Cursor - $.ui.keyCode.UP 4882 , 40: "south" // Down Cursor - $.ui.keyCode.DOWN 4883 , 37: "west" // Left Cursor - $.ui.keyCode.LEFT 4884 , 39: "east" // Right Cursor - $.ui.keyCode.RIGHT 4885 } 4886 , ALT = evt.altKey // no worky! 4887 , SHIFT = evt.shiftKey 4888 , CTRL = evt.ctrlKey 4889 , CURSOR = (CTRL && code >= 37 && code <= 40) 4890 , o, k, m, pane 4891 ; 4892 4893 if (CURSOR && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey 4894 pane = PANE[code]; 4895 else if (CTRL || SHIFT) // check to see if this matches a custom-hotkey 4896 $.each(_c.borderPanes, function (i, p) { // loop each pane to check its hotkey 4897 o = options[p]; 4898 k = o.customHotkey; 4899 m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT" 4900 if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches 4901 if (k && code === (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches 4902 pane = p; 4903 return false; // BREAK 4904 } 4905 } 4906 }); 4907 4908 // validate pane 4909 if (!pane || !$Ps[pane] || !options[pane].closable || state[pane].isHidden) 4910 return true; 4911 4912 toggle(pane); 4913 4914 evt.stopPropagation(); 4915 evt.returnValue = false; // CANCEL key 4916 return false; 4917 }; 4918 4919 4920/* 4921 * ###################################### 4922 * UTILITY METHODS 4923 * called externally or by initButtons 4924 * ###################################### 4925 */ 4926 4927 /** 4928 * Change/reset a pane overflow setting & zIndex to allow popups/drop-downs to work 4929 * 4930 * @param {Object=} [el] (optional) Can also be 'bound' to a click, mouseOver, or other event 4931 */ 4932 function allowOverflow (el) { 4933 if (!isInitialized()) return; 4934 if (this && this.tagName) el = this; // BOUND to element 4935 var $P; 4936 if (isStr(el)) 4937 $P = $Ps[el]; 4938 else if ($(el).data("layoutRole")) 4939 $P = $(el); 4940 else 4941 $(el).parents().each(function(){ 4942 if ($(this).data("layoutRole")) { 4943 $P = $(this); 4944 return false; // BREAK 4945 } 4946 }); 4947 if (!$P || !$P.length) return; // INVALID 4948 4949 var 4950 pane = $P.data("layoutEdge") 4951 , s = state[pane] 4952 ; 4953 4954 // if pane is already raised, then reset it before doing it again! 4955 // this would happen if allowOverflow is attached to BOTH the pane and an element 4956 if (s.cssSaved) 4957 resetOverflow(pane); // reset previous CSS before continuing 4958 4959 // if pane is raised by sliding or resizing, or its closed, then abort 4960 if (s.isSliding || s.isResizing || s.isClosed) { 4961 s.cssSaved = false; 4962 return; 4963 } 4964 4965 var 4966 newCSS = { zIndex: (options.zIndexes.resizer_normal + 1) } 4967 , curCSS = {} 4968 , of = $P.css("overflow") 4969 , ofX = $P.css("overflowX") 4970 , ofY = $P.css("overflowY") 4971 ; 4972 // determine which, if any, overflow settings need to be changed 4973 if (of != "visible") { 4974 curCSS.overflow = of; 4975 newCSS.overflow = "visible"; 4976 } 4977 if (ofX && !ofX.match(/(visible|auto)/)) { 4978 curCSS.overflowX = ofX; 4979 newCSS.overflowX = "visible"; 4980 } 4981 if (ofY && !ofY.match(/(visible|auto)/)) { 4982 curCSS.overflowY = ofX; 4983 newCSS.overflowY = "visible"; 4984 } 4985 4986 // save the current overflow settings - even if blank! 4987 s.cssSaved = curCSS; 4988 4989 // apply new CSS to raise zIndex and, if necessary, make overflow 'visible' 4990 $P.css( newCSS ); 4991 4992 // make sure the zIndex of all other panes is normal 4993 $.each(_c.allPanes, function(i, p) { 4994 if (p != pane) resetOverflow(p); 4995 }); 4996 4997 }; 4998 /** 4999 * @param {Object=} [el] (optional) Can also be 'bound' to a click, mouseOver, or other event 5000 */ 5001 function resetOverflow (el) { 5002 if (!isInitialized()) return; 5003 if (this && this.tagName) el = this; // BOUND to element 5004 var $P; 5005 if (isStr(el)) 5006 $P = $Ps[el]; 5007 else if ($(el).data("layoutRole")) 5008 $P = $(el); 5009 else 5010 $(el).parents().each(function(){ 5011 if ($(this).data("layoutRole")) { 5012 $P = $(this); 5013 return false; // BREAK 5014 } 5015 }); 5016 if (!$P || !$P.length) return; // INVALID 5017 5018 var 5019 pane = $P.data("layoutEdge") 5020 , s = state[pane] 5021 , CSS = s.cssSaved || {} 5022 ; 5023 // reset the zIndex 5024 if (!s.isSliding && !s.isResizing) 5025 $P.css("zIndex", options.zIndexes.pane_normal); 5026 5027 // reset Overflow - if necessary 5028 $P.css( CSS ); 5029 5030 // clear var 5031 s.cssSaved = false; 5032 }; 5033 5034/* 5035 * ##################### 5036 * CREATE/RETURN LAYOUT 5037 * ##################### 5038 */ 5039 5040 // validate that container exists 5041 var $N = $(this).eq(0); // FIRST matching Container element 5042 if (!$N.length) { 5043 return _log( options.errors.containerMissing ); 5044 }; 5045 5046 // Users retrieve Instance of a layout with: $N.layout() OR $N.data("layout") 5047 // return the Instance-pointer if layout has already been initialized 5048 if ($N.data("layoutContainer") && $N.data("layout")) 5049 return $N.data("layout"); // cached pointer 5050 5051 // init global vars 5052 var 5053 $Ps = {} // Panes x5 - set in initPanes() 5054 , $Cs = {} // Content x5 - set in initPanes() 5055 , $Rs = {} // Resizers x4 - set in initHandles() 5056 , $Ts = {} // Togglers x4 - set in initHandles() 5057 , $Ms = $([]) // Masks - up to 2 masks per pane (IFRAME + DIV) 5058 // aliases for code brevity 5059 , sC = state.container // alias for easy access to 'container dimensions' 5060 , sID = state.id // alias for unique layout ID/namespace - eg: "layout435" 5061 ; 5062 5063 // create Instance object to expose data & option Properties, and primary action Methods 5064 var Instance = { 5065 // layout data 5066 options: options // property - options hash 5067 , state: state // property - dimensions hash 5068 // object pointers 5069 , container: $N // property - object pointers for layout container 5070 , panes: $Ps // property - object pointers for ALL Panes: panes.north, panes.center 5071 , contents: $Cs // property - object pointers for ALL Content: contents.north, contents.center 5072 , resizers: $Rs // property - object pointers for ALL Resizers, eg: resizers.north 5073 , togglers: $Ts // property - object pointers for ALL Togglers, eg: togglers.north 5074 // border-pane open/close 5075 , hide: hide // method - ditto 5076 , show: show // method - ditto 5077 , toggle: toggle // method - pass a 'pane' ("north", "west", etc) 5078 , open: open // method - ditto 5079 , close: close // method - ditto 5080 , slideOpen: slideOpen // method - ditto 5081 , slideClose: slideClose // method - ditto 5082 , slideToggle: slideToggle // method - ditto 5083 // pane actions 5084 , setSizeLimits: setSizeLimits // method - pass a 'pane' - update state min/max data 5085 , _sizePane: sizePane // method -intended for user by plugins only! 5086 , sizePane: manualSizePane // method - pass a 'pane' AND an 'outer-size' in pixels or percent, or 'auto' 5087 , sizeContent: sizeContent // method - pass a 'pane' 5088 , swapPanes: swapPanes // method - pass TWO 'panes' - will swap them 5089 , showMasks: showMasks // method - pass a 'pane' OR list of panes - default = all panes with mask option set 5090 , hideMasks: hideMasks // method - ditto' 5091 // pane element methods 5092 , initContent: initContent // method - ditto 5093 , addPane: addPane // method - pass a 'pane' 5094 , removePane: removePane // method - pass a 'pane' to remove from layout, add 'true' to delete the pane-elem 5095 , createChildren: createChildren // method - pass a 'pane' and (optional) layout-options (OVERRIDES options[pane].children 5096 , refreshChildren: refreshChildren // method - pass a 'pane' and a layout-instance 5097 // special pane option setting 5098 , enableClosable: enableClosable // method - pass a 'pane' 5099 , disableClosable: disableClosable // method - ditto 5100 , enableSlidable: enableSlidable // method - ditto 5101 , disableSlidable: disableSlidable // method - ditto 5102 , enableResizable: enableResizable // method - ditto 5103 , disableResizable: disableResizable// method - ditto 5104 // utility methods for panes 5105 , allowOverflow: allowOverflow // utility - pass calling element (this) 5106 , resetOverflow: resetOverflow // utility - ditto 5107 // layout control 5108 , destroy: destroy // method - no parameters 5109 , initPanes: isInitialized // method - no parameters 5110 , resizeAll: resizeAll // method - no parameters 5111 // callback triggering 5112 , runCallbacks: _runCallbacks // method - pass evtName & pane (if a pane-event), eg: trigger("onopen", "west") 5113 // alias collections of options, state and children - created in addPane and extended elsewhere 5114 , hasParentLayout: false // set by initContainer() 5115 , children: children // pointers to child-layouts, eg: Instance.children.west.layoutName 5116 , north: false // alias group: { name: pane, pane: $Ps[pane], options: options[pane], state: state[pane], children: children[pane] } 5117 , south: false // ditto 5118 , west: false // ditto 5119 , east: false // ditto 5120 , center: false // ditto 5121 }; 5122 5123 // create the border layout NOW 5124 if (_create() === 'cancel') // onload_start callback returned false to CANCEL layout creation 5125 return null; 5126 else // true OR false -- if layout-elements did NOT init (hidden or do not exist), can auto-init later 5127 return Instance; // return the Instance object 5128 5129} 5130 5131 5132})( jQuery ); 5133 5134 5135 5136 5137/** 5138 * jquery.layout.state 1.2 5139 * $Date: 2014-08-30 08:00:00 (Sat, 30 Aug 2014) $ 5140 * 5141 * Copyright (c) 2014 5142 * Kevin Dalman (http://allpro.net) 5143 * 5144 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) 5145 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. 5146 * 5147 * @requires: UI Layout 1.4.0 or higher 5148 * @requires: $.ui.cookie (above) 5149 * 5150 * @see: http://groups.google.com/group/jquery-ui-layout 5151 */ 5152;(function ($) { 5153 5154if (!$.layout) return; 5155 5156 5157/** 5158 * UI COOKIE UTILITY 5159 * 5160 * A $.cookie OR $.ui.cookie namespace *should be standard*, but until then... 5161 * This creates $.ui.cookie so Layout does not need the cookie.jquery.js plugin 5162 * NOTE: This utility is REQUIRED by the layout.state plugin 5163 * 5164 * Cookie methods in Layout are created as part of State Management 5165 */ 5166if (!$.ui) $.ui = {}; 5167$.ui.cookie = { 5168 5169 // cookieEnabled is not in DOM specs, but DOES works in all browsers,including IE6 5170 acceptsCookies: !!navigator.cookieEnabled 5171 5172, read: function (name) { 5173 var 5174 c = document.cookie 5175 , cs = c ? c.split(';') : [] 5176 , pair, data, i 5177 ; 5178 for (i=0; pair=cs[i]; i++) { 5179 data = $.trim(pair).split('='); // name=value => [ name, value ] 5180 if (data[0] == name) // found the layout cookie 5181 return decodeURIComponent(data[1]); 5182 } 5183 return null; 5184 } 5185 5186, write: function (name, val, cookieOpts) { 5187 var params = "" 5188 , date = "" 5189 , clear = false 5190 , o = cookieOpts || {} 5191 , x = o.expires || null 5192 , t = $.type(x) 5193 ; 5194 if (t === "date") 5195 date = x; 5196 else if (t === "string" && x > 0) { 5197 x = parseInt(x,10); 5198 t = "number"; 5199 } 5200 if (t === "number") { 5201 date = new Date(); 5202 if (x > 0) 5203 date.setDate(date.getDate() + x); 5204 else { 5205 date.setFullYear(1970); 5206 clear = true; 5207 } 5208 } 5209 if (date) params += ";expires="+ date.toUTCString(); 5210 if (o.path) params += ";path="+ o.path; 5211 if (o.domain) params += ";domain="+ o.domain; 5212 if (o.secure) params += ";secure"; 5213 document.cookie = name +"="+ (clear ? "" : encodeURIComponent( val )) + params; // write or clear cookie 5214 } 5215 5216, clear: function (name) { 5217 $.ui.cookie.write(name, "", {expires: -1}); 5218 } 5219 5220}; 5221// if cookie.jquery.js is not loaded, create an alias to replicate it 5222// this may be useful to other plugins or code dependent on that plugin 5223if (!$.cookie) $.cookie = function (k, v, o) { 5224 var C = $.ui.cookie; 5225 if (v === null) 5226 C.clear(k); 5227 else if (v === undefined) 5228 return C.read(k); 5229 else 5230 C.write(k, v, o); 5231}; 5232 5233 5234 5235/** 5236 * State-management options stored in options.stateManagement, which includes a .cookie hash 5237 * Default options saves ALL KEYS for ALL PANES, ie: pane.size, pane.isClosed, pane.isHidden 5238 * 5239 * // STATE/COOKIE OPTIONS 5240 * @example $(el).layout({ 5241 stateManagement: { 5242 enabled: true 5243 , stateKeys: "east.size,west.size,east.isClosed,west.isClosed" 5244 , cookie: { name: "appLayout", path: "/" } 5245 } 5246 }) 5247 * @example $(el).layout({ stateManagement__enabled: true }) // enable auto-state-management using cookies 5248 * @example $(el).layout({ stateManagement__cookie: { name: "appLayout", path: "/" } }) 5249 * @example $(el).layout({ stateManagement__cookie__name: "appLayout", stateManagement__cookie__path: "/" }) 5250 * 5251 * // STATE/COOKIE METHODS 5252 * @example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} ); 5253 * @example myLayout.loadCookie(); 5254 * @example myLayout.deleteCookie(); 5255 * @example var JSON = myLayout.readState(); // CURRENT Layout State 5256 * @example var JSON = myLayout.readCookie(); // SAVED Layout State (from cookie) 5257 * @example var JSON = myLayout.state.stateData; // LAST LOADED Layout State (cookie saved in layout.state hash) 5258 * 5259 * CUSTOM STATE-MANAGEMENT (eg, saved in a database) 5260 * @example var JSON = myLayout.readState( "west.isClosed,north.size,south.isHidden" ); 5261 * @example myLayout.loadState( JSON ); 5262 */ 5263 5264// tell Layout that the state plugin is available 5265$.layout.plugins.stateManagement = true; 5266 5267// Add State-Management options to layout.defaults 5268$.layout.defaults.stateManagement = { 5269 enabled: false // true = enable state-management, even if not using cookies 5270, autoSave: true // Save a state-cookie when page exits? 5271, autoLoad: true // Load the state-cookie when Layout inits? 5272, animateLoad: true // animate panes when loading state into an active layout 5273, includeChildren: true // recurse into child layouts to include their state as well 5274 // List state-data to save - must be pane-specific 5275, stateKeys: "north.size,south.size,east.size,west.size,"+ 5276 "north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+ 5277 "north.isHidden,south.isHidden,east.isHidden,west.isHidden" 5278, cookie: { 5279 name: "" // If not specified, will use Layout.name, else just "Layout" 5280 , domain: "" // blank = current domain 5281 , path: "" // blank = current page, "/" = entire website 5282 , expires: "" // 'days' to keep cookie - leave blank for 'session cookie' 5283 , secure: false 5284 } 5285}; 5286 5287// Set stateManagement as a 'layout-option', NOT a 'pane-option' 5288$.layout.optionsMap.layout.push("stateManagement"); 5289// Update config so layout does not move options into the pane-default branch (panes) 5290$.layout.config.optionRootKeys.push("stateManagement"); 5291 5292/* 5293 * State Management methods 5294 */ 5295$.layout.state = { 5296 5297 /** 5298 * Get the current layout state and save it to a cookie 5299 * 5300 * myLayout.saveCookie( keys, cookieOpts ) 5301 * 5302 * @param {Object} inst 5303 * @param {(string|Array)=} keys 5304 * @param {Object=} cookieOpts 5305 */ 5306 saveCookie: function (inst, keys, cookieOpts) { 5307 var o = inst.options 5308 , sm = o.stateManagement 5309 , oC = $.extend(true, {}, sm.cookie, cookieOpts || null) 5310 , data = inst.state.stateData = inst.readState( keys || sm.stateKeys ) // read current panes-state 5311 ; 5312 $.ui.cookie.write( oC.name || o.name || "Layout", $.layout.state.encodeJSON(data), oC ); 5313 return $.extend(true, {}, data); // return COPY of state.stateData data 5314 } 5315 5316 /** 5317 * Remove the state cookie 5318 * 5319 * @param {Object} inst 5320 */ 5321, deleteCookie: function (inst) { 5322 var o = inst.options; 5323 $.ui.cookie.clear( o.stateManagement.cookie.name || o.name || "Layout" ); 5324 } 5325 5326 /** 5327 * Read & return data from the cookie - as JSON 5328 * 5329 * @param {Object} inst 5330 */ 5331, readCookie: function (inst) { 5332 var o = inst.options; 5333 var c = $.ui.cookie.read( o.stateManagement.cookie.name || o.name || "Layout" ); 5334 // convert cookie string back to a hash and return it 5335 return c ? $.layout.state.decodeJSON(c) : {}; 5336 } 5337 5338 /** 5339 * Get data from the cookie and USE IT to loadState 5340 * 5341 * @param {Object} inst 5342 */ 5343, loadCookie: function (inst) { 5344 var c = $.layout.state.readCookie(inst); // READ the cookie 5345 if (c && !$.isEmptyObject( c )) { 5346 inst.state.stateData = $.extend(true, {}, c); // SET state.stateData 5347 inst.loadState(c); // LOAD the retrieved state 5348 } 5349 return c; 5350 } 5351 5352 /** 5353 * Update layout options from the cookie, if one exists 5354 * 5355 * @param {Object} inst 5356 * @param {Object=} stateData 5357 * @param {boolean=} animate 5358 */ 5359, loadState: function (inst, data, opts) { 5360 if (!$.isPlainObject( data ) || $.isEmptyObject( data )) return; 5361 5362 // normalize data & cache in the state object 5363 data = inst.state.stateData = $.layout.transformData( data ); // panes = default subkey 5364 5365 // add missing/default state-restore options 5366 var smo = inst.options.stateManagement; 5367 opts = $.extend({ 5368 animateLoad: false //smo.animateLoad 5369 , includeChildren: smo.includeChildren 5370 }, opts ); 5371 5372 if (!inst.state.initialized) { 5373 /* 5374 * layout NOT initialized, so just update its options 5375 */ 5376 // MUST remove pane.children keys before applying to options 5377 // use a copy so we don't remove keys from original data 5378 var o = $.extend(true, {}, data); 5379 //delete o.center; // center has no state-data - only children 5380 $.each($.layout.config.allPanes, function (idx, pane) { 5381 if (o[pane]) delete o[pane].children; 5382 }); 5383 // update CURRENT layout-options with saved state data 5384 $.extend(true, inst.options, o); 5385 } 5386 else { 5387 /* 5388 * layout already initialized, so modify layout's configuration 5389 */ 5390 var noAnimate = !opts.animateLoad 5391 , o, c, h, state, open 5392 ; 5393 $.each($.layout.config.borderPanes, function (idx, pane) { 5394 o = data[ pane ]; 5395 if (!$.isPlainObject( o )) return; // no key, skip pane 5396 5397 s = o.size; 5398 c = o.initClosed; 5399 h = o.initHidden; 5400 ar = o.autoResize 5401 state = inst.state[pane]; 5402 open = state.isVisible; 5403 5404 // reset autoResize 5405 if (ar) 5406 state.autoResize = ar; 5407 // resize BEFORE opening 5408 if (!open) 5409 inst._sizePane(pane, s, false, false, false); // false=skipCallback/noAnimation/forceResize 5410 // open/close as necessary - DO NOT CHANGE THIS ORDER! 5411 if (h === true) inst.hide(pane, noAnimate); 5412 else if (c === true) inst.close(pane, false, noAnimate); 5413 else if (c === false) inst.open (pane, false, noAnimate); 5414 else if (h === false) inst.show (pane, false, noAnimate); 5415 // resize AFTER any other actions 5416 if (open) 5417 inst._sizePane(pane, s, false, false, noAnimate); // animate resize if option passed 5418 }); 5419 5420 /* 5421 * RECURSE INTO CHILD-LAYOUTS 5422 */ 5423 if (opts.includeChildren) { 5424 var paneStateChildren, childState; 5425 $.each(inst.children, function (pane, paneChildren) { 5426 paneStateChildren = data[pane] ? data[pane].children : 0; 5427 if (paneStateChildren && paneChildren) { 5428 $.each(paneChildren, function (stateKey, child) { 5429 childState = paneStateChildren[stateKey]; 5430 if (child && childState) 5431 child.loadState( childState ); 5432 }); 5433 } 5434 }); 5435 } 5436 } 5437 } 5438 5439 /** 5440 * Get the *current layout state* and return it as a hash 5441 * 5442 * @param {Object=} inst // Layout instance to get state for 5443 * @param {object=} [opts] // State-Managements override options 5444 */ 5445, readState: function (inst, opts) { 5446 // backward compatility 5447 if ($.type(opts) === 'string') opts = { keys: opts }; 5448 if (!opts) opts = {}; 5449 var sm = inst.options.stateManagement 5450 , ic = opts.includeChildren 5451 , recurse = ic !== undefined ? ic : sm.includeChildren 5452 , keys = opts.stateKeys || sm.stateKeys 5453 , alt = { isClosed: 'initClosed', isHidden: 'initHidden' } 5454 , state = inst.state 5455 , panes = $.layout.config.allPanes 5456 , data = {} 5457 , pair, pane, key, val 5458 , ps, pC, child, array, count, branch 5459 ; 5460 if ($.isArray(keys)) keys = keys.join(","); 5461 // convert keys to an array and change delimiters from '__' to '.' 5462 keys = keys.replace(/__/g, ".").split(','); 5463 // loop keys and create a data hash 5464 for (var i=0, n=keys.length; i < n; i++) { 5465 pair = keys[i].split("."); 5466 pane = pair[0]; 5467 key = pair[1]; 5468 if ($.inArray(pane, panes) < 0) continue; // bad pane! 5469 val = state[ pane ][ key ]; 5470 if (val == undefined) continue; 5471 if (key=="isClosed" && state[pane]["isSliding"]) 5472 val = true; // if sliding, then *really* isClosed 5473 ( data[pane] || (data[pane]={}) )[ alt[key] ? alt[key] : key ] = val; 5474 } 5475 5476 // recurse into the child-layouts for each pane 5477 if (recurse) { 5478 $.each(panes, function (idx, pane) { 5479 pC = inst.children[pane]; 5480 ps = state.stateData[pane]; 5481 if ($.isPlainObject( pC ) && !$.isEmptyObject( pC )) { 5482 // ensure a key exists for this 'pane', eg: branch = data.center 5483 branch = data[pane] || (data[pane] = {}); 5484 if (!branch.children) branch.children = {}; 5485 $.each( pC, function (key, child) { 5486 // ONLY read state from an initialize layout 5487 if ( child.state.initialized ) 5488 branch.children[ key ] = $.layout.state.readState( child ); 5489 // if we have PREVIOUS (onLoad) state for this child-layout, KEEP IT! 5490 else if ( ps && ps.children && ps.children[ key ] ) { 5491 branch.children[ key ] = $.extend(true, {}, ps.children[ key ] ); 5492 } 5493 }); 5494 } 5495 }); 5496 } 5497 5498 return data; 5499 } 5500 5501 /** 5502 * Stringify a JSON hash so can save in a cookie or db-field 5503 */ 5504, encodeJSON: function (json) { 5505 var local = window.JSON || {}; 5506 return (local.stringify || stringify)(json); 5507 5508 function stringify (h) { 5509 var D=[], i=0, k, v, t // k = key, v = value 5510 , a = $.isArray(h) 5511 ; 5512 for (k in h) { 5513 v = h[k]; 5514 t = typeof v; 5515 if (t == 'string') // STRING - add quotes 5516 v = '"'+ v +'"'; 5517 else if (t == 'object') // SUB-KEY - recurse into it 5518 v = parse(v); 5519 D[i++] = (!a ? '"'+ k +'":' : '') + v; 5520 } 5521 return (a ? '[' : '{') + D.join(',') + (a ? ']' : '}'); 5522 }; 5523 } 5524 5525 /** 5526 * Convert stringified JSON back to a hash object 5527 * @see $.parseJSON(), adding in jQuery 1.4.1 5528 */ 5529, decodeJSON: function (str) { 5530 try { return $.parseJSON ? $.parseJSON(str) : window["eval"]("("+ str +")") || {}; } 5531 catch (e) { return {}; } 5532 } 5533 5534 5535, _create: function (inst) { 5536 var s = $.layout.state 5537 , o = inst.options 5538 , sm = o.stateManagement 5539 ; 5540 // ADD State-Management plugin methods to inst 5541 $.extend( inst, { 5542 // readCookie - update options from cookie - returns hash of cookie data 5543 readCookie: function () { return s.readCookie(inst); } 5544 // deleteCookie 5545 , deleteCookie: function () { s.deleteCookie(inst); } 5546 // saveCookie - optionally pass keys-list and cookie-options (hash) 5547 , saveCookie: function (keys, cookieOpts) { return s.saveCookie(inst, keys, cookieOpts); } 5548 // loadCookie - readCookie and use to loadState() - returns hash of cookie data 5549 , loadCookie: function () { return s.loadCookie(inst); } 5550 // loadState - pass a hash of state to use to update options 5551 , loadState: function (stateData, opts) { s.loadState(inst, stateData, opts); } 5552 // readState - returns hash of current layout-state 5553 , readState: function (keys) { return s.readState(inst, keys); } 5554 // add JSON utility methods too... 5555 , encodeJSON: s.encodeJSON 5556 , decodeJSON: s.decodeJSON 5557 }); 5558 5559 // init state.stateData key, even if plugin is initially disabled 5560 inst.state.stateData = {}; 5561 5562 // autoLoad MUST BE one of: data-array, data-hash, callback-function, or TRUE 5563 if ( !sm.autoLoad ) return; 5564 5565 // When state-data exists in the autoLoad key USE IT, 5566 // even if stateManagement.enabled == false 5567 if ($.isPlainObject( sm.autoLoad )) { 5568 if (!$.isEmptyObject( sm.autoLoad )) { 5569 inst.loadState( sm.autoLoad ); 5570 } 5571 } 5572 else if ( sm.enabled ) { 5573 // update the options from cookie or callback 5574 // if options is a function, call it to get stateData 5575 if ($.isFunction( sm.autoLoad )) { 5576 var d = {}; 5577 try { 5578 d = sm.autoLoad( inst, inst.state, inst.options, inst.options.name || '' ); // try to get data from fn 5579 } catch (e) {} 5580 if (d && $.isPlainObject( d ) && !$.isEmptyObject( d )) 5581 inst.loadState(d); 5582 } 5583 else // any other truthy value will trigger loadCookie 5584 inst.loadCookie(); 5585 } 5586 } 5587 5588, _unload: function (inst) { 5589 var sm = inst.options.stateManagement; 5590 if (sm.enabled && sm.autoSave) { 5591 // if options is a function, call it to save the stateData 5592 if ($.isFunction( sm.autoSave )) { 5593 try { 5594 sm.autoSave( inst, inst.state, inst.options, inst.options.name || '' ); // try to get data from fn 5595 } catch (e) {} 5596 } 5597 else // any truthy value will trigger saveCookie 5598 inst.saveCookie(); 5599 } 5600 } 5601 5602}; 5603 5604// add state initialization method to Layout's onCreate array of functions 5605$.layout.onCreate.push( $.layout.state._create ); 5606$.layout.onUnload.push( $.layout.state._unload ); 5607 5608})( jQuery ); 5609 5610 5611 5612/** 5613 * @preserve jquery.layout.buttons 1.0 5614 * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $ 5615 * 5616 * Copyright (c) 2011 5617 * Kevin Dalman (http://allpro.net) 5618 * 5619 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) 5620 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. 5621 * 5622 * @dependancies: UI Layout 1.3.0.rc30.1 or higher 5623 * 5624 * @support: http://groups.google.com/group/jquery-ui-layout 5625 * 5626 * Docs: [ to come ] 5627 * Tips: [ to come ] 5628 */ 5629;(function ($) { 5630 5631if (!$.layout) return; 5632 5633 5634// tell Layout that the state plugin is available 5635$.layout.plugins.buttons = true; 5636 5637// Add State-Management options to layout.defaults 5638$.layout.defaults.autoBindCustomButtons = false; 5639// Set stateManagement as a layout-option, NOT a pane-option 5640$.layout.optionsMap.layout.push("autoBindCustomButtons"); 5641 5642var lang = $.layout.language; 5643 5644/* 5645 * Button methods 5646 */ 5647$.layout.buttons = { 5648 // set data used by multiple methods below 5649 config: { 5650 borderPanes: "north,south,west,east" 5651 } 5652 5653 /** 5654 * Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons 5655 * 5656 * @see _create() 5657 */ 5658, init: function (inst) { 5659 var pre = "ui-layout-button-" 5660 , layout = inst.options.name || "" 5661 , name; 5662 $.each("toggle,open,close,pin,toggle-slide,open-slide".split(","), function (i, action) { 5663 $.each($.layout.buttons.config.borderPanes.split(","), function (ii, pane) { 5664 $("."+pre+action+"-"+pane).each(function(){ 5665 // if button was previously 'bound', data.layoutName was set, but is blank if layout has no 'name' 5666 name = $(this).data("layoutName") || $(this).attr("layoutName"); 5667 if (name == undefined || name === layout) 5668 inst.bindButton(this, action, pane); 5669 }); 5670 }); 5671 }); 5672 } 5673 5674 /** 5675 * Helper function to validate params received by addButton utilities 5676 * 5677 * Two classes are added to the element, based on the buttonClass... 5678 * The type of button is appended to create the 2nd className: 5679 * - ui-layout-button-pin 5680 * - ui-layout-pane-button-toggle 5681 * - ui-layout-pane-button-open 5682 * - ui-layout-pane-button-close 5683 * 5684 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" 5685 * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. 5686 * @return {Array.<Object>} If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null 5687 */ 5688, get: function (inst, selector, pane, action) { 5689 var $E = $(selector) 5690 , o = inst.options 5691 , err = o.showErrorMessages 5692 ; 5693 if (!$E.length) { // element not found 5694 if (err) alert(lang.errButton + lang.selector +": "+ selector); 5695 } 5696 else if ($.layout.buttons.config.borderPanes.indexOf(pane) === -1) { // invalid 'pane' sepecified 5697 if (err) alert(lang.errButton + lang.pane +": "+ pane); 5698 $E = $(""); // NO BUTTON 5699 } 5700 else { // VALID 5701 var btn = o[pane].buttonClass +"-"+ action; 5702 $E .addClass( btn +" "+ btn +"-"+ pane ) 5703 .data("layoutName", o.name); // add layout identifier - even if blank! 5704 } 5705 return $E; 5706 } 5707 5708 5709 /** 5710 * NEW syntax for binding layout-buttons - will eventually replace addToggle, addOpen, etc. 5711 * 5712 * @param {(string|!Object)} sel jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" 5713 * @param {string} action 5714 * @param {string} pane 5715 */ 5716, bind: function (inst, sel, action, pane) { 5717 var _ = $.layout.buttons; 5718 switch (action.toLowerCase()) { 5719 case "toggle": _.addToggle (inst, sel, pane); break; 5720 case "open": _.addOpen (inst, sel, pane); break; 5721 case "close": _.addClose (inst, sel, pane); break; 5722 case "pin": _.addPin (inst, sel, pane); break; 5723 case "toggle-slide": _.addToggle (inst, sel, pane, true); break; 5724 case "open-slide": _.addOpen (inst, sel, pane, true); break; 5725 } 5726 return inst; 5727 } 5728 5729 /** 5730 * Add a custom Toggler button for a pane 5731 * 5732 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" 5733 * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. 5734 * @param {boolean=} slide true = slide-open, false = pin-open 5735 */ 5736, addToggle: function (inst, selector, pane, slide) { 5737 $.layout.buttons.get(inst, selector, pane, "toggle") 5738 .click(function(evt){ 5739 inst.toggle(pane, !!slide); 5740 evt.stopPropagation(); 5741 }); 5742 return inst; 5743 } 5744 5745 /** 5746 * Add a custom Open button for a pane 5747 * 5748 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" 5749 * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. 5750 * @param {boolean=} slide true = slide-open, false = pin-open 5751 */ 5752, addOpen: function (inst, selector, pane, slide) { 5753 $.layout.buttons.get(inst, selector, pane, "open") 5754 .attr("title", lang.Open) 5755 .click(function (evt) { 5756 inst.open(pane, !!slide); 5757 evt.stopPropagation(); 5758 }); 5759 return inst; 5760 } 5761 5762 /** 5763 * Add a custom Close button for a pane 5764 * 5765 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" 5766 * @param {string} pane Name of the pane the button is for: 'north', 'south', etc. 5767 */ 5768, addClose: function (inst, selector, pane) { 5769 $.layout.buttons.get(inst, selector, pane, "close") 5770 .attr("title", lang.Close) 5771 .click(function (evt) { 5772 inst.close(pane); 5773 evt.stopPropagation(); 5774 }); 5775 return inst; 5776 } 5777 5778 /** 5779 * Add a custom Pin button for a pane 5780 * 5781 * Four classes are added to the element, based on the paneClass for the associated pane... 5782 * Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin: 5783 * - ui-layout-pane-pin 5784 * - ui-layout-pane-west-pin 5785 * - ui-layout-pane-pin-up 5786 * - ui-layout-pane-west-pin-up 5787 * 5788 * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" 5789 * @param {string} pane Name of the pane the pin is for: 'north', 'south', etc. 5790 */ 5791, addPin: function (inst, selector, pane) { 5792 var $E = $.layout.buttons.get(inst, selector, pane, "pin"); 5793 if ($E.length) { 5794 var s = inst.state[pane]; 5795 $E.click(function (evt) { 5796 $.layout.buttons.setPinState(inst, $(this), pane, (s.isSliding || s.isClosed)); 5797 if (s.isSliding || s.isClosed) inst.open( pane ); // change from sliding to open 5798 else inst.close( pane ); // slide-closed 5799 evt.stopPropagation(); 5800 }); 5801 // add up/down pin attributes and classes 5802 $.layout.buttons.setPinState(inst, $E, pane, (!s.isClosed && !s.isSliding)); 5803 // add this pin to the pane data so we can 'sync it' automatically 5804 // PANE.pins key is an array so we can store multiple pins for each pane 5805 s.pins.push( selector ); // just save the selector string 5806 } 5807 return inst; 5808 } 5809 5810 /** 5811 * Change the class of the pin button to make it look 'up' or 'down' 5812 * 5813 * @see addPin(), syncPins() 5814 * @param {Array.<Object>} $Pin The pin-span element in a jQuery wrapper 5815 * @param {string} pane These are the params returned to callbacks by layout() 5816 * @param {boolean} doPin true = set the pin 'down', false = set it 'up' 5817 */ 5818, setPinState: function (inst, $Pin, pane, doPin) { 5819 var updown = $Pin.attr("pin"); 5820 if (updown && doPin === (updown=="down")) return; // already in correct state 5821 var 5822 pin = inst.options[pane].buttonClass +"-pin" 5823 , side = pin +"-"+ pane 5824 , UP = pin +"-up "+ side +"-up" 5825 , DN = pin +"-down "+side +"-down" 5826 ; 5827 $Pin 5828 .attr("pin", doPin ? "down" : "up") // logic 5829 .attr("title", doPin ? lang.Unpin : lang.Pin) 5830 .removeClass( doPin ? UP : DN ) 5831 .addClass( doPin ? DN : UP ) 5832 ; 5833 } 5834 5835 /** 5836 * INTERNAL function to sync 'pin buttons' when pane is opened or closed 5837 * Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes 5838 * 5839 * @see open(), close() 5840 * @param {string} pane These are the params returned to callbacks by layout() 5841 * @param {boolean} doPin True means set the pin 'down', False means 'up' 5842 */ 5843, syncPinBtns: function (inst, pane, doPin) { 5844 // REAL METHOD IS _INSIDE_ LAYOUT - THIS IS HERE JUST FOR REFERENCE 5845 $.each(state[pane].pins, function (i, selector) { 5846 $.layout.buttons.setPinState(inst, $(selector), pane, doPin); 5847 }); 5848 } 5849 5850 5851, _load: function (inst) { 5852 // ADD Button methods to Layout Instance 5853 $.extend( inst, { 5854 bindButton: function (selector, action, pane) { return $.layout.buttons.bind(inst, selector, action, pane); } 5855 // DEPRECATED METHODS... 5856 , addToggleBtn: function (selector, pane, slide) { return $.layout.buttons.addToggle(inst, selector, pane, slide); } 5857 , addOpenBtn: function (selector, pane, slide) { return $.layout.buttons.addOpen(inst, selector, pane, slide); } 5858 , addCloseBtn: function (selector, pane) { return $.layout.buttons.addClose(inst, selector, pane); } 5859 , addPinBtn: function (selector, pane) { return $.layout.buttons.addPin(inst, selector, pane); } 5860 }); 5861 5862 // init state array to hold pin-buttons 5863 for (var i=0; i<4; i++) { 5864 var pane = $.layout.buttons.config.borderPanes[i]; 5865 inst.state[pane].pins = []; 5866 } 5867 5868 // auto-init buttons onLoad if option is enabled 5869 if ( inst.options.autoBindCustomButtons ) 5870 $.layout.buttons.init(inst); 5871 } 5872 5873, _unload: function (inst) { 5874 // TODO: unbind all buttons??? 5875 } 5876 5877}; 5878 5879// add initialization method to Layout's onLoad array of functions 5880$.layout.onLoad.push( $.layout.buttons._load ); 5881//$.layout.onUnload.push( $.layout.buttons._unload ); 5882 5883})( jQuery ); 5884 5885 5886 5887 5888/** 5889 * jquery.layout.browserZoom 1.0 5890 * $Date: 2011-12-29 08:00:00 (Thu, 29 Dec 2011) $ 5891 * 5892 * Copyright (c) 2012 5893 * Kevin Dalman (http://allpro.net) 5894 * 5895 * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) 5896 * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. 5897 * 5898 * @requires: UI Layout 1.3.0.rc30.1 or higher 5899 * 5900 * @see: http://groups.google.com/group/jquery-ui-layout 5901 * 5902 * TODO: Extend logic to handle other problematic zooming in browsers 5903 * TODO: Add hotkey/mousewheel bindings to _instantly_ respond to these zoom event 5904 */ 5905(function ($) { 5906 5907// tell Layout that the plugin is available 5908$.layout.plugins.browserZoom = true; 5909 5910$.layout.defaults.browserZoomCheckInterval = 1000; 5911$.layout.optionsMap.layout.push("browserZoomCheckInterval"); 5912 5913/* 5914 * browserZoom methods 5915 */ 5916$.layout.browserZoom = { 5917 5918 _init: function (inst) { 5919 // abort if browser does not need this check 5920 if ($.layout.browserZoom.ratio() !== false) 5921 $.layout.browserZoom._setTimer(inst); 5922 } 5923 5924, _setTimer: function (inst) { 5925 // abort if layout destroyed or browser does not need this check 5926 if (inst.destroyed) return; 5927 var o = inst.options 5928 , s = inst.state 5929 // don't need check if inst has parentLayout, but check occassionally in case parent destroyed! 5930 // MINIMUM 100ms interval, for performance 5931 , ms = inst.hasParentLayout ? 5000 : Math.max( o.browserZoomCheckInterval, 100 ) 5932 ; 5933 // set the timer 5934 setTimeout(function(){ 5935 if (inst.destroyed || !o.resizeWithWindow) return; 5936 var d = $.layout.browserZoom.ratio(); 5937 if (d !== s.browserZoom) { 5938 s.browserZoom = d; 5939 inst.resizeAll(); 5940 } 5941 // set a NEW timeout 5942 $.layout.browserZoom._setTimer(inst); 5943 } 5944 , ms ); 5945 } 5946 5947, ratio: function () { 5948 var w = window 5949 , s = screen 5950 , d = document 5951 , dE = d.documentElement || d.body 5952 , b = $.layout.browser 5953 , v = b.version 5954 , r, sW, cW 5955 ; 5956 // we can ignore all browsers that fire window.resize event onZoom 5957 if (!b.msie || v > 8) 5958 return false; // don't need to track zoom 5959 if (s.deviceXDPI && s.systemXDPI) // syntax compiler hack 5960 return calc(s.deviceXDPI, s.systemXDPI); 5961 // everything below is just for future reference! 5962 if (b.webkit && (r = d.body.getBoundingClientRect)) 5963 return calc((r.left - r.right), d.body.offsetWidth); 5964 if (b.webkit && (sW = w.outerWidth)) 5965 return calc(sW, w.innerWidth); 5966 if ((sW = s.width) && (cW = dE.clientWidth)) 5967 return calc(sW, cW); 5968 return false; // no match, so cannot - or don't need to - track zoom 5969 5970 function calc (x,y) { return (parseInt(x,10) / parseInt(y,10) * 100).toFixed(); } 5971 } 5972 5973}; 5974// add initialization method to Layout's onLoad array of functions 5975$.layout.onReady.push( $.layout.browserZoom._init ); 5976 5977 5978})( jQuery ); 5979 5980 5981 5982 5983/** 5984 * UI Layout Plugin: Slide-Offscreen Animation 5985 * 5986 * Prevent panes from being 'hidden' so that an iframes/objects 5987 * does not reload/refresh when pane 'opens' again. 5988 * This plug-in adds a new animation called "slideOffscreen". 5989 * It is identical to the normal "slide" effect, but avoids hiding the element 5990 * 5991 * Requires Layout 1.3.0.RC30.1 or later for Close offscreen 5992 * Requires Layout 1.3.0.RC30.5 or later for Hide, initClosed & initHidden offscreen 5993 * 5994 * Version: 1.1 - 2012-11-18 5995 * Author: Kevin Dalman (kevin@jquery-dev.com) 5996 * @preserve jquery.layout.slideOffscreen-1.1.js 5997 */ 5998;(function ($) { 5999 6000// Add a new "slideOffscreen" effect 6001if ($.effects) { 6002 6003 // add an option so initClosed and initHidden will work 6004 $.layout.defaults.panes.useOffscreenClose = false; // user must enable when needed 6005 /* set the new animation as the default for all panes 6006 $.layout.defaults.panes.fxName = "slideOffscreen"; 6007 */ 6008 6009 if ($.layout.plugins) 6010 $.layout.plugins.effects.slideOffscreen = true; 6011 6012 // dupe 'slide' effect defaults as new effect defaults 6013 $.layout.effects.slideOffscreen = $.extend(true, {}, $.layout.effects.slide); 6014 6015 // add new effect to jQuery UI 6016 $.effects.slideOffscreen = function(o) { 6017 return this.queue(function(){ 6018 6019 var fx = $.effects 6020 , opt = o.options 6021 , $el = $(this) 6022 , pane = $el.data('layoutEdge') 6023 , state = $el.data('parentLayout').state 6024 , dist = state[pane].size 6025 , s = this.style 6026 , props = ['top','bottom','left','right'] 6027 // Set options 6028 , mode = fx.setMode($el, opt.mode || 'show') // Set Mode 6029 , show = (mode == 'show') 6030 , dir = opt.direction || 'left' // Default Direction 6031 , ref = (dir == 'up' || dir == 'down') ? 'top' : 'left' 6032 , pos = (dir == 'up' || dir == 'left') 6033 , offscrn = $.layout.config.offscreenCSS || {} 6034 , keyLR = $.layout.config.offscreenReset 6035 , keyTB = 'offscreenResetTop' // only used internally 6036 , animation = {} 6037 ; 6038 // Animation settings 6039 animation[ref] = (show ? (pos ? '+=' : '-=') : (pos ? '-=' : '+=')) + dist; 6040 6041 if (show) { // show() animation, so save top/bottom but retain left/right set when 'hidden' 6042 $el.data(keyTB, { top: s.top, bottom: s.bottom }); 6043 6044 // set the top or left offset in preparation for animation 6045 // Note: ALL animations work by shifting the top or left edges 6046 if (pos) { // top (north) or left (west) 6047 $el.css(ref, isNaN(dist) ? "-" + dist : -dist); // Shift outside the left/top edge 6048 } 6049 else { // bottom (south) or right (east) - shift all the way across container 6050 if (dir === 'right') 6051 $el.css({ left: state.container.layoutWidth, right: 'auto' }); 6052 else // dir === bottom 6053 $el.css({ top: state.container.layoutHeight, bottom: 'auto' }); 6054 } 6055 // restore the left/right setting if is a top/bottom animation 6056 if (ref === 'top') 6057 $el.css( $el.data( keyLR ) || {} ); 6058 } 6059 else { // hide() animation, so save ALL CSS 6060 $el.data(keyTB, { top: s.top, bottom: s.bottom }); 6061 $el.data(keyLR, { left: s.left, right: s.right }); 6062 } 6063 6064 // Animate 6065 $el.show().animate(animation, { queue: false, duration: o.duration, easing: opt.easing, complete: function(){ 6066 // Restore top/bottom 6067 if ($el.data( keyTB )) 6068 $el.css($el.data( keyTB )).removeData( keyTB ); 6069 if (show) // Restore left/right too 6070 $el.css($el.data( keyLR ) || {}).removeData( keyLR ); 6071 else // Move the pane off-screen (left: -99999, right: 'auto') 6072 $el.css( offscrn ); 6073 6074 if (o.callback) o.callback.apply(this, arguments); // Callback 6075 $el.dequeue(); 6076 }}); 6077 6078 }); 6079 }; 6080 6081} 6082 6083})( jQuery );