2 'url:dijit/layout/templates/AccordionButton.html':"<div data-dojo-attach-event='onclick:_onTitleClick' class='dijitAccordionTitle' role=\"presentation\">\n\t<div data-dojo-attach-point='titleNode,focusNode' data-dojo-attach-event='onkeypress:_onTitleKeyPress'\n\t\t\tclass='dijitAccordionTitleFocus' role=\"tab\" aria-expanded=\"false\"\n\t\t><span class='dijitInline dijitAccordionArrow' role=\"presentation\"></span\n\t\t><span class='arrowTextUp' role=\"presentation\">+</span\n\t\t><span class='arrowTextDown' role=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" class=\"dijitIcon\" data-dojo-attach-point='iconNode' style=\"vertical-align: middle\" role=\"presentation\"/>\n\t\t<span role=\"presentation\" data-dojo-attach-point='titleTextNode' class='dijitAccordionText'></span>\n\t</div>\n</div>\n"}});
3 define("dijit/layout/AccordionContainer", [
5 "dojo/_base/array", // array.forEach array.map
6 "dojo/_base/declare", // declare
7 "dojo/_base/event", // event.stop
8 "dojo/_base/fx", // fx.Animation
9 "dojo/dom", // dom.setSelectable
10 "dojo/dom-attr", // domAttr.attr
11 "dojo/dom-class", // domClass.remove
12 "dojo/dom-construct", // domConstruct.place
15 "dojo/_base/lang", // lang.getObject lang.hitch
16 "dojo/sniff", // has("ie") has("dijit-legacy-requires")
17 "dojo/topic", // publish
18 "../focus", // focus.focus()
19 "../_base/manager", // manager.defaultDuration
27 "dojo/text!./templates/AccordionButton.html"
28 ], function(require
, array
, declare
, event
, fx
, dom
, domAttr
, domClass
, domConstruct
, domGeometry
,
29 keys
, lang
, has
, topic
, focus
, manager
, ready
,
30 _Widget
, _Container
, _TemplatedMixin
, _CssStateMixin
, StackContainer
, ContentPane
, template
){
33 // dijit/layout/AccordionContainer
38 // An AccordionContainer is a StackContainer, but each child (typically ContentPane)
39 // is wrapped in a _AccordionInnerContainer. This is hidden from the caller.
41 // The resulting markup will look like:
43 // <div class=dijitAccordionContainer>
44 // <div class=dijitAccordionInnerContainer> (one pane)
45 // <div class=dijitAccordionTitle> (title bar) ... </div>
46 // <div class=dijtAccordionChildWrapper> (content pane) </div>
50 // Normally the dijtAccordionChildWrapper is hidden for all but one child (the shown
51 // child), so the space for the content pane is all the title bars + the one dijtAccordionChildWrapper,
52 // which on claro has a 1px border plus a 2px bottom margin.
54 // During animation there are two dijtAccordionChildWrapper's shown, so we need
55 // to compensate for that.
58 var AccordionButton
= declare("dijit.layout._AccordionButton", [_Widget
, _TemplatedMixin
, _CssStateMixin
], {
60 // The title bar to click to open up an accordion pane.
61 // Internal widget used by AccordionContainer.
65 templateString
: template
,
70 _setLabelAttr
: {node
: "titleTextNode", type
: "innerHTML" },
73 // Tooltip that appears on hover
75 _setTitleAttr
: {node
: "titleTextNode", type
: "attribute", attribute
: "title"},
77 // iconClassAttr: String
78 // CSS class for icon to left of label
80 _setIconClassAttr
: { node
: "iconNode", type
: "class" },
82 baseClass
: "dijitAccordionTitle",
84 getParent: function(){
86 // Returns the AccordionContainer parent.
92 buildRendering: function(){
93 this.inherited(arguments
);
94 var titleTextNodeId
= this.id
.replace(' ','_');
95 domAttr
.set(this.titleTextNode
, "id", titleTextNodeId
+"_title");
96 this.focusNode
.setAttribute("aria-labelledby", domAttr
.get(this.titleTextNode
, "id"));
97 dom
.setSelectable(this.domNode
, false);
100 getTitleHeight: function(){
102 // Returns the height of the title dom node.
103 return domGeometry
.getMarginSize(this.domNode
).h
; // Integer
106 // TODO: maybe the parent should set these methods directly rather than forcing the code
107 // into the button widget?
108 _onTitleClick: function(){
110 // Callback when someone clicks my title.
111 var parent
= this.getParent();
112 parent
.selectChild(this.contentWidget
, true);
113 focus
.focus(this.focusNode
);
116 _onTitleKeyPress: function(/*Event*/ evt
){
117 return this.getParent()._onKeyPress(evt
, this.contentWidget
);
120 _setSelectedAttr: function(/*Boolean*/ isSelected
){
121 this._set("selected", isSelected
);
122 this.focusNode
.setAttribute("aria-expanded", isSelected
? "true" : "false");
123 this.focusNode
.setAttribute("aria-selected", isSelected
? "true" : "false");
124 this.focusNode
.setAttribute("tabIndex", isSelected
? "0" : "-1");
128 var AccordionInnerContainer
= declare("dijit.layout._AccordionInnerContainer", [_Widget
, _CssStateMixin
], {
130 // Internal widget placed as direct child of AccordionContainer.containerNode.
131 // When other widgets are added as children to an AccordionContainer they are wrapped in
135 // buttonWidget: Function|String
136 // Class to use to instantiate title
137 // (Wish we didn't have a separate widget for just the title but maintaining it
138 // for backwards compatibility, is it worth it?)
143 // contentWidget: dijit/_WidgetBase
144 // Pointer to the real child widget
148 baseClass
: "dijitAccordionInnerContainer",
150 // tell nested layout widget that we will take care of sizing
151 isLayoutContainer
: true,
153 buildRendering: function(){
154 // Builds a template like:
155 // <div class=dijitAccordionInnerContainer>
157 // <div class=dijitAccordionChildWrapper>
162 // Create wrapper div, placed where the child is now
163 this.domNode
= domConstruct
.place("<div class='" + this.baseClass
+
164 "' role='presentation'>", this.contentWidget
.domNode
, "after");
166 // wrapper div's first child is the button widget (ie, the title bar)
167 var child
= this.contentWidget
,
168 cls
= lang
.isString(this.buttonWidget
) ? lang
.getObject(this.buttonWidget
) : this.buttonWidget
;
169 this.button
= child
._buttonWidget
= (new cls({
170 contentWidget
: child
,
172 title
: child
.tooltip
,
175 textDir
: child
.textDir
,
176 iconClass
: child
.iconClass
,
177 id
: child
.id
+ "_button",
179 })).placeAt(this.domNode
);
181 // and then the actual content widget (changing it from prior-sibling to last-child),
182 // wrapped by a <div class=dijitAccordionChildWrapper>
183 this.containerNode
= domConstruct
.place("<div class='dijitAccordionChildWrapper' style='display:none'>", this.domNode
);
184 domConstruct
.place(this.contentWidget
.domNode
, this.containerNode
);
187 postCreate: function(){
188 this.inherited(arguments
);
190 // Map changes in content widget's title etc. to changes in the button
191 var button
= this.button
;
192 this._contentWidgetWatches
= [
193 this.contentWidget
.watch('title', lang
.hitch(this, function(name
, oldValue
, newValue
){
194 button
.set("label", newValue
);
196 this.contentWidget
.watch('tooltip', lang
.hitch(this, function(name
, oldValue
, newValue
){
197 button
.set("title", newValue
);
199 this.contentWidget
.watch('iconClass', lang
.hitch(this, function(name
, oldValue
, newValue
){
200 button
.set("iconClass", newValue
);
205 _setSelectedAttr: function(/*Boolean*/ isSelected
){
206 this._set("selected", isSelected
);
207 this.button
.set("selected", isSelected
);
209 var cw
= this.contentWidget
;
210 if(cw
.onSelected
){ cw
.onSelected(); }
215 // Called by _Container.addChild()
216 this.contentWidget
.startup();
220 this.button
.destroyRecursive();
222 array
.forEach(this._contentWidgetWatches
|| [], function(w
){ w
.unwatch(); });
224 delete this.contentWidget
._buttonWidget
;
225 delete this.contentWidget
._wrapperWidget
;
227 this.inherited(arguments
);
230 destroyDescendants: function(/*Boolean*/ preserveDom
){
231 // since getChildren isn't working for me, have to code this manually
232 this.contentWidget
.destroyRecursive(preserveDom
);
236 var AccordionContainer
= declare("dijit.layout.AccordionContainer", StackContainer
, {
238 // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time,
239 // and switching between panes is visualized by sliding the other panes up/down.
241 // | <div data-dojo-type="dijit/layout/AccordionContainer">
242 // | <div data-dojo-type="dijit/layout/ContentPane" title="pane 1">
244 // | <div data-dojo-type="dijit/layout/ContentPane" title="pane 2">
245 // | <p>This is some text</p>
250 // Amount of time (in ms) it takes to slide panes
251 duration
: manager
.defaultDuration
,
253 // buttonWidget: [const] String
254 // The name of the widget used to display the title of each pane
255 buttonWidget
: AccordionButton
,
258 // _verticalSpace: Number
259 // Pixels of space available for the open pane
260 // (my content box size minus the cumulative size of all the title bars)
263 baseClass
: "dijitAccordionContainer",
265 buildRendering: function(){
266 this.inherited(arguments
);
267 this.domNode
.style
.overflow
= "hidden"; // TODO: put this in dijit.css
268 this.domNode
.setAttribute("role", "tablist"); // TODO: put this in template
272 if(this._started
){ return; }
273 this.inherited(arguments
);
274 if(this.selectedChildWidget
){
275 this.selectedChildWidget
._wrapperWidget
.set("selected", true);
280 // Implement _LayoutWidget.layout() virtual method.
281 // Set the height of the open pane based on what room remains.
283 var openPane
= this.selectedChildWidget
;
285 if(!openPane
){ return;}
287 // space taken up by title, plus wrapper div (with border/margin) for open pane
288 var wrapperDomNode
= openPane
._wrapperWidget
.domNode
,
289 wrapperDomNodeMargin
= domGeometry
.getMarginExtents(wrapperDomNode
),
290 wrapperDomNodePadBorder
= domGeometry
.getPadBorderExtents(wrapperDomNode
),
291 wrapperContainerNode
= openPane
._wrapperWidget
.containerNode
,
292 wrapperContainerNodeMargin
= domGeometry
.getMarginExtents(wrapperContainerNode
),
293 wrapperContainerNodePadBorder
= domGeometry
.getPadBorderExtents(wrapperContainerNode
),
294 mySize
= this._contentBox
;
296 // get cumulative height of all the unselected title bars
297 var totalCollapsedHeight
= 0;
298 array
.forEach(this.getChildren(), function(child
){
299 if(child
!= openPane
){
300 // Using domGeometry.getMarginSize() rather than domGeometry.position() since claro has 1px bottom margin
301 // to separate accordion panes. Not sure that works perfectly, it's probably putting a 1px
302 // margin below the bottom pane (even though we don't want one).
303 totalCollapsedHeight
+= domGeometry
.getMarginSize(child
._wrapperWidget
.domNode
).h
;
306 this._verticalSpace
= mySize
.h
- totalCollapsedHeight
- wrapperDomNodeMargin
.h
307 - wrapperDomNodePadBorder
.h
- wrapperContainerNodeMargin
.h
- wrapperContainerNodePadBorder
.h
308 - openPane
._buttonWidget
.getTitleHeight();
310 // Memo size to make displayed child
311 this._containerContentBox
= {
312 h
: this._verticalSpace
,
313 w
: this._contentBox
.w
- wrapperDomNodeMargin
.w
- wrapperDomNodePadBorder
.w
314 - wrapperContainerNodeMargin
.w
- wrapperContainerNodePadBorder
.w
318 openPane
.resize(this._containerContentBox
);
322 _setupChild: function(child
){
323 // Overrides _LayoutWidget._setupChild().
324 // Put wrapper widget around the child widget, showing title
326 child
._wrapperWidget
= AccordionInnerContainer({
327 contentWidget
: child
,
328 buttonWidget
: this.buttonWidget
,
329 id
: child
.id
+ "_wrapper",
332 textDir
: child
.textDir
,
336 this.inherited(arguments
);
339 addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex
){
340 // Overrides _LayoutWidget.addChild().
342 // Adding a child to a started Accordion is complicated because children have
343 // wrapper widgets. Default code path (calling this.inherited()) would add
344 // the new child inside another child's wrapper.
346 // First add in child as a direct child of this AccordionContainer
347 var refNode
= this.containerNode
;
348 if(insertIndex
&& typeof insertIndex
== "number"){
349 var children
= _Widget
.prototype.getChildren
.call(this); // get wrapper panes
350 if(children
&& children
.length
>= insertIndex
){
351 refNode
= children
[insertIndex
-1].domNode
;
352 insertIndex
= "after";
355 domConstruct
.place(child
.domNode
, refNode
, insertIndex
);
361 // Then stick the wrapper widget around the child widget
362 this._setupChild(child
);
364 // Code below copied from StackContainer
365 topic
.publish(this.id
+"-addChild", child
, insertIndex
); // publish
367 if(!this.selectedChildWidget
){
368 this.selectChild(child
);
371 // We haven't been started yet so just add in the child widget directly,
372 // and the wrapper will be created on startup()
373 this.inherited(arguments
);
377 removeChild: function(child
){
378 // Overrides _LayoutWidget.removeChild().
380 // Destroy wrapper widget first, before StackContainer.getChildren() call.
381 // Replace wrapper widget with true child widget (ContentPane etc.).
382 // This step only happens if the AccordionContainer has been started; otherwise there's no wrapper.
383 if(child
._wrapperWidget
){
384 domConstruct
.place(child
.domNode
, child
._wrapperWidget
.domNode
, "after");
385 child
._wrapperWidget
.destroy();
386 delete child
._wrapperWidget
;
389 domClass
.remove(child
.domNode
, "dijitHidden");
391 this.inherited(arguments
);
394 getChildren: function(){
395 // Overrides _Container.getChildren() to return content panes rather than internal AccordionInnerContainer panes
396 return array
.map(this.inherited(arguments
), function(child
){
397 return child
.declaredClass
== "dijit.layout._AccordionInnerContainer" ? child
.contentWidget
: child
;
403 this._animation
.stop();
405 array
.forEach(this.getChildren(), function(child
){
406 // If AccordionContainer has been started, then each child has a wrapper widget which
407 // also needs to be destroyed.
408 if(child
._wrapperWidget
){
409 child
._wrapperWidget
.destroy();
411 child
.destroyRecursive();
414 this.inherited(arguments
);
417 _showChild: function(child
){
418 // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
419 child
._wrapperWidget
.containerNode
.style
.display
="block";
420 return this.inherited(arguments
);
423 _hideChild: function(child
){
424 // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
425 child
._wrapperWidget
.containerNode
.style
.display
="none";
426 this.inherited(arguments
);
429 _transition: function(/*dijit/_WidgetBase?*/ newWidget, /*dijit/_WidgetBase?*/ oldWidget, /*Boolean*/ animate
){
430 // Overrides StackContainer._transition() to provide sliding of title bars etc.
433 // workaround animation bugs by not animating; not worth supporting animation for IE6 & 7
438 // there's an in-progress animation. speedily end it so we can do the newly requested one
439 this._animation
.stop(true);
440 delete this._animation
;
446 newWidget
._wrapperWidget
.set("selected", true);
448 var d
= this._showChild(newWidget
); // prepare widget to be slid in
450 // Size the new widget, in case this is the first time it's being shown,
451 // or I have been resized since the last time it was shown.
452 // Note that page must be visible for resizing to work.
453 if(this.doLayout
&& newWidget
.resize
){
454 newWidget
.resize(this._containerContentBox
);
459 oldWidget
._wrapperWidget
.set("selected", false);
461 this._hideChild(oldWidget
);
466 var newContents
= newWidget
._wrapperWidget
.containerNode
,
467 oldContents
= oldWidget
._wrapperWidget
.containerNode
;
469 // During the animation we will be showing two dijitAccordionChildWrapper nodes at once,
470 // which on claro takes up 4px extra space (compared to stable AccordionContainer).
471 // Have to compensate for that by immediately shrinking the pane being closed.
472 var wrapperContainerNode
= newWidget
._wrapperWidget
.containerNode
,
473 wrapperContainerNodeMargin
= domGeometry
.getMarginExtents(wrapperContainerNode
),
474 wrapperContainerNodePadBorder
= domGeometry
.getPadBorderExtents(wrapperContainerNode
),
475 animationHeightOverhead
= wrapperContainerNodeMargin
.h
+ wrapperContainerNodePadBorder
.h
;
477 oldContents
.style
.height
= (self
._verticalSpace
- animationHeightOverhead
) + "px";
479 this._animation
= new fx
.Animation({
481 duration
: this.duration
,
482 curve
: [1, this._verticalSpace
- animationHeightOverhead
- 1],
483 onAnimate: function(value
){
484 value
= Math
.floor(value
); // avoid fractional values
485 newContents
.style
.height
= value
+ "px";
486 oldContents
.style
.height
= (self
._verticalSpace
- animationHeightOverhead
- value
) + "px";
489 delete self
._animation
;
490 newContents
.style
.height
= "auto";
491 oldWidget
._wrapperWidget
.containerNode
.style
.display
= "none";
492 oldContents
.style
.height
= "auto";
493 self
._hideChild(oldWidget
);
496 this._animation
.onStop
= this._animation
.onEnd
;
497 this._animation
.play();
500 return d
; // If child has an href, promise that fires when the widget has finished loading
503 // note: we are treating the container as controller here
504 _onKeyPress: function(/*Event*/ e
, /*dijit/_WidgetBase*/ fromTitle){
506 // Handle keypress events
508 // This is called from a handler on AccordionContainer.domNode
509 // (setup in StackContainer), and is also called directly from
510 // the click handler for accordion labels
511 if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){
514 var c = e.charOrCode;
515 if((fromTitle && (c == keys.LEFT_ARROW || c == keys.UP_ARROW)) ||
516 (e.ctrlKey && c == keys.PAGE_UP)){
517 this._adjacent(false)._buttonWidget._onTitleClick();
519 }else if((fromTitle && (c == keys.RIGHT_ARROW || c == keys.DOWN_ARROW)) ||
520 (e.ctrlKey && (c == keys.PAGE_DOWN || c == keys.TAB))){
521 this._adjacent(true)._buttonWidget._onTitleClick();
527 // Back compat w/1.6, remove for 2.0
528 if(has("dijit-legacy-requires")){
530 var requires = ["dijit/layout/AccordionPane"];
531 require(requires); // use indirection so modules not rolled into a build
535 // For monkey patching
536 AccordionContainer._InnerContainer = AccordionInnerContainer;
537 AccordionContainer._Button = AccordionButton;
539 return AccordionContainer;