AccordionContainer.js.uncompressed.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. require({cache:{
  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", [
  4. "require",
  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
  13. "dojo/dom-geometry",
  14. "dojo/keys", // keys
  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
  20. "dojo/ready",
  21. "../_Widget",
  22. "../_Container",
  23. "../_TemplatedMixin",
  24. "../_CssStateMixin",
  25. "./StackContainer",
  26. "./ContentPane",
  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){
  31. // module:
  32. // dijit/layout/AccordionContainer
  33. // Design notes:
  34. //
  35. // An AccordionContainer is a StackContainer, but each child (typically ContentPane)
  36. // is wrapped in a _AccordionInnerContainer. This is hidden from the caller.
  37. //
  38. // The resulting markup will look like:
  39. //
  40. // <div class=dijitAccordionContainer>
  41. // <div class=dijitAccordionInnerContainer> (one pane)
  42. // <div class=dijitAccordionTitle> (title bar) ... </div>
  43. // <div class=dijtAccordionChildWrapper> (content pane) </div>
  44. // </div>
  45. // </div>
  46. //
  47. // Normally the dijtAccordionChildWrapper is hidden for all but one child (the shown
  48. // child), so the space for the content pane is all the title bars + the one dijtAccordionChildWrapper,
  49. // which on claro has a 1px border plus a 2px bottom margin.
  50. //
  51. // During animation there are two dijtAccordionChildWrapper's shown, so we need
  52. // to compensate for that.
  53. var AccordionButton = declare("dijit.layout._AccordionButton", [_Widget, _TemplatedMixin, _CssStateMixin], {
  54. // summary:
  55. // The title bar to click to open up an accordion pane.
  56. // Internal widget used by AccordionContainer.
  57. // tags:
  58. // private
  59. templateString: template,
  60. // label: String
  61. // Title of the pane
  62. label: "",
  63. _setLabelAttr: {node: "titleTextNode", type: "innerHTML" },
  64. // title: String
  65. // Tooltip that appears on hover
  66. title: "",
  67. _setTitleAttr: {node: "titleTextNode", type: "attribute", attribute: "title"},
  68. // iconClassAttr: String
  69. // CSS class for icon to left of label
  70. iconClassAttr: "",
  71. _setIconClassAttr: { node: "iconNode", type: "class" },
  72. baseClass: "dijitAccordionTitle",
  73. getParent: function(){
  74. // summary:
  75. // Returns the AccordionContainer parent.
  76. // tags:
  77. // private
  78. return this.parent;
  79. },
  80. buildRendering: function(){
  81. this.inherited(arguments);
  82. var titleTextNodeId = this.id.replace(' ','_');
  83. domAttr.set(this.titleTextNode, "id", titleTextNodeId+"_title");
  84. this.focusNode.setAttribute("aria-labelledby", domAttr.get(this.titleTextNode, "id"));
  85. dom.setSelectable(this.domNode, false);
  86. },
  87. getTitleHeight: function(){
  88. // summary:
  89. // Returns the height of the title dom node.
  90. return domGeometry.getMarginSize(this.domNode).h; // Integer
  91. },
  92. // TODO: maybe the parent should set these methods directly rather than forcing the code
  93. // into the button widget?
  94. _onTitleClick: function(){
  95. // summary:
  96. // Callback when someone clicks my title.
  97. var parent = this.getParent();
  98. parent.selectChild(this.contentWidget, true);
  99. focus.focus(this.focusNode);
  100. },
  101. _onTitleKeyPress: function(/*Event*/ evt){
  102. return this.getParent()._onKeyPress(evt, this.contentWidget);
  103. },
  104. _setSelectedAttr: function(/*Boolean*/ isSelected){
  105. this._set("selected", isSelected);
  106. this.focusNode.setAttribute("aria-expanded", isSelected ? "true" : "false");
  107. this.focusNode.setAttribute("aria-selected", isSelected ? "true" : "false");
  108. this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1");
  109. }
  110. });
  111. var AccordionInnerContainer = declare("dijit.layout._AccordionInnerContainer", [_Widget, _CssStateMixin], {
  112. // summary:
  113. // Internal widget placed as direct child of AccordionContainer.containerNode.
  114. // When other widgets are added as children to an AccordionContainer they are wrapped in
  115. // this widget.
  116. /*=====
  117. // buttonWidget: Function|String
  118. // Class to use to instantiate title
  119. // (Wish we didn't have a separate widget for just the title but maintaining it
  120. // for backwards compatibility, is it worth it?)
  121. buttonWidget: null,
  122. =====*/
  123. /*=====
  124. // contentWidget: dijit/_WidgetBase
  125. // Pointer to the real child widget
  126. contentWidget: null,
  127. =====*/
  128. baseClass: "dijitAccordionInnerContainer",
  129. // tell nested layout widget that we will take care of sizing
  130. isLayoutContainer: true,
  131. buildRendering: function(){
  132. // Builds a template like:
  133. // <div class=dijitAccordionInnerContainer>
  134. // Button
  135. // <div class=dijitAccordionChildWrapper>
  136. // ContentPane
  137. // </div>
  138. // </div>
  139. // Create wrapper div, placed where the child is now
  140. this.domNode = domConstruct.place("<div class='" + this.baseClass +
  141. "' role='presentation'>", this.contentWidget.domNode, "after");
  142. // wrapper div's first child is the button widget (ie, the title bar)
  143. var child = this.contentWidget,
  144. cls = lang.isString(this.buttonWidget) ? lang.getObject(this.buttonWidget) : this.buttonWidget;
  145. this.button = child._buttonWidget = (new cls({
  146. contentWidget: child,
  147. label: child.title,
  148. title: child.tooltip,
  149. dir: child.dir,
  150. lang: child.lang,
  151. textDir: child.textDir,
  152. iconClass: child.iconClass,
  153. id: child.id + "_button",
  154. parent: this.parent
  155. })).placeAt(this.domNode);
  156. // and then the actual content widget (changing it from prior-sibling to last-child),
  157. // wrapped by a <div class=dijitAccordionChildWrapper>
  158. this.containerNode = domConstruct.place("<div class='dijitAccordionChildWrapper' style='display:none'>", this.domNode);
  159. domConstruct.place(this.contentWidget.domNode, this.containerNode);
  160. },
  161. postCreate: function(){
  162. this.inherited(arguments);
  163. // Map changes in content widget's title etc. to changes in the button
  164. var button = this.button;
  165. this._contentWidgetWatches = [
  166. this.contentWidget.watch('title', lang.hitch(this, function(name, oldValue, newValue){
  167. button.set("label", newValue);
  168. })),
  169. this.contentWidget.watch('tooltip', lang.hitch(this, function(name, oldValue, newValue){
  170. button.set("title", newValue);
  171. })),
  172. this.contentWidget.watch('iconClass', lang.hitch(this, function(name, oldValue, newValue){
  173. button.set("iconClass", newValue);
  174. }))
  175. ];
  176. },
  177. _setSelectedAttr: function(/*Boolean*/ isSelected){
  178. this._set("selected", isSelected);
  179. this.button.set("selected", isSelected);
  180. if(isSelected){
  181. var cw = this.contentWidget;
  182. if(cw.onSelected){ cw.onSelected(); }
  183. }
  184. },
  185. startup: function(){
  186. // Called by _Container.addChild()
  187. this.contentWidget.startup();
  188. },
  189. destroy: function(){
  190. this.button.destroyRecursive();
  191. array.forEach(this._contentWidgetWatches || [], function(w){ w.unwatch(); });
  192. delete this.contentWidget._buttonWidget;
  193. delete this.contentWidget._wrapperWidget;
  194. this.inherited(arguments);
  195. },
  196. destroyDescendants: function(/*Boolean*/ preserveDom){
  197. // since getChildren isn't working for me, have to code this manually
  198. this.contentWidget.destroyRecursive(preserveDom);
  199. }
  200. });
  201. var AccordionContainer = declare("dijit.layout.AccordionContainer", StackContainer, {
  202. // summary:
  203. // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time,
  204. // and switching between panes is visualized by sliding the other panes up/down.
  205. // example:
  206. // | <div data-dojo-type="dijit/layout/AccordionContainer">
  207. // | <div data-dojo-type="dijit/layout/ContentPane" title="pane 1">
  208. // | </div>
  209. // | <div data-dojo-type="dijit/layout/ContentPane" title="pane 2">
  210. // | <p>This is some text</p>
  211. // | </div>
  212. // | </div>
  213. // duration: Integer
  214. // Amount of time (in ms) it takes to slide panes
  215. duration: manager.defaultDuration,
  216. // buttonWidget: [const] String
  217. // The name of the widget used to display the title of each pane
  218. buttonWidget: AccordionButton,
  219. /*=====
  220. // _verticalSpace: Number
  221. // Pixels of space available for the open pane
  222. // (my content box size minus the cumulative size of all the title bars)
  223. _verticalSpace: 0,
  224. =====*/
  225. baseClass: "dijitAccordionContainer",
  226. buildRendering: function(){
  227. this.inherited(arguments);
  228. this.domNode.style.overflow = "hidden"; // TODO: put this in dijit.css
  229. this.domNode.setAttribute("role", "tablist"); // TODO: put this in template
  230. },
  231. startup: function(){
  232. if(this._started){ return; }
  233. this.inherited(arguments);
  234. if(this.selectedChildWidget){
  235. this.selectedChildWidget._wrapperWidget.set("selected", true);
  236. }
  237. },
  238. layout: function(){
  239. // Implement _LayoutWidget.layout() virtual method.
  240. // Set the height of the open pane based on what room remains.
  241. var openPane = this.selectedChildWidget;
  242. if(!openPane){ return;}
  243. // space taken up by title, plus wrapper div (with border/margin) for open pane
  244. var wrapperDomNode = openPane._wrapperWidget.domNode,
  245. wrapperDomNodeMargin = domGeometry.getMarginExtents(wrapperDomNode),
  246. wrapperDomNodePadBorder = domGeometry.getPadBorderExtents(wrapperDomNode),
  247. wrapperContainerNode = openPane._wrapperWidget.containerNode,
  248. wrapperContainerNodeMargin = domGeometry.getMarginExtents(wrapperContainerNode),
  249. wrapperContainerNodePadBorder = domGeometry.getPadBorderExtents(wrapperContainerNode),
  250. mySize = this._contentBox;
  251. // get cumulative height of all the unselected title bars
  252. var totalCollapsedHeight = 0;
  253. array.forEach(this.getChildren(), function(child){
  254. if(child != openPane){
  255. // Using domGeometry.getMarginSize() rather than domGeometry.position() since claro has 1px bottom margin
  256. // to separate accordion panes. Not sure that works perfectly, it's probably putting a 1px
  257. // margin below the bottom pane (even though we don't want one).
  258. totalCollapsedHeight += domGeometry.getMarginSize(child._wrapperWidget.domNode).h;
  259. }
  260. });
  261. this._verticalSpace = mySize.h - totalCollapsedHeight - wrapperDomNodeMargin.h
  262. - wrapperDomNodePadBorder.h - wrapperContainerNodeMargin.h - wrapperContainerNodePadBorder.h
  263. - openPane._buttonWidget.getTitleHeight();
  264. // Memo size to make displayed child
  265. this._containerContentBox = {
  266. h: this._verticalSpace,
  267. w: this._contentBox.w - wrapperDomNodeMargin.w - wrapperDomNodePadBorder.w
  268. - wrapperContainerNodeMargin.w - wrapperContainerNodePadBorder.w
  269. };
  270. if(openPane){
  271. openPane.resize(this._containerContentBox);
  272. }
  273. },
  274. _setupChild: function(child){
  275. // Overrides _LayoutWidget._setupChild().
  276. // Put wrapper widget around the child widget, showing title
  277. child._wrapperWidget = AccordionInnerContainer({
  278. contentWidget: child,
  279. buttonWidget: this.buttonWidget,
  280. id: child.id + "_wrapper",
  281. dir: child.dir,
  282. lang: child.lang,
  283. textDir: child.textDir,
  284. parent: this
  285. });
  286. this.inherited(arguments);
  287. },
  288. addChild: function(/*dijit/_WidgetBase*/ child, /*Integer?*/ insertIndex){
  289. // Overrides _LayoutWidget.addChild().
  290. if(this._started){
  291. // Adding a child to a started Accordion is complicated because children have
  292. // wrapper widgets. Default code path (calling this.inherited()) would add
  293. // the new child inside another child's wrapper.
  294. // First add in child as a direct child of this AccordionContainer
  295. var refNode = this.containerNode;
  296. if(insertIndex && typeof insertIndex == "number"){
  297. var children = _Widget.prototype.getChildren.call(this); // get wrapper panes
  298. if(children && children.length >= insertIndex){
  299. refNode = children[insertIndex-1].domNode;
  300. insertIndex = "after";
  301. }
  302. }
  303. domConstruct.place(child.domNode, refNode, insertIndex);
  304. if(!child._started){
  305. child.startup();
  306. }
  307. // Then stick the wrapper widget around the child widget
  308. this._setupChild(child);
  309. // Code below copied from StackContainer
  310. topic.publish(this.id+"-addChild", child, insertIndex); // publish
  311. this.layout();
  312. if(!this.selectedChildWidget){
  313. this.selectChild(child);
  314. }
  315. }else{
  316. // We haven't been started yet so just add in the child widget directly,
  317. // and the wrapper will be created on startup()
  318. this.inherited(arguments);
  319. }
  320. },
  321. removeChild: function(child){
  322. // Overrides _LayoutWidget.removeChild().
  323. // Destroy wrapper widget first, before StackContainer.getChildren() call.
  324. // Replace wrapper widget with true child widget (ContentPane etc.).
  325. // This step only happens if the AccordionContainer has been started; otherwise there's no wrapper.
  326. if(child._wrapperWidget){
  327. domConstruct.place(child.domNode, child._wrapperWidget.domNode, "after");
  328. child._wrapperWidget.destroy();
  329. delete child._wrapperWidget;
  330. }
  331. domClass.remove(child.domNode, "dijitHidden");
  332. this.inherited(arguments);
  333. },
  334. getChildren: function(){
  335. // Overrides _Container.getChildren() to return content panes rather than internal AccordionInnerContainer panes
  336. return array.map(this.inherited(arguments), function(child){
  337. return child.declaredClass == "dijit.layout._AccordionInnerContainer" ? child.contentWidget : child;
  338. }, this);
  339. },
  340. destroy: function(){
  341. if(this._animation){
  342. this._animation.stop();
  343. }
  344. array.forEach(this.getChildren(), function(child){
  345. // If AccordionContainer has been started, then each child has a wrapper widget which
  346. // also needs to be destroyed.
  347. if(child._wrapperWidget){
  348. child._wrapperWidget.destroy();
  349. }else{
  350. child.destroyRecursive();
  351. }
  352. });
  353. this.inherited(arguments);
  354. },
  355. _showChild: function(child){
  356. // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
  357. child._wrapperWidget.containerNode.style.display="block";
  358. return this.inherited(arguments);
  359. },
  360. _hideChild: function(child){
  361. // Override StackContainer._showChild() to set visibility of _wrapperWidget.containerNode
  362. child._wrapperWidget.containerNode.style.display="none";
  363. this.inherited(arguments);
  364. },
  365. _transition: function(/*dijit/_WidgetBase?*/ newWidget, /*dijit/_WidgetBase?*/ oldWidget, /*Boolean*/ animate){
  366. // Overrides StackContainer._transition() to provide sliding of title bars etc.
  367. if(has("ie") < 8){
  368. // workaround animation bugs by not animating; not worth supporting animation for IE6 & 7
  369. animate = false;
  370. }
  371. if(this._animation){
  372. // there's an in-progress animation. speedily end it so we can do the newly requested one
  373. this._animation.stop(true);
  374. delete this._animation;
  375. }
  376. var self = this;
  377. if(newWidget){
  378. newWidget._wrapperWidget.set("selected", true);
  379. var d = this._showChild(newWidget); // prepare widget to be slid in
  380. // Size the new widget, in case this is the first time it's being shown,
  381. // or I have been resized since the last time it was shown.
  382. // Note that page must be visible for resizing to work.
  383. if(this.doLayout && newWidget.resize){
  384. newWidget.resize(this._containerContentBox);
  385. }
  386. }
  387. if(oldWidget){
  388. oldWidget._wrapperWidget.set("selected", false);
  389. if(!animate){
  390. this._hideChild(oldWidget);
  391. }
  392. }
  393. if(animate){
  394. var newContents = newWidget._wrapperWidget.containerNode,
  395. oldContents = oldWidget._wrapperWidget.containerNode;
  396. // During the animation we will be showing two dijitAccordionChildWrapper nodes at once,
  397. // which on claro takes up 4px extra space (compared to stable AccordionContainer).
  398. // Have to compensate for that by immediately shrinking the pane being closed.
  399. var wrapperContainerNode = newWidget._wrapperWidget.containerNode,
  400. wrapperContainerNodeMargin = domGeometry.getMarginExtents(wrapperContainerNode),
  401. wrapperContainerNodePadBorder = domGeometry.getPadBorderExtents(wrapperContainerNode),
  402. animationHeightOverhead = wrapperContainerNodeMargin.h + wrapperContainerNodePadBorder.h;
  403. oldContents.style.height = (self._verticalSpace - animationHeightOverhead) + "px";
  404. this._animation = new fx.Animation({
  405. node: newContents,
  406. duration: this.duration,
  407. curve: [1, this._verticalSpace - animationHeightOverhead - 1],
  408. onAnimate: function(value){
  409. value = Math.floor(value); // avoid fractional values
  410. newContents.style.height = value + "px";
  411. oldContents.style.height = (self._verticalSpace - animationHeightOverhead - value) + "px";
  412. },
  413. onEnd: function(){
  414. delete self._animation;
  415. newContents.style.height = "auto";
  416. oldWidget._wrapperWidget.containerNode.style.display = "none";
  417. oldContents.style.height = "auto";
  418. self._hideChild(oldWidget);
  419. }
  420. });
  421. this._animation.onStop = this._animation.onEnd;
  422. this._animation.play();
  423. }
  424. return d; // If child has an href, promise that fires when the widget has finished loading
  425. },
  426. // note: we are treating the container as controller here
  427. _onKeyPress: function(/*Event*/ e, /*dijit/_WidgetBase*/ fromTitle){
  428. // summary:
  429. // Handle keypress events
  430. // description:
  431. // This is called from a handler on AccordionContainer.domNode
  432. // (setup in StackContainer), and is also called directly from
  433. // the click handler for accordion labels
  434. if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){
  435. return;
  436. }
  437. var c = e.charOrCode;
  438. if((fromTitle && (c == keys.LEFT_ARROW || c == keys.UP_ARROW)) ||
  439. (e.ctrlKey && c == keys.PAGE_UP)){
  440. this._adjacent(false)._buttonWidget._onTitleClick();
  441. event.stop(e);
  442. }else if((fromTitle && (c == keys.RIGHT_ARROW || c == keys.DOWN_ARROW)) ||
  443. (e.ctrlKey && (c == keys.PAGE_DOWN || c == keys.TAB))){
  444. this._adjacent(true)._buttonWidget._onTitleClick();
  445. event.stop(e);
  446. }
  447. }
  448. });
  449. // Back compat w/1.6, remove for 2.0
  450. if(has("dijit-legacy-requires")){
  451. ready(0, function(){
  452. var requires = ["dijit/layout/AccordionPane"];
  453. require(requires); // use indirection so modules not rolled into a build
  454. });
  455. }
  456. // For monkey patching
  457. AccordionContainer._InnerContainer = AccordionInnerContainer;
  458. AccordionContainer._Button = AccordionButton;
  459. return AccordionContainer;
  460. });