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