source: Dev/trunk/src/client/dojox/layout/RotatorContainer.js @ 532

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

Added Dojo 1.9.3 release.

  • Property svn:executable set to *
File size: 14.6 KB
Line 
1define(["dojo/_base/declare","dojo/_base/html","dojo/_base/connect","dojo/_base/lang","dojo/_base/array",
2        "dojo/_base/fx","dojo/fx","dijit/_base/manager","dijit/layout/StackContainer","dijit/layout/StackController","dijit/_Widget",
3        "dijit/_Templated","dijit/_Contained"
4],function(declare,html,connect,lang,array,baseFx,coreFx,manager,
5        StackContainer,StackController,Widget,Templated,Contained){
6
7var RotatorContainer = declare("dojox.layout.RotatorContainer",[StackContainer, Templated], {
8        // summary:
9        //              Extends a StackContainer to automatically transition between children
10        //              and display navigation in the form of tabs or a pager.
11        //
12        // description:
13        //              The RotatorContainer cycles through the children with a transition.
14        //
15        //              ####published topics:
16        //
17        //              [widgetId]-update - Notifies pager(s) that a child has changed.
18        //              Parameters:
19        //
20        //              - /*boolean*/ playing - true if playing, false if paused
21        //              - /*int*/ current     - current selected child
22        //              - /*int*/ total       - total number of children
23        //
24        // example:
25        // |    <div dojoType="dojox.layout.RotatorContainer" id="myRotator" showTabs="true" autoStart="true" transitionDelay="5000">
26        // |            <div id="pane1" dojoType="dijit.layout.ContentPane" title="1">
27        // |                    Pane 1!
28        // |            </div>
29        // |            <div id="pane2" dojoType="dijit.layout.ContentPane" title="2">
30        // |                    Pane 2!
31        // |            </div>
32        // |            <div id="pane3" dojoType="dijit.layout.ContentPane" title="3" transitionDelay="10000">
33        // |                    Pane 3 with overrided transitionDelay!
34        // |            </div>
35        // |    </div>
36
37        templateString: '<div class="dojoxRotatorContainer"><div dojoAttachPoint="tabNode"></div><div class="dojoxRotatorPager" dojoAttachPoint="pagerNode"></div><div class="dojoxRotatorContent" dojoAttachPoint="containerNode"></div></div>',
38
39        // showTabs: Boolean
40        //              Sets the display of the tabs.  The tabs are actually a StackController.
41        //              The child's title is used for the tab's label.
42        showTabs: true,
43
44        // transitionDelay: int
45        //              The delay in milliseconds before transitioning to the next child.
46        transitionDelay: 5000,
47
48        // transition: String
49        //              The type of transition to perform when switching children.
50        //              A null transition will transition instantly.
51        transition: "fade",
52
53        // transitionDuration: int
54        //              The duration of the transition in milliseconds.
55        transitionDuration: 1000,
56
57        // autoStart: Boolean
58        //              Starts the timer to transition children upon creation.
59        autoStart: true,
60
61        // suspendOnHover: Boolean
62        //              Pause the rotator when the mouse hovers over it.
63        suspendOnHover: false,
64
65        // pauseOnManualChange: Boolean
66        //              Pause the rotator when the tab is changed or the pager's next/previous
67        //              buttons are clicked.
68        pauseOnManualChange: null,
69
70        // reverse: Boolean
71        //              Causes the rotator to rotate in reverse order.
72        reverse: false,
73
74        // pagerId: String
75        //              ID the pager widget.
76        pagerId: "",
77
78        // cycles: int
79        //              Number of cycles before pausing.
80        cycles: -1,
81
82        // pagerClass: String
83        //              The declared Class of the Pager used for this Widget
84        pagerClass: "dojox.layout.RotatorPager",
85
86        postCreate: function(){
87                // summary:
88                //              Initializes the DOM nodes, tabs, and transition stuff.
89                this.inherited(arguments);
90
91                // force this DOM node to a relative position and make sure the children are absolute positioned
92                html.style(this.domNode, "position", "relative");
93
94                // validate the cycles counter
95                if(this.cycles-0 == this.cycles && this.cycles != -1){
96                        // we need to add 1 because we decrement cycles before the animation starts
97                        this.cycles++;
98                }else{
99                        this.cycles = -1;
100                }
101
102                // if they didn't specify the pauseOnManualChange, then we want it to be the opposite of
103                // the suspendOnHover since it doesn't make sense to do both, unless you really want to
104                if(this.pauseOnManualChange === null){
105                        this.pauseOnManualChange = !this.suspendOnHover;
106                }
107
108                // create the stack controller if we are using tabs
109                var id = this.id || "rotator"+(new Date()).getTime(),
110                        sc = new StackController({ containerId:id }, this.tabNode);
111                this.tabNode = sc.domNode;
112                this._stackController = sc;
113                html.style(this.tabNode, "display", this.showTabs ? "" : "none");
114
115                // if the controller's tabs are clicked, check if we should pause and reset the cycle counter
116                this.connect(sc, "onButtonClick","_manualChange");
117
118                // set up our topic listeners
119                this._subscriptions = [
120                        connect.subscribe(this.id+"-cycle", this, "_cycle"),
121                        connect.subscribe(this.id+"-state", this, "_state")
122                ];
123
124                // make sure the transition duration isn't less than the transition delay
125                var d = Math.round(this.transitionDelay * 0.75);
126                if(d < this.transitionDuration){
127                        this.transitionDuration = d;
128                }
129
130                // wire up the mouse hover events
131                if(this.suspendOnHover){
132                        this.connect(this.domNode, "onmouseover", "_onMouseOver");
133                        this.connect(this.domNode, "onmouseout", "_onMouseOut");
134                }
135        },
136
137        startup: function(){
138                // summary:
139                //              Initializes the pagers.
140                if(this._started){ return; }
141
142                // check if the pager is defined within the rotator container
143                var c = this.getChildren();
144                for(var i=0, len=c.length; i<len; i++){
145                        if(c[i].declaredClass == this.pagerClass){
146                                this.pagerNode.appendChild(c[i].domNode);
147                                break;
148                        }
149                }
150
151                // process the child widgets
152                this.inherited(arguments);
153
154                // check if we should start automatically
155                if(this.autoStart){
156                        // start playing
157                        setTimeout(lang.hitch(this, "_play"), 10);
158                }else{
159                        // update the pagers with the initial state
160                        this._updatePager();
161                }
162        },
163
164        destroy: function(){
165                // summary:
166                //              Unsubscribe to all of our topics
167                array.forEach(this._subscriptions, connect.unsubscribe);
168                this.inherited(arguments);
169        },
170
171        _setShowTabsAttr: function(/*anything*/value){
172                this.showTabs = value;
173                html.style(this.tabNode, "display", value ? "" : "none");
174        },
175
176        _updatePager: function(){
177                // summary:
178                //              Notify the pager's current and total numbers.
179                var c = this.getChildren();
180                connect.publish(this.id+"-update", [this._playing, array.indexOf(c, this.selectedChildWidget)+1, c.length]);
181        },
182
183        _onMouseOver: function(){
184                // summary:
185                //              Triggered when the mouse is moved over the rotator container.
186
187                // temporarily suspend the cycling, but don't officially pause it
188                this._resetTimer();
189                this._over = true;
190        },
191
192        _onMouseOut: function(){
193                // summary:
194                //              Triggered when the mouse is moved off the rotator container.
195                this._over = false;
196
197                // if we were playing, resume playback in 200ms
198                // we need to wait because we may be moused over again right away
199                if(this._playing){
200                        clearTimeout(this._timer);
201                        this._timer = setTimeout(lang.hitch(this, "_play", true), 200);
202                }
203        },
204
205        _resetTimer: function(){
206                // summary:
207                //              Resets the timer used to start the next transition.
208                clearTimeout(this._timer);
209                this._timer = null;
210        },
211
212        _cycle: function(/*boolean or int*/next){
213                // summary:
214                //              Cycles to the next/previous child.
215
216                // if next is an int, then _cycle() was called via a timer
217                // if next is a boolean, then _cycle() was called via the next/prev buttons, stop playing and reset cycles
218                if(next instanceof Boolean || typeof next == "boolean"){
219                        this._manualChange();
220                }
221
222                var c = this.getChildren(),
223                        len = c.length,
224                        i = array.indexOf(c, this.selectedChildWidget) + (next === false || (next !== true && this.reverse) ? -1 : 1);
225                this.selectChild(c[(i < len ? (i < 0 ? len-1 : i) : 0)]);
226                this._updatePager();
227        },
228
229        _manualChange: function(){
230                // summary:
231                //              This function is only called when a manual change occurs in which
232                //              case we may need to stop playing and we need to reset the cycle counter
233                if(this.pauseOnManualChange){
234                        this._playing = false;
235                }
236                this.cycles = -1;
237        },
238
239        _play: function(skip){
240                // summary:
241                //              Schedules the next transition.
242                this._playing = true;
243                this._resetTimer();
244                if(skip !== true && this.cycles>0){
245                        this.cycles--;
246                }
247                if(this.cycles==0){
248                        this._pause();
249                }else if((!this.suspendOnHover || !this._over) && this.transitionDelay){
250                        // check if current pane has a delay
251                        this._timer = setTimeout(lang.hitch(this, "_cycle"), this.selectedChildWidget.domNode.getAttribute("transitionDelay") || this.transitionDelay);
252                }
253                this._updatePager();
254        },
255
256        _pause: function(){
257                // summary:
258                //              Clears the transition timer and pauses the rotator.
259                this._playing = false;
260                this._resetTimer();
261        },
262
263        _state: function(playing){
264                // summary:
265                //              Fired when the play/pause pager button is toggled.
266                if(playing){
267                        // since we were manually changed, disable the cycle counter
268                        this.cycles = -1;
269                        this._play();
270                }else{
271                        this._pause();
272                }
273        },
274
275        _transition: function(/*dijit._Widget*/ next, /*dijit._Widget*/ prev){
276                // summary:
277                //              Dispatches the appropriate transition.
278                this._resetTimer();
279
280                // check if we have anything to transition
281                if(prev && this.transitionDuration){
282                        switch(this.transition){
283                                case "fade": this._fade(next, prev); return;
284                        }
285                }
286
287                this._transitionEnd();
288                this.inherited(arguments);
289        },
290
291        _transitionEnd: function(){
292                if(this._playing){
293                        this._play();
294                }else{
295                        this._updatePager();
296                }
297        },
298
299        _fade: function(/*dijit._Widget*/ next, /*dijit._Widget*/ prev){
300                // summary:
301                //              Crossfades two children.
302                this._styleNode(prev.domNode, 1, 1);
303                this._styleNode(next.domNode, 0, 2);
304
305                // show the next child and make sure it's sized properly
306                this._showChild(next);
307                if(this.doLayout && next.resize){
308                        next.resize(this._containerContentBox || this._contentBox);
309                }
310
311                // create the crossfade animation
312                var args = { duration:this.transitionDuration },
313                        anim = coreFx.combine([
314                                baseFx["fadeOut"](lang.mixin({node:prev.domNode}, args)),
315                                baseFx["fadeIn"](lang.mixin({node:next.domNode}, args))
316                        ]);
317
318                this.connect(anim, "onEnd", lang.hitch(this,function(){
319                        this._hideChild(prev);
320                        this._transitionEnd();
321                }));
322
323                anim.play();
324        },
325
326        _styleNode: function(/*DomNode*/node, /*number*/opacity, /*int*/zIndex){
327                // summary:
328                //              Helper function to style the children.
329                html.style(node, "opacity", opacity);
330                html.style(node, "zIndex", zIndex);
331                html.style(node, "position", "absolute");
332        }
333});
334
335declare("dojox.layout.RotatorPager", [Widget, Templated, Contained], {
336        // summary:
337        //              Defines controls used to manipulate a RotatorContainer
338        //
339        // description:
340        //              A pager can be defined one of two ways:
341        //
342        //              - Externally of the RotatorContainer's template and tell the
343        //              RotatorPager the rotatorId of the RotatorContainer
344        //              - As a direct descendant of the RotatorContainer (i.e. inside the
345        //              RotatorContainer's template)
346        //
347        //              The pager can contain the following components:
348        //
349        //              - Previous button
350        //                      - Must be a dijit.form.Button
351        //                      - dojoAttachPoint must be named "previous"
352        //              - Next button
353        //                      - Must be a dijit.form.Button
354        //                      - dojoAttachPoint must be named "next"
355        //              - Play/Pause toggle button
356        //                      - Must be a dijit.form.ToggleButton
357        //                      - dojoAttachPoint must be named "playPause"
358        //                      - Use iconClass to specify toggled state
359        //              - Current child #
360        //                      - dojoAttachPoint must be named "current"
361        //              - Total # of children
362        //                      - dojoAttachPoint must be named "total"
363        //
364        //              You can choose to exclude specific controls as well as add elements
365        //              for styling.
366        //
367        //              Should you need a pager, but don't want to use Dijit buttons, you can
368        //              write your own pager widget and just wire it into the topics.  The
369        //              topic names are prefixed with the widget ID of the RotatorContainer.
370        //              Notifications are received from and sent to the RotatorContainer as
371        //              well as other RotatorPagers.
372        //
373        //              ####published topics:
374        //
375        //              [widgetId]-cycle - Notify that the next or previous button was pressed.
376        //              Parameters:
377        //
378        //              - /*boolean*/ next - true if next, false if previous
379        //
380        //              [widgetId]-state - Notify that the play/pause button was toggled.
381        //              Parameters:
382        //
383        //              - /*boolean*/ playing - true if playing, false if paused
384        //
385        // example:
386        //              A pager with the current/total children and previous/next buttons.
387        // |    <div dojoType="dojox.layout.RotatorPager" rotatorId="myRotator">
388        // |            <button dojoType="dijit.form.Button" dojoAttachPoint="previous">Prev</button>
389        // |            <span dojoAttachPoint="current"></span> / <span dojoAttachPoint="total"></span>
390        // |            <button dojoType="dijit.form.Button" dojoAttachPoint="next">Next</button>
391        // |    </div>
392        //
393        // example:
394        //              A pager with only a play/pause toggle button.
395        // |    <div dojoType="dojox.layout.RotatorPager" rotatorId="myRotator">
396        // |            <button dojoType="dijit.form.ToggleButton" dojoAttachPoint="playPause"></button>
397        // |    </div>
398        //
399        // example:
400        //              A pager styled with iconClass.
401        // |    <div dojoType="dojox.layout.RotatorPager" class="rotatorIcons" rotatorId="myRotator">
402        // |            <button dojoType="dijit.form.Button" iconClass="previous" dojoAttachPoint="previous">Prev</button>
403        // |            <button dojoType="dijit.form.ToggleButton" iconClass="playPause" dojoAttachPoint="playPause"></button>
404        // |            <button dojoType="dijit.form.Button" iconClass="next" dojoAttachPoint="next">Next</button>
405        // |            <span dojoAttachPoint="current"></span> / <span dojoAttachPoint="total"></span>
406        // |    </div>
407
408        widgetsInTemplate: true,
409
410        // rotatorId: int
411        //              The ID of the rotator this pager is tied to.
412        //              Only required if defined outside of the RotatorContainer's container.
413        rotatorId: "",
414
415        postMixInProperties: function(){
416                this.templateString = "<div>" + this.srcNodeRef.innerHTML + "</div>";
417        },
418
419        postCreate: function(){
420                var p = manager.byId(this.rotatorId) || this.getParent();
421                if(p && p.declaredClass == "dojox.layout.RotatorContainer"){
422                        if(this.previous){
423                                connect.connect(this.previous, "onClick", function(){
424                                        connect.publish(p.id+"-cycle", [false]);
425                                });
426                        }
427                        if(this.next){
428                                connect.connect(this.next, "onClick", function(){
429                                        connect.publish(p.id+"-cycle", [true]);
430                                });
431                        }
432                        if(this.playPause){
433                                connect.connect(this.playPause, "onClick", function(){
434                                        this.set('label', this.checked ? "Pause" : "Play");
435                                        connect.publish(p.id+"-state", [this.checked]);
436                                });
437                        }
438                        this._subscriptions = [
439                                connect.subscribe(p.id+"-state", this, "_state"),
440                                connect.subscribe(p.id+"-update", this, "_update")
441                        ];
442                }
443        },
444
445        destroy: function(){
446                // Unsubscribe to all of our topics
447                array.forEach(this._subscriptions, connect.unsubscribe);
448                this.inherited(arguments);
449        },
450
451        _state: function(/*boolean*/playing){
452                // summary:
453                //              Updates the display of the play/pause button
454                if(this.playPause && this.playPause.checked != playing){
455                        this.playPause.set("label", playing ? "Pause" : "Play");
456                        this.playPause.set("checked", playing);
457                }
458        },
459
460        _update: function(/*boolean*/playing, /*int*/current, /*int*/total){
461                // summary:
462                //              Updates the pager's play/pause button, current child, and total number of children.
463                this._state(playing);
464                if(this.current && current){
465                        this.current.innerHTML = current;
466                }
467                if(this.total && total){
468                        this.total.innerHTML = total;
469                }
470        }
471});
472return RotatorContainer;
473});
Note: See TracBrowser for help on using the repository browser.