jquery.layout.js 81 KB

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