www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit c611f0f99f5ac8dd799be0b4bcd101410a237bb5
parent 797306dea4fe3a102679fc1f20cac0cd03b5ceb4
Author: gduperon <gduperon@5d9ba3ac-444b-4713-9fb3-0b58e79229a2>
Date:   Thu, 29 Apr 2010 11:34:49 +0000

connexion de ports (partie graphique), nettoyage

git-svn-id: https://projetud.info-ufr.univ-montp2.fr/svn/flin607-2009-gduperon@26 5d9ba3ac-444b-4713-9fb3-0b58e79229a2

Diffstat:
Ajside/.url/jquery.layout.url | 1+
Mjside/ide.js | 212+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mjside/index.html | 17+++++++++++++++--
Djside/index2.html | 119-------------------------------------------------------------------------------
Djside/index3.html | 113-------------------------------------------------------------------------------
Ajside/jquery/jquery.layout.js | 2508+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajside/jquery/ui/jquery.ui.position.js | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjside/style.css | 30++++++++++++++++++++++++++++++
Djside/style2.css | 59-----------------------------------------------------------
Djside/style3.css | 198-------------------------------------------------------------------------------
10 files changed, 2981 insertions(+), 505 deletions(-)

diff --git a/jside/.url/jquery.layout.url b/jside/.url/jquery.layout.url @@ -0,0 +1 @@ +http://plugins.jquery.com/files/jquery.layout.js_1.txt diff --git a/jside/ide.js b/jside/ide.js @@ -5,28 +5,30 @@ $(document).ready(function () { function init() { $w = new world(); + // Lier les blocs + lienBlocsActif = { actif: false, elems: $([])}; + $('#rechercher').click(uiRechercher); $('#nouveau-bloc').click(uiNouveauBloc); $('#serialiser').click(uiSerialiser); //$('#nouveau-lien').click(uiNouveauLien); logPause = false; $('#log-pause').click(logPauseToggle); - + log("Démarré."); log("Ajoutez des blocs à l'espace de travail pour construire un programme."); $('#nouveau-bloc').blink(); - - test(); - var b = nouveauBloc("Scratch"); - editer(b.uid); - rechercher(''); -} - -function test() { - nouveauBloc("abcd"); - nouveauBloc("bc"); - nouveauBloc("xyz"); + // Test + var a = nouveauBloc("abcd"); + var b = nouveauBloc("bc"); + var c = nouveauBloc("xyz"); + var d = nouveauBloc("Scratch"); + utiliser(a.uid, d.uid); + + arreterRecherche(); + editer(d.uid); + //rechercher(''); } String.prototype.escapeXML = function() { @@ -101,9 +103,112 @@ jQuery.fn.extend({ this.data('notResizable', ! this.data('notResizable')) return this; + }, + offX: function(value) { + if (value === undefined) { + return this.offset().left; + } else { + return this.offset({left: value}); + } + }, + offY: function(value) { + if (value === undefined) { + return this.offset().top; + } else { + return this.offset({top: value}); + } + }, + leftX: function() { + return this.offX.apply(this, arguments); + }, + topY: function() { + return this.offY.apply(this, arguments); + }, + centerX: function() { + debug = this; + return this.offX() + (this.width() / 2); + }, + centerY: function() { + return this.offY() + (this.height() / 2); + }, + rightX: function() { + return this.offX() + this.width(); + }, + bottomY: function() { + return this.offY() + this.height(); } }); +function surchargeAccesseur(nom, type, get, set) { + var _old = $.fn[nom]; + $.fn[nom] = function(options) { + var args = arguments; + if (options !== undefined) { + var that = this; + return this.each(function (i) { + if (that[i] instanceof type) { + set(that[i], options) + } else { + _old.apply($(that[i]), args); + } + }); + } else { + if (this[0] instanceof type) { + return get(this); + } else { + return _old.call(this); + } + } + }; + +} + +function surchargeAccesseurSimple(nom, defaut, type) { + surchargeAccesseur( + nom, + type, + function (obj) { ret = obj[0][nom]; return (ret !== undefined) ? ret : defaut; }, + function (obj, val) { obj[nom] = val; } + ); +} + +// This is the beauty of JavaScript ♥ +surchargeAccesseurSimple('height', 0, $.Event); +surchargeAccesseurSimple('width', 0, $.Event); +surchargeAccesseurSimple('scrollLeft', 0, $.Event); +surchargeAccesseurSimple('scrollTop', 0, $.Event); +surchargeAccesseurSimple('outerWidth', 0, $.Event); +surchargeAccesseurSimple('outerHeight', 0, $.Event); +surchargeAccesseur( + 'offset', + $.Event, + function (obj) { + return { + left: obj[0].pageX, + top: obj[0].pageY + }; + }, + function (obj, val) { + if ('left' in val) { that[i].pageX = val.left; } + if ('top' in val) { that[i].pageY = val.top; } + } +); + +// Fix firefox bug : when top or left are set to a non-integer value, flicker occurs. +(function ($) { + var _offset = $.fn.offset; + + $.fn.offset = function(options) { + var args = arguments; + if (options !== undefined) { + if ('left' in options) { args[0].left = Math.floor(options.left); } + if ('top' in options) { args[0].top = Math.floor(options.top); } + } + + return _offset.apply(this, args); + } +}(jQuery)); + function uiRechercher() { log("Recherche…"); rechercher($('#nom-bloc').val()); @@ -183,9 +288,11 @@ function uiReduireBloc () { function uiUtiliser(uid) { var uidParent = $w.blocActif; - log("Utilisation de " + $w.blocs[uid].nom + " pour " + $w.blocs[uidParent].nom); - + utiliser(uid, uidParent); +} + +function utiliser(uid, uidParent) { $('#modele-utilisation-bloc') .jqote($w.blocs[uid]) .toDom() @@ -194,6 +301,9 @@ function uiUtiliser(uid) { .find('.reduire') .click(uiReduireBloc) .end() + .find('.port') + .click(uiLierBlocs) + .end() .appendTo('#edition-' + uidParent); } @@ -220,6 +330,80 @@ function nouveauBloc(nom) { return b; } +function uiActualiserLien(_de, _vers, segments) { + if ($(_de).centerX() < $(_vers).centerX()) { + de = $(_de); + vers = $(_vers); + } else { + de = $(_vers); + vers = $(_de); + } + segments = $(segments); + var segment1 = segments.find('.segment-1'); + var segment2 = segments.find('.segment-2'); + var segment3 = segments.find('.segment-3'); + + $(segment1) + .width((vers.centerX() - de.centerX()) / 2) + .position({my: 'left center', at: 'center', of: de}); + $(segment3) + .width((vers.centerX() - de.centerX()) / 2) + .position({my: 'right center', at: 'center', of: vers}); + $w.debug = [$(segment3), vers]; + + var neg = segment3.centerY() - segment1.centerY(); + + $(segment2) + .height((neg > 0) ? segment3.bottomY() - segment1.topY() : segment1.bottomY() - segment3.topY()) + .position({ + my: (neg > 0) ? 'center top' : 'center bottom', + at: (neg > 0) ? 'right top' : 'right bottom', + of: (neg > 0) ? segment1 : segment1 + }); +} + +function uiLierBlocs() { + if (!lienBlocsActif.actif) { + log("Début lien blocs"); + lienBlocsActif.actif = true; + + var segments = $('#modele-lien-blocs') + .jqote() + .toDom() + .appendTo($('body')); + + var start = $(this); + + var elems = $(this) + .parents(".editionBloc") + .add(segments); + + lienBlocsActif.start = start; + lienBlocsActif.elems = elems; + lienBlocsActif.segments = segments; + + elems.bind('mousemove.creerLien', function (event) { + uiActualiserLien(start, event, segments); + }); + elems.bind('click.creerLien', function (event) { + log("Fin lien blocs"); + lienBlocsActif.elems.unbind('.creerLien'); + segments.remove(); + lienBlocsActif.actif = false; + }); + } else { + log("Connexion lien blocs"); + with (lienBlocsActif) { + elems.unbind('.creerLien'); + actif = false; + debug = this; + uiActualiserLien(start, this, segments); + } + } + + return false; +} + jQuery.fn.extend({ blink: function (count, speed) { elem = this; diff --git a/jside/index.html b/jside/index.html @@ -17,6 +17,7 @@ <script type="text/javascript" src="jquery/ui/jquery.ui.draggable.js"></script> <script type="text/javascript" src="jquery/ui/jquery.ui.droppable.js"></script> <script type="text/javascript" src="jquery/ui/jquery.ui.resizable.js"></script> + <script type="text/javascript" src="jquery/ui/jquery.ui.position.js"></script> <script type="text/javascript" src="jquery/ui/jquery.ui.effects.core.js"></script> <script type="text/javascript" src="jquery/ui/jquery.effects.core.js"></script> @@ -100,7 +101,7 @@ <tbody> <% for (i = 0; i < this.entrees; i++) { %> <tr> - <td class="port entree">●</td> + <td class="port entree"><div class="port symbole"></td><!--●--> </tr> <% } %> </tbody> @@ -114,7 +115,7 @@ <tbody> <% for (i = 0; i < this.sorties; i++) { %> <tr> - <td class="port sortie">●</td> + <td class="port sortie"><div class="symbole"></div></td> </tr> <% } %> </tbody> @@ -126,6 +127,18 @@ </div> ]]> </script> + <script type="text/x-jqote-template" id="modele-lien-blocs"> + <![CDATA[ + <div class="segments"> + <div class="lien horizontal segment-1"> + </div> + <div class="lien vertical segment-2"> + </div> + <div class="lien horizontal segment-3"> + </div> + </div> + ]]> + </script> </div> </div> <div class="log"> diff --git a/jside/index2.html b/jside/index2.html @@ -1,119 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr"> - <head> - <title>IDE</title> - <link rel="stylesheet" type="text/css" href="jquery/themes/custom-theme/jquery.ui.all.css" /> - <link rel="stylesheet" type="text/css" href="style2.css" /> - - <script type="text/javascript" src="jquery/jquery-1.4.2.js"></script> - - <script type="text/javascript" src="jquery/jquery.scrollTo.js"></script> - <script type="text/javascript" src="jquery/jquery.jqote2.js"></script> - - <script type="text/javascript" src="jquery/ui/jquery.ui.core.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.widget.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.mouse.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.draggable.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.droppable.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.resizable.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.effects.core.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.effects.core.js"></script> - - <script type="text/javascript" src="ide.js"></script> - </head> - <body id="body"> - <div id="wrapper"> - <table id="top"> - <tr> - <td> - - <div id="outils"> - <input type="text" class="bouton" id="nom-bloc" /> - <a href="#" class="bouton" id="rechercher">Rechercher</a> - <a href="#" class="bouton" id="nouveau-bloc">Nouveau bloc</a> - <a href="#" class="bouton" id="serialiser">Serialiser</a> - <div class="clear"></div> - </div> - - </td> - </tr> - <tr> - <td> - - <div id="edition"> - <table id="resultats-recherche"> - <thead> - <tr> - <th> - Uid - </th> - <th> - Nom - </th> - <th> - Description - </th> - <th> - Actions - </th> - </tr> - </thead> - <script type="text/x-jqote-template" id="modele-resultat-recherche"> - <![CDATA[ - <tr class="resultat-recherche"> - <td> - <%= this.uid %> - </td> - <td> - <%= this.nom %> - </td> - <td> - <%= this.description %> - </td> - <td> - <a class="button editer" href="#">Éditer</a> - <a class="button utiliser" href="#">Utiliser</a> - </td> - </tr> - ]]> - </script> - <tbody> - </tbody> - </table> - <div id="edition-blocs"> - <script type="text/x-jqote-template" id="modele-utilisation-bloc"> - <![CDATA[ - <div class="bloc"> - <div class="tete"> - <span class="reduire icone icone-moins"></span> - <%= this.nom %> - </div> - <div class="contenu"> - <%= this.description %> - </div> - </div> - ]]> - </script> - </div> - </div> - - </td> - </tr> - <tr> - <td> - <div id="log"> - <div class="tete"> - <h2>Log</h2> - <a href="#" class="bouton" id="log-pause">pause</a> - </div> - <div class="contenu"> - <!-- Log --> - </div> - </div> - </td> - </tr> - </table> - </div> - </body> -</html> diff --git a/jside/index3.html b/jside/index3.html @@ -1,113 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr"> - <head> - <title>IDE</title> - <link rel="stylesheet" type="text/css" href="jquery/themes/custom-theme/jquery.ui.all.css" /> - <link rel="stylesheet" type="text/css" href="style3.css" /> - - <script type="text/javascript" src="jquery/jquery-1.4.2.js"></script> - - <script type="text/javascript" src="jquery/jquery.scrollTo.js"></script> - <script type="text/javascript" src="jquery/jquery.jqote2.js"></script> - - <script type="text/javascript" src="jquery/ui/jquery.ui.core.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.widget.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.mouse.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.draggable.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.droppable.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.resizable.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.ui.effects.core.js"></script> - <script type="text/javascript" src="jquery/ui/jquery.effects.core.js"></script> - - <script type="text/javascript" src="ide.js"></script> - </head> - <body> - <table id="hauteur-max"> - <tbody> - <tr> - <td> - <div id="outils"> - <input type="text" class="bouton" id="nom-bloc" /> - <input type="button" class="bouton" id="rechercher" value="Rechercher" /> - <input type="button" class="bouton" id="nouveau-bloc" value="Nouveau bloc" /> - <input type="button" class="bouton" id="serialiser" value="Serialiser" /> - </div> - </td> - </tr> - <tr id="elastique"> - <td id="edition"> - <table id="resultats-recherche"> - <thead> - <tr> - <th> - Uid - </th> - <th> - Nom - </th> - <th> - Description - </th> - <th> - Actions - </th> - </tr> - </thead> - <script type="text/x-jqote-template" id="modele-resultat-recherche"> - <![CDATA[ - <tr class="resultat-recherche"> - <td> - <%= this.uid %> - </td> - <td> - <%= this.nom %> - </td> - <td> - <%= this.description %> - </td> - <td> - <a class="button editer" href="#">Éditer</a> - <a class="button utiliser" href="#">Utiliser</a> - </td> - </tr> - ]]> - </script> - <tbody> - </tbody> - </table> - <div id="edition-blocs"> - <script type="text/x-jqote-template" id="modele-utilisation-bloc"> - <![CDATA[ - <div class="bloc"> - <div class="tete"> - <span class="reduire icone icone-moins"></span> - <%= this.nom %> - </div> - <div class="contenu"> - <%= this.description %> - </div> - </div> - ]]> - </script> - </div> - </td> - </tr> - <tr> - <td class="log"> - <div class="tete"> - <h2>Log</h2> - <input type="button" class="bouton" id="log-pause" value="pause" /> - </div> - </td> - </tr> - <tr> - <td id="td-log-contenu" class="log"> - <div id="log" class="contenu"> - </div> - </td> - </tr> - </tbody> - </table> - </body> -</html> diff --git a/jside/jquery/jquery.layout.js b/jside/jquery/jquery.layout.js @@ -0,0 +1,2507 @@ +/* + * jquery.layout 1.2.0 + * + * Copyright (c) 2008 + * Fabrizio Balliano (http://www.fabrizioballiano.net) + * Kevin Dalman (http://allpro.net) + * + * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) + * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. + * + * $Date: 2008-12-27 02:17:22 +0100 (sab, 27 dic 2008) $ + * $Rev: 203 $ + * + * NOTE: For best code readability, view this with a fixed-space font and tabs equal to 4-chars + */ +(function($) { + +$.fn.layout = function (opts) { + +/* + * ########################### + * WIDGET CONFIG & OPTIONS + * ########################### + */ + + // DEFAULTS for options + var + prefix = "ui-layout-" // prefix for ALL selectors and classNames + , defaults = { // misc default values + paneClass: prefix+"pane" // ui-layout-pane + , resizerClass: prefix+"resizer" // ui-layout-resizer + , togglerClass: prefix+"toggler" // ui-layout-toggler + , togglerInnerClass: prefix+"" // ui-layout-open / ui-layout-closed + , buttonClass: prefix+"button" // ui-layout-button + , contentSelector: "."+prefix+"content"// ui-layout-content + , contentIgnoreSelector: "."+prefix+"ignore" // ui-layout-mask + } + ; + + // DEFAULT PANEL OPTIONS - CHANGE IF DESIRED + var options = { + name: "" // FUTURE REFERENCE - not used right now + , scrollToBookmarkOnLoad: true // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark) + , defaults: { // default options for 'all panes' - will be overridden by 'per-pane settings' + applyDefaultStyles: false // apply basic styles directly to resizers & buttons? If not, then stylesheet must handle it + , closable: true // pane can open & close + , resizable: true // when open, pane can be resized + , slidable: true // when closed, pane can 'slide' open over other panes - closes on mouse-out + //, paneSelector: [ ] // MUST be pane-specific! + , contentSelector: defaults.contentSelector // INNER div/element to auto-size so only it scrolls, not the entire pane! + , contentIgnoreSelector: defaults.contentIgnoreSelector // elem(s) to 'ignore' when measuring 'content' + , paneClass: defaults.paneClass // border-Pane - default: 'ui-layout-pane' + , resizerClass: defaults.resizerClass // Resizer Bar - default: 'ui-layout-resizer' + , togglerClass: defaults.togglerClass // Toggler Button - default: 'ui-layout-toggler' + , buttonClass: defaults.buttonClass // CUSTOM Buttons - default: 'ui-layout-button-toggle/-open/-close/-pin' + , resizerDragOpacity: 1 // option for ui.draggable + //, resizerCursor: "" // MUST be pane-specific - cursor when over resizer-bar + , maskIframesOnResize: true // true = all iframes OR = iframe-selector(s) - adds masking-div during resizing/dragging + //, size: 100 // inital size of pane - defaults are set 'per pane' + , minSize: 0 // when manually resizing a pane + , maxSize: 0 // ditto, 0 = no limit + , spacing_open: 6 // space between pane and adjacent panes - when pane is 'open' + , spacing_closed: 6 // ditto - when pane is 'closed' + , togglerLength_open: 50 // Length = WIDTH of toggler button on north/south edges - HEIGHT on east/west edges + , togglerLength_closed: 50 // 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden' + , togglerAlign_open: "center" // top/left, bottom/right, center, OR... + , togglerAlign_closed: "center" // 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right + , togglerTip_open: "Close" // Toggler tool-tip (title) + , togglerTip_closed: "Open" // ditto + , resizerTip: "Resize" // Resizer tool-tip (title) + , sliderTip: "Slide Open" // resizer-bar triggers 'sliding' when pane is closed + , sliderCursor: "pointer" // cursor when resizer-bar will trigger 'sliding' + , slideTrigger_open: "click" // click, dblclick, mouseover + , slideTrigger_close: "mouseout" // click, mouseout + , hideTogglerOnSlide: false // when pane is slid-open, should the toggler show? + , togglerContent_open: "" // text or HTML to put INSIDE the toggler + , togglerContent_closed: "" // ditto + , showOverflowOnHover: false // will bind allowOverflow() utility to pane.onMouseOver + , enableCursorHotkey: true // enabled 'cursor' hotkeys + //, customHotkey: "" // MUST be pane-specific - EITHER a charCode OR a character + , customHotkeyModifier: "SHIFT" // either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT' + // NOTE: fxSss_open & fxSss_close options (eg: fxName_open) are auto-generated if not passed + , fxName: "slide" // ('none' or blank), slide, drop, scale + , fxSpeed: null // slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration + , fxSettings: {} // can be passed, eg: { easing: "easeOutBounce", duration: 1500 } + , initClosed: false // true = init pane as 'closed' + , initHidden: false // true = init pane as 'hidden' - no resizer or spacing + + /* callback options do not have to be set - listed here for reference only + , onshow_start: "" // CALLBACK when pane STARTS to Show - BEFORE onopen/onhide_start + , onshow_end: "" // CALLBACK when pane ENDS being Shown - AFTER onopen/onhide_end + , onhide_start: "" // CALLBACK when pane STARTS to Close - BEFORE onclose_start + , onhide_end: "" // CALLBACK when pane ENDS being Closed - AFTER onclose_end + , onopen_start: "" // CALLBACK when pane STARTS to Open + , onopen_end: "" // CALLBACK when pane ENDS being Opened + , onclose_start: "" // CALLBACK when pane STARTS to Close + , onclose_end: "" // CALLBACK when pane ENDS being Closed + , onresize_start: "" // CALLBACK when pane STARTS to be ***MANUALLY*** Resized + , onresize_end: "" // CALLBACK when pane ENDS being Resized ***FOR ANY REASON*** + */ + } + , north: { + paneSelector: "."+prefix+"north" // default = .ui-layout-north + , size: "auto" + , resizerCursor: "n-resize" + } + , south: { + paneSelector: "."+prefix+"south" // default = .ui-layout-south + , size: "auto" + , resizerCursor: "s-resize" + } + , east: { + paneSelector: "."+prefix+"east" // default = .ui-layout-east + , size: 200 + , resizerCursor: "e-resize" + } + , west: { + paneSelector: "."+prefix+"west" // default = .ui-layout-west + , size: 200 + , resizerCursor: "w-resize" + } + , center: { + paneSelector: "."+prefix+"center" // default = .ui-layout-center + } + + }; + + + var effects = { // LIST *PREDEFINED EFFECTS* HERE, even if effect has no settings + slide: { + all: { duration: "fast" } // eg: duration: 1000, easing: "easeOutBounce" + , north: { direction: "up" } + , south: { direction: "down" } + , east: { direction: "right"} + , west: { direction: "left" } + } + , drop: { + all: { duration: "slow" } // eg: duration: 1000, easing: "easeOutQuint" + , north: { direction: "up" } + , south: { direction: "down" } + , east: { direction: "right"} + , west: { direction: "left" } + } + , scale: { + all: { duration: "fast" } + } + }; + + + // STATIC, INTERNAL CONFIG - DO NOT CHANGE THIS! + var config = { + allPanes: "north,south,east,west,center" + , borderPanes: "north,south,east,west" + , zIndex: { // set z-index values here + resizer_normal: 1 // normal z-index for resizer-bars + , pane_normal: 2 // normal z-index for panes + , mask: 4 // overlay div used to mask pane(s) during resizing + , sliding: 100 // applied to both the pane and its resizer when a pane is 'slid open' + , resizing: 10000 // applied to the CLONED resizer-bar when being 'dragged' + , animation: 10000 // applied to the pane when being animated - not applied to the resizer + } + , resizers: { + cssReq: { + position: "absolute" + , padding: 0 + , margin: 0 + , fontSize: "1px" + , textAlign: "left" // to counter-act "center" alignment! + , overflow: "hidden" // keep toggler button from overflowing + , zIndex: 1 + } + , cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true + background: "#DDD" + , border: "none" + } + } + , togglers: { + cssReq: { + position: "absolute" + , display: "block" + , padding: 0 + , margin: 0 + , overflow: "hidden" + , textAlign: "center" + , fontSize: "1px" + , cursor: "pointer" + , zIndex: 1 + } + , cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true + background: "#AAA" + } + } + , content: { + cssReq: { + overflow: "auto" + } + , cssDef: {} + } + , defaults: { // defaults for ALL panes - overridden by 'per-pane settings' below + cssReq: { + position: "absolute" + , margin: 0 + , zIndex: 2 + } + , cssDef: { + padding: "10px" + , background: "#FFF" + , border: "1px solid #BBB" + , overflow: "auto" + } + } + , north: { + edge: "top" + , sizeType: "height" + , dir: "horz" + , cssReq: { + top: 0 + , bottom: "auto" + , left: 0 + , right: 0 + , width: "auto" + // height: DYNAMIC + } + } + , south: { + edge: "bottom" + , sizeType: "height" + , dir: "horz" + , cssReq: { + top: "auto" + , bottom: 0 + , left: 0 + , right: 0 + , width: "auto" + // height: DYNAMIC + } + } + , east: { + edge: "right" + , sizeType: "width" + , dir: "vert" + , cssReq: { + left: "auto" + , right: 0 + , top: "auto" // DYNAMIC + , bottom: "auto" // DYNAMIC + , height: "auto" + // width: DYNAMIC + } + } + , west: { + edge: "left" + , sizeType: "width" + , dir: "vert" + , cssReq: { + left: 0 + , right: "auto" + , top: "auto" // DYNAMIC + , bottom: "auto" // DYNAMIC + , height: "auto" + // width: DYNAMIC + } + } + , center: { + dir: "center" + , cssReq: { + left: "auto" // DYNAMIC + , right: "auto" // DYNAMIC + , top: "auto" // DYNAMIC + , bottom: "auto" // DYNAMIC + , height: "auto" + , width: "auto" + } + } + }; + + + // DYNAMIC DATA + var state = { + // generate random 'ID#' to identify layout - used to create global namespace for timers + id: Math.floor(Math.random() * 10000) + , container: {} + , north: {} + , south: {} + , east: {} + , west: {} + , center: {} + }; + + + var + altEdge = { + top: "bottom" + , bottom: "top" + , left: "right" + , right: "left" + } + , altSide = { + north: "south" + , south: "north" + , east: "west" + , west: "east" + } + ; + + +/* + * ########################### + * INTERNAL HELPER FUNCTIONS + * ########################### + */ + + /** + * isStr + * + * Returns true if passed param is EITHER a simple string OR a 'string object' - otherwise returns false + */ + var isStr = function (o) { + if (typeof o == "string") + return true; + else if (typeof o == "object") { + try { + var match = o.constructor.toString().match(/string/i); + return (match !== null); + } catch (e) {} + } + return false; + }; + + /** + * str + * + * Returns a simple string if the passed param is EITHER a simple string OR a 'string object', + * else returns the original object + */ + var str = function (o) { + if (typeof o == "string" || isStr(o)) return $.trim(o); // trim converts 'String object' to a simple string + else return o; + }; + + /** + * min / max + * + * Alias for Math.min/.max to simplify coding + */ + var min = function (x,y) { return Math.min(x,y); }; + var max = function (x,y) { return Math.max(x,y); }; + + /** + * transformData + * + * Processes the options passed in and transforms them into the format used by layout() + * Missing keys are added, and converts the data if passed in 'flat-format' (no sub-keys) + * In flat-format, pane-specific-settings are prefixed like: north__optName (2-underscores) + * To update effects, options MUST use nested-keys format, with an effects key + * + * @callers initOptions() + * @params JSON d Data/options passed by user - may be a single level or nested levels + * @returns JSON Creates a data struture that perfectly matches 'options', ready to be imported + */ + var transformData = function (d) { + var json = { defaults:{fxSettings:{}}, north:{fxSettings:{}}, south:{fxSettings:{}}, east:{fxSettings:{}}, west:{fxSettings:{}}, center:{fxSettings:{}} }; + d = d || {}; + if (d.effects || d.defaults || d.north || d.south || d.west || d.east || d.center) + json = $.extend( json, d ); // already in json format - add to base keys + else + // convert 'flat' to 'nest-keys' format - also handles 'empty' user-options + $.each( d, function (key,val) { + a = key.split("__"); + json[ a[1] ? a[0] : "defaults" ][ a[1] ? a[1] : a[0] ] = val; + }); + return json; + }; + + /** + * setFlowCallback + * + * Set an INTERNAL callback to avoid simultaneous animation + * Runs only if needed and only if all callbacks are not 'already set'! + * + * @param String action Either 'open' or 'close' + * @pane String pane A valid border-pane name, eg 'west' + * @pane Boolean param Extra param for callback (optional) + */ + var setFlowCallback = function (action, pane, param) { + var + cb = action +","+ pane +","+ (param ? 1 : 0) + , cP, cbPane + ; + $.each(c.borderPanes.split(","), function (i,p) { + if (c[p].isMoving) { + bindCallback(p); // TRY to bind a callback + return false; // BREAK + } + }); + + function bindCallback (p, test) { + cP = c[p]; + if (!cP.doCallback) { + cP.doCallback = true; + cP.callback = cb; + } + else { // try to 'chain' this callback + cpPane = cP.callback.split(",")[1]; // 2nd param is 'pane' + if (cpPane != p && cpPane != pane) // callback target NOT 'itself' and NOT 'this pane' + bindCallback (cpPane, true); // RECURSE + } + } + }; + + /** + * execFlowCallback + * + * RUN the INTERNAL callback for this pane - if one exists + * + * @param String action Either 'open' or 'close' + * @pane String pane A valid border-pane name, eg 'west' + * @pane Boolean param Extra param for callback (optional) + */ + var execFlowCallback = function (pane) { + var cP = c[pane]; + + // RESET flow-control flaGs + c.isLayoutBusy = false; + delete cP.isMoving; + if (!cP.doCallback || !cP.callback) return; + + cP.doCallback = false; // RESET logic flag + + // EXECUTE the callback + var + cb = cP.callback.split(",") + , param = (cb[2] > 0 ? true : false) + ; + if (cb[0] == "open") + open( cb[1], param ); + else if (cb[0] == "close") + close( cb[1], param ); + + if (!cP.doCallback) cP.callback = null; // RESET - unless callback above enabled it again! + }; + + /** + * execUserCallback + * + * Executes a Callback function after a trigger event, like resize, open or close + * + * @param String pane This is passed only so we can pass the 'pane object' to the callback + * @param String v_fn Accepts a function name, OR a comma-delimited array: [0]=function name, [1]=argument + */ + var execUserCallback = function (pane, v_fn) { + if (!v_fn) return; + var fn; + try { + if (typeof v_fn == "function") + fn = v_fn; + else if (typeof v_fn != "string") + return; + else if (v_fn.indexOf(",") > 0) { + // function name cannot contain a comma, so must be a function name AND a 'name' parameter + var + args = v_fn.split(",") + , fn = eval(args[0]) + ; + if (typeof fn=="function" && args.length > 1) + return fn(args[1]); // pass the argument parsed from 'list' + } + else // just the name of an external function? + fn = eval(v_fn); + + if (typeof fn=="function") + // pass data: pane-name, pane-element, pane-state, pane-options, and layout-name + return fn( pane, $Ps[pane], $.extend({},state[pane]), $.extend({},options[pane]), options.name ); + } + catch (ex) {} + }; + + /** + * cssNum + * + * Returns the 'current CSS value' for an element - returns 0 if property does not exist + * + * @callers Called by many methods + * @param jQuery $Elem Must pass a jQuery object - first element is processed + * @param String property The name of the CSS property, eg: top, width, etc. + * @returns Variant Usually is used to get an integer value for position (top, left) or size (height, width) + */ + var cssNum = function ($E, prop) { + var + val = 0 + , hidden = false + , visibility = "" + ; + if (!$.browser.msie) { // IE CAN read dimensions of 'hidden' elements - FF CANNOT + if ($.curCSS($E[0], "display", true) == "none") { + hidden = true; + visibility = $.curCSS($E[0], "visibility", true); // SAVE current setting + $E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so we can measure it + } + } + + val = parseInt($.curCSS($E[0], prop, true), 10) || 0; + + if (hidden) { // WAS hidden, so put back the way it was + $E.css({ display: "none" }); + if (visibility && visibility != "hidden") + $E.css({ visibility: visibility }); // reset 'visibility' + } + + return val; + }; + + /** + * cssW / cssH / cssSize + * + * Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype + * + * @callers initPanes(), sizeMidPanes(), initHandles(), sizeHandles() + * @param Variant elem Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object + * @param Integer outerWidth/outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized + * @returns Integer Returns the innerHeight of the elem by subtracting padding and borders + * + * @TODO May need to add additional logic to handle more browser/doctype variations? + */ + var cssW = function (e, outerWidth) { + var $E; + if (isStr(e)) { + e = str(e); + $E = $Ps[e]; + } + else + $E = $(e); + + // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed + if (outerWidth <= 0) + return 0; + else if (!(outerWidth>0)) + outerWidth = isStr(e) ? getPaneSize(e) : $E.outerWidth(); + + if (!$.boxModel) + return outerWidth; + + else // strip border and padding size from outerWidth to get CSS Width + return outerWidth + - cssNum($E, "paddingLeft") + - cssNum($E, "paddingRight") + - ($.curCSS($E[0], "borderLeftStyle", true) == "none" ? 0 : cssNum($E, "borderLeftWidth")) + - ($.curCSS($E[0], "borderRightStyle", true) == "none" ? 0 : cssNum($E, "borderRightWidth")) + ; + }; + var cssH = function (e, outerHeight) { + var $E; + if (isStr(e)) { + e = str(e); + $E = $Ps[e]; + } + else + $E = $(e); + + // a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed + if (outerHeight <= 0) + return 0; + else if (!(outerHeight>0)) + outerHeight = (isStr(e)) ? getPaneSize(e) : $E.outerHeight(); + + if (!$.boxModel) + return outerHeight; + + else // strip border and padding size from outerHeight to get CSS Height + return outerHeight + - cssNum($E, "paddingTop") + - cssNum($E, "paddingBottom") + - ($.curCSS($E[0], "borderTopStyle", true) == "none" ? 0 : cssNum($E, "borderTopWidth")) + - ($.curCSS($E[0], "borderBottomStyle", true) == "none" ? 0 : cssNum($E, "borderBottomWidth")) + ; + }; + var cssSize = function (pane, outerSize) { + if (c[pane].dir=="horz") // pane = north or south + return cssH(pane, outerSize); + else // pane = east or west + return cssW(pane, outerSize); + }; + + /** + * getPaneSize + * + * Calculates the current 'size' (width or height) of a border-pane - optionally with 'pane spacing' added + * + * @returns Integer Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser + */ + var getPaneSize = function (pane, inclSpace) { + var + $P = $Ps[pane] + , o = options[pane] + , s = state[pane] + , oSp = (inclSpace ? o.spacing_open : 0) + , cSp = (inclSpace ? o.spacing_closed : 0) + ; + if (!$P || s.isHidden) + return 0; + else if (s.isClosed || (s.isSliding && inclSpace)) + return cSp; + else if (c[pane].dir == "horz") + return $P.outerHeight() + oSp; + else // dir == "vert" + return $P.outerWidth() + oSp; + }; + + var setPaneMinMaxSizes = function (pane) { + var + d = cDims + , edge = c[pane].edge + , dir = c[pane].dir + , o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $altPane = $Ps[ altSide[pane] ] + , paneSpacing = o.spacing_open + , altPaneSpacing = options[ altSide[pane] ].spacing_open + , altPaneSize = (!$altPane ? 0 : (dir=="horz" ? $altPane.outerHeight() : $altPane.outerWidth())) + , containerSize = (dir=="horz" ? d.innerHeight : d.innerWidth) + // limitSize prevents this pane from 'overlapping' opposite pane - even if opposite pane is currently closed + , limitSize = containerSize - paneSpacing - altPaneSize - altPaneSpacing + , minSize = s.minSize || 0 + , maxSize = Math.min(s.maxSize || 9999, limitSize) + , minPos, maxPos // used to set resizing limits + ; + switch (pane) { + case "north": minPos = d.offsetTop + minSize; + maxPos = d.offsetTop + maxSize; + break; + case "west": minPos = d.offsetLeft + minSize; + maxPos = d.offsetLeft + maxSize; + break; + case "south": minPos = d.offsetTop + d.innerHeight - maxSize; + maxPos = d.offsetTop + d.innerHeight - minSize; + break; + case "east": minPos = d.offsetLeft + d.innerWidth - maxSize; + maxPos = d.offsetLeft + d.innerWidth - minSize; + break; + } + // save data to pane-state + $.extend(s, { minSize: minSize, maxSize: maxSize, minPosition: minPos, maxPosition: maxPos }); + }; + + /** + * getPaneDims + * + * Returns data for setting the size/position of center pane. Date is also used to set Height for east/west panes + * + * @returns JSON Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height + */ + var getPaneDims = function () { + var d = { + top: getPaneSize("north", true) // true = include 'spacing' value for p + , bottom: getPaneSize("south", true) + , left: getPaneSize("west", true) + , right: getPaneSize("east", true) + , width: 0 + , height: 0 + }; + + with (d) { + width = cDims.innerWidth - left - right; + height = cDims.innerHeight - bottom - top; + // now add the 'container border/padding' to get final positions - relative to the container + top += cDims.top; + bottom += cDims.bottom; + left += cDims.left; + right += cDims.right; + } + + return d; + }; + + + /** + * getElemDims + * + * Returns data for setting size of an element (container or a pane). + * + * @callers create(), onWindowResize() for container, plus others for pane + * @returns JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc + */ + var getElemDims = function ($E) { + var + d = {} // dimensions hash + , e, b, p // edge, border, padding + ; + + $.each("Left,Right,Top,Bottom".split(","), function () { + e = str(this); + b = d["border" +e] = cssNum($E, "border"+e+"Width"); + p = d["padding"+e] = cssNum($E, "padding"+e); + d["offset" +e] = b + p; // total offset of content from outer edge + // if BOX MODEL, then 'position' = PADDING (ignore borderWidth) + if ($E == $Container) + d[e.toLowerCase()] = ($.boxModel ? p : 0); + }); + + d.innerWidth = d.outerWidth = $E.outerWidth(); + d.innerHeight = d.outerHeight = $E.outerHeight(); + if ($.boxModel) { + d.innerWidth -= (d.offsetLeft + d.offsetRight); + d.innerHeight -= (d.offsetTop + d.offsetBottom); + } + + return d; + }; + + + var setTimer = function (pane, action, fn, ms) { + var + Layout = window.layout = window.layout || {} + , Timers = Layout.timers = Layout.timers || {} + , name = "layout_"+ state.id +"_"+ pane +"_"+ action // UNIQUE NAME for every layout-pane-action + ; + if (Timers[name]) return; // timer already set! + else Timers[name] = setTimeout(fn, ms); + }; + + var clearTimer = function (pane, action) { + var + Layout = window.layout = window.layout || {} + , Timers = Layout.timers = Layout.timers || {} + , name = "layout_"+ state.id +"_"+ pane +"_"+ action // UNIQUE NAME for every layout-pane-action + ; + if (Timers[name]) { + clearTimeout( Timers[name] ); + delete Timers[name]; + return true; + } + else + return false; + }; + + +/* + * ########################### + * INITIALIZATION METHODS + * ########################### + */ + + /** + * create + * + * Initialize the layout - called automatically whenever an instance of layout is created + * + * @callers NEVER explicity called + * @returns An object pointer to the instance created + */ + var create = function () { + // initialize config/options + initOptions(); + + // initialize all objects + initContainer(); // set CSS as needed and init state.container dimensions + initPanes(); // size & position all panes + initHandles(); // create and position all resize bars & togglers buttons + initResizable(); // activate resizing on all panes where resizable=true + sizeContent("all"); // AFTER panes & handles have been initialized, size 'content' divs + + if (options.scrollToBookmarkOnLoad) + with (self.location) if (hash) replace( hash ); // scrollTo Bookmark + + // bind hotkey function - keyDown - if required + initHotkeys(); + + // bind resizeAll() for 'this layout instance' to window.resize event + $(window).resize(function () { + var timerID = "timerLayout_"+state.id; + if (window[timerID]) clearTimeout(window[timerID]); + window[timerID] = null; + if (true || $.browser.msie) // use a delay for IE because the resize event fires repeatly + window[timerID] = setTimeout(resizeAll, 100); + else // most other browsers have a built-in delay before firing the resize event + resizeAll(); // resize all layout elements NOW! + }); + }; + + /** + * initContainer + * + * Validate and initialize container CSS and events + * + * @callers create() + */ + var initContainer = function () { + try { // format html/body if this is a full page layout + if ($Container[0].tagName == "BODY") { + $("html").css({ + height: "100%" + , overflow: "hidden" + }); + $("body").css({ + position: "relative" + , height: "100%" + , overflow: "hidden" + , margin: 0 + , padding: 0 // TODO: test whether body-padding could be handled? + , border: "none" // a body-border creates problems because it cannot be measured! + }); + } + else { // set required CSS - overflow and position + var + CSS = { overflow: "hidden" } // make sure container will not 'scroll' + , p = $Container.css("position") + , h = $Container.css("height") + ; + // if this is a NESTED layout, then outer-pane ALREADY has position and height + if (!$Container.hasClass("ui-layout-pane")) { + if (!p || "fixed,absolute,relative".indexOf(p) < 0) + CSS.position = "relative"; // container MUST have a 'position' + if (!h || h=="auto") + CSS.height = "100%"; // container MUST have a 'height' + } + $Container.css( CSS ); + } + } catch (ex) {} + + // get layout-container dimensions (updated when necessary) + cDims = state.container = getElemDims( $Container ); // update data-pointer too + }; + + /** + * initHotkeys + * + * Bind layout hotkeys - if options enabled + * + * @callers create() + */ + var initHotkeys = function () { + // bind keyDown to capture hotkeys, if option enabled for ANY pane + $.each(c.borderPanes.split(","), function (i,pane) { + var o = options[pane]; + if (o.enableCursorHotkey || o.customHotkey) { + $(document).keydown( keyDown ); // only need to bind this ONCE + return false; // BREAK - binding was done + } + }); + }; + + /** + * initOptions + * + * Build final CONFIG and OPTIONS data + * + * @callers create() + */ + var initOptions = function () { + // simplify logic by making sure passed 'opts' var has basic keys + opts = transformData( opts ); + + // update default effects, if case user passed key + if (opts.effects) { + $.extend( effects, opts.effects ); + delete opts.effects; + } + + // see if any 'global options' were specified + $.each("name,scrollToBookmarkOnLoad".split(","), function (idx,key) { + if (opts[key] !== undefined) + options[key] = opts[key]; + else if (opts.defaults[key] !== undefined) { + options[key] = opts.defaults[key]; + delete opts.defaults[key]; + } + }); + + // remove any 'defaults' that MUST be set 'per-pane' + $.each("paneSelector,resizerCursor,customHotkey".split(","), + function (idx,key) { delete opts.defaults[key]; } // is OK if key does not exist + ); + + // now update options.defaults + $.extend( options.defaults, opts.defaults ); + // make sure required sub-keys exist + //if (typeof options.defaults.fxSettings != "object") options.defaults.fxSettings = {}; + + // merge all config & options for the 'center' pane + c.center = $.extend( true, {}, c.defaults, c.center ); + $.extend( options.center, opts.center ); + // Most 'default options' do not apply to 'center', so add only those that DO + var o_Center = $.extend( true, {}, options.defaults, opts.defaults, options.center ); // TEMP data + $.each("paneClass,contentSelector,contentIgnoreSelector,applyDefaultStyles,showOverflowOnHover".split(","), + function (idx,key) { options.center[key] = o_Center[key]; } + ); + + var defs = options.defaults; + + // create a COMPLETE set of options for EACH border-pane + $.each(c.borderPanes.split(","), function(i,pane) { + // apply 'pane-defaults' to CONFIG.PANE + c[pane] = $.extend( true, {}, c.defaults, c[pane] ); + // apply 'pane-defaults' + user-options to OPTIONS.PANE + o = options[pane] = $.extend( true, {}, options.defaults, options[pane], opts.defaults, opts[pane] ); + + // make sure we have base-classes + if (!o.paneClass) o.paneClass = defaults.paneClass; + if (!o.resizerClass) o.resizerClass = defaults.resizerClass; + if (!o.togglerClass) o.togglerClass = defaults.togglerClass; + + // create FINAL fx options for each pane, ie: options.PANE.fxName/fxSpeed/fxSettings[_open|_close] + $.each(["_open","_close",""], function (i,n) { + var + sName = "fxName"+n + , sSpeed = "fxSpeed"+n + , sSettings = "fxSettings"+n + ; + // recalculate fxName according to specificity rules + o[sName] = + opts[pane][sName] // opts.west.fxName_open + || opts[pane].fxName // opts.west.fxName + || opts.defaults[sName] // opts.defaults.fxName_open + || opts.defaults.fxName // opts.defaults.fxName + || o[sName] // options.west.fxName_open + || o.fxName // options.west.fxName + || defs[sName] // options.defaults.fxName_open + || defs.fxName // options.defaults.fxName + || "none" + ; + // validate fxName to be sure is a valid effect + var fxName = o[sName]; + if (fxName == "none" || !$.effects || !$.effects[fxName] || (!effects[fxName] && !o[sSettings] && !o.fxSettings)) + fxName = o[sName] = "none"; // effect not loaded, OR undefined FX AND fxSettings not passed + // set vars for effects subkeys to simplify logic + var + fx = effects[fxName] || {} // effects.slide + , fx_all = fx.all || {} // effects.slide.all + , fx_pane = fx[pane] || {} // effects.slide.west + ; + // RECREATE the fxSettings[_open|_close] keys using specificity rules + o[sSettings] = $.extend( + {} + , fx_all // effects.slide.all + , fx_pane // effects.slide.west + , defs.fxSettings || {} // options.defaults.fxSettings + , defs[sSettings] || {} // options.defaults.fxSettings_open + , o.fxSettings // options.west.fxSettings + , o[sSettings] // options.west.fxSettings_open + , opts.defaults.fxSettings // opts.defaults.fxSettings + , opts.defaults[sSettings] || {} // opts.defaults.fxSettings_open + , opts[pane].fxSettings // opts.west.fxSettings + , opts[pane][sSettings] || {} // opts.west.fxSettings_open + ); + // recalculate fxSpeed according to specificity rules + o[sSpeed] = + opts[pane][sSpeed] // opts.west.fxSpeed_open + || opts[pane].fxSpeed // opts.west.fxSpeed (pane-default) + || opts.defaults[sSpeed] // opts.defaults.fxSpeed_open + || opts.defaults.fxSpeed // opts.defaults.fxSpeed + || o[sSpeed] // options.west.fxSpeed_open + || o[sSettings].duration // options.west.fxSettings_open.duration + || o.fxSpeed // options.west.fxSpeed + || o.fxSettings.duration // options.west.fxSettings.duration + || defs.fxSpeed // options.defaults.fxSpeed + || defs.fxSettings.duration// options.defaults.fxSettings.duration + || fx_pane.duration // effects.slide.west.duration + || fx_all.duration // effects.slide.all.duration + || "normal" // DEFAULT + ; + // DEBUG: if (pane=="east") debugData( $.extend({}, {speed: o[sSpeed], fxSettings_duration: o[sSettings].duration}, o[sSettings]), pane+"."+sName+" = "+fxName ); + }); + }); + }; + + /** + * initPanes + * + * Initialize module objects, styling, size and position for all panes + * + * @callers create() + */ + var initPanes = function () { + // NOTE: do north & south FIRST so we can measure their height - do center LAST + $.each(c.allPanes.split(","), function() { + var + pane = str(this) + , o = options[pane] + , s = state[pane] + , fx = s.fx + , dir = c[pane].dir + // if o.size is not > 0, then we will use MEASURE the pane and use that as it's 'size' + , size = o.size=="auto" || isNaN(o.size) ? 0 : o.size + , minSize = o.minSize || 1 + , maxSize = o.maxSize || 9999 + , spacing = o.spacing_open || 0 + , sel = o.paneSelector + , isIE6 = ($.browser.msie && $.browser.version < 7) + , CSS = {} + , $P, $C + ; + $Cs[pane] = false; // init + + if (sel.substr(0,1)==="#") // ID selector + // NOTE: elements selected 'by ID' DO NOT have to be 'children' + $P = $Ps[pane] = $Container.find(sel+":first"); + else { // class or other selector + $P = $Ps[pane] = $Container.children(sel+":first"); + // look for the pane nested inside a 'form' element + if (!$P.length) $P = $Ps[pane] = $Container.children("form:first").children(sel+":first"); + } + + if (!$P.length) { + $Ps[pane] = false; // logic + return true; // SKIP to next + } + + // add basic classes & attributes + $P + .attr("pane", pane) // add pane-identifier + .addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector' + ; + + // init pane-logic vars, etc. + if (pane != "center") { + s.isClosed = false; // true = pane is closed + s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes + s.isResizing= false; // true = pane is in process of being resized + s.isHidden = false; // true = pane is hidden - no spacing, resizer or toggler is visible! + s.noRoom = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically + // create special keys for internal use + c[pane].pins = []; // used to track and sync 'pin-buttons' for border-panes + } + + CSS = $.extend({ visibility: "visible", display: "block" }, c.defaults.cssReq, c[pane].cssReq ); + if (o.applyDefaultStyles) $.extend( CSS, c.defaults.cssDef, c[pane].cssDef ); // cosmetic defaults + $P.css(CSS); // add base-css BEFORE 'measuring' to calc size & position + CSS = {}; // reset var + + // set css-position to account for container borders & padding + switch (pane) { + case "north": CSS.top = cDims.top; + CSS.left = cDims.left; + CSS.right = cDims.right; + break; + case "south": CSS.bottom = cDims.bottom; + CSS.left = cDims.left; + CSS.right = cDims.right; + break; + case "west": CSS.left = cDims.left; // top, bottom & height set by sizeMidPanes() + break; + case "east": CSS.right = cDims.right; // ditto + break; + case "center": // top, left, width & height set by sizeMidPanes() + } + + if (dir == "horz") { // north or south pane + if (size === 0 || size == "auto") { + $P.css({ height: "auto" }); + size = $P.outerHeight(); + } + size = max(size, minSize); + size = min(size, maxSize); + size = min(size, cDims.innerHeight - spacing); + CSS.height = max(1, cssH(pane, size)); + s.size = size; // update state + // make sure minSize is sufficient to avoid errors + s.maxSize = maxSize; // init value + s.minSize = max(minSize, size - CSS.height + 1); // = pane.outerHeight when css.height = 1px + // handle IE6 + //if (isIE6) CSS.width = cssW($P, cDims.innerWidth); + $P.css(CSS); // apply size & position + } + else if (dir == "vert") { // east or west pane + if (size === 0 || size == "auto") { + $P.css({ width: "auto", float: "left" }); // float = FORCE pane to auto-size + size = $P.outerWidth(); + $P.css({ float: "none" }); // RESET + } + size = max(size, minSize); + size = min(size, maxSize); + size = min(size, cDims.innerWidth - spacing); + CSS.width = max(1, cssW(pane, size)); + s.size = size; // update state + s.maxSize = maxSize; // init value + // make sure minSize is sufficient to avoid errors + s.minSize = max(minSize, size - CSS.width + 1); // = pane.outerWidth when css.width = 1px + $P.css(CSS); // apply size - top, bottom & height set by sizeMidPanes + sizeMidPanes(pane, null, true); // true = onInit + } + else if (pane == "center") { + $P.css(CSS); // top, left, width & height set by sizeMidPanes... + sizeMidPanes("center", null, true); // true = onInit + } + + // close or hide the pane if specified in settings + if (o.initClosed && o.closable) { + $P.hide().addClass("closed"); + s.isClosed = true; + } + else if (o.initHidden || o.initClosed) { + hide(pane, true); // will be completely invisible - no resizer or spacing + s.isHidden = true; + } + else + $P.addClass("open"); + + // check option for auto-handling of pop-ups & drop-downs + if (o.showOverflowOnHover) + $P.hover( allowOverflow, resetOverflow ); + + /* + * see if this pane has a 'content element' that we need to auto-size + */ + if (o.contentSelector) { + $C = $Cs[pane] = $P.children(o.contentSelector+":first"); // match 1-element only + if (!$C.length) { + $Cs[pane] = false; + return true; // SKIP to next + } + $C.css( c.content.cssReq ); + if (o.applyDefaultStyles) $C.css( c.content.cssDef ); // cosmetic defaults + // NO PANE-SCROLLING when there is a content-div + $P.css({ overflow: "hidden" }); + } + }); + }; + + /** + * initHandles + * + * Initialize module objects, styling, size and position for all resize bars and toggler buttons + * + * @callers create() + */ + var initHandles = function () { + // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV + $.each(c.borderPanes.split(","), function() { + var + pane = str(this) + , o = options[pane] + , s = state[pane] + , rClass = o.resizerClass + , tClass = o.togglerClass + , $P = $Ps[pane] + ; + $Rs[pane] = false; // INIT + $Ts[pane] = false; + + if (!$P || (!o.closable && !o.resizable)) return; // pane does not exist - skip + + var + edge = c[pane].edge + , isOpen = $P.is(":visible") + , spacing = (isOpen ? o.spacing_open : o.spacing_closed) + , _pane = "-"+ pane // used for classNames + , _state = (isOpen ? "-open" : "-closed") // used for classNames + , $R, $T + ; + // INIT RESIZER BAR + $R = $Rs[pane] = $("<span></span>"); + + if (isOpen && o.resizable) + ; // this is handled by initResizable + else if (!isOpen && o.slidable) + $R.attr("title", o.sliderTip).css("cursor", o.sliderCursor); + + $R + // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer" + .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-resizer" : "")) + .attr("resizer", pane) // so we can read this from the resizer + .css(c.resizers.cssReq) // add base/required styles + // POSITION of resizer bar - allow for container border & padding + .css(edge, cDims[edge] + getPaneSize(pane)) + // ADD CLASSNAMES - eg: class="resizer resizer-west resizer-open" + .addClass( rClass +" "+ rClass+_pane +" "+ rClass+_state +" "+ rClass+_pane+_state ) + .appendTo($Container) // append DIV to container + ; + // ADD VISUAL STYLES + if (o.applyDefaultStyles) + $R.css(c.resizers.cssDef); + + if (o.closable) { + // INIT COLLAPSER BUTTON + $T = $Ts[pane] = $("<div></div>"); + $T + // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-toggler" + .attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-toggler" : "")) + .css(c.togglers.cssReq) // add base/required styles + .attr("title", (isOpen ? o.togglerTip_open : o.togglerTip_closed)) + .click(function(evt){ toggle(pane); evt.stopPropagation(); }) + .mouseover(function(evt){ evt.stopPropagation(); }) // prevent resizer event + // ADD CLASSNAMES - eg: class="toggler toggler-west toggler-west-open" + .addClass( tClass +" "+ tClass+_pane +" "+ tClass+_state +" "+ tClass+_pane+_state ) + .appendTo($R) // append SPAN to resizer DIV + ; + + // ADD INNER-SPANS TO TOGGLER + if (o.togglerContent_open) // ui-layout-open + $("<span>"+ o.togglerContent_open +"</span>") + .addClass("content content-open") + .css("display", s.isClosed ? "none" : "block") + .appendTo( $T ) + ; + if (o.togglerContent_closed) // ui-layout-closed + $("<span>"+ o.togglerContent_closed +"</span>") + .addClass("content content-closed") + .css("display", s.isClosed ? "block" : "none") + .appendTo( $T ) + ; + + // ADD BASIC VISUAL STYLES + if (o.applyDefaultStyles) + $T.css(c.togglers.cssDef); + + if (!isOpen) bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true + } + + }); + + // SET ALL HANDLE SIZES & LENGTHS + sizeHandles("all", true); // true = onInit + }; + + /** + * initResizable + * + * Add resize-bars to all panes that specify it in options + * + * @dependancies $.fn.resizable - will abort if not found + * @callers create() + */ + var initResizable = function () { + var + draggingAvailable = (typeof $.fn.draggable == "function") + , minPosition, maxPosition, edge // set in start() + ; + + $.each(c.borderPanes.split(","), function() { + var + pane = str(this) + , o = options[pane] + , s = state[pane] + ; + if (!draggingAvailable || !$Ps[pane] || !o.resizable) { + o.resizable = false; + return true; // skip to next + } + + var + rClass = o.resizerClass + // 'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process + , dragClass = rClass+"-drag" // resizer-drag + , dragPaneClass = rClass+"-"+pane+"-drag" // resizer-north-drag + // 'dragging' class is applied to the CLONED resizer-bar while it is being dragged + , draggingClass = rClass+"-dragging" // resizer-dragging + , draggingPaneClass = rClass+"-"+pane+"-dragging" // resizer-north-dragging + , draggingClassSet = false // logic var + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + + if (!s.isClosed) + $R + .attr("title", o.resizerTip) + .css("cursor", o.resizerCursor) // n-resize, s-resize, etc + ; + + $R.draggable({ + containment: $Container[0] // limit resizing to layout container + , axis: (c[pane].dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis + , delay: 200 + , distance: 1 + // basic format for helper - style it using class: .ui-draggable-dragging + , helper: "clone" + , opacity: o.resizerDragOpacity + //, iframeFix: o.draggableIframeFix // TODO: consider using when bug is fixed + , zIndex: c.zIndex.resizing + + , start: function (e, ui) { + // onresize_start callback - will CANCEL hide if returns false + // TODO: CONFIRM that dragging can be cancelled like this??? + if (false === execUserCallback(pane, o.onresize_start)) return false; + + s.isResizing = true; // prevent pane from closing while resizing + clearTimer(pane, "closeSlider"); // just in case already triggered + + $R.addClass( dragClass +" "+ dragPaneClass ); // add drag classes + draggingClassSet = false; // reset logic var - see drag() + + // SET RESIZING LIMITS - used in drag() + var resizerWidth = (pane=="east" || pane=="south" ? o.spacing_open : 0); + setPaneMinMaxSizes(pane); // update pane-state + s.minPosition -= resizerWidth; + s.maxPosition -= resizerWidth; + edge = (c[pane].dir=="horz" ? "top" : "left"); + + // MASK PANES WITH IFRAMES OR OTHER TROUBLESOME ELEMENTS + $(o.maskIframesOnResize === true ? "iframe" : o.maskIframesOnResize).each(function() { + $('<div class="ui-layout-mask"/>') + .css({ + background: "#fff" + , opacity: "0.001" + , zIndex: 9 + , position: "absolute" + , width: this.offsetWidth+"px" + , height: this.offsetHeight+"px" + }) + .css($(this).offset()) // top & left + .appendTo(this.parentNode) // put div INSIDE pane to avoid zIndex issues + ; + }); + } + + , drag: function (e, ui) { + if (!draggingClassSet) { // can only add classes after clone has been added to the DOM + $(".ui-draggable-dragging") + .addClass( draggingClass +" "+ draggingPaneClass ) // add dragging classes + .children().css("visibility","hidden") // hide toggler inside dragged resizer-bar + ; + draggingClassSet = true; + // draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane! + if (s.isSliding) $Ps[pane].css("zIndex", c.zIndex.sliding); + } + // CONTAIN RESIZER-BAR TO RESIZING LIMITS + if (ui.position[edge] < s.minPosition) ui.position[edge] = s.minPosition; + else if (ui.position[edge] > s.maxPosition) ui.position[edge] = s.maxPosition; + } + + , stop: function (e, ui) { + var + dragPos = ui.position + , resizerPos + , newSize + ; + $R.removeClass( dragClass +" "+ dragPaneClass ); // remove drag classes + + switch (pane) { + case "north": resizerPos = dragPos.top; break; + case "west": resizerPos = dragPos.left; break; + case "south": resizerPos = cDims.outerHeight - dragPos.top - $R.outerHeight(); break; + case "east": resizerPos = cDims.outerWidth - dragPos.left - $R.outerWidth(); break; + } + // remove container margin from resizer position to get the pane size + newSize = resizerPos - cDims[ c[pane].edge ]; + + sizePane(pane, newSize); + + // UN-MASK PANES MASKED IN drag.start + $("div.ui-layout-mask").remove(); // Remove iframe masks + + s.isResizing = false; + } + + }); + }); + }; + + + +/* + * ########################### + * ACTION METHODS + * ########################### + */ + + /** + * hide / show + * + * Completely 'hides' a pane, including its spacing - as if it does not exist + * The pane is not actually 'removed' from the source, so can use 'show' to un-hide it + * + * @param String pane The pane being hidden, ie: north, south, east, or west + */ + var hide = function (pane, onInit) { + var + o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + if (!$P || s.isHidden) return; // pane does not exist OR is already hidden + + // onhide_start callback - will CANCEL hide if returns false + if (false === execUserCallback(pane, o.onhide_start)) return; + + s.isSliding = false; // just in case + + // now hide the elements + if ($R) $R.hide(); // hide resizer-bar + if (onInit || s.isClosed) { + s.isClosed = true; // to trigger open-animation on show() + s.isHidden = true; + $P.hide(); // no animation when loading page + sizeMidPanes(c[pane].dir == "horz" ? "all" : "center"); + execUserCallback(pane, o.onhide_end || o.onhide); + } + else { + s.isHiding = true; // used by onclose + close(pane, false); // adjust all panes to fit + //s.isHidden = true; - will be set by close - if not cancelled + } + }; + + var show = function (pane, openPane) { + var + o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden + + // onhide_start callback - will CANCEL hide if returns false + if (false === execUserCallback(pane, o.onshow_start)) return; + + s.isSliding = false; // just in case + s.isShowing = true; // used by onopen/onclose + //s.isHidden = false; - will be set by open/close - if not cancelled + + // now show the elements + if ($R && o.spacing_open > 0) $R.show(); + if (openPane === false) + close(pane, true); // true = force + else + open(pane); // adjust all panes to fit + }; + + + /** + * toggle + * + * Toggles a pane open/closed by calling either open or close + * + * @param String pane The pane being toggled, ie: north, south, east, or west + */ + var toggle = function (pane) { + var s = state[pane]; + if (s.isHidden) + show(pane); // will call 'open' after unhiding it + else if (s.isClosed) + open(pane); + else + close(pane); + }; + + /** + * close + * + * Close the specified pane (animation optional), and resize all other panes as needed + * + * @param String pane The pane being closed, ie: north, south, east, or west + */ + var close = function (pane, force, noAnimation) { + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none") + , edge = c[pane].edge + , rClass = o.resizerClass + , tClass = o.togglerClass + , _pane = "-"+ pane // used for classNames + , _open = "-open" + , _sliding= "-sliding" + , _closed = "-closed" + // transfer logic vars to temp vars + , isShowing = s.isShowing + , isHiding = s.isHiding + ; + // now clear the logic vars + delete s.isShowing; + delete s.isHiding; + + if (!$P || (!o.resizable && !o.closable)) return; // invalid request + else if (!force && s.isClosed && !isShowing) return; // already closed + + if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation + setFlowCallback("close", pane, force); // set a callback for this action, if possible + return; // ABORT + } + + // onclose_start callback - will CANCEL hide if returns false + // SKIP if just 'showing' a hidden pane as 'closed' + if (!isShowing && false === execUserCallback(pane, o.onclose_start)) return; + + // SET flow-control flags + c[pane].isMoving = true; + c.isLayoutBusy = true; + + s.isClosed = true; + // update isHidden BEFORE sizing panes + if (isHiding) s.isHidden = true; + else if (isShowing) s.isHidden = false; + + // sync any 'pin buttons' + syncPinBtns(pane, false); + + // resize panes adjacent to this one + if (!s.isSliding) sizeMidPanes(c[pane].dir == "horz" ? "all" : "center"); + + // if this pane has a resizer bar, move it now + if ($R) { + $R + .css(edge, cDims[edge]) // move the resizer bar + .removeClass( rClass+_open +" "+ rClass+_pane+_open ) + .removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) + .addClass( rClass+_closed +" "+ rClass+_pane+_closed ) + ; + // DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent + if (o.resizable) + $R + .draggable("disable") + .css("cursor", "default") + .attr("title","") + ; + // if pane has a toggler button, adjust that too + if ($T) { + $T + .removeClass( tClass+_open +" "+ tClass+_pane+_open ) + .addClass( tClass+_closed +" "+ tClass+_pane+_closed ) + .attr("title", o.togglerTip_closed) // may be blank + ; + } + sizeHandles(); // resize 'length' and position togglers for adjacent panes + } + + // ANIMATE 'CLOSE' - if no animation, then was ALREADY shown above + if (doFX) { + lockPaneForFX(pane, true); // need to set left/top so animation will work + $P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () { + lockPaneForFX(pane, false); // undo + if (!s.isClosed) return; // pane was opened before animation finished! + close_2(); + }); + } + else { + $P.hide(); // just hide pane NOW + close_2(); + } + + // SUBROUTINE + function close_2 () { + bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true + + // onclose callback - UNLESS just 'showing' a hidden pane as 'closed' + if (!isShowing) execUserCallback(pane, o.onclose_end || o.onclose); + // onhide OR onshow callback + if (isShowing) execUserCallback(pane, o.onshow_end || o.onshow); + if (isHiding) execUserCallback(pane, o.onhide_end || o.onhide); + + // internal flow-control callback + execFlowCallback(pane); + } + }; + + /** + * open + * + * Open the specified pane (animation optional), and resize all other panes as needed + * + * @param String pane The pane being opened, ie: north, south, east, or west + */ + var open = function (pane, slide, noAnimation) { + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , doFX = !noAnimation && s.isClosed && (o.fxName_open != "none") + , edge = c[pane].edge + , rClass = o.resizerClass + , tClass = o.togglerClass + , _pane = "-"+ pane // used for classNames + , _open = "-open" + , _closed = "-closed" + , _sliding= "-sliding" + // transfer logic var to temp var + , isShowing = s.isShowing + ; + // now clear the logic var + delete s.isShowing; + + if (!$P || (!o.resizable && !o.closable)) return; // invalid request + else if (!s.isClosed && !s.isSliding) return; // already open + + // pane can ALSO be unhidden by just calling show(), so handle this scenario + if (s.isHidden && !isShowing) { + show(pane, true); + return; + } + + if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation + setFlowCallback("open", pane, slide); // set a callback for this action, if possible + return; // ABORT + } + + // onopen_start callback - will CANCEL hide if returns false + if (false === execUserCallback(pane, o.onopen_start)) return; + + // SET flow-control flags + c[pane].isMoving = true; + c.isLayoutBusy = true; + + // 'PIN PANE' - stop sliding + if (s.isSliding && !slide) // !slide = 'open pane normally' - NOT sliding + bindStopSlidingEvents(pane, false); // will set isSliding=false + + s.isClosed = false; + // update isHidden BEFORE sizing panes + if (isShowing) s.isHidden = false; + + // Container size may have changed - shrink the pane if now 'too big' + setPaneMinMaxSizes(pane); // update pane-state + if (s.size > s.maxSize) // pane is too big! resize it before opening + $P.css( c[pane].sizeType, max(1, cssSize(pane, s.maxSize)) ); + + bindStartSlidingEvent(pane, false); // remove trigger event from resizer-bar + + if (doFX) { // ANIMATE + lockPaneForFX(pane, true); // need to set left/top so animation will work + $P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() { + lockPaneForFX(pane, false); // undo + if (s.isClosed) return; // pane was closed before animation finished! + open_2(); // continue + }); + } + else {// no animation + $P.show(); // just show pane and... + open_2(); // continue + } + + // SUBROUTINE + function open_2 () { + // NOTE: if isSliding, then other panes are NOT 'resized' + if (!s.isSliding) // resize all panes adjacent to this one + sizeMidPanes(c[pane].dir=="vert" ? "center" : "all"); + + // if this pane has a toggler, move it now + if ($R) { + $R + .css(edge, cDims[edge] + getPaneSize(pane)) // move the toggler + .removeClass( rClass+_closed +" "+ rClass+_pane+_closed ) + .addClass( rClass+_open +" "+ rClass+_pane+_open ) + .addClass( !s.isSliding ? "" : rClass+_sliding +" "+ rClass+_pane+_sliding ) + ; + if (o.resizable) + $R + .draggable("enable") + .css("cursor", o.resizerCursor) + .attr("title", o.resizerTip) + ; + else + $R.css("cursor", "default"); // n-resize, s-resize, etc + // if pane also has a toggler button, adjust that too + if ($T) { + $T + .removeClass( tClass+_closed +" "+ tClass+_pane+_closed ) + .addClass( tClass+_open +" "+ tClass+_pane+_open ) + .attr("title", o.togglerTip_open) // may be blank + ; + } + sizeHandles("all"); // resize resizer & toggler sizes for all panes + } + + // resize content every time pane opens - to be sure + sizeContent(pane); + + // sync any 'pin buttons' + syncPinBtns(pane, !s.isSliding); + + // onopen callback + execUserCallback(pane, o.onopen_end || o.onopen); + + // onshow callback + if (isShowing) execUserCallback(pane, o.onshow_end || o.onshow); + + // internal flow-control callback + execFlowCallback(pane); + } + }; + + + /** + * lockPaneForFX + * + * Must set left/top on East/South panes so animation will work properly + * + * @param String pane The pane to lock, 'east' or 'south' - any other is ignored! + * @param Boolean doLock true = set left/top, false = remove + */ + var lockPaneForFX = function (pane, doLock) { + var $P = $Ps[pane]; + if (doLock) { + $P.css({ zIndex: c.zIndex.animation }); // overlay all elements during animation + if (pane=="south") + $P.css({ top: cDims.top + cDims.innerHeight - $P.outerHeight() }); + else if (pane=="east") + $P.css({ left: cDims.left + cDims.innerWidth - $P.outerWidth() }); + } + else { + if (!state[pane].isSliding) $P.css({ zIndex: c.zIndex.pane_normal }); + if (pane=="south") + $P.css({ top: "auto" }); + else if (pane=="east") + $P.css({ left: "auto" }); + } + }; + + + /** + * bindStartSlidingEvent + * + * Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger + * + * @callers open(), close() + * @param String pane The pane to enable/disable, 'north', 'south', etc. + * @param Boolean enable Enable or Disable sliding? + */ + var bindStartSlidingEvent = function (pane, enable) { + var + o = options[pane] + , $R = $Rs[pane] + , trigger = o.slideTrigger_open + ; + if (!$R || !o.slidable) return; + // make sure we have a valid event + if (trigger != "click" && trigger != "dblclick" && trigger != "mouseover") trigger = "click"; + $R + // add or remove trigger event + [enable ? "bind" : "unbind"](trigger, slideOpen) + // set the appropriate cursor & title/tip + .css("cursor", (enable ? o.sliderCursor: "default")) + .attr("title", (enable ? o.sliderTip : "")) + ; + }; + + /** + * bindStopSlidingEvents + * + * Add or remove 'mouseout' events to 'slide close' when pane is 'sliding' open or closed + * Also increases zIndex when pane is sliding open + * See bindStartSlidingEvent for code to control 'slide open' + * + * @callers slideOpen(), slideClosed() + * @param String pane The pane to process, 'north', 'south', etc. + * @param Boolean isOpen Is pane open or closed? + */ + var bindStopSlidingEvents = function (pane, enable) { + var + o = options[pane] + , s = state[pane] + , trigger = o.slideTrigger_close + , action = (enable ? "bind" : "unbind") // can't make 'unbind' work! - see disabled code below + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + + s.isSliding = enable; // logic + clearTimer(pane, "closeSlider"); // just in case + + // raise z-index when sliding + $P.css({ zIndex: (enable ? c.zIndex.sliding : c.zIndex.pane_normal) }); + $R.css({ zIndex: (enable ? c.zIndex.sliding : c.zIndex.resizer_normal) }); + + // make sure we have a valid event + if (trigger != "click" && trigger != "mouseout") trigger = "mouseout"; + + // when trigger is 'mouseout', must cancel timer when mouse moves between 'pane' and 'resizer' + if (enable) { // BIND trigger events + $P.bind(trigger, slideClosed ); + $R.bind(trigger, slideClosed ); + if (trigger = "mouseout") { + $P.bind("mouseover", cancelMouseOut ); + $R.bind("mouseover", cancelMouseOut ); + } + } + else { // UNBIND trigger events + // TODO: why does unbind of a 'single function' not work reliably? + //$P[action](trigger, slideClosed ); + $P.unbind(trigger); + $R.unbind(trigger); + if (trigger = "mouseout") { + //$P[action]("mouseover", cancelMouseOut ); + $P.unbind("mouseover"); + $R.unbind("mouseover"); + clearTimer(pane, "closeSlider"); + } + } + + // SUBROUTINE for mouseout timer clearing + function cancelMouseOut (evt) { + clearTimer(pane, "closeSlider"); + evt.stopPropagation(); + } + }; + + var slideOpen = function () { + var pane = $(this).attr("resizer"); // attr added by initHandles + if (state[pane].isClosed) { // skip if already open! + bindStopSlidingEvents(pane, true); // pane is opening, so BIND trigger events to close it + open(pane, true); // true = slide - ie, called from here! + } + }; + + var slideClosed = function () { + var + $E = $(this) + , pane = $E.attr("pane") || $E.attr("resizer") + , o = options[pane] + , s = state[pane] + ; + if (s.isClosed || s.isResizing) + return; // skip if already closed OR in process of resizing + else if (o.slideTrigger_close == "click") + close_NOW(); // close immediately onClick + else // trigger = mouseout - use a delay + setTimer(pane, "closeSlider", close_NOW, 300); // .3 sec delay + + // SUBROUTINE for timed close + function close_NOW () { + bindStopSlidingEvents(pane, false); // pane is being closed, so UNBIND trigger events + if (!s.isClosed) close(pane); // skip if already closed! + } + }; + + + /** + * sizePane + * + * @callers initResizable.stop() + * @param String pane The pane being resized - usually west or east, but potentially north or south + * @param Integer newSize The new size for this pane - will be validated + */ + var sizePane = function (pane, size) { + // TODO: accept "auto" as size, and size-to-fit pane content + var + edge = c[pane].edge + , dir = c[pane].dir + , o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + // calculate 'current' min/max sizes + setPaneMinMaxSizes(pane); // update pane-state + // compare/update calculated min/max to user-options + s.minSize = max(s.minSize, o.minSize); + if (o.maxSize > 0) s.maxSize = min(s.maxSize, o.maxSize); + // validate passed size + size = max(size, s.minSize); + size = min(size, s.maxSize); + s.size = size; // update state + + // move the resizer bar and resize the pane + $R.css( edge, size + cDims[edge] ); + $P.css( c[pane].sizeType, max(1, cssSize(pane, size)) ); + + // resize all the adjacent panes, and adjust their toggler buttons + if (!s.isSliding) sizeMidPanes(dir=="horz" ? "all" : "center"); + sizeHandles(); + sizeContent(pane); + execUserCallback(pane, o.onresize_end || o.onresize); + }; + + /** + * sizeMidPanes + * + * @callers create(), open(), close(), onWindowResize() + */ + var sizeMidPanes = function (panes, overrideDims, onInit) { + if (!panes || panes == "all") panes = "east,west,center"; + + var d = getPaneDims(); + if (overrideDims) $.extend( d, overrideDims ); + + $.each(panes.split(","), function() { + if (!$Ps[this]) return; // NO PANE - skip + var + pane = str(this) + , o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , hasRoom = true + , CSS = {} + ; + + if (pane == "center") { + d = getPaneDims(); // REFRESH Dims because may have just 'unhidden' East or West pane after a 'resize' + CSS = $.extend( {}, d ); // COPY ALL of the paneDims + CSS.width = max(1, cssW(pane, CSS.width)); + CSS.height = max(1, cssH(pane, CSS.height)); + hasRoom = (CSS.width > 1 && CSS.height > 1); + /* + * Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes + * Normally these panes have only 'left' & 'right' positions so pane auto-sizes + */ + if ($.browser.msie && (!$.boxModel || $.browser.version < 7)) { + if ($Ps.north) $Ps.north.css({ width: cssW($Ps.north, cDims.innerWidth) }); + if ($Ps.south) $Ps.south.css({ width: cssW($Ps.south, cDims.innerWidth) }); + } + } + else { // for east and west, set only the height + CSS.top = d.top; + CSS.bottom = d.bottom; + CSS.height = max(1, cssH(pane, d.height)); + hasRoom = (CSS.height > 1); + } + + if (hasRoom) { + $P.css(CSS); + if (s.noRoom) { + s.noRoom = false; + if (s.isHidden) return; + else show(pane, !s.isClosed); + /* OLD CODE - keep until sure line above works right! + if (!s.isClosed) $P.show(); // in case was previously hidden due to NOT hasRoom + if ($R) $R.show(); + */ + } + if (!onInit) { + sizeContent(pane); + execUserCallback(pane, o.onresize_end || o.onresize); + } + } + else if (!s.noRoom) { // no room for pane, so just hide it (if not already) + s.noRoom = true; // update state + if (s.isHidden) return; + if (onInit) { // skip onhide callback and other logic onLoad + $P.hide(); + if ($R) $R.hide(); + } + else hide(pane); + } + }); + }; + + + var sizeContent = function (panes) { + if (!panes || panes == "all") panes = c.allPanes; + + $.each(panes.split(","), function() { + if (!$Cs[this]) return; // NO CONTENT - skip + var + pane = str(this) + , ignore = options[pane].contentIgnoreSelector + , $P = $Ps[pane] + , $C = $Cs[pane] + , e_C = $C[0] // DOM element + , height = cssH($P); // init to pane.innerHeight + ; + $P.children().each(function() { + if (this == e_C) return; // Content elem - skip + var $E = $(this); + if (!ignore || !$E.is(ignore)) + height -= $E.outerHeight(); + }); + if (height > 0) + height = cssH($C, height); + if (height < 1) + $C.hide(); // no room for content! + else + $C.css({ height: height }).show(); + }); + }; + + + /** + * sizeHandles + * + * Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary + * + * @callers initHandles(), open(), close(), resizeAll() + */ + var sizeHandles = function (panes, onInit) { + if (!panes || panes == "all") panes = c.borderPanes; + + $.each(panes.split(","), function() { + var + pane = str(this) + , o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + ; + if (!$P || !$R || (!o.resizable && !o.closable)) return; // skip + + var + dir = c[pane].dir + , _state = (s.isClosed ? "_closed" : "_open") + , spacing = o["spacing"+ _state] + , togAlign = o["togglerAlign"+ _state] + , togLen = o["togglerLength"+ _state] + , paneLen + , offset + , CSS = {} + ; + if (spacing == 0) { + $R.hide(); + return; + } + else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason + $R.show(); // in case was previously hidden + + // Resizer Bar is ALWAYS same width/height of pane it is attached to + if (dir == "horz") { // north/south + paneLen = $P.outerWidth(); + $R.css({ + width: max(1, cssW($R, paneLen)) // account for borders & padding + , height: max(1, cssH($R, spacing)) // ditto + , left: cssNum($P, "left") + }); + } + else { // east/west + paneLen = $P.outerHeight(); + $R.css({ + height: max(1, cssH($R, paneLen)) // account for borders & padding + , width: max(1, cssW($R, spacing)) // ditto + , top: cDims.top + getPaneSize("north", true) + //, top: cssNum($Ps["center"], "top") + }); + + } + + if ($T) { + if (togLen == 0 || (s.isSliding && o.hideTogglerOnSlide)) { + $T.hide(); // always HIDE the toggler when 'sliding' + return; + } + else + $T.show(); // in case was previously hidden + + if (!(togLen > 0) || togLen == "100%" || togLen > paneLen) { + togLen = paneLen; + offset = 0; + } + else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed + if (typeof togAlign == "string") { + switch (togAlign) { + case "top": + case "left": offset = 0; + break; + case "bottom": + case "right": offset = paneLen - togLen; + break; + case "middle": + case "center": + default: offset = Math.floor((paneLen - togLen) / 2); // 'default' catches typos + } + } + else { // togAlign = number + var x = parseInt(togAlign); // + if (togAlign >= 0) offset = x; + else offset = paneLen - togLen + x; // NOTE: x is negative! + } + } + + var + $TC_o = (o.togglerContent_open ? $T.children(".content-open") : false) + , $TC_c = (o.togglerContent_closed ? $T.children(".content-closed") : false) + , $TC = (s.isClosed ? $TC_c : $TC_o) + ; + if ($TC_o) $TC_o.css("display", s.isClosed ? "none" : "block"); + if ($TC_c) $TC_c.css("display", s.isClosed ? "block" : "none"); + + if (dir == "horz") { // north/south + var width = cssW($T, togLen); + $T.css({ + width: max(0, width) // account for borders & padding + , height: max(1, cssH($T, spacing)) // ditto + , left: offset // TODO: VERIFY that toggler positions correctly for ALL values + }); + if ($TC) // CENTER the toggler content SPAN + $TC.css("marginLeft", Math.floor((width-$TC.outerWidth())/2)); // could be negative + } + else { // east/west + var height = cssH($T, togLen); + $T.css({ + height: max(0, height) // account for borders & padding + , width: max(1, cssW($T, spacing)) // ditto + , top: offset // POSITION the toggler + }); + if ($TC) // CENTER the toggler content SPAN + $TC.css("marginTop", Math.floor((height-$TC.outerHeight())/2)); // could be negative + } + + + } + + // DONE measuring and sizing this resizer/toggler, so can be 'hidden' now + if (onInit && o.initHidden) { + $R.hide(); + if ($T) $T.hide(); + } + }); + }; + + + /** + * resizeAll + * + * @callers window.onresize(), callbacks or custom code + */ + var resizeAll = function () { + var + oldW = cDims.innerWidth + , oldH = cDims.innerHeight + ; + cDims = state.container = getElemDims($Container); // UPDATE container dimensions + + var + checkH = (cDims.innerHeight < oldH) + , checkW = (cDims.innerWidth < oldW) + , s, dir + ; + + if (checkH || checkW) + // NOTE special order for sizing: S-N-E-W + $.each(["south","north","east","west"], function(i,pane) { + s = state[pane]; + dir = c[pane].dir; + if (!s.isClosed && ((checkH && dir=="horz") || (checkW && dir=="vert"))) { + setPaneMinMaxSizes(pane); // update pane-state + // shrink pane if 'too big' to fit + if (s.size > s.maxSize) + sizePane(pane, s.maxSize); + } + }); + + sizeMidPanes("all"); + sizeHandles("all"); // reposition the toggler elements + }; + + + /** + * keyDown + * + * Capture keys when enableCursorHotkey - toggle pane if hotkey pressed + * + * @callers document.keydown() + */ + function keyDown (evt) { + if (!evt) return true; + var code = evt.keyCode; + if (code < 33) return true; // ignore special keys: ENTER, TAB, etc + + var + PANE = { + 38: "north" // Up Cursor + , 40: "south" // Down Cursor + , 37: "west" // Left Cursor + , 39: "east" // Right Cursor + } + , isCursorKey = (code >= 37 && code <= 40) + , ALT = evt.altKey // no worky! + , SHIFT = evt.shiftKey + , CTRL = evt.ctrlKey + , pane = false + , s, o, k, m, el + ; + + if (!CTRL && !SHIFT) + return true; // no modifier key - abort + else if (isCursorKey && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey + pane = PANE[code]; + else // check to see if this matches a custom-hotkey + $.each(c.borderPanes.split(","), function(i,p) { // loop each pane to check its hotkey + o = options[p]; + k = o.customHotkey; + m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT" + if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches + if (k && code == (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches + pane = p; + return false; // BREAK + } + } + }); + + if (!pane) return true; // no hotkey - abort + + // validate pane + o = options[pane]; // get pane options + s = state[pane]; // get pane options + if (!o.enableCursorHotkey || s.isHidden || !$Ps[pane]) return true; + + // see if user is in a 'form field' because may be 'selecting text'! + el = evt.target || evt.srcElement; + if (el && SHIFT && isCursorKey && (el.tagName=="TEXTAREA" || (el.tagName=="INPUT" && (code==37 || code==39)))) + return true; // allow text-selection + + // SYNTAX NOTES + // use "returnValue=false" to abort keystroke but NOT abort function - can run another command afterwards + // use "return false" to abort keystroke AND abort function + toggle(pane); + evt.stopPropagation(); + evt.returnValue = false; // CANCEL key + return false; + }; + + +/* + * ########################### + * UTILITY METHODS + * called externally only + * ########################### + */ + + function allowOverflow (elem) { + if (this && this.tagName) elem = this; // BOUND to element + var $P; + if (typeof elem=="string") + $P = $Ps[elem]; + else { + if ($(elem).attr("pane")) $P = $(elem); + else $P = $(elem).parents("div[pane]:first"); + } + if (!$P.length) return; // INVALID + + var + pane = $P.attr("pane") + , s = state[pane] + ; + + // if pane is already raised, then reset it before doing it again! + // this would happen if allowOverflow is attached to BOTH the pane and an element + if (s.cssSaved) + resetOverflow(pane); // reset previous CSS before continuing + + // if pane is raised by sliding or resizing, or it's closed, then abort + if (s.isSliding || s.isResizing || s.isClosed) { + s.cssSaved = false; + return; + } + + var + newCSS = { zIndex: (c.zIndex.pane_normal + 1) } + , curCSS = {} + , of = $P.css("overflow") + , ofX = $P.css("overflowX") + , ofY = $P.css("overflowY") + ; + // determine which, if any, overflow settings need to be changed + if (of != "visible") { + curCSS.overflow = of; + newCSS.overflow = "visible"; + } + if (ofX && ofX != "visible" && ofX != "auto") { + curCSS.overflowX = ofX; + newCSS.overflowX = "visible"; + } + if (ofY && ofY != "visible" && ofY != "auto") { + curCSS.overflowY = ofX; + newCSS.overflowY = "visible"; + } + + // save the current overflow settings - even if blank! + s.cssSaved = curCSS; + + // apply new CSS to raise zIndex and, if necessary, make overflow 'visible' + $P.css( newCSS ); + + // make sure the zIndex of all other panes is normal + $.each(c.allPanes.split(","), function(i, p) { + if (p != pane) resetOverflow(p); + }); + + }; + + function resetOverflow (elem) { + if (this && this.tagName) elem = this; // BOUND to element + var $P; + if (typeof elem=="string") + $P = $Ps[elem]; + else { + if ($(elem).hasClass("ui-layout-pane")) $P = $(elem); + else $P = $(elem).parents("div[pane]:first"); + } + if (!$P.length) return; // INVALID + + var + pane = $P.attr("pane") + , s = state[pane] + , CSS = s.cssSaved || {} + ; + // reset the zIndex + if (!s.isSliding && !s.isResizing) + $P.css("zIndex", c.zIndex.pane_normal); + + // reset Overflow - if necessary + $P.css( CSS ); + + // clear var + s.cssSaved = false; + }; + + + /** + * getBtn + * + * Helper function to validate params received by addButton utilities + * + * @param String selector jQuery selector for button, eg: ".ui-layout-north .toggle-button" + * @param String pane Name of the pane the button is for: 'north', 'south', etc. + * @returns If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise 'false' + */ + function getBtn(selector, pane, action) { + var + $E = $(selector) + , err = "Error Adding Button \n\nInvalid " + ; + if (!$E.length) // element not found + alert(err+"selector: "+ selector); + else if (c.borderPanes.indexOf(pane) == -1) // invalid 'pane' sepecified + alert(err+"pane: "+ pane); + else { // VALID + var btn = options[pane].buttonClass +"-"+ action; + $E.addClass( btn +" "+ btn +"-"+ pane ); + return $E; + } + return false; // INVALID + }; + + + /** + * addToggleBtn + * + * Add a custom Toggler button for a pane + * + * @param String selector jQuery selector for button, eg: ".ui-layout-north .toggle-button" + * @param String pane Name of the pane the button is for: 'north', 'south', etc. + */ + function addToggleBtn (selector, pane) { + var $E = getBtn(selector, pane, "toggle"); + if ($E) + $E + .attr("title", state[pane].isClosed ? "Open" : "Close") + .click(function (evt) { + toggle(pane); + evt.stopPropagation(); + }) + ; + }; + + /** + * addOpenBtn + * + * Add a custom Open button for a pane + * + * @param String selector jQuery selector for button, eg: ".ui-layout-north .open-button" + * @param String pane Name of the pane the button is for: 'north', 'south', etc. + */ + function addOpenBtn (selector, pane) { + var $E = getBtn(selector, pane, "open"); + if ($E) + $E + .attr("title", "Open") + .click(function (evt) { + open(pane); + evt.stopPropagation(); + }) + ; + }; + + /** + * addCloseBtn + * + * Add a custom Close button for a pane + * + * @param String selector jQuery selector for button, eg: ".ui-layout-north .close-button" + * @param String pane Name of the pane the button is for: 'north', 'south', etc. + */ + function addCloseBtn (selector, pane) { + var $E = getBtn(selector, pane, "close"); + if ($E) + $E + .attr("title", "Close") + .click(function (evt) { + close(pane); + evt.stopPropagation(); + }) + ; + }; + + /** + * addPinBtn + * + * Add a custom Pin button for a pane + * + * Four classes are added to the element, based on the paneClass for the associated pane... + * Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin: + * - ui-layout-pane-pin + * - ui-layout-pane-west-pin + * - ui-layout-pane-pin-up + * - ui-layout-pane-west-pin-up + * + * @param String selector jQuery selector for button, eg: ".ui-layout-north .ui-layout-pin" + * @param String pane Name of the pane the pin is for: 'north', 'south', etc. + */ + function addPinBtn (selector, pane) { + var $E = getBtn(selector, pane, "pin"); + if ($E) { + var s = state[pane]; + $E.click(function (evt) { + setPinState($(this), pane, (s.isSliding || s.isClosed)); + if (s.isSliding || s.isClosed) open( pane ); // change from sliding to open + else close( pane ); // slide-closed + evt.stopPropagation(); + }); + // add up/down pin attributes and classes + setPinState ($E, pane, (!s.isClosed && !s.isSliding)); + // add this pin to the pane data so we can 'sync it' automatically + // PANE.pins key is an array so we can store multiple pins for each pane + c[pane].pins.push( selector ); // just save the selector string + } + }; + + /** + * syncPinBtns + * + * INTERNAL function to sync 'pin buttons' when pane is opened or closed + * Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes + * + * @callers open(), close() + * @params pane These are the params returned to callbacks by layout() + * @params doPin True means set the pin 'down', False means 'up' + */ + function syncPinBtns (pane, doPin) { + $.each(c[pane].pins, function (i, selector) { + setPinState($(selector), pane, doPin); + }); + }; + + /** + * setPinState + * + * Change the class of the pin button to make it look 'up' or 'down' + * + * @callers addPinBtn(), syncPinBtns() + * @param Element $Pin The pin-span element in a jQuery wrapper + * @param Boolean doPin True = set the pin 'down', False = set it 'up' + * @param String pinClass The root classname for pins - will add '-up' or '-down' suffix + */ + function setPinState ($Pin, pane, doPin) { + var updown = $Pin.attr("pin"); + if (updown && doPin == (updown=="down")) return; // already in correct state + var + root = options[pane].buttonClass + , class1 = root +"-pin" + , class2 = class1 +"-"+ pane + , UP1 = class1 + "-up" + , UP2 = class2 + "-up" + , DN1 = class1 + "-down" + , DN2 = class2 + "-down" + ; + $Pin + .attr("pin", doPin ? "down" : "up") // logic + .attr("title", doPin ? "Un-Pin" : "Pin") + .removeClass( doPin ? UP1 : DN1 ) + .removeClass( doPin ? UP2 : DN2 ) + .addClass( doPin ? DN1 : UP1 ) + .addClass( doPin ? DN2 : UP2 ) + ; + }; + + +/* + * ########################### + * CREATE/RETURN BORDER-LAYOUT + * ########################### + */ + + // init global vars + var + $Container = $(this).css({ overflow: "hidden" }) // Container elem + , $Ps = {} // Panes x4 - set in initPanes() + , $Cs = {} // Content x4 - set in initPanes() + , $Rs = {} // Resizers x4 - set in initHandles() + , $Ts = {} // Togglers x4 - set in initHandles() + // object aliases + , c = config // alias for config hash + , cDims = state.container // alias for easy access to 'container dimensions' + ; + + // create the border layout NOW + create(); + + // return object pointers to expose data & option Properties, and primary action Methods + return { + options: options // property - options hash + , state: state // property - dimensions hash + , panes: $Ps // property - object pointers for ALL panes: panes.north, panes.center + , toggle: toggle // method - pass a 'pane' ("north", "west", etc) + , open: open // method - ditto + , close: close // method - ditto + , hide: hide // method - ditto + , show: show // method - ditto + , resizeContent: sizeContent // method - ditto + , sizePane: sizePane // method - pass a 'pane' AND a 'size' in pixels + , resizeAll: resizeAll // method - no parameters + , addToggleBtn: addToggleBtn // utility - pass element selector and 'pane' + , addOpenBtn: addOpenBtn // utility - ditto + , addCloseBtn: addCloseBtn // utility - ditto + , addPinBtn: addPinBtn // utility - ditto + , allowOverflow: allowOverflow // utility - pass calling element + , resetOverflow: resetOverflow // utility - ditto + , cssWidth: cssW + , cssHeight: cssH + }; + +} +})( jQuery ); +\ No newline at end of file diff --git a/jside/jquery/ui/jquery.ui.position.js b/jside/jquery/ui/jquery.ui.position.js @@ -0,0 +1,229 @@ +/* + * jQuery UI Position 1.8 + * + * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Position + */ +(function( $ ) { + +$.ui = $.ui || {}; + +var horizontalPositions = /left|center|right/, + horizontalDefault = "center", + verticalPositions = /top|center|bottom/, + verticalDefault = "center", + _position = $.fn.position, + _offset = $.fn.offset; + +$.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var target = $( options.of ), + collision = ( options.collision || "flip" ).split( " " ), + offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ], + targetWidth, + targetHeight, + basePosition; + + if ( options.of.nodeType === 9 ) { + targetWidth = target.width(); + targetHeight = target.height(); + basePosition = { top: 0, left: 0 }; + } else if ( options.of.scrollTo && options.of.document ) { + targetWidth = target.width(); + targetHeight = target.height(); + basePosition = { top: target.scrollTop(), left: target.scrollLeft() }; + } else if ( options.of.preventDefault ) { + // force left top to allow flipping + options.at = "left top"; + targetWidth = targetHeight = 0; + basePosition = { top: options.of.pageY, left: options.of.pageX }; + } else { + targetWidth = target.outerWidth(); + targetHeight = target.outerHeight(); + basePosition = target.offset(); + } + + // force my and at to have valid horizontal and veritcal positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[this] || "" ).split( " " ); + if ( pos.length === 1) { + pos = horizontalPositions.test( pos[0] ) ? + pos.concat( [verticalDefault] ) : + verticalPositions.test( pos[0] ) ? + [ horizontalDefault ].concat( pos ) : + [ horizontalDefault, verticalDefault ]; + } + pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : horizontalDefault; + pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : verticalDefault; + options[ this ] = pos; + }); + + // normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + // normalize offset option + offset[ 0 ] = parseInt( offset[0], 10 ) || 0; + if ( offset.length === 1 ) { + offset[ 1 ] = offset[ 0 ]; + } + offset[ 1 ] = parseInt( offset[1], 10 ) || 0; + + if ( options.at[0] === "right" ) { + basePosition.left += targetWidth; + } else if (options.at[0] === horizontalDefault ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[1] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[1] === verticalDefault ) { + basePosition.top += targetHeight / 2; + } + + basePosition.left += offset[ 0 ]; + basePosition.top += offset[ 1 ]; + + return this.each(function() { + var elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + position = $.extend( {}, basePosition ); + + if ( options.my[0] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[0] === horizontalDefault ) { + position.left -= elemWidth / 2; + } + + if ( options.my[1] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[1] === verticalDefault ) { + position.top -= elemHeight / 2; + } + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[i] ] ) { + $.ui.position[ collision[i] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + offset: offset, + my: options.my, + at: options.at + }); + } + }); + + if ( $.fn.bgiframe ) { + elem.bgiframe(); + } + elem.offset( $.extend( position, { using: options.using } ) ); + }); +}; + +$.ui.position = { + fit: { + left: function( position, data ) { + var win = $( window ), + over = position.left + data.elemWidth - win.width() - win.scrollLeft(); + position.left = over > 0 ? position.left - over : Math.max( 0, position.left ); + }, + top: function( position, data ) { + var win = $( window ), + over = position.top + data.elemHeight - win.height() - win.scrollTop(); + position.top = over > 0 ? position.top - over : Math.max( 0, position.top ); + } + }, + + flip: { + left: function( position, data ) { + if ( data.at[0] === "center" ) { + return; + } + var win = $( window ), + over = position.left + data.elemWidth - win.width() - win.scrollLeft(), + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + offset = -2 * data.offset[ 0 ]; + position.left += position.left < 0 ? + myOffset + data.targetWidth + offset : + over > 0 ? + myOffset - data.targetWidth + offset : + 0; + }, + top: function( position, data ) { + if ( data.at[1] === "center" ) { + return; + } + var win = $( window ), + over = position.top + data.elemHeight - win.height() - win.scrollTop(), + myOffset = data.my[ 1 ] === "top" ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + -data.targetHeight, + offset = -2 * data.offset[ 1 ]; + position.top += position.top < 0 ? + myOffset + data.targetHeight + offset : + over > 0 ? + myOffset + atOffset + offset : + 0; + } + } +}; + +// offset setter from jQuery 1.4 +if ( !$.offset.setOffset ) { + $.offset.setOffset = function( elem, options ) { + // set position first, in-case top/left are set even on static elem + if ( /static/.test( $.curCSS( elem, "position" ) ) ) { + elem.style.position = "relative"; + } + var curElem = $( elem ), + curOffset = curElem.offset(), + curTop = parseInt( $.curCSS( elem, "top", true ), 10 ) || 0, + curLeft = parseInt( $.curCSS( elem, "left", true ), 10) || 0, + props = { + top: (options.top - curOffset.top) + curTop, + left: (options.left - curOffset.left) + curLeft + }; + + if ( 'using' in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + }; + + $.fn.offset = function( options ) { + var elem = this[ 0 ]; + if ( !elem || !elem.ownerDocument ) { return null; } + if ( options ) { + return this.each(function() { + $.offset.setOffset( this, options ); + }); + } + return _offset.call( this ); + }; +} + +}( jQuery )); diff --git a/jside/style.css b/jside/style.css @@ -184,6 +184,36 @@ body { color: green; } +/* Liens */ +.lien { + background-color: lightblue; + position: absolute; + height: 5px; + width: 5px; +} + +.port .symbole { + width: 10px; + height: 10px; + background-color: lightblue; + border: thin solid blue; + /* z-index: 2000; */ +} + +.port:hover .symbole { + background-color: lightgreen; + border-color: green; +} + + +/*.segment-1 { + background-color: lightgreen; +} + +.segment-3 { + background-color: pink; +}*/ + /* Log */ diff --git a/jside/style2.css b/jside/style2.css @@ -1,58 +0,0 @@ -body { - margin: 0; -} - -#wrapper { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - border: thick solid blue; -} - -table#top { - height: 100%; - width: 100%; - border: thin solid black; -} - -td { - border: thin solid gray; -} - -#tr-log-en-tete { - height: 1em; -} - -#log-en-tete { - border: thin solid red; - height: 1em; - overflow: auto; -} - -.bouton { - display: inline-block; - padding: 0.1em 0.2em; - margin: 0.2em; - text-decoration: none; - border-width: thin; - border-style: solid; -} - -.tete { - margin: 0.2em; - padding: 0.3em; - background-color: lightgray; -} - -#log .tete h2 { - display: inline; - font-size: 100%; - margin-right : 1em; -} - -#log { - height: 20%; - overflow: auto; -} -\ No newline at end of file diff --git a/jside/style3.css b/jside/style3.css @@ -1,198 +0,0 @@ -/* Mise en page */ - -html, body, #hauteur-max { - height:100%; - width: 100%; - margin: 0; - padding: 0; -} - -#hauteur-max { - border-collapse: collapse; -} - -#hauteur-max > tbody > tr { - height:1em; - margin: 0; - padding: 0; -} - -#hauteur-max > tbody > tr > td { - vertical-align: top; - margin: 0; - padding: 0; -} - -#hauteur-max > tbody > tr#elastique { - height: auto; -} - -#hauteur-max > tbody > tr > td#td-log-contenu{ - height:10em; /* On ne peut pas mettre 20% ici… */ -} - -.log .contenu { - height: 100%; - overflow: auto; -} - - - -/* Tous */ - -.bouton { - display: inline-block; - margin: 0.2em; - text-decoration: none; - border-width: thin; - border-style: solid; -} - -.bouton { - color: #333; - border-color: #999; - background-color: #eee -} - -.bouton:hover { - color: #555; - border-color: #ccc; - background-color: #fff -} - -/* Icônes */ -.icone { - display: block; - font-weight: bolder; - color: black; - text-align: center; - min-width: 1em; -} - -.icone-plus:before { - content: "+"; -} - -.icone-moins:before { - content: "-"; -} - - - -/* Outils */ - -#outils .bouton { - color: #242; - border-color: #8a8; - background-color: #efd; -} - -#outils .boutonNormal { - color: #242; - border-color: #8a8; - background-color: #efd; -} - -#outils .bouton:hover { - color: #422; - border-color: #a88; - background-color: #fed; -} - -#outils .boutonHover { - color: #422; - border-color: #a88; - background-color: #fed; -} - - - -/* Zone principale */ - -#elastique > td { - border-top: thick solid gray; - border-bottom: thick solid gray; -} - -#edition { - border: thin solid green; - height: 100%; -} - -/* Résultats de la recherche */ - -#resultats-recherche { - width: 100%; - border-collapse: collapse; -} - -#resultats-recherche thead th { - border-bottom: thin solid black; -} - -#resultats-recherche thead tr { -} - -#resultats-recherche tbody tr:first-child td { - /*padding-top: 0.2em;*/ -} - -#resultats-recherche td { - border-bottom: thin solid lightgray; -} - -#resultats-recherche th { - text-align: left; -} - -/* Édition des blocs */ -#edition-blocs > * { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; -} - -.bloc { - border: medium solid gray; - position: absolute; - width: 25%; - height: 30%; - top: 10%; - left: 20%; -} - -.bloc .tete { - margin: 0.2em; - padding: 0.3em; - background-color: lightgray; -} - -.bloc .tete .icone { - float: right; -} - -.bloc .contenu { - padding: 0.4em; -} - - - -/* Log */ - -.log .tete { - padding: 0 0.3em; -} - -.log h2 { - display: inline; - font-size: 100%; - margin-right : 1em; -} - -.log p { - border-bottom: thin solid lightgray; - padding: 0.7em 0.3em 0.3em; - margin: 0; -}