source: Dev/trunk/src/client/dojox/mobile/Accordion.js

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 15.6 KB
Line 
1define([
2        "dojo/_base/array",
3        "dojo/_base/declare",
4        "dojo/_base/lang",
5        "dojo/sniff",
6        "dojo/dom",
7        "dojo/dom-class",
8        "dojo/dom-construct",
9        "dojo/dom-attr",
10        "dijit/_Contained",
11        "dijit/_Container",
12        "dijit/_WidgetBase",
13        "./iconUtils",
14        "./lazyLoadUtils",
15        "./_css3",
16        "require",
17        "dojo/has!dojo-bidi?dojox/mobile/bidi/Accordion"
18], function(array, declare, lang, has, dom, domClass, domConstruct, domAttr, Contained, Container, WidgetBase, iconUtils, lazyLoadUtils, css3, require, BidiAccordion){
19
20        // module:
21        //              dojox/mobile/Accordion
22
23        // inner class
24        var _AccordionTitle = declare([WidgetBase, Contained], {
25                // summary:
26                //              A widget for the title of the accordion.
27       
28                // label: String
29                //              The title of the accordion.
30                label: "Label",
31               
32                // icon1: String
33                //              A path for the unselected (typically dark) icon. If icon is not
34                //              specified, the iconBase parameter of the parent widget is used.
35                icon1: "",
36
37                // icon2: String
38                //              A path for the selected (typically highlight) icon. If icon is
39                //              not specified, the iconBase parameter of the parent widget or
40                //              icon1 is used.
41                icon2: "",
42
43                // iconPos1: String
44                //              The position of an aggregated unselected (typically dark)
45                //              icon. IconPos1 is a comma-separated list of values like
46                //              top,left,width,height (ex. "0,0,29,29"). If iconPos1 is not
47                //              specified, the iconPos parameter of the parent widget is used.
48                iconPos1: "",
49
50                // iconPos2: String
51                //              The position of an aggregated selected (typically highlight)
52                //              icon. IconPos2 is a comma-separated list of values like
53                //              top,left,width,height (ex. "0,0,29,29"). If iconPos2 is not
54                //              specified, the iconPos parameter of the parent widget or
55                //              iconPos1 is used.
56                iconPos2: "",
57
58                // selected: Boolean
59                //              If true, the widget is in the selected state.
60                selected: false,
61
62                // baseClass: String
63                //              The name of the CSS class of this widget.
64                baseClass: "mblAccordionTitle",
65
66                buildRendering: function(){
67                        this.inherited(arguments);
68
69                        var a = this.anchorNode = domConstruct.create("a", {
70                                className: "mblAccordionTitleAnchor",
71                                role: "presentation"
72                        }, this.domNode);
73
74                        // text box
75                        this.textBoxNode = domConstruct.create("div", {className:"mblAccordionTitleTextBox"}, a);
76                        this.labelNode = domConstruct.create("span", {
77                                className: "mblAccordionTitleLabel",
78                                innerHTML: this._cv ? this._cv(this.label) : this.label
79                        }, this.textBoxNode);
80                        this._isOnLine = this.inheritParams();
81
82                        domAttr.set(this.textBoxNode, "role", "tab"); // A11Y
83                        domAttr.set(this.textBoxNode, "tabindex", "0");
84                },
85
86                postCreate: function(){
87                        this.connect(this.domNode, "onclick", "_onClick");
88                        dom.setSelectable(this.domNode, false);
89                },
90
91                inheritParams: function(){
92                        var parent = this.getParent();
93                        if(parent){
94                                if(this.icon1 && parent.iconBase &&
95                                        parent.iconBase.charAt(parent.iconBase.length - 1) === '/'){
96                                        this.icon1 = parent.iconBase + this.icon1;
97                                }
98                                if(!this.icon1){ this.icon1 = parent.iconBase; }
99                                if(!this.iconPos1){ this.iconPos1 = parent.iconPos; }
100                                if(this.icon2 && parent.iconBase &&
101                                        parent.iconBase.charAt(parent.iconBase.length - 1) === '/'){
102                                        this.icon2 = parent.iconBase + this.icon2;
103                                }
104                                if(!this.icon2){ this.icon2 = parent.iconBase || this.icon1; }
105                                if(!this.iconPos2){ this.iconPos2 = parent.iconPos || this.iconPos1; }
106                        }
107                        return !!parent;
108                },
109
110                _setIcon: function(icon, n){
111                        // tags:
112                        //              private
113                        if(!this.getParent()){ return; } // icon may be invalid because inheritParams is not called yet
114                        this._set("icon" + n, icon);
115                        if(!this["iconParentNode" + n]){
116                                this["iconParentNode" + n] = domConstruct.create("div",
117                                        {className:"mblAccordionIconParent mblAccordionIconParent" + n}, this.anchorNode, "first");
118                        }
119                        this["iconNode" + n] = iconUtils.setIcon(icon, this["iconPos" + n],
120                                this["iconNode" + n], this.alt, this["iconParentNode" + n]);
121                        this["icon" + n] = icon;
122                        domClass.toggle(this.domNode, "mblAccordionHasIcon", icon && icon !== "none");
123                },
124
125                _setIcon1Attr: function(icon){
126                        // tags:
127                        //              private
128                        this._setIcon(icon, 1);
129                },
130
131                _setIcon2Attr: function(icon){
132                        // tags:
133                        //              private
134                        this._setIcon(icon, 2);
135                },
136
137                startup: function(){
138                        if(this._started){ return; }
139                        if(!this._isOnLine){
140                                this.inheritParams();
141                        }
142                        if(!this._isOnLine){
143                                this.set({ // retry applying the attribute
144                                        icon1: this.icon1,
145                                        icon2: this.icon2
146                                });
147                        }
148                        this.inherited(arguments);
149                },
150
151                _onClick: function(e){
152                        // summary:
153                        //              Internal handler for click events.
154                        // tags:
155                        //              private
156                        if(this.onClick(e) === false){ return; } // user's click action
157                        var p = this.getParent();
158                        if(!p.fixedHeight && this.contentWidget.domNode.style.display !== "none"){
159                                p.collapse(this.contentWidget, !p.animation);
160                        }else{
161                                p.expand(this.contentWidget, !p.animation);
162                        }
163                },
164
165                onClick: function(/*Event*/ /*===== e =====*/){
166                        // summary:
167                        //              User-defined function to handle clicks
168                        // tags:
169                        //              callback
170                },
171
172                _setSelectedAttr: function(/*Boolean*/selected){
173                        // tags:
174                        //              private
175                        domClass.toggle(this.domNode, "mblAccordionTitleSelected", selected);
176                        this._set("selected", selected);
177                }
178        });
179
180        var Accordion = declare(has("dojo-bidi") ? "dojox.mobile.NonBidiAccordion" : "dojox.mobile.Accordion", [WidgetBase, Container, Contained], {
181                // summary:
182                //              A container widget that can display a group of child panes in a stacked format.
183                // description:
184                //              Typically, dojox/mobile/Pane, dojox/mobile/Container, or dojox/mobile/ContentPane are
185                //              used as child widgets, but Accordion requires no specific child widget.
186                //              Accordion supports three modes for opening child panes: multiselect, fixed-height,
187                //              and single-select. Accordion can have rounded corners, and it can lazy-load the
188                //              content modules.
189
190                // iconBase: String
191                //              The default icon path for child widgets.
192                iconBase: "",
193
194                // iconPos: String
195                //              The default icon position for child widgets.
196                iconPos: "",
197
198                // fixedHeight: Boolean
199                //              If true, the entire accordion widget has fixed height regardless
200                //              of the height of each pane; in this mode, there is always an open pane and
201                //              collapsing a pane can only be done by opening a different pane.
202                fixedHeight: false,
203
204                // singleOpen: Boolean
205                //              If true, only one pane is open at a time. The current open pane
206                //              is collapsed, when another pane is opened.
207                singleOpen: false,
208
209                // animation: Boolean
210                //              If true, animation is used when a pane is opened or
211                //              collapsed. The animation works only on webkit browsers.
212                animation: true,
213
214                // roundRect: Boolean
215                //              If true, the widget shows rounded corners.
216                //              Adding the "mblAccordionRoundRect" class to domNode has the same effect.
217                roundRect: false,
218
219                /* internal properties */
220                duration: .3, // [seconds]
221
222                // baseClass: String
223                //              The name of the CSS class of this widget.
224                baseClass: "mblAccordion",
225
226                // _openSpace: [private] Number|String
227                _openSpace: 1,
228
229                buildRendering: function(){
230                        this.inherited(arguments);
231                        domAttr.set(this.domNode, "role", "tablist"); // A11Y
232                        domAttr.set(this.domNode, "aria-multiselectable", !this.singleOpen); // A11Y
233                },
234               
235                startup: function(){
236                        if(this._started){ return; }
237
238                        if(domClass.contains(this.domNode, "mblAccordionRoundRect")){
239                                this.roundRect = true;
240                        }else if(this.roundRect){
241                                domClass.add(this.domNode, "mblAccordionRoundRect");
242                        }
243
244                        if(this.fixedHeight){
245                                this.singleOpen = true;
246                        }
247                        var children = this.getChildren();
248                        array.forEach(children, this._setupChild, this);
249                        var sel;
250                        var posinset = 1;
251                        array.forEach(children, function(child){
252                                child.startup();
253                                child._at.startup();
254                                this.collapse(child, true);
255                                domAttr.set(child._at.textBoxNode, "aria-setsize", children.length);
256                                domAttr.set(child._at.textBoxNode, "aria-posinset", posinset++);
257                                if(child.selected){
258                                        sel = child;
259                                }
260                        }, this);
261                        if(!sel && this.fixedHeight){
262                                sel = children[children.length - 1];
263                        }
264                        if(sel){
265                                this.expand(sel, true);
266                        }else{
267                                this._updateLast();
268                        }
269                        this.defer(function(){ this.resize(); });
270
271                        this._started = true;
272                },
273
274                _setupChild: function(/*Widget*/ child){
275                        // tags:
276                        //              private
277                        if(child.domNode.style.overflow != "hidden"){
278                                child.domNode.style.overflow = this.fixedHeight ? "auto" : "hidden";
279                        }
280                        child._at = new _AccordionTitle({
281                                label: child.label,
282                                alt: child.alt,
283                                icon1: child.icon1,
284                                icon2: child.icon2,
285                                iconPos1: child.iconPos1,
286                                iconPos2: child.iconPos2,
287                                contentWidget: child
288                        });
289                        domConstruct.place(child._at.domNode, child.domNode, "before");
290                        domClass.add(child.domNode, "mblAccordionPane");
291                        domAttr.set(child._at.textBoxNode, "aria-controls", child.domNode.id); // A11Y
292                        domAttr.set(child.domNode, "role", "tabpanel"); // A11Y
293                        domAttr.set(child.domNode, "aria-labelledby", child._at.id); // A11Y
294                },
295
296                addChild: function(/*Widget*/ widget, /*int?*/ insertIndex){
297                        this.inherited(arguments);
298                        if(this._started){
299                                this._setupChild(widget);
300                                widget._at.startup();
301                                if(widget.selected){
302                                        this.expand(widget, true);
303                                        this.defer(function(){
304                                                widget.domNode.style.height = "";
305                                        });
306                                }else{
307                                        this.collapse(widget);
308                                }
309                        }
310                        this._addChildAriaAttrs();
311                },
312
313                removeChild: function(/*Widget|int*/ widget){
314                        if(typeof widget == "number"){
315                                widget = this.getChildren()[widget];
316                        }
317                        if(widget){
318                                widget._at.destroy();
319                        }
320                        this.inherited(arguments);
321                        this._addChildAriaAttrs();
322                },
323               
324                _addChildAriaAttrs: function(){
325                        var posinset = 1;
326                        var children = this.getChildren();
327                        array.forEach(children, function(child){
328                                domAttr.set(child._at.textBoxNode, "aria-posinset", posinset++);
329                                domAttr.set(child._at.textBoxNode, "aria-setsize", children.length);
330                        });
331                },
332
333                getChildren: function(){
334                        return array.filter(this.inherited(arguments), function(child){
335                                return !(child instanceof _AccordionTitle);
336                        });
337                },
338
339                getSelectedPanes: function(){
340                        return array.filter(this.getChildren(), function(pane){
341                                return pane.domNode.style.display != "none";
342                        });
343                },
344
345                resize: function(){
346                        if(this.fixedHeight){
347                                var panes = array.filter(this.getChildren(), function(child){ // active pages
348                                        return child._at.domNode.style.display != "none";
349                                });
350                                var openSpace = this.domNode.clientHeight; // height of all panes
351                                array.forEach(panes, function(child){
352                                        openSpace -= child._at.domNode.offsetHeight;
353                                });
354                                this._openSpace = openSpace > 0 ? openSpace : 0;
355                                var sel = this.getSelectedPanes()[0];
356                                sel.domNode.style[css3.name("transition")] = "";
357                                sel.domNode.style.height = this._openSpace + "px";
358                        }
359                },
360
361                _updateLast: function(){
362                        // tags:
363                        //              private
364                        var children = this.getChildren();
365                        array.forEach(children, function(c, i){
366                                // add "mblAccordionTitleLast" to the last, closed accordion title
367                                domClass.toggle(c._at.domNode, "mblAccordionTitleLast",
368                                        i === children.length - 1 && !domClass.contains(c._at.domNode, "mblAccordionTitleSelected"))
369                        }, this);
370                },
371
372                expand: function(/*Widget*/pane, /*boolean*/noAnimation){
373                        // summary:
374                        //              Expands the given pane to make it visible.
375                        // pane:
376                        //              A pane widget to expand.
377                        // noAnimation:
378                        //              If true, the pane expands immediately without animation effect.
379                        if(pane.lazy){
380                                lazyLoadUtils.instantiateLazyWidgets(pane.containerNode, pane.requires);
381                                pane.lazy = false;
382                        }
383                        var children = this.getChildren();
384                        array.forEach(children, function(c, i){
385                                c.domNode.style[css3.name("transition")] = noAnimation ? "" : "height "+this.duration+"s linear";
386                                if(c === pane){
387                                        c.domNode.style.display = "";
388                                        var h;
389                                        if(this.fixedHeight){
390                                                h = this._openSpace;
391                                        }else{
392                                                h = parseInt(c.height || c.domNode.getAttribute("height")); // ScrollableView may have the height property
393                                                if(!h){
394                                                        c.domNode.style.height = "";
395                                                        h = c.domNode.offsetHeight;
396                                                        c.domNode.style.height = "0px";
397                                                }
398                                        }
399                                        this.defer(function(){ // necessary for webkitTransition to work
400                                                c.domNode.style.height = h + "px";
401                                        });
402                                        this.select(pane);
403                                }else if(this.singleOpen){
404                                        this.collapse(c, noAnimation);
405                                }
406                        }, this);
407                        this._updateLast();
408                        domAttr.set(pane.domNode, "aria-expanded", "true"); // A11Y
409                        domAttr.set(pane.domNode, "aria-hidden", "false"); // A11Y
410                },
411
412                collapse: function(/*Widget*/pane, /*boolean*/noAnimation){
413                        // summary:
414                        //              Collapses the given pane to close it.
415                        // pane:
416                        //              A pane widget to collapse.
417                        // noAnimation:
418                        //              If true, the pane collapses immediately without animation effect.
419                        if(pane.domNode.style.display === "none"){ return; } // already collapsed
420                        pane.domNode.style[css3.name("transition")] = noAnimation ? "" : "height "+this.duration+"s linear";
421                        pane.domNode.style.height = "0px";
422                        if(!has("css3-animations") || noAnimation){
423                                pane.domNode.style.display = "none";
424                                this._updateLast();
425                        }else{
426                                // Adding a webkitTransitionEnd handler to panes may cause conflict
427                                // when the panes already have the one. (e.g. ScrollableView)
428                                var _this = this;
429                                _this.defer(function(){
430                                        pane.domNode.style.display = "none";
431                                        _this._updateLast();
432
433                                        // Need to call parent view's resize() especially when the Accordion is
434                                        // on a ScrollableView, the ScrollableView is scrolled to
435                                        // the bottom, and then expand any other pane while in the
436                                        // non-fixed singleOpen mode.
437                                        if(!_this.fixedHeight && _this.singleOpen){
438                                                for(var v = _this.getParent(); v; v = v.getParent()){
439                                                        if(domClass.contains(v.domNode, "mblView")){
440                                                                if(v && v.resize){ v.resize(); }
441                                                                break;
442                                                        }
443                                                }
444                                        }
445                                }, this.duration*1000);
446                        }
447                        this.deselect(pane);
448                        domAttr.set(pane.domNode, "aria-expanded", "false"); // A11Y
449                        domAttr.set(pane.domNode, "aria-hidden", "true"); // A11Y
450                },
451
452                select: function(/*Widget*/pane){
453                        // summary:
454                        //              Highlights the title bar of the given pane.
455                        // pane:
456                        //              A pane widget to highlight.
457                        pane._at.set("selected", true);
458                        domAttr.set(pane._at.textBoxNode, "aria-selected", "true"); // A11Y
459                },
460
461                deselect: function(/*Widget*/pane){
462                        // summary:
463                        //              Unhighlights the title bar of the given pane.
464                        // pane:
465                        //              A pane widget to unhighlight.
466                        pane._at.set("selected", false);
467                        domAttr.set(pane._at.textBoxNode, "aria-selected", "false"); // A11Y
468                }
469        });
470       
471        Accordion.ChildWidgetProperties = {
472                // summary:
473                //              These properties can be specified for the children of a dojox/mobile/Accordion.
474
475                // alt: String
476                //              The alternate text of the Accordion title.
477                alt: "",
478                // label: String
479                //              The label of the Accordion title.
480                label: "",
481                // icon1: String
482                //              The unselected icon of the Accordion title.
483                icon1: "",
484                // icon2: String
485                //              The selected icon of the Accordion title.
486                icon2: "",
487                // iconPos1: String
488                //              The position ("top,left,width,height") of the unselected aggregated icon of the Accordion title.
489                iconPos1: "",
490                // iconPos2: String
491                //              The position ("top,left,width,height") of the selected aggregated icon of the Accordion title.
492                iconPos2: "",
493                // selected: Boolean
494                //              The selected state of the Accordion title.
495                selected: false,
496                // lazy: Boolean
497                //              Specifies that the Accordion child must be lazily loaded.
498                lazy: false
499        };
500
501        // Since any widget can be specified as an Accordion child, mix ChildWidgetProperties
502        // into the base widget class.  (This is a hack, but it's effective.)
503        // This is for the benefit of the parser.   Remove for 2.0.  Also, hide from doc viewer.
504        lang.extend(WidgetBase, /*===== {} || =====*/ Accordion.ChildWidgetProperties);
505
506        return has("dojo-bidi") ? declare("dojox.mobile.Accordion", [Accordion, BidiAccordion]) : Accordion;
507});
Note: See TracBrowser for help on using the repository browser.