source: Dev/branches/rest-dojo-ui/client/dojox/layout/RotatorContainer.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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