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