diff options
author | mensonge <mensonge@b3834d28-1941-0410-a4f8-b48e95affb8f> | 2008-11-13 09:49:11 +0000 |
---|---|---|
committer | mensonge <mensonge@b3834d28-1941-0410-a4f8-b48e95affb8f> | 2008-11-13 09:49:11 +0000 |
commit | e44a7e37b6c7b5961adaffc62b9042b8d442938e (patch) | |
tree | 95b67c356e93163467db2451f2b8cce84ed5d582 /includes/js/dijit/layout | |
parent | a62b9742ee5e28bcec6872d88f50f25b820914f6 (diff) | |
download | semanticscuttle-e44a7e37b6c7b5961adaffc62b9042b8d442938e.tar.gz semanticscuttle-e44a7e37b6c7b5961adaffc62b9042b8d442938e.tar.bz2 |
New feature: basic Ajax suggestion for tags and implementation of Dojo toolkit
git-svn-id: https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/trunk@151 b3834d28-1941-0410-a4f8-b48e95affb8f
Diffstat (limited to 'includes/js/dijit/layout')
-rw-r--r-- | includes/js/dijit/layout/AccordionContainer.js | 229 | ||||
-rw-r--r-- | includes/js/dijit/layout/BorderContainer.js | 515 | ||||
-rw-r--r-- | includes/js/dijit/layout/ContentPane.js | 445 | ||||
-rw-r--r-- | includes/js/dijit/layout/LayoutContainer.js | 74 | ||||
-rw-r--r-- | includes/js/dijit/layout/LinkPane.js | 36 | ||||
-rw-r--r-- | includes/js/dijit/layout/SplitContainer.js | 553 | ||||
-rw-r--r-- | includes/js/dijit/layout/StackContainer.js | 493 | ||||
-rw-r--r-- | includes/js/dijit/layout/TabContainer.js | 184 | ||||
-rw-r--r-- | includes/js/dijit/layout/_LayoutWidget.js | 188 | ||||
-rw-r--r-- | includes/js/dijit/layout/templates/AccordionPane.html | 11 | ||||
-rw-r--r-- | includes/js/dijit/layout/templates/TabContainer.html | 4 | ||||
-rw-r--r-- | includes/js/dijit/layout/templates/TooltipDialog.html | 6 | ||||
-rw-r--r-- | includes/js/dijit/layout/templates/_TabButton.html | 10 |
13 files changed, 2748 insertions, 0 deletions
diff --git a/includes/js/dijit/layout/AccordionContainer.js b/includes/js/dijit/layout/AccordionContainer.js new file mode 100644 index 0000000..12f8945 --- /dev/null +++ b/includes/js/dijit/layout/AccordionContainer.js @@ -0,0 +1,229 @@ +if(!dojo._hasResource["dijit.layout.AccordionContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.AccordionContainer"] = true; +dojo.provide("dijit.layout.AccordionContainer"); + +dojo.require("dojo.fx"); + +dojo.require("dijit._Container"); +dojo.require("dijit._Templated"); +dojo.require("dijit.layout.StackContainer"); +dojo.require("dijit.layout.ContentPane"); + +dojo.declare( + "dijit.layout.AccordionContainer", + dijit.layout.StackContainer, + { + // summary: + // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time, + // and switching between panes is visualized by sliding the other panes up/down. + // example: + // | <div dojoType="dijit.layout.AccordionContainer"> + // | <div dojoType="dijit.layout.AccordionPane" title="pane 1"> + // | <div dojoType="dijit.layout.ContentPane">...</div> + // | </div> + // | <div dojoType="dijit.layout.AccordionPane" title="pane 2"> + // | <p>This is some text</p> + // || ... + // | </div> + // + // duration: Integer + // Amount of time (in ms) it takes to slide panes + duration: 250, + + _verticalSpace: 0, + + postCreate: function(){ + this.domNode.style.overflow="hidden"; + this.inherited("postCreate",arguments); + dijit.setWaiRole(this.domNode, "tablist"); + dojo.addClass(this.domNode,"dijitAccordionContainer"); + }, + + startup: function(){ + if(this._started){ return; } + this.inherited("startup",arguments); + if(this.selectedChildWidget){ + var style = this.selectedChildWidget.containerNode.style; + style.display = ""; + style.overflow = "auto"; + this.selectedChildWidget._setSelectedState(true); + } + }, + + layout: function(){ + // summary: + // Set the height of the open pane based on what room remains + + // get cumulative height of all the title bars, and figure out which pane is open + var totalCollapsedHeight = 0; + var openPane = this.selectedChildWidget; + dojo.forEach(this.getChildren(), function(child){ + totalCollapsedHeight += child.getTitleHeight(); + }); + var mySize = this._contentBox; + this._verticalSpace = (mySize.h - totalCollapsedHeight); + if(openPane){ + openPane.containerNode.style.height = this._verticalSpace + "px"; +/*** +TODO: this is wrong. probably you wanted to call resize on the SplitContainer +inside the AccordionPane?? + if(openPane.resize){ + openPane.resize({h: this._verticalSpace}); + } +***/ + } + }, + + _setupChild: function(/*Widget*/ page){ + // Summary: prepare the given child + return page; + }, + + _transition: function(/*Widget?*/newWidget, /*Widget?*/oldWidget){ +//TODO: should be able to replace this with calls to slideIn/slideOut + if(this._inTransition){ return; } + this._inTransition = true; + var animations = []; + var paneHeight = this._verticalSpace; + if(newWidget){ + newWidget.setSelected(true); + var newContents = newWidget.containerNode; + newContents.style.display = ""; + + animations.push(dojo.animateProperty({ + node: newContents, + duration: this.duration, + properties: { + height: { start: "1", end: paneHeight } + }, + onEnd: function(){ + newContents.style.overflow = "auto"; + } + })); + } + if(oldWidget){ + oldWidget.setSelected(false); + var oldContents = oldWidget.containerNode; + oldContents.style.overflow = "hidden"; + animations.push(dojo.animateProperty({ + node: oldContents, + duration: this.duration, + properties: { + height: { start: paneHeight, end: "1" } + }, + onEnd: function(){ + oldContents.style.display = "none"; + } + })); + } + + this._inTransition = false; + + dojo.fx.combine(animations).play(); + }, + + // note: we are treating the container as controller here + _onKeyPress: function(/*Event*/ e){ + if(this.disabled || e.altKey || !(e._dijitWidget || e.ctrlKey)){ return; } + var k = dojo.keys; + var fromTitle = e._dijitWidget; + switch(e.keyCode){ + case k.LEFT_ARROW: + case k.UP_ARROW: + if (fromTitle){ + this._adjacent(false)._onTitleClick(); + dojo.stopEvent(e); + } + break; + case k.PAGE_UP: + if (e.ctrlKey){ + this._adjacent(false)._onTitleClick(); + dojo.stopEvent(e); + } + break; + case k.RIGHT_ARROW: + case k.DOWN_ARROW: + if (fromTitle){ + this._adjacent(true)._onTitleClick(); + dojo.stopEvent(e); + } + break; + case k.PAGE_DOWN: + if (e.ctrlKey){ + this._adjacent(true)._onTitleClick(); + dojo.stopEvent(e); + } + break; + default: + if(e.ctrlKey && e.keyCode == k.TAB){ + this._adjacent(e._dijitWidget, !e.shiftKey)._onTitleClick(); + dojo.stopEvent(e); + } + + } + } + } +); + +dojo.declare("dijit.layout.AccordionPane", + [dijit.layout.ContentPane, dijit._Templated, dijit._Contained], + { + // summary: + // AccordionPane is a ContentPane with a title that may contain another widget. + // Nested layout widgets, such as SplitContainer, are not supported at this time. + // example: + // | see dijit.layout.AccordionContainer + + templateString:"<div class='dijitAccordionPane'\n\t><div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='ondijitclick:_onTitleClick,onkeypress:_onTitleKeyPress,onfocus:_handleFocus,onblur:_handleFocus'\n\t\tclass='dijitAccordionTitle' wairole=\"tab\"\n\t\t><div class='dijitAccordionArrow' waiRole=\"presentation\"></div\n\t\t><div class='arrowTextUp' waiRole=\"presentation\">▲</div\n\t\t><div class='arrowTextDown' waiRole=\"presentation\">▼</div\n\t\t><div waiRole=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'>${title}</div></div\n\t><div><div dojoAttachPoint='containerNode' style='overflow: hidden; height: 1px; display: none'\n\t\tclass='dijitAccordionBody' wairole=\"tabpanel\"\n\t></div></div>\n</div>\n", + + postCreate: function(){ + this.inherited("postCreate",arguments) + dojo.setSelectable(this.titleNode, false); + this.setSelected(this.selected); + }, + + getTitleHeight: function(){ + // summary: returns the height of the title dom node + return dojo.marginBox(this.titleNode).h; // Integer + }, + + _onTitleClick: function(){ + // summary: callback when someone clicks my title + var parent = this.getParent(); + if(!parent._inTransition){ + parent.selectChild(this); + dijit.focus(this.focusNode); + } + }, + + _onTitleKeyPress: function(/*Event*/ evt){ + evt._dijitWidget = this; + return this.getParent()._onKeyPress(evt); + }, + + _setSelectedState: function(/*Boolean*/ isSelected){ + this.selected = isSelected; + dojo[(isSelected ? "addClass" : "removeClass")](this.titleNode,"dijitAccordionTitle-selected"); + this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1"); + }, + + _handleFocus: function(/*Event*/e){ + // summary: handle the blur and focus state of this widget + dojo[(e.type=="focus" ? "addClass" : "removeClass")](this.focusNode,"dijitAccordionFocused"); + }, + + setSelected: function(/*Boolean*/ isSelected){ + // summary: change the selected state on this pane + this._setSelectedState(isSelected); + if(isSelected){ + this.onSelected(); + this._loadCheck(true); // if href specified, trigger load + } + }, + + onSelected: function(){ + // summary: called when this pane is selected + } +}); + +} diff --git a/includes/js/dijit/layout/BorderContainer.js b/includes/js/dijit/layout/BorderContainer.js new file mode 100644 index 0000000..4419fe4 --- /dev/null +++ b/includes/js/dijit/layout/BorderContainer.js @@ -0,0 +1,515 @@ +if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.BorderContainer"] = true; +dojo.provide("dijit.layout.BorderContainer"); + +dojo.require("dijit.layout._LayoutWidget"); +dojo.require("dojo.cookie"); + +dojo.declare( + "dijit.layout.BorderContainer", +// [dijit._Widget, dijit._Container, dijit._Contained], + dijit.layout._LayoutWidget, +{ + // summary: + // Provides layout in 5 regions, a center and borders along its 4 sides. + // + // description: + // A BorderContainer is a box with a specified size (like style="width: 500px; height: 500px;"), + // that contains a child widget marked region="center" and optionally children widgets marked + // region equal to "top", "bottom", "leading", "trailing", "left" or "right". + // Children along the edges will be laid out according to width or height dimensions. The remaining + // space is designated for the center region. + // The outer size must be specified on the BorderContainer node. Width must be specified for the sides + // and height for the top and bottom, respectively. No dimensions should be specified on the center; + // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like + // "left" and "right" except that they will be reversed in right-to-left environments. + // Optional splitters may be specified on the edge widgets only to make them resizable by the user. + // + // example: + // | <style> + // | html, body { height: 100%; width: 100%; } + // | </style> + // | <div dojoType="BorderContainer" design="sidebar" style="width: 100%; height: 100%"> + // | <div dojoType="ContentPane" region="top">header text</div> + // | <div dojoType="ContentPane" region="right" style="width: 200px;">table of contents</div> + // | <div dojoType="ContentPane" region="center">client area</div> + // | </div> + // + // design: String + // choose which design is used for the layout: "headline" (default) where the top and bottom extend + // the full width of the container, or "sidebar" where the left and right sides extend from top to bottom. + design: "headline", + + // liveSplitters: Boolean + // specifies whether splitters resize as you drag (true) or only upon mouseup (false) + liveSplitters: true, + + // persist: Boolean + // Save splitter positions in a cookie. + persist: false, // Boolean + + // _splitterClass: String + // Optional hook to override the default Splitter widget used by BorderContainer + _splitterClass: "dijit.layout._Splitter", + + postCreate: function(){ + this.inherited(arguments); + + this._splitters = {}; + this._splitterThickness = {}; + dojo.addClass(this.domNode, "dijitBorderContainer"); + }, + + startup: function(){ + if(this._started){ return; } + dojo.forEach(this.getChildren(), this._setupChild, this); + this.inherited(arguments); + }, + + _setupChild: function(/*Widget*/child){ + var region = child.region; + if(region){ +// dojo.addClass(child.domNode, "dijitBorderContainerPane"); + child.domNode.style.position = "absolute"; // bill says not to set this in CSS, since we can't keep others + // from destroying the class list + + var ltr = this.isLeftToRight(); + if(region == "leading"){ region = ltr ? "left" : "right"; } + if(region == "trailing"){ region = ltr ? "right" : "left"; } + + this["_"+region] = child.domNode; + this["_"+region+"Widget"] = child; + + if(child.splitter){ + var _Splitter = dojo.getObject(this._splitterClass); + var flip = {left:'right', right:'left', top:'bottom', bottom:'top', leading:'trailing', trailing:'leading'}; + var oppNodeList = dojo.query('[region=' + flip[child.region] + ']', this.domNode); + var splitter = new _Splitter({ container: this, child: child, region: region, + oppNode: oppNodeList[0], live: this.liveSplitters }); + this._splitters[region] = splitter.domNode; + dojo.place(splitter.domNode, child.domNode, "after"); + this._computeSplitterThickness(region); + } + child.region = region; + } + }, + + _computeSplitterThickness: function(region){ + var re = new RegExp("top|bottom"); + this._splitterThickness[region] = + dojo.marginBox(this._splitters[region])[(re.test(region) ? 'h' : 'w')]; + }, + + layout: function(){ + this._layoutChildren(); + }, + + addChild: function(/*Widget*/ child, /*Integer?*/ insertIndex){ + this.inherited(arguments); + this._setupChild(child); + if(this._started){ + this._layoutChildren(); //OPT + } + }, + + removeChild: function(/*Widget*/ child){ + var region = child.region; + var splitter = this._splitters[region]; + if(splitter){ + dijit.byNode(splitter).destroy(); + delete this._splitters[region]; + delete this._splitterThickness[region]; + } + this.inherited(arguments); + delete this["_"+region]; + delete this["_" +region+"Widget"]; + if(this._started){ + this._layoutChildren(child.region); + } + }, + + _layoutChildren: function(/*String?*/changedRegion){ + var sidebarLayout = (this.design == "sidebar"); + var topHeight = 0, bottomHeight = 0, leftWidth = 0, rightWidth = 0; + var topStyle = {}, leftStyle = {}, rightStyle = {}, bottomStyle = {}, + centerStyle = (this._center && this._center.style) || {}; + + var changedSide = /left|right/.test(changedRegion); + + var layoutSides = !changedRegion || (!changedSide && !sidebarLayout); + var layoutTopBottom = !changedRegion || (changedSide && sidebarLayout); + if(this._top){ + topStyle = layoutTopBottom && this._top.style; + topHeight = dojo.marginBox(this._top).h; + } + if(this._left){ + leftStyle = layoutSides && this._left.style; + leftWidth = dojo.marginBox(this._left).w; + } + if(this._right){ + rightStyle = layoutSides && this._right.style; + rightWidth = dojo.marginBox(this._right).w; + } + if(this._bottom){ + bottomStyle = layoutTopBottom && this._bottom.style; + bottomHeight = dojo.marginBox(this._bottom).h; + } + + var splitters = this._splitters; + var topSplitter = splitters.top; + var bottomSplitter = splitters.bottom; + var leftSplitter = splitters.left; + var rightSplitter = splitters.right; + var splitterThickness = this._splitterThickness; + var topSplitterThickness = splitterThickness.top || 0; + var leftSplitterThickness = splitterThickness.left || 0; + var rightSplitterThickness = splitterThickness.right || 0; + var bottomSplitterThickness = splitterThickness.bottom || 0; + + // Check for race condition where CSS hasn't finished loading, so + // the splitter width == the viewport width (#5824) + if(leftSplitterThickness > 50 || rightSplitterThickness > 50){ + setTimeout(dojo.hitch(this, function(){ + for(var region in this._splitters){ + this._computeSplitterThickness(region); + } + this._layoutChildren(); + }), 50); + return false; + } + + var splitterBounds = { + left: (sidebarLayout ? leftWidth + leftSplitterThickness: "0") + "px", + right: (sidebarLayout ? rightWidth + rightSplitterThickness: "0") + "px" + }; + + if(topSplitter){ + dojo.mixin(topSplitter.style, splitterBounds); + topSplitter.style.top = topHeight + "px"; + } + + if(bottomSplitter){ + dojo.mixin(bottomSplitter.style, splitterBounds); + bottomSplitter.style.bottom = bottomHeight + "px"; + } + + splitterBounds = { + top: (sidebarLayout ? "0" : topHeight + topSplitterThickness) + "px", + bottom: (sidebarLayout ? "0" : bottomHeight + bottomSplitterThickness) + "px" + }; + + if(leftSplitter){ + dojo.mixin(leftSplitter.style, splitterBounds); + leftSplitter.style.left = leftWidth + "px"; + } + + if(rightSplitter){ + dojo.mixin(rightSplitter.style, splitterBounds); + rightSplitter.style.right = rightWidth + "px"; + } + + dojo.mixin(centerStyle, { + top: topHeight + topSplitterThickness + "px", + left: leftWidth + leftSplitterThickness + "px", + right: rightWidth + rightSplitterThickness + "px", + bottom: bottomHeight + bottomSplitterThickness + "px" + }); + + var bounds = { + top: sidebarLayout ? "0" : centerStyle.top, + bottom: sidebarLayout ? "0" : centerStyle.bottom + }; + dojo.mixin(leftStyle, bounds); + dojo.mixin(rightStyle, bounds); + leftStyle.left = rightStyle.right = topStyle.top = bottomStyle.bottom = "0"; + if(sidebarLayout){ + topStyle.left = bottomStyle.left = leftWidth + (this.isLeftToRight() ? leftSplitterThickness : 0) + "px"; + topStyle.right = bottomStyle.right = rightWidth + (this.isLeftToRight() ? 0 : rightSplitterThickness) + "px"; + }else{ + topStyle.left = topStyle.right = bottomStyle.left = bottomStyle.right = "0"; + } + + // Nodes in IE respond to t/l/b/r, and TEXTAREA doesn't respond in any browser + var janky = dojo.isIE || dojo.some(this.getChildren(), function(child){ + return child.domNode.tagName == "TEXTAREA"; + }); + if(janky){ + // Set the size of the children the old fashioned way, by calling + // childNode.resize({h: int, w: int}) for each child node) + + var borderBox = function(n, b){ + n=dojo.byId(n); + var s = dojo.getComputedStyle(n); + if(!b){ return dojo._getBorderBox(n, s); } + var me = dojo._getMarginExtents(n, s); + dojo._setMarginBox(n, b.l, b.t, b.w + me.w, b.h + me.h, s); + return null; + }; + + var resizeWidget = function(widget, dim){ + if(widget){ + widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim); + } + }; + + // TODO: use dim passed in to resize() (see _LayoutWidget.js resize()) + // Then can make borderBox setBorderBox(), since no longer need to ever get the borderBox() size + var thisBorderBox = borderBox(this.domNode); + + var containerHeight = thisBorderBox.h; + var middleHeight = containerHeight; + if(this._top){ middleHeight -= topHeight; } + if(this._bottom){ middleHeight -= bottomHeight; } + if(topSplitter){ middleHeight -= topSplitterThickness; } + if(bottomSplitter){ middleHeight -= bottomSplitterThickness; } + var centerDim = { h: middleHeight }; + + var sidebarHeight = sidebarLayout ? containerHeight : middleHeight; + if(leftSplitter){ leftSplitter.style.height = sidebarHeight; } + if(rightSplitter){ rightSplitter.style.height = sidebarHeight; } + resizeWidget(this._leftWidget, {h: sidebarHeight}); + resizeWidget(this._rightWidget, {h: sidebarHeight}); + + var containerWidth = thisBorderBox.w; + var middleWidth = containerWidth; + if(this._left){ middleWidth -= leftWidth; } + if(this._right){ middleWidth -= rightWidth; } + if(leftSplitter){ middleWidth -= leftSplitterThickness; } + if(rightSplitter){ middleWidth -= rightSplitterThickness; } + centerDim.w = middleWidth; + + var sidebarWidth = sidebarLayout ? middleWidth : containerWidth; + if(topSplitter){ topSplitter.style.width = sidebarWidth; } + if(bottomSplitter){ bottomSplitter.style.width = sidebarWidth; } + resizeWidget(this._topWidget, {w: sidebarWidth}); + resizeWidget(this._bottomWidget, {w: sidebarWidth}); + + resizeWidget(this._centerWidget, centerDim); + }else{ + + // We've already sized the children by setting style.top/bottom/left/right... + // Now just need to call resize() on those children so they can re-layout themselves + + // TODO: calling child.resize() without an argument is bad, because it forces + // the child to query it's own size (even though this function already knows + // the size), plus which querying the size of a node right after setting it + // is known to cause problems (incorrect answer or an exception). + // This is a setback from older layout widgets, which + // don't do that. See #3399, #2678, #3624 and #2955, #1988 + + var resizeList = {}; + if(changedRegion){ + resizeList[changedRegion] = resizeList.center = true; + if(/top|bottom/.test(changedRegion) && this.design != "sidebar"){ + resizeList.left = resizeList.right = true; + }else if(/left|right/.test(changedRegion) && this.design == "sidebar"){ + resizeList.top = resizeList.bottom = true; + } + } + + dojo.forEach(this.getChildren(), function(child){ + if(child.resize && (!changedRegion || child.region in resizeList)){ + // console.log(this.id, ": resizing child id=" + child.id + " (region=" + child.region + "), style before resize is " + + // "{ t: " + child.domNode.style.top + + // ", b: " + child.domNode.style.bottom + + // ", l: " + child.domNode.style.left + + // ", r: " + child.domNode.style.right + + // ", w: " + child.domNode.style.width + + // ", h: " + child.domNode.style.height + + // "}" + // ); + child.resize(); + // console.log(this.id, ": after resize of child id=" + child.id + " (region=" + child.region + ") " + + // "{ t: " + child.domNode.style.top + + // ", b: " + child.domNode.style.bottom + + // ", l: " + child.domNode.style.left + + // ", r: " + child.domNode.style.right + + // ", w: " + child.domNode.style.width + + // ", h: " + child.domNode.style.height + + // "}" + // ); + } + }, this); + } + } +}); + +// This argument can be specified for the children of a BorderContainer. +// Since any widget can be specified as a LayoutContainer child, mix it +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // region: String + // "top", "bottom", "leading", "trailing", "left", "right", "center". + // See the BorderContainer description for details on this parameter. + region: '', + + // splitter: Boolean + splitter: false, + + // minSize: Number + minSize: 0, + + // maxSize: Number + maxSize: Infinity +}); + +dojo.require("dijit._Templated"); + +dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ], +{ +/*===== + container: null, + child: null, + region: null, +=====*/ + + // live: Boolean + // If true, the child's size changes and the child widget is redrawn as you drag the splitter; + // otherwise, the size doesn't change until you drop the splitter (by mouse-up) + live: true, + + // summary: A draggable spacer between two items in a BorderContainer + templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag" tabIndex="0" waiRole="separator"><div class="dijitSplitterThumb"></div></div>', + + postCreate: function(){ + this.inherited(arguments); + this.horizontal = /top|bottom/.test(this.region); + dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V")); +// dojo.addClass(this.child.domNode, "dijitSplitterPane"); +// dojo.setSelectable(this.domNode, false); //TODO is this necessary? + + this._factor = /top|left/.test(this.region) ? 1 : -1; + this._minSize = this.child.minSize; + + this._computeMaxSize(); + //TODO: might be more accurate to recompute constraints on resize? + this.connect(this.container, "layout", dojo.hitch(this, this._computeMaxSize)); + + this._cookieName = this.container.id + "_" + this.region; + if(this.container.persist){ + // restore old size + var persistSize = dojo.cookie(this._cookieName); + if(persistSize){ + this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize; + } + } + }, + + _computeMaxSize: function(){ + var dim = this.horizontal ? 'h' : 'w'; + var available = dojo.contentBox(this.container.domNode)[dim] - (this.oppNode ? dojo.marginBox(this.oppNode)[dim] : 0); + this._maxSize = Math.min(this.child.maxSize, available); + }, + + _startDrag: function(e){ + if(!this.cover){ + this.cover = dojo.doc.createElement('div'); + dojo.addClass(this.cover, "dijitSplitterCover"); + dojo.place(this.cover, this.child.domNode, "after"); + }else{ + this.cover.style.zIndex = 1; + } + + // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up. + if(this.fake){ dojo._destroyElement(this.fake); } + if(!(this._resize = this.live)){ //TODO: disable live for IE6? + // create fake splitter to display at old position while we drag + (this.fake = this.domNode.cloneNode(true)).removeAttribute("id"); + dojo.addClass(this.domNode, "dijitSplitterShadow"); + dojo.place(this.fake, this.domNode, "after"); + } + dojo.addClass(this.domNode, "dijitSplitterActive"); + + //Performance: load data info local vars for onmousevent function closure + var factor = this._factor, + max = this._maxSize, + min = this._minSize || 10; + var axis = this.horizontal ? "pageY" : "pageX"; + var pageStart = e[axis]; + var splitterStyle = this.domNode.style; + var dim = this.horizontal ? 'h' : 'w'; + var childStart = dojo.marginBox(this.child.domNode)[dim]; + var splitterStart = parseInt(this.domNode.style[this.region]); + var resize = this._resize; + var region = this.region; + var mb = {}; + var childNode = this.child.domNode; + var layoutFunc = dojo.hitch(this.container, this.container._layoutChildren); + + var de = dojo.doc.body; + this._handlers = (this._handlers || []).concat([ + dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){ + var delta = e[axis] - pageStart, + childSize = factor * delta + childStart, + boundChildSize = Math.max(Math.min(childSize, max), min); + + if(resize || forceResize){ + mb[dim] = boundChildSize; + // TODO: inefficient; we set the marginBox here and then immediately layoutFunc() needs to query it + dojo.marginBox(childNode, mb); + layoutFunc(region); + } + splitterStyle[region] = factor * delta + splitterStart + (boundChildSize - childSize) + "px"; + }), + dojo.connect(de, "onmouseup", this, "_stopDrag") + ]); + dojo.stopEvent(e); + }, + + _stopDrag: function(e){ + try{ + if(this.cover){ this.cover.style.zIndex = -1; } + if(this.fake){ dojo._destroyElement(this.fake); } + dojo.removeClass(this.domNode, "dijitSplitterActive"); + dojo.removeClass(this.domNode, "dijitSplitterShadow"); + this._drag(e); //TODO: redundant with onmousemove? + this._drag(e, true); + }finally{ + this._cleanupHandlers(); + delete this._drag; + } + + if(this.container.persist){ + dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"]); + } + }, + + _cleanupHandlers: function(){ + dojo.forEach(this._handlers, dojo.disconnect); + delete this._handlers; + }, + + _onKeyPress: function(/*Event*/ e){ + // should we apply typematic to this? + this._resize = true; + var horizontal = this.horizontal; + var tick = 1; + var dk = dojo.keys; + switch(e.keyCode){ + case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW: + tick *= -1; + break; + case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW: + break; + default: +// this.inherited(arguments); + return; + } + var childSize = dojo.marginBox(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick; + var mb = {}; + mb[ this.horizontal ? "h" : "w"] = Math.max(Math.min(childSize, this._maxSize), this._minSize); + dojo.marginBox(this.child.domNode, mb); + this.container._layoutChildren(this.region); + dojo.stopEvent(e); + }, + + destroy: function(){ + this._cleanupHandlers(); + delete this.child; + delete this.container; + delete this.fake; + this.inherited(arguments); + } +}); + +} diff --git a/includes/js/dijit/layout/ContentPane.js b/includes/js/dijit/layout/ContentPane.js new file mode 100644 index 0000000..c3a4ca4 --- /dev/null +++ b/includes/js/dijit/layout/ContentPane.js @@ -0,0 +1,445 @@ +if(!dojo._hasResource["dijit.layout.ContentPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.ContentPane"] = true; +dojo.provide("dijit.layout.ContentPane"); + +dojo.require("dijit._Widget"); +dojo.require("dijit.layout._LayoutWidget"); + +dojo.require("dojo.parser"); +dojo.require("dojo.string"); +dojo.requireLocalization("dijit", "loading", null, "zh,pt,da,tr,ru,ROOT,de,sv,ja,he,fi,nb,el,ar,pt-pt,cs,fr,es,ko,nl,zh-tw,pl,it,hu"); + +dojo.declare( + "dijit.layout.ContentPane", + dijit._Widget, +{ + // summary: + // A widget that acts as a Container for other widgets, and includes a ajax interface + // description: + // A widget that can be used as a standalone widget + // or as a baseclass for other widgets + // Handles replacement of document fragment using either external uri or javascript + // generated markup or DOM content, instantiating widgets within that content. + // Don't confuse it with an iframe, it only needs/wants document fragments. + // It's useful as a child of LayoutContainer, SplitContainer, or TabContainer. + // But note that those classes can contain any widget as a child. + // example: + // Some quick samples: + // To change the innerHTML use .setContent('<b>new content</b>') + // + // Or you can send it a NodeList, .setContent(dojo.query('div [class=selected]', userSelection)) + // please note that the nodes in NodeList will copied, not moved + // + // To do a ajax update use .setHref('url') + // + // href: String + // The href of the content that displays now. + // Set this at construction if you want to load data externally when the + // pane is shown. (Set preload=true to load it immediately.) + // Changing href after creation doesn't have any effect; see setHref(); + href: "", + + // extractContent: Boolean + // Extract visible content from inside of <body> .... </body> + extractContent: false, + + // parseOnLoad: Boolean + // parse content and create the widgets, if any + parseOnLoad: true, + + // preventCache: Boolean + // Cache content retreived externally + preventCache: false, + + // preload: Boolean + // Force load of data even if pane is hidden. + preload: false, + + // refreshOnShow: Boolean + // Refresh (re-download) content when pane goes from hidden to shown + refreshOnShow: false, + + // loadingMessage: String + // Message that shows while downloading + loadingMessage: "<span class='dijitContentPaneLoading'>${loadingState}</span>", + + // errorMessage: String + // Message that shows if an error occurs + errorMessage: "<span class='dijitContentPaneError'>${errorState}</span>", + + // isLoaded: Boolean + // Tells loading status see onLoad|onUnload for event hooks + isLoaded: false, + + // class: String + // Class name to apply to ContentPane dom nodes + // TODO: this should be called "baseClass" like in the other widgets + "class": "dijitContentPane", + + // doLayout: String/Boolean + // false - don't adjust size of children + // true - looks for the first sizable child widget (ie, having resize() method) and sets it's size to + // however big the ContentPane is (TODO: implement) + // auto - if there is a single sizable child widget (ie, having resize() method), set it's size to + // however big the ContentPane is + doLayout: "auto", + + postCreate: function(){ + // remove the title attribute so it doesn't show up when i hover + // over a node + this.domNode.title = ""; + + if(!this.containerNode){ + // make getDescendants() work + this.containerNode = this.domNode; + } + + if(this.preload){ + this._loadCheck(); + } + + var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang); + this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages); + this.errorMessage = dojo.string.substitute(this.errorMessage, messages); + var curRole = dijit.getWaiRole(this.domNode); + if (!curRole){ + dijit.setWaiRole(this.domNode, "group"); + } + + // for programatically created ContentPane (with <span> tag), need to muck w/CSS + // or it's as though overflow:visible is set + dojo.addClass(this.domNode, this["class"]); + }, + + startup: function(){ + if(this._started){ return; } + if(this.doLayout != "false" && this.doLayout !== false){ + this._checkIfSingleChild(); + if(this._singleChild){ + this._singleChild.startup(); + } + } + this._loadCheck(); + this.inherited(arguments); + }, + + _checkIfSingleChild: function(){ + // summary: + // Test if we have exactly one widget as a child, and if so assume that we are a container for that widget, + // and should propogate startup() and resize() calls to it. + + // TODO: if there are two child widgets (a data store and a TabContainer, for example), + // should still find the TabContainer + var childNodes = dojo.query(">", this.containerNode || this.domNode), + childWidgets = childNodes.filter("[widgetId]"); + + if(childNodes.length == 1 && childWidgets.length == 1){ + this.isContainer = true; + this._singleChild = dijit.byNode(childWidgets[0]); + }else{ + delete this.isContainer; + delete this._singleChild; + } + }, + + refresh: function(){ + // summary: + // Force a refresh (re-download) of content, be sure to turn off cache + + // we return result of _prepareLoad here to avoid code dup. in dojox.layout.ContentPane + return this._prepareLoad(true); + }, + + setHref: function(/*String|Uri*/ href){ + // summary: + // Reset the (external defined) content of this pane and replace with new url + // Note: It delays the download until widget is shown if preload is false + // href: + // url to the page you want to get, must be within the same domain as your mainpage + this.href = href; + + // we return result of _prepareLoad here to avoid code dup. in dojox.layout.ContentPane + return this._prepareLoad(); + }, + + setContent: function(/*String|DomNode|Nodelist*/data){ + // summary: + // Replaces old content with data content, include style classes from old content + // data: + // the new Content may be String, DomNode or NodeList + // + // if data is a NodeList (or an array of nodes) nodes are copied + // so you can import nodes from another document implicitly + + // clear href so we cant run refresh and clear content + // refresh should only work if we downloaded the content + if(!this._isDownloaded){ + this.href = ""; + this._onUnloadHandler(); + } + + this._setContent(data || ""); + + this._isDownloaded = false; // must be set after _setContent(..), pathadjust in dojox.layout.ContentPane + + if(this.parseOnLoad){ + this._createSubWidgets(); + } + + if(this.doLayout != "false" && this.doLayout !== false){ + this._checkIfSingleChild(); + if(this._singleChild && this._singleChild.resize){ + this._singleChild.startup(); + this._singleChild.resize(this._contentBox || dojo.contentBox(this.containerNode || this.domNode)); + } + } + + this._onLoadHandler(); + }, + + cancel: function(){ + // summary: + // Cancels a inflight download of content + if(this._xhrDfd && (this._xhrDfd.fired == -1)){ + this._xhrDfd.cancel(); + } + delete this._xhrDfd; // garbage collect + }, + + destroy: function(){ + // if we have multiple controllers destroying us, bail after the first + if(this._beingDestroyed){ + return; + } + // make sure we call onUnload + this._onUnloadHandler(); + this._beingDestroyed = true; + this.inherited("destroy",arguments); + }, + + resize: function(size){ + dojo.marginBox(this.domNode, size); + + // Compute content box size in case we [later] need to size child + // If either height or width wasn't specified by the user, then query node for it. + // But note that setting the margin box and then immediately querying dimensions may return + // inaccurate results, so try not to depend on it. + var node = this.containerNode || this.domNode, + mb = dojo.mixin(dojo.marginBox(node), size||{}); + + this._contentBox = dijit.layout.marginBox2contentBox(node, mb); + + // If we have a single widget child then size it to fit snugly within my borders + if(this._singleChild && this._singleChild.resize){ + this._singleChild.resize(this._contentBox); + } + }, + + _prepareLoad: function(forceLoad){ + // sets up for a xhrLoad, load is deferred until widget onShow + // cancels a inflight download + this.cancel(); + this.isLoaded = false; + this._loadCheck(forceLoad); + }, + + _isShown: function(){ + // summary: returns true if the content is currently shown + if("open" in this){ + return this.open; // for TitlePane, etc. + }else{ + var node = this.domNode; + return (node.style.display != 'none') && (node.style.visibility != 'hidden'); + } + }, + + _loadCheck: function(/*Boolean*/ forceLoad){ + // call this when you change onShow (onSelected) status when selected in parent container + // it's used as a trigger for href download when this.domNode.display != 'none' + + // sequence: + // if no href -> bail + // forceLoad -> always load + // this.preload -> load when download not in progress, domNode display doesn't matter + // this.refreshOnShow -> load when download in progress bails, domNode display !='none' AND + // this.open !== false (undefined is ok), isLoaded doesn't matter + // else -> load when download not in progress, if this.open !== false (undefined is ok) AND + // domNode display != 'none', isLoaded must be false + + var displayState = this._isShown(); + + if(this.href && + (forceLoad || + (this.preload && !this._xhrDfd) || + (this.refreshOnShow && displayState && !this._xhrDfd) || + (!this.isLoaded && displayState && !this._xhrDfd) + ) + ){ + this._downloadExternalContent(); + } + }, + + _downloadExternalContent: function(){ + this._onUnloadHandler(); + + // display loading message + this._setContent( + this.onDownloadStart.call(this) + ); + + var self = this; + var getArgs = { + preventCache: (this.preventCache || this.refreshOnShow), + url: this.href, + handleAs: "text" + }; + if(dojo.isObject(this.ioArgs)){ + dojo.mixin(getArgs, this.ioArgs); + } + + var hand = this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs); + + hand.addCallback(function(html){ + try{ + self.onDownloadEnd.call(self); + self._isDownloaded = true; + self.setContent.call(self, html); // onload event is called from here + }catch(err){ + self._onError.call(self, 'Content', err); // onContentError + } + delete self._xhrDfd; + return html; + }); + + hand.addErrback(function(err){ + if(!hand.cancelled){ + // show error message in the pane + self._onError.call(self, 'Download', err); // onDownloadError + } + delete self._xhrDfd; + return err; + }); + }, + + _onLoadHandler: function(){ + this.isLoaded = true; + try{ + this.onLoad.call(this); + }catch(e){ + console.error('Error '+this.widgetId+' running custom onLoad code'); + } + }, + + _onUnloadHandler: function(){ + this.isLoaded = false; + this.cancel(); + try{ + this.onUnload.call(this); + }catch(e){ + console.error('Error '+this.widgetId+' running custom onUnload code'); + } + }, + + _setContent: function(cont){ + this.destroyDescendants(); + + try{ + var node = this.containerNode || this.domNode; + while(node.firstChild){ + dojo._destroyElement(node.firstChild); + } + if(typeof cont == "string"){ + // dijit.ContentPane does only minimal fixes, + // No pathAdjustments, script retrieval, style clean etc + // some of these should be available in the dojox.layout.ContentPane + if(this.extractContent){ + match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); + if(match){ cont = match[1]; } + } + node.innerHTML = cont; + }else{ + // domNode or NodeList + if(cont.nodeType){ // domNode (htmlNode 1 or textNode 3) + node.appendChild(cont); + }else{// nodelist or array such as dojo.Nodelist + dojo.forEach(cont, function(n){ + node.appendChild(n.cloneNode(true)); + }); + } + } + }catch(e){ + // check if a domfault occurs when we are appending this.errorMessage + // like for instance if domNode is a UL and we try append a DIV + var errMess = this.onContentError(e); + try{ + node.innerHTML = errMess; + }catch(e){ + console.error('Fatal '+this.id+' could not change content due to '+e.message, e); + } + } + }, + + _onError: function(type, err, consoleText){ + // shows user the string that is returned by on[type]Error + // overide on[type]Error and return your own string to customize + var errText = this['on' + type + 'Error'].call(this, err); + if(consoleText){ + console.error(consoleText, err); + }else if(errText){// a empty string won't change current content + this._setContent.call(this, errText); + } + }, + + _createSubWidgets: function(){ + // summary: scan my contents and create subwidgets + var rootNode = this.containerNode || this.domNode; + try{ + dojo.parser.parse(rootNode, true); + }catch(e){ + this._onError('Content', e, "Couldn't create widgets in "+this.id + +(this.href ? " from "+this.href : "")); + } + }, + + // EVENT's, should be overide-able + onLoad: function(e){ + // summary: + // Event hook, is called after everything is loaded and widgetified + }, + + onUnload: function(e){ + // summary: + // Event hook, is called before old content is cleared + }, + + onDownloadStart: function(){ + // summary: + // called before download starts + // the string returned by this function will be the html + // that tells the user we are loading something + // override with your own function if you want to change text + return this.loadingMessage; + }, + + onContentError: function(/*Error*/ error){ + // summary: + // called on DOM faults, require fault etc in content + // default is to display errormessage inside pane + }, + + onDownloadError: function(/*Error*/ error){ + // summary: + // Called when download error occurs, default is to display + // errormessage inside pane. Overide function to change that. + // The string returned by this function will be the html + // that tells the user a error happend + return this.errorMessage; + }, + + onDownloadEnd: function(){ + // summary: + // called when download is finished + } +}); + +} diff --git a/includes/js/dijit/layout/LayoutContainer.js b/includes/js/dijit/layout/LayoutContainer.js new file mode 100644 index 0000000..11b8c9f --- /dev/null +++ b/includes/js/dijit/layout/LayoutContainer.js @@ -0,0 +1,74 @@ +if(!dojo._hasResource["dijit.layout.LayoutContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.LayoutContainer"] = true; +dojo.provide("dijit.layout.LayoutContainer"); + +dojo.require("dijit.layout._LayoutWidget"); + +dojo.declare("dijit.layout.LayoutContainer", + dijit.layout._LayoutWidget, + { + // summary: + // Provides Delphi-style panel layout semantics. + // + // description: + // A LayoutContainer is a box with a specified size (like style="width: 500px; height: 500px;"), + // that contains children widgets marked with "layoutAlign" of "left", "right", "bottom", "top", and "client". + // It takes it's children marked as left/top/bottom/right, and lays them out along the edges of the box, + // and then it takes the child marked "client" and puts it into the remaining space in the middle. + // + // Left/right positioning is similar to CSS's "float: left" and "float: right", + // and top/bottom positioning would be similar to "float: top" and "float: bottom", if there were such + // CSS. + // + // Note that there can only be one client element, but there can be multiple left, right, top, + // or bottom elements. + // + // example: + // | <style> + // | html, body{ height: 100%; width: 100%; } + // | </style> + // | <div dojoType="dijit.layout.LayoutContainer" style="width: 100%; height: 100%"> + // | <div dojoType="dijit.layout.ContentPane" layoutAlign="top">header text</div> + // | <div dojoType="dijit.layout.ContentPane" layoutAlign="left" style="width: 200px;">table of contents</div> + // | <div dojoType="dijit.layout.ContentPane" layoutAlign="client">client area</div> + // | </div> + // | + // | Lays out each child in the natural order the children occur in. + // | Basically each child is laid out into the "remaining space", where "remaining space" is initially + // | the content area of this widget, but is reduced to a smaller rectangle each time a child is added. + // + + constructor: function(){ + dojo.deprecated("dijit.layout.LayoutContainer is deprecated", "use BorderContainer instead", 2.0); + }, + + layout: function(){ + dijit.layout.layoutChildren(this.domNode, this._contentBox, this.getChildren()); + }, + + addChild: function(/*Widget*/ child, /*Integer?*/ insertIndex){ + dijit._Container.prototype.addChild.apply(this, arguments); + if(this._started){ + dijit.layout.layoutChildren(this.domNode, this._contentBox, this.getChildren()); + } + }, + + removeChild: function(/*Widget*/ widget){ + dijit._Container.prototype.removeChild.apply(this, arguments); + if(this._started){ + dijit.layout.layoutChildren(this.domNode, this._contentBox, this.getChildren()); + } + } +}); + +// This argument can be specified for the children of a LayoutContainer. +// Since any widget can be specified as a LayoutContainer child, mix it +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // layoutAlign: String + // "none", "left", "right", "bottom", "top", and "client". + // See the LayoutContainer description for details on this parameter. + layoutAlign: 'none' +}); + +} diff --git a/includes/js/dijit/layout/LinkPane.js b/includes/js/dijit/layout/LinkPane.js new file mode 100644 index 0000000..cddaaee --- /dev/null +++ b/includes/js/dijit/layout/LinkPane.js @@ -0,0 +1,36 @@ +if(!dojo._hasResource["dijit.layout.LinkPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.LinkPane"] = true; +dojo.provide("dijit.layout.LinkPane"); + +dojo.require("dijit.layout.ContentPane"); +dojo.require("dijit._Templated"); + +dojo.declare("dijit.layout.LinkPane", + [dijit.layout.ContentPane, dijit._Templated], + { + // summary: + // A ContentPane that loads data remotely + // description: + // LinkPane is just a ContentPane that loads data remotely (via the href attribute), + // and has markup similar to an anchor. The anchor's body (the words between `<a>` and `</a>`) + // become the title of the widget (used for TabContainer, AccordionContainer, etc.) + // example: + // <a href="foo.html">my title</a> + + // I'm using a template because the user may specify the input as + // <a href="foo.html">title</a>, in which case we need to get rid of the + // <a> because we don't want a link. + templateString: '<div class="dijitLinkPane"></div>', + + postCreate: function(){ + + // If user has specified node contents, they become the title + // (the link must be plain text) + if(this.srcNodeRef){ + this.title += this.srcNodeRef.innerHTML; + } + this.inherited("postCreate",arguments); + } +}); + +} diff --git a/includes/js/dijit/layout/SplitContainer.js b/includes/js/dijit/layout/SplitContainer.js new file mode 100644 index 0000000..671d1fc --- /dev/null +++ b/includes/js/dijit/layout/SplitContainer.js @@ -0,0 +1,553 @@ +if(!dojo._hasResource["dijit.layout.SplitContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.SplitContainer"] = true; +dojo.provide("dijit.layout.SplitContainer"); + +// +// FIXME: make it prettier +// FIXME: active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case) +// + +dojo.require("dojo.cookie"); +dojo.require("dijit.layout._LayoutWidget"); + +dojo.declare("dijit.layout.SplitContainer", + dijit.layout._LayoutWidget, + { + // summary: + // A Container widget with sizing handles in-between each child + // description: + // Contains multiple children widgets, all of which are displayed side by side + // (either horizontally or vertically); there's a bar between each of the children, + // and you can adjust the relative size of each child by dragging the bars. + // + // You must specify a size (width and height) for the SplitContainer. + + constructor: function(){ + dojo.deprecated("dijit.layout.SplitContainer is deprecated", "use BorderContainer with splitter instead", 2.0); + }, + + // activeSizing: Boolean + // If true, the children's size changes as you drag the bar; + // otherwise, the sizes don't change until you drop the bar (by mouse-up) + activeSizing: false, + + // sizerWidth: Integer + // Size in pixels of the bar between each child + sizerWidth: 7, // FIXME: this should be a CSS attribute (at 7 because css wants it to be 7 until we fix to css) + + // orientation: String + // either 'horizontal' or vertical; indicates whether the children are + // arranged side-by-side or up/down. + orientation: 'horizontal', + + // persist: Boolean + // Save splitter positions in a cookie + persist: true, + + postMixInProperties: function(){ + this.inherited("postMixInProperties",arguments); + this.isHorizontal = (this.orientation == 'horizontal'); + }, + + postCreate: function(){ + this.inherited("postCreate",arguments); + this.sizers = []; + dojo.addClass(this.domNode, "dijitSplitContainer"); + // overflow has to be explicitly hidden for splitContainers using gekko (trac #1435) + // to keep other combined css classes from inadvertantly making the overflow visible + if(dojo.isMozilla){ + this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work + } + + // create the fake dragger + if(typeof this.sizerWidth == "object"){ + try{ //FIXME: do this without a try/catch + this.sizerWidth = parseInt(this.sizerWidth.toString()); + }catch(e){ this.sizerWidth = 7; } + } + var sizer = this.virtualSizer = dojo.doc.createElement('div'); + sizer.style.position = 'relative'; + + // #1681: work around the dreaded 'quirky percentages in IE' layout bug + // If the splitcontainer's dimensions are specified in percentages, it + // will be resized when the virtualsizer is displayed in _showSizingLine + // (typically expanding its bounds unnecessarily). This happens because + // we use position: relative for .dijitSplitContainer. + // The workaround: instead of changing the display style attribute, + // switch to changing the zIndex (bring to front/move to back) + + sizer.style.zIndex = 10; + sizer.className = this.isHorizontal ? 'dijitSplitContainerVirtualSizerH' : 'dijitSplitContainerVirtualSizerV'; + this.domNode.appendChild(sizer); + dojo.setSelectable(sizer, false); + }, + + destroy: function(){ + delete this.virtualSizer; + dojo.forEach(this._ownconnects, dojo.disconnect); + this.inherited(arguments); + }, + startup: function(){ + if(this._started){ return; } + + dojo.forEach(this.getChildren(), function(child, i, children){ + // attach the children and create the draggers + this._injectChild(child); + + if(i < children.length-1){ + this._addSizer(); + } + }, this); + + if(this.persist){ + this._restoreState(); + } + + this.inherited(arguments); + }, + + _injectChild: function(child){ + child.domNode.style.position = "absolute"; + dojo.addClass(child.domNode, "dijitSplitPane"); + }, + + _addSizer: function(){ + var i = this.sizers.length; + + // TODO: use a template for this!!! + var sizer = this.sizers[i] = dojo.doc.createElement('div'); + this.domNode.appendChild(sizer); + + sizer.className = this.isHorizontal ? 'dijitSplitContainerSizerH' : 'dijitSplitContainerSizerV'; + + // add the thumb div + var thumb = dojo.doc.createElement('div'); + thumb.className = 'thumb'; + sizer.appendChild(thumb); + + // FIXME: are you serious? why aren't we using mover start/stop combo? + var self = this; + var handler = (function(){ var sizer_i = i; return function(e){ self.beginSizing(e, sizer_i); } })(); + this.connect(sizer, "onmousedown", handler); + + dojo.setSelectable(sizer, false); + }, + + removeChild: function(widget){ + // summary: Remove sizer, but only if widget is really our child and + // we have at least one sizer to throw away + if(this.sizers.length){ + var i=dojo.indexOf(this.getChildren(), widget) + if(i != -1){ + if(i==this.sizers.length){ + i--; + } + dojo._destroyElement(this.sizers[i]); + this.sizers.splice(i,1); + } + } + + // Remove widget and repaint + this.inherited(arguments); + if(this._started){ + this.layout(); + } + }, + + addChild: function(/*Widget*/ child, /*Integer?*/ insertIndex){ + // summary: Add a child widget to the container + // child: a widget to add + // insertIndex: postion in the "stack" to add the child widget + + this.inherited("addChild",arguments); + + if(this._started){ + // Do the stuff that startup() does for each widget + this._injectChild(child); + var children = this.getChildren(); + if(children.length > 1){ + this._addSizer(); + } + + // and then reposition (ie, shrink) every pane to make room for the new guy + this.layout(); + } + }, + + layout: function(){ + // summary: + // Do layout of panels + + // base class defines this._contentBox on initial creation and also + // on resize + this.paneWidth = this._contentBox.w; + this.paneHeight = this._contentBox.h; + + var children = this.getChildren(); + if(!children.length){ return; } + + // + // calculate space + // + + var space = this.isHorizontal ? this.paneWidth : this.paneHeight; + if(children.length > 1){ + space -= this.sizerWidth * (children.length - 1); + } + + // + // calculate total of SizeShare values + // + var outOf = 0; + dojo.forEach(children, function(child){ + outOf += child.sizeShare; + }); + + // + // work out actual pixels per sizeshare unit + // + var pixPerUnit = space / outOf; + + // + // set the SizeActual member of each pane + // + var totalSize = 0; + dojo.forEach(children.slice(0, children.length - 1), function(child){ + var size = Math.round(pixPerUnit * child.sizeShare); + child.sizeActual = size; + totalSize += size; + }); + + children[children.length-1].sizeActual = space - totalSize; + + // + // make sure the sizes are ok + // + this._checkSizes(); + + // + // now loop, positioning each pane and letting children resize themselves + // + + var pos = 0; + var size = children[0].sizeActual; + this._movePanel(children[0], pos, size); + children[0].position = pos; + pos += size; + + // if we don't have any sizers, our layout method hasn't been called yet + // so bail until we are called..TODO: REVISIT: need to change the startup + // algorithm to guaranteed the ordering of calls to layout method + if(!this.sizers){ + return; + } + + dojo.some(children.slice(1), function(child, i){ + // error-checking + if(!this.sizers[i]){ + return true; + } + // first we position the sizing handle before this pane + this._moveSlider(this.sizers[i], pos, this.sizerWidth); + this.sizers[i].position = pos; + pos += this.sizerWidth; + + size = child.sizeActual; + this._movePanel(child, pos, size); + child.position = pos; + pos += size; + }, this); + }, + + _movePanel: function(panel, pos, size){ + if(this.isHorizontal){ + panel.domNode.style.left = pos + 'px'; // TODO: resize() takes l and t parameters too, don't need to set manually + panel.domNode.style.top = 0; + var box = {w: size, h: this.paneHeight}; + if(panel.resize){ + panel.resize(box); + }else{ + dojo.marginBox(panel.domNode, box); + } + }else{ + panel.domNode.style.left = 0; // TODO: resize() takes l and t parameters too, don't need to set manually + panel.domNode.style.top = pos + 'px'; + var box = {w: this.paneWidth, h: size}; + if(panel.resize){ + panel.resize(box); + }else{ + dojo.marginBox(panel.domNode, box); + } + } + }, + + _moveSlider: function(slider, pos, size){ + if(this.isHorizontal){ + slider.style.left = pos + 'px'; + slider.style.top = 0; + dojo.marginBox(slider, { w: size, h: this.paneHeight }); + }else{ + slider.style.left = 0; + slider.style.top = pos + 'px'; + dojo.marginBox(slider, { w: this.paneWidth, h: size }); + } + }, + + _growPane: function(growth, pane){ + if(growth > 0){ + if(pane.sizeActual > pane.sizeMin){ + if((pane.sizeActual - pane.sizeMin) > growth){ + + // stick all the growth in this pane + pane.sizeActual = pane.sizeActual - growth; + growth = 0; + }else{ + // put as much growth in here as we can + growth -= pane.sizeActual - pane.sizeMin; + pane.sizeActual = pane.sizeMin; + } + } + } + return growth; + }, + + _checkSizes: function(){ + + var totalMinSize = 0; + var totalSize = 0; + var children = this.getChildren(); + + dojo.forEach(children, function(child){ + totalSize += child.sizeActual; + totalMinSize += child.sizeMin; + }); + + // only make adjustments if we have enough space for all the minimums + + if(totalMinSize <= totalSize){ + + var growth = 0; + + dojo.forEach(children, function(child){ + if(child.sizeActual < child.sizeMin){ + growth += child.sizeMin - child.sizeActual; + child.sizeActual = child.sizeMin; + } + }); + + if(growth > 0){ + var list = this.isDraggingLeft ? children.reverse() : children; + dojo.forEach(list, function(child){ + growth = this._growPane(growth, child); + }, this); + } + }else{ + dojo.forEach(children, function(child){ + child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize)); + }); + } + }, + + beginSizing: function(e, i){ + var children = this.getChildren(); + this.paneBefore = children[i]; + this.paneAfter = children[i+1]; + + this.isSizing = true; + this.sizingSplitter = this.sizers[i]; + + if(!this.cover){ + this.cover = dojo.doc.createElement('div'); + this.domNode.appendChild(this.cover); + var s = this.cover.style; + s.position = 'absolute'; + s.zIndex = 1; + s.top = 0; + s.left = 0; + s.width = "100%"; + s.height = "100%"; + }else{ + this.cover.style.zIndex = 1; + } + this.sizingSplitter.style.zIndex = 2; + + // TODO: REVISIT - we want MARGIN_BOX and core hasn't exposed that yet (but can't we use it anyway if we pay attention? we do elsewhere.) + this.originPos = dojo.coords(children[0].domNode, true); + if(this.isHorizontal){ + var client = (e.layerX ? e.layerX : e.offsetX); + var screen = e.pageX; + this.originPos = this.originPos.x; + }else{ + var client = (e.layerY ? e.layerY : e.offsetY); + var screen = e.pageY; + this.originPos = this.originPos.y; + } + this.startPoint = this.lastPoint = screen; + this.screenToClientOffset = screen - client; + this.dragOffset = this.lastPoint - this.paneBefore.sizeActual - this.originPos - this.paneBefore.position; + + if(!this.activeSizing){ + this._showSizingLine(); + } + + // + // attach mouse events + // + this._ownconnects = []; + this._ownconnects.push(dojo.connect(dojo.doc.documentElement, "onmousemove", this, "changeSizing")); + this._ownconnects.push(dojo.connect(dojo.doc.documentElement, "onmouseup", this, "endSizing")); + + dojo.stopEvent(e); + }, + + changeSizing: function(e){ + if(!this.isSizing){ return; } + this.lastPoint = this.isHorizontal ? e.pageX : e.pageY; + this.movePoint(); + if(this.activeSizing){ + this._updateSize(); + }else{ + this._moveSizingLine(); + } + dojo.stopEvent(e); + }, + + endSizing: function(e){ + if(!this.isSizing){ return; } + if(this.cover){ + this.cover.style.zIndex = -1; + } + if(!this.activeSizing){ + this._hideSizingLine(); + } + + this._updateSize(); + + this.isSizing = false; + + if(this.persist){ + this._saveState(this); + } + + dojo.forEach(this._ownconnects,dojo.disconnect); + }, + + movePoint: function(){ + + // make sure lastPoint is a legal point to drag to + var p = this.lastPoint - this.screenToClientOffset; + + var a = p - this.dragOffset; + a = this.legaliseSplitPoint(a); + p = a + this.dragOffset; + + this.lastPoint = p + this.screenToClientOffset; + }, + + legaliseSplitPoint: function(a){ + + a += this.sizingSplitter.position; + + this.isDraggingLeft = !!(a > 0); + + if(!this.activeSizing){ + var min = this.paneBefore.position + this.paneBefore.sizeMin; + if(a < min){ + a = min; + } + + var max = this.paneAfter.position + (this.paneAfter.sizeActual - (this.sizerWidth + this.paneAfter.sizeMin)); + if(a > max){ + a = max; + } + } + + a -= this.sizingSplitter.position; + + this._checkSizes(); + + return a; + }, + + _updateSize: function(){ + //FIXME: sometimes this.lastPoint is NaN + var pos = this.lastPoint - this.dragOffset - this.originPos; + + var start_region = this.paneBefore.position; + var end_region = this.paneAfter.position + this.paneAfter.sizeActual; + + this.paneBefore.sizeActual = pos - start_region; + this.paneAfter.position = pos + this.sizerWidth; + this.paneAfter.sizeActual = end_region - this.paneAfter.position; + + dojo.forEach(this.getChildren(), function(child){ + child.sizeShare = child.sizeActual; + }); + + if(this._started){ + this.layout(); + } + }, + + _showSizingLine: function(){ + + this._moveSizingLine(); + + dojo.marginBox(this.virtualSizer, + this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth }); + + this.virtualSizer.style.display = 'block'; + }, + + _hideSizingLine: function(){ + this.virtualSizer.style.display = 'none'; + }, + + _moveSizingLine: function(){ + var pos = (this.lastPoint - this.startPoint) + this.sizingSplitter.position; + dojo.style(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px"); + // this.virtualSizer.style[ this.isHorizontal ? "left" : "top" ] = pos + 'px'; // FIXME: remove this line if the previous is better + }, + + _getCookieName: function(i){ + return this.id + "_" + i; + }, + + _restoreState: function(){ + dojo.forEach(this.getChildren(), function(child, i){ + var cookieName = this._getCookieName(i); + var cookieValue = dojo.cookie(cookieName); + if(cookieValue){ + var pos = parseInt(cookieValue); + if(typeof pos == "number"){ + child.sizeShare = pos; + } + } + }, this); + }, + + _saveState: function(){ + dojo.forEach(this.getChildren(), function(child, i){ + dojo.cookie(this._getCookieName(i), child.sizeShare); + }, this); + } +}); + +// These arguments can be specified for the children of a SplitContainer. +// Since any widget can be specified as a SplitContainer child, mix them +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // sizeMin: Integer + // Minimum size (width or height) of a child of a SplitContainer. + // The value is relative to other children's sizeShare properties. + sizeMin: 10, + + // sizeShare: Integer + // Size (width or height) of a child of a SplitContainer. + // The value is relative to other children's sizeShare properties. + // For example, if there are two children and each has sizeShare=10, then + // each takes up 50% of the available space. + sizeShare: 10 +}); + +} diff --git a/includes/js/dijit/layout/StackContainer.js b/includes/js/dijit/layout/StackContainer.js new file mode 100644 index 0000000..3119972 --- /dev/null +++ b/includes/js/dijit/layout/StackContainer.js @@ -0,0 +1,493 @@ +if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.StackContainer"] = true; +dojo.provide("dijit.layout.StackContainer"); + +dojo.require("dijit._Templated"); +dojo.require("dijit.layout._LayoutWidget"); +dojo.require("dijit.form.Button"); +dojo.require("dijit.Menu"); +dojo.requireLocalization("dijit", "common", null, "zh,pt,da,tr,ru,de,sv,ja,he,fi,nb,el,ar,ROOT,pt-pt,cs,fr,es,ko,nl,zh-tw,pl,it,hu"); + +dojo.declare( + "dijit.layout.StackContainer", + dijit.layout._LayoutWidget, + { + // summary: + // A container that has multiple children, but shows only + // one child at a time + // + // description: + // A container for widgets (ContentPanes, for example) That displays + // only one Widget at a time. + // + // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild + // + // Can be base class for container, Wizard, Show, etc. + // + // + // doLayout: Boolean + // if true, change the size of my currently displayed child to match my size + doLayout: true, + + _started: false, +/*===== + // selectedChildWidget: Widget + // References the currently selected child widget, if any + // + selectedChildWidget: null, +=====*/ + postCreate: function(){ + dijit.setWaiRole((this.containerNode || this.domNode), "tabpanel"); + this.connect(this.domNode, "onkeypress", this._onKeyPress); + }, + + startup: function(){ + if(this._started){ return; } + + var children = this.getChildren(); + + // Setup each page panel + dojo.forEach(children, this._setupChild, this); + + // Figure out which child to initially display + dojo.some(children, function(child){ + if(child.selected){ + this.selectedChildWidget = child; + } + return child.selected; + }, this); + + var selected = this.selectedChildWidget; + + // Default to the first child + if(!selected && children[0]){ + selected = this.selectedChildWidget = children[0]; + selected.selected = true; + } + if(selected){ + this._showChild(selected); + } + + // Now publish information about myself so any StackControllers can initialize.. + dojo.publish(this.id+"-startup", [{children: children, selected: selected}]); + + this.inherited(arguments); + }, + + _setupChild: function(/*Widget*/ page){ + // Summary: prepare the given child + + page.domNode.style.display = "none"; + + // since we are setting the width/height of the child elements, they need + // to be position:relative, or IE has problems (See bug #2033) + page.domNode.style.position = "relative"; + + return page; // dijit._Widget + }, + + addChild: function(/*Widget*/ child, /*Integer?*/ insertIndex){ + // summary: Adds a widget to the stack + + dijit._Container.prototype.addChild.apply(this, arguments); + child = this._setupChild(child); + + if(this._started){ + // in case the tab titles have overflowed from one line to two lines + this.layout(); + + dojo.publish(this.id+"-addChild", [child, insertIndex]); + + // if this is the first child, then select it + if(!this.selectedChildWidget){ + this.selectChild(child); + } + } + }, + + removeChild: function(/*Widget*/ page){ + // summary: Removes the pane from the stack + + dijit._Container.prototype.removeChild.apply(this, arguments); + + // If we are being destroyed than don't run the code below (to select another page), because we are deleting + // every page one by one + if(this._beingDestroyed){ return; } + + if(this._started){ + // this will notify any tablists to remove a button; do this first because it may affect sizing + dojo.publish(this.id+"-removeChild", [page]); + + // in case the tab titles now take up one line instead of two lines + this.layout(); + } + + if(this.selectedChildWidget === page){ + this.selectedChildWidget = undefined; + if(this._started){ + var children = this.getChildren(); + if(children.length){ + this.selectChild(children[0]); + } + } + } + }, + + selectChild: function(/*Widget*/ page){ + // summary: + // Show the given widget (which must be one of my children) + + page = dijit.byId(page); + + if(this.selectedChildWidget != page){ + // Deselect old page and select new one + this._transition(page, this.selectedChildWidget); + this.selectedChildWidget = page; + dojo.publish(this.id+"-selectChild", [page]); + } + }, + + _transition: function(/*Widget*/newWidget, /*Widget*/oldWidget){ + if(oldWidget){ + this._hideChild(oldWidget); + } + this._showChild(newWidget); + + // Size the new widget, in case this is the first time it's being shown, + // or I have been resized since the last time it was shown. + // page must be visible for resizing to work + if(this.doLayout && newWidget.resize){ + newWidget.resize(this._containerContentBox || this._contentBox); + } + }, + + _adjacent: function(/*Boolean*/ forward){ + // summary: Gets the next/previous child widget in this container from the current selection + var children = this.getChildren(); + var index = dojo.indexOf(children, this.selectedChildWidget); + index += forward ? 1 : children.length - 1; + return children[ index % children.length ]; // dijit._Widget + }, + + forward: function(){ + // Summary: advance to next page + this.selectChild(this._adjacent(true)); + }, + + back: function(){ + // Summary: go back to previous page + this.selectChild(this._adjacent(false)); + }, + + _onKeyPress: function(e){ + dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]); + }, + + layout: function(){ + if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){ + this.selectedChildWidget.resize(this._contentBox); + } + }, + + _showChild: function(/*Widget*/ page){ + var children = this.getChildren(); + page.isFirstChild = (page == children[0]); + page.isLastChild = (page == children[children.length-1]); + page.selected = true; + + page.domNode.style.display=""; + if(page._loadCheck){ + page._loadCheck(); // trigger load in ContentPane + } + if(page.onShow){ + page.onShow(); + } + }, + + _hideChild: function(/*Widget*/ page){ + page.selected=false; + page.domNode.style.display="none"; + if(page.onHide){ + page.onHide(); + } + }, + + closeChild: function(/*Widget*/ page){ + // summary: + // callback when user clicks the [X] to remove a page + // if onClose() returns true then remove and destroy the child + var remove = page.onClose(this, page); + if(remove){ + this.removeChild(page); + // makes sure we can clean up executeScripts in ContentPane onUnLoad + page.destroyRecursive(); + } + }, + + destroy: function(){ + this._beingDestroyed = true; + this.inherited(arguments); + } +}); + +dojo.declare( + "dijit.layout.StackController", + [dijit._Widget, dijit._Templated, dijit._Container], + { + // summary: + // Set of buttons to select a page in a page list. + // Monitors the specified StackContainer, and whenever a page is + // added, deleted, or selected, updates itself accordingly. + + templateString: "<span wairole='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>", + + // containerId: String + // the id of the page container that I point to + containerId: "", + + // buttonWidget: String + // the name of the button widget to create to correspond to each page + buttonWidget: "dijit.layout._StackButton", + + postCreate: function(){ + dijit.setWaiRole(this.domNode, "tablist"); + + // TODO: change key from object to id, to get more separation from StackContainer + this.pane2button = {}; // mapping from panes to buttons + this.pane2menu = {}; // mapping from panes to close menu + + this._subscriptions=[ + dojo.subscribe(this.containerId+"-startup", this, "onStartup"), + dojo.subscribe(this.containerId+"-addChild", this, "onAddChild"), + dojo.subscribe(this.containerId+"-removeChild", this, "onRemoveChild"), + dojo.subscribe(this.containerId+"-selectChild", this, "onSelectChild"), + dojo.subscribe(this.containerId+"-containerKeyPress", this, "onContainerKeyPress") + ]; + }, + + onStartup: function(/*Object*/ info){ + // summary: called after StackContainer has finished initializing + dojo.forEach(info.children, this.onAddChild, this); + this.onSelectChild(info.selected); + }, + + destroy: function(){ + for(var pane in this.pane2button){ + this.onRemoveChild(pane); + } + dojo.forEach(this._subscriptions, dojo.unsubscribe); + this.inherited(arguments); + }, + + onAddChild: function(/*Widget*/ page, /*Integer?*/ insertIndex){ + // summary: + // Called whenever a page is added to the container. + // Create button corresponding to the page. + + // add a node that will be promoted to the button widget + var refNode = dojo.doc.createElement("span"); + this.domNode.appendChild(refNode); + // create an instance of the button widget + var cls = dojo.getObject(this.buttonWidget); + var button = new cls({label: page.title, closeButton: page.closable}, refNode); + this.addChild(button, insertIndex); + this.pane2button[page] = button; + page.controlButton = button; // this value might be overwritten if two tabs point to same container + + dojo.connect(button, "onClick", dojo.hitch(this,"onButtonClick",page)); + if(page.closable){ + dojo.connect(button, "onClickCloseButton", dojo.hitch(this,"onCloseButtonClick",page)); + // add context menu onto title button + var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); + var closeMenu = new dijit.Menu({targetNodeIds:[button.id], id:button.id+"_Menu"}); + var mItem = new dijit.MenuItem({label:_nlsResources.itemClose}); + dojo.connect(mItem, "onClick", dojo.hitch(this, "onCloseButtonClick", page)); + closeMenu.addChild(mItem); + this.pane2menu[page] = closeMenu; + } + if(!this._currentChild){ // put the first child into the tab order + button.focusNode.setAttribute("tabIndex", "0"); + this._currentChild = page; + } + //make sure all tabs have the same length + if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){ + this._rectifyRtlTabList(); + } + }, + + onRemoveChild: function(/*Widget*/ page){ + // summary: + // Called whenever a page is removed from the container. + // Remove the button corresponding to the page. + if(this._currentChild === page){ this._currentChild = null; } + var button = this.pane2button[page]; + var menu = this.pane2menu[page]; + if (menu){ + menu.destroy(); + } + if(button){ + // TODO? if current child { reassign } + button.destroy(); + } + this.pane2button[page] = null; + }, + + onSelectChild: function(/*Widget*/ page){ + // summary: + // Called when a page has been selected in the StackContainer, either by me or by another StackController + + if(!page){ return; } + + if(this._currentChild){ + var oldButton=this.pane2button[this._currentChild]; + oldButton.setAttribute('checked', false); + oldButton.focusNode.setAttribute("tabIndex", "-1"); + } + + var newButton=this.pane2button[page]; + newButton.setAttribute('checked', true); + this._currentChild = page; + newButton.focusNode.setAttribute("tabIndex", "0"); + var container = dijit.byId(this.containerId); + dijit.setWaiState(container.containerNode || container.domNode, "labelledby", newButton.id); + }, + + onButtonClick: function(/*Widget*/ page){ + // summary: + // Called whenever one of my child buttons is pressed in an attempt to select a page + var container = dijit.byId(this.containerId); // TODO: do this via topics? + container.selectChild(page); + }, + + onCloseButtonClick: function(/*Widget*/ page){ + // summary: + // Called whenever one of my child buttons [X] is pressed in an attempt to close a page + var container = dijit.byId(this.containerId); + container.closeChild(page); + var b = this.pane2button[this._currentChild]; + if(b){ + dijit.focus(b.focusNode || b.domNode); + } + }, + + // TODO: this is a bit redundant with forward, back api in StackContainer + adjacent: function(/*Boolean*/ forward){ + if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; } + // find currently focused button in children array + var children = this.getChildren(); + var current = dojo.indexOf(children, this.pane2button[this._currentChild]); + // pick next button to focus on + var offset = forward ? 1 : children.length - 1; + return children[ (current + offset) % children.length ]; // dijit._Widget + }, + + onkeypress: function(/*Event*/ e){ + // summary: + // Handle keystrokes on the page list, for advancing to next/previous button + // and closing the current page if the page is closable. + + if(this.disabled || e.altKey ){ return; } + var forward = null; + if(e.ctrlKey || !e._djpage){ + var k = dojo.keys; + switch(e.keyCode){ + case k.LEFT_ARROW: + case k.UP_ARROW: + if(!e._djpage){ forward = false; } + break; + case k.PAGE_UP: + if(e.ctrlKey){ forward = false; } + break; + case k.RIGHT_ARROW: + case k.DOWN_ARROW: + if(!e._djpage){ forward = true; } + break; + case k.PAGE_DOWN: + if(e.ctrlKey){ forward = true; } + break; + case k.DELETE: + if(this._currentChild.closable){ + this.onCloseButtonClick(this._currentChild); + } + dojo.stopEvent(e); + break; + default: + if(e.ctrlKey){ + if(e.keyCode == k.TAB){ + this.adjacent(!e.shiftKey).onClick(); + dojo.stopEvent(e); + }else if(e.keyChar == "w"){ + if(this._currentChild.closable){ + this.onCloseButtonClick(this._currentChild); + } + dojo.stopEvent(e); // avoid browser tab closing. + } + } + } + // handle page navigation + if(forward !== null){ + this.adjacent(forward).onClick(); + dojo.stopEvent(e); + } + } + }, + + onContainerKeyPress: function(/*Object*/ info){ + info.e._djpage = info.page; + this.onkeypress(info.e); + } +}); + +dojo.declare("dijit.layout._StackButton", + dijit.form.ToggleButton, + { + // summary + // Internal widget used by StackContainer. + // The button-like or tab-like object you click to select or delete a page + + tabIndex: "-1", // StackContainer buttons are not in the tab order by default + + postCreate: function(/*Event*/ evt){ + dijit.setWaiRole((this.focusNode || this.domNode), "tab"); + this.inherited(arguments); + }, + + onClick: function(/*Event*/ evt){ + // summary: This is for TabContainer where the tabs are <span> rather than button, + // so need to set focus explicitly (on some browsers) + dijit.focus(this.focusNode); + + // ... now let StackController catch the event and tell me what to do + }, + + onClickCloseButton: function(/*Event*/ evt){ + // summary + // StackContainer connects to this function; if your widget contains a close button + // then clicking it should call this function. + evt.stopPropagation(); + } +}); + +// These arguments can be specified for the children of a StackContainer. +// Since any widget can be specified as a StackContainer child, mix them +// into the base widget class. (This is a hack, but it's effective.) +dojo.extend(dijit._Widget, { + // title: String + // Title of this widget. Used by TabContainer to the name the tab, etc. + title: "", + + // selected: Boolean + // Is this child currently selected? + selected: false, + + // closable: Boolean + // True if user can close (destroy) this child, such as (for example) clicking the X on the tab. + closable: false, // true if user can close this tab pane + + onClose: function(){ + // summary: Callback if someone tries to close the child, child will be closed if func returns true + return true; + } +}); + +} diff --git a/includes/js/dijit/layout/TabContainer.js b/includes/js/dijit/layout/TabContainer.js new file mode 100644 index 0000000..769a25f --- /dev/null +++ b/includes/js/dijit/layout/TabContainer.js @@ -0,0 +1,184 @@ +if(!dojo._hasResource["dijit.layout.TabContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout.TabContainer"] = true; +dojo.provide("dijit.layout.TabContainer"); + +dojo.require("dijit.layout.StackContainer"); +dojo.require("dijit._Templated"); + +dojo.declare("dijit.layout.TabContainer", + [dijit.layout.StackContainer, dijit._Templated], + { + // summary: + // A Container with Title Tabs, each one pointing at a pane in the container. + // description: + // A TabContainer is a container that has multiple panes, but shows only + // one pane at a time. There are a set of tabs corresponding to each pane, + // where each tab has the title (aka title) of the pane, and optionally a close button. + // + // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild + // (where [widgetId] is the id of the TabContainer itself. + // + // tabPosition: String + // Defines where tabs go relative to tab content. + // "top", "bottom", "left-h", "right-h" + tabPosition: "top", + + templateString: null, // override setting in StackContainer + templateString:"<div class=\"dijitTabContainer\">\n\t<div dojoAttachPoint=\"tablistNode\"></div>\n\t<div class=\"dijitTabPaneWrapper\" dojoAttachPoint=\"containerNode\"></div>\n</div>\n", + + // _controllerWidget: String + // An optional parameter to overrider the default TabContainer controller used. + _controllerWidget: "dijit.layout.TabController", + + postCreate: function(){ + this.inherited(arguments); + // create the tab list that will have a tab (a.k.a. tab button) for each tab panel + var TabController = dojo.getObject(this._controllerWidget); + this.tablist = new TabController({ + id: this.id + "_tablist", + tabPosition: this.tabPosition, + doLayout: this.doLayout, + containerId: this.id + }, this.tablistNode); + }, + + _setupChild: function(/* Widget */tab){ + dojo.addClass(tab.domNode, "dijitTabPane"); + this.inherited(arguments); + return tab; // Widget + }, + + startup: function(){ + if(this._started){ return; } + + // wire up the tablist and its tabs + this.tablist.startup(); + this.inherited(arguments); + + if(dojo.isSafari){ + // sometimes safari 3.0.3 miscalculates the height of the tab labels, see #4058 + setTimeout(dojo.hitch(this, "layout"), 0); + } + + if(dojo.isIE && !this.isLeftToRight() && this.tabPosition == "right-h" && + this.tablist && this.tablist.pane2button){ + //need rectify non-closable tab in IE, only for "right-h" mode + for(var pane in this.tablist.pane2button){ + var tabButton = this.tablist.pane2button[pane]; + if(!tabButton.closeButton){ continue; } + tabButtonStyle = tabButton.closeButtonNode.style; + tabButtonStyle.position ="absolute"; + if(dojo.isIE < 7){ + tabButtonStyle.left = tabButton.domNode.offsetWidth + "px"; + }else{ + tabButtonStyle.padding = "0px"; + } + } + } + }, + + layout: function(){ + // Summary: Configure the content pane to take up all the space except for where the tabs are + if(!this.doLayout){ return; } + + // position and size the titles and the container node + var titleAlign = this.tabPosition.replace(/-h/,""); + var children = [ + { domNode: this.tablist.domNode, layoutAlign: titleAlign }, + { domNode: this.containerNode, layoutAlign: "client" } + ]; + dijit.layout.layoutChildren(this.domNode, this._contentBox, children); + + // Compute size to make each of my children. + // children[1] is the margin-box size of this.containerNode, set by layoutChildren() call above + this._containerContentBox = dijit.layout.marginBox2contentBox(this.containerNode, children[1]); + + if(this.selectedChildWidget){ + this._showChild(this.selectedChildWidget); + if(this.doLayout && this.selectedChildWidget.resize){ + this.selectedChildWidget.resize(this._containerContentBox); + } + } + }, + + destroy: function(){ + if(this.tablist){ + this.tablist.destroy(); + } + this.inherited(arguments); + } +}); + +//TODO: make private? +dojo.declare("dijit.layout.TabController", + dijit.layout.StackController, + { + // summary: + // Set of tabs (the things with titles and a close button, that you click to show a tab panel). + // description: + // Lets the user select the currently shown pane in a TabContainer or StackContainer. + // TabController also monitors the TabContainer, and whenever a pane is + // added or deleted updates itself accordingly. + + templateString: "<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>", + + // tabPosition: String + // Defines where tabs go relative to the content. + // "top", "bottom", "left-h", "right-h" + tabPosition: "top", + + // doLayout: Boolean + // TODOC: deprecate doLayout? not sure. + doLayout: true, + + // buttonWidget: String + // The name of the tab widget to create to correspond to each page + buttonWidget: "dijit.layout._TabButton", + + postMixInProperties: function(){ + this["class"] = "dijitTabLabels-" + this.tabPosition + (this.doLayout ? "" : " dijitTabNoLayout"); + this.inherited(arguments); + }, + +//TODO: can this be accomplished in CSS? + _rectifyRtlTabList: function(){ + //Summary: Rectify the length of all tabs in rtl, otherwise the tab lengths are different in IE + if(0 >= this.tabPosition.indexOf('-h')){ return; } + if(!this.pane2button){ return; } + + var maxLen = 0; + for(var pane in this.pane2button){ + maxLen = Math.max(maxLen, dojo.marginBox(this.pane2button[pane].innerDiv).w); + } + //unify the length of all the tabs + for(pane in this.pane2button){ + this.pane2button[pane].innerDiv.style.width = maxLen + 'px'; + } + } +}); + +dojo.declare("dijit.layout._TabButton", + dijit.layout._StackButton, + { + // summary: + // A tab (the thing you click to select a pane). + // description: + // Contains the title of the pane, and optionally a close-button to destroy the pane. + // This is an internal widget and should not be instantiated directly. + + baseClass: "dijitTab", + + templateString:"<div waiRole=\"presentation\" dojoAttachEvent='onclick:onClick,onmouseenter:_onMouse,onmouseleave:_onMouse'>\n <div waiRole=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div waiRole=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent'>\n\t <span dojoAttachPoint='containerNode,focusNode' class='tabLabel'>${!label}</span>\n\t <span dojoAttachPoint='closeButtonNode' class='closeImage' dojoAttachEvent='onmouseenter:_onMouse, onmouseleave:_onMouse, onclick:onClickCloseButton' stateModifier='CloseButton'>\n\t <span dojoAttachPoint='closeText' class='closeText'>x</span>\n\t </span>\n </div>\n </div>\n</div>\n", + + postCreate: function(){ + if(this.closeButton){ + dojo.addClass(this.innerDiv, "dijitClosable"); + }else{ + this.closeButtonNode.style.display="none"; + } + this.inherited(arguments); + dojo.setSelectable(this.containerNode, false); + } +}); + +} diff --git a/includes/js/dijit/layout/_LayoutWidget.js b/includes/js/dijit/layout/_LayoutWidget.js new file mode 100644 index 0000000..3877802 --- /dev/null +++ b/includes/js/dijit/layout/_LayoutWidget.js @@ -0,0 +1,188 @@ +if(!dojo._hasResource["dijit.layout._LayoutWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.layout._LayoutWidget"] = true; +dojo.provide("dijit.layout._LayoutWidget"); + +dojo.require("dijit._Widget"); +dojo.require("dijit._Container"); + +dojo.declare("dijit.layout._LayoutWidget", + [dijit._Widget, dijit._Container, dijit._Contained], + { + // summary + // Mixin for widgets that contain a list of children like SplitContainer. + // Widgets which mixin this code must define layout() to lay out the children + + isLayoutContainer: true, + + postCreate: function(){ + dojo.addClass(this.domNode, "dijitContainer"); + }, + + startup: function(){ + // summary: + // Called after all the widgets have been instantiated and their + // dom nodes have been inserted somewhere under dojo.doc.body. + // + // Widgets should override this method to do any initialization + // dependent on other widgets existing, and then call + // this superclass method to finish things off. + // + // startup() in subclasses shouldn't do anything + // size related because the size of the widget hasn't been set yet. + + if(this._started){ return; } + + dojo.forEach(this.getChildren(), function(child){ child.startup(); }); + + // If I am a top level widget + if(!this.getParent || !this.getParent()){ + // Do recursive sizing and layout of all my descendants + // (passing in no argument to resize means that it has to glean the size itself) + this.resize(); + + // since my parent isn't a layout container, and my style is width=height=100% (or something similar), + // then I need to watch when the window resizes, and size myself accordingly + // (passing in no argument to resize means that it has to glean the size itself) + this.connect(window, 'onresize', function(){this.resize();}); + } + + this.inherited(arguments); + }, + + resize: function(args){ + // summary: + // Explicitly set this widget's size (in pixels), + // and then call layout() to resize contents (and maybe adjust child widgets) + // + // args: Object? + // {w: int, h: int, l: int, t: int} + + var node = this.domNode; + + // set margin box size, unless it wasn't specified, in which case use current size + if(args){ + dojo.marginBox(node, args); + + // set offset of the node + if(args.t){ node.style.top = args.t + "px"; } + if(args.l){ node.style.left = args.l + "px"; } + } + // If either height or width wasn't specified by the user, then query node for it. + // But note that setting the margin box and then immediately querying dimensions may return + // inaccurate results, so try not to depend on it. + var mb = dojo.mixin(dojo.marginBox(node), args||{}); + +// console.log(this, ": setting size to ", mb); + + // Save the size of my content box. + this._contentBox = dijit.layout.marginBox2contentBox(node, mb); + + // Callback for widget to adjust size of it's children + this.layout(); + }, + + layout: function(){ + // summary + // Widgets override this method to size & position their contents/children. + // When this is called this._contentBox is guaranteed to be set (see resize()). + // + // This is called after startup(), and also when the widget's size has been + // changed. + } + } +); + +dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){ + // summary: + // Given the margin-box size of a node, return it's content box size. + // Functions like dojo.contentBox() but is more reliable since it doesn't have + // to wait for the browser to compute sizes. + var cs = dojo.getComputedStyle(node); + var me=dojo._getMarginExtents(node, cs); + var pb=dojo._getPadBorderExtents(node, cs); + return { + l: dojo._toPixelValue(node, cs.paddingLeft), + t: dojo._toPixelValue(node, cs.paddingTop), + w: mb.w - (me.w + pb.w), + h: mb.h - (me.h + pb.h) + }; +}; + +(function(){ + var capitalize = function(word){ + return word.substring(0,1).toUpperCase() + word.substring(1); + }; + + var size = function(widget, dim){ + // size the child + widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim); + + // record child's size, but favor our own numbers when we have them. + // the browser lies sometimes + dojo.mixin(widget, dojo.marginBox(widget.domNode)); + dojo.mixin(widget, dim); + }; + + dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Object[]*/ children){ + /** + * summary + * Layout a bunch of child dom nodes within a parent dom node + * container: + * parent node + * dim: + * {l, t, w, h} object specifying dimensions of container into which to place children + * children: + * an array like [ {domNode: foo, layoutAlign: "bottom" }, {domNode: bar, layoutAlign: "client"} ] + */ + + // copy dim because we are going to modify it + dim = dojo.mixin({}, dim); + + dojo.addClass(container, "dijitLayoutContainer"); + + // Move "client" elements to the end of the array for layout. a11y dictates that the author + // needs to be able to put them in the document in tab-order, but this algorithm requires that + // client be last. + children = dojo.filter(children, function(item){ return item.layoutAlign != "client"; }) + .concat(dojo.filter(children, function(item){ return item.layoutAlign == "client"; })); + + // set positions/sizes + dojo.forEach(children, function(child){ + var elm = child.domNode, + pos = child.layoutAlign; + + // set elem to upper left corner of unused space; may move it later + var elmStyle = elm.style; + elmStyle.left = dim.l+"px"; + elmStyle.top = dim.t+"px"; + elmStyle.bottom = elmStyle.right = "auto"; + + dojo.addClass(elm, "dijitAlign" + capitalize(pos)); + + // set size && adjust record of remaining space. + // note that setting the width of a <div> may affect it's height. + if(pos=="top" || pos=="bottom"){ + size(child, { w: dim.w }); + dim.h -= child.h; + if(pos=="top"){ + dim.t += child.h; + }else{ + elmStyle.top = dim.t + dim.h + "px"; + } + }else if(pos=="left" || pos=="right"){ + size(child, { h: dim.h }); + dim.w -= child.w; + if(pos=="left"){ + dim.l += child.w; + }else{ + elmStyle.left = dim.l + dim.w + "px"; + } + }else if(pos=="client"){ + size(child, dim); + } + }); + }; + +})(); + +} diff --git a/includes/js/dijit/layout/templates/AccordionPane.html b/includes/js/dijit/layout/templates/AccordionPane.html new file mode 100644 index 0000000..a675d3a --- /dev/null +++ b/includes/js/dijit/layout/templates/AccordionPane.html @@ -0,0 +1,11 @@ +<div class='dijitAccordionPane' + ><div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='ondijitclick:_onTitleClick,onkeypress:_onTitleKeyPress,onfocus:_handleFocus,onblur:_handleFocus' + class='dijitAccordionTitle' wairole="tab" + ><div class='dijitAccordionArrow' waiRole="presentation"></div + ><div class='arrowTextUp' waiRole="presentation">▲</div + ><div class='arrowTextDown' waiRole="presentation">▼</div + ><div waiRole="presentation" dojoAttachPoint='titleTextNode' class='dijitAccordionText'>${title}</div></div + ><div><div dojoAttachPoint='containerNode' style='overflow: hidden; height: 1px; display: none' + class='dijitAccordionBody' wairole="tabpanel" + ></div></div> +</div> diff --git a/includes/js/dijit/layout/templates/TabContainer.html b/includes/js/dijit/layout/templates/TabContainer.html new file mode 100644 index 0000000..105e8c6 --- /dev/null +++ b/includes/js/dijit/layout/templates/TabContainer.html @@ -0,0 +1,4 @@ +<div class="dijitTabContainer"> + <div dojoAttachPoint="tablistNode"></div> + <div class="dijitTabPaneWrapper" dojoAttachPoint="containerNode"></div> +</div> diff --git a/includes/js/dijit/layout/templates/TooltipDialog.html b/includes/js/dijit/layout/templates/TooltipDialog.html new file mode 100644 index 0000000..5925500 --- /dev/null +++ b/includes/js/dijit/layout/templates/TooltipDialog.html @@ -0,0 +1,6 @@ +<div class="dijitTooltipDialog" waiRole="presentation"> + <div class="dijitTooltipContainer" waiRole="presentation"> + <div class ="dijitTooltipContents dijitTooltipFocusNode" dojoAttachPoint="containerNode" tabindex="-1" waiRole="dialog"></div> + </div> + <div class="dijitTooltipConnector" waiRole="presenation"></div> +</div> diff --git a/includes/js/dijit/layout/templates/_TabButton.html b/includes/js/dijit/layout/templates/_TabButton.html new file mode 100644 index 0000000..b7b37ac --- /dev/null +++ b/includes/js/dijit/layout/templates/_TabButton.html @@ -0,0 +1,10 @@ +<div waiRole="presentation" dojoAttachEvent='onclick:onClick,onmouseenter:_onMouse,onmouseleave:_onMouse'> + <div waiRole="presentation" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'> + <div waiRole="presentation" class='dijitTabContent' dojoAttachPoint='tabContent'> + <span dojoAttachPoint='containerNode,focusNode' class='tabLabel'>${!label}</span> + <span dojoAttachPoint='closeButtonNode' class='closeImage' dojoAttachEvent='onmouseenter:_onMouse, onmouseleave:_onMouse, onclick:onClickCloseButton' stateModifier='CloseButton'> + <span dojoAttachPoint='closeText' class='closeText'>x</span> + </span> + </div> + </div> +</div> |