UNPKG

dojox

Version:

Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.

474 lines (414 loc) 15 kB
define(["dojo/_base/declare","dojo/_base/html","dojo/_base/connect","dojo/_base/lang","dojo/_base/array", "dojo/_base/fx","dojo/fx","dijit/_base/manager","dijit/layout/StackContainer","dijit/layout/StackController","dijit/_Widget", "dijit/_Templated","dijit/_Contained" ],function(declare,html,connect,lang,array,baseFx,coreFx,manager, StackContainer,StackController,Widget,Templated,Contained){ var RotatorContainer = declare("dojox.layout.RotatorContainer",[StackContainer, Templated], { // summary: // Extends a StackContainer to automatically transition between children // and display navigation in the form of tabs or a pager. // // description: // The RotatorContainer cycles through the children with a transition. // // ####published topics: // // [widgetId]-update - Notifies pager(s) that a child has changed. // Parameters: // // - /*boolean*/ playing - true if playing, false if paused // - /*int*/ current - current selected child // - /*int*/ total - total number of children // // example: // | <div dojoType="dojox.layout.RotatorContainer" id="myRotator" showTabs="true" autoStart="true" transitionDelay="5000"> // | <div id="pane1" dojoType="dijit.layout.ContentPane" title="1"> // | Pane 1! // | </div> // | <div id="pane2" dojoType="dijit.layout.ContentPane" title="2"> // | Pane 2! // | </div> // | <div id="pane3" dojoType="dijit.layout.ContentPane" title="3" transitionDelay="10000"> // | Pane 3 with overrided transitionDelay! // | </div> // | </div> templateString: '<div class="dojoxRotatorContainer"><div dojoAttachPoint="tabNode"></div><div class="dojoxRotatorPager" dojoAttachPoint="pagerNode"></div><div class="dojoxRotatorContent" dojoAttachPoint="containerNode"></div></div>', // showTabs: Boolean // Sets the display of the tabs. The tabs are actually a StackController. // The child's title is used for the tab's label. showTabs: true, // transitionDelay: int // The delay in milliseconds before transitioning to the next child. transitionDelay: 5000, // transition: String // The type of transition to perform when switching children. // A null transition will transition instantly. transition: "fade", // transitionDuration: int // The duration of the transition in milliseconds. transitionDuration: 1000, // autoStart: Boolean // Starts the timer to transition children upon creation. autoStart: true, // suspendOnHover: Boolean // Pause the rotator when the mouse hovers over it. suspendOnHover: false, // pauseOnManualChange: Boolean // Pause the rotator when the tab is changed or the pager's next/previous // buttons are clicked. pauseOnManualChange: null, // reverse: Boolean // Causes the rotator to rotate in reverse order. reverse: false, // pagerId: String // ID the pager widget. pagerId: "", // cycles: int // Number of cycles before pausing. cycles: -1, // pagerClass: String // The declared Class of the Pager used for this Widget pagerClass: "dojox.layout.RotatorPager", postCreate: function(){ // summary: // Initializes the DOM nodes, tabs, and transition stuff. this.inherited(arguments); // force this DOM node to a relative position and make sure the children are absolute positioned html.style(this.domNode, "position", "relative"); // validate the cycles counter if(this.cycles-0 == this.cycles && this.cycles != -1){ // we need to add 1 because we decrement cycles before the animation starts this.cycles++; }else{ this.cycles = -1; } // if they didn't specify the pauseOnManualChange, then we want it to be the opposite of // the suspendOnHover since it doesn't make sense to do both, unless you really want to if(this.pauseOnManualChange === null){ this.pauseOnManualChange = !this.suspendOnHover; } // create the stack controller if we are using tabs var id = this.id || "rotator"+(new Date()).getTime(), sc = new StackController({ containerId:id }, this.tabNode); this.tabNode = sc.domNode; this._stackController = sc; html.style(this.tabNode, "display", this.showTabs ? "" : "none"); // if the controller's tabs are clicked, check if we should pause and reset the cycle counter this.connect(sc, "onButtonClick","_manualChange"); // set up our topic listeners this._subscriptions = [ connect.subscribe(this.id+"-cycle", this, "_cycle"), connect.subscribe(this.id+"-state", this, "_state") ]; // make sure the transition duration isn't less than the transition delay var d = Math.round(this.transitionDelay * 0.75); if(d < this.transitionDuration){ this.transitionDuration = d; } // wire up the mouse hover events if(this.suspendOnHover){ this.connect(this.domNode, "onmouseover", "_onMouseOver"); this.connect(this.domNode, "onmouseout", "_onMouseOut"); } }, startup: function(){ // summary: // Initializes the pagers. if(this._started){ return; } // check if the pager is defined within the rotator container var c = this.getChildren(); for(var i=0, len=c.length; i<len; i++){ if(c[i].declaredClass == this.pagerClass){ this.pagerNode.appendChild(c[i].domNode); break; } } // process the child widgets this.inherited(arguments); // check if we should start automatically if(this.autoStart){ // start playing setTimeout(lang.hitch(this, "_play"), 10); }else{ // update the pagers with the initial state this._updatePager(); } }, destroy: function(){ // summary: // Unsubscribe to all of our topics array.forEach(this._subscriptions, connect.unsubscribe); this.inherited(arguments); }, _setShowTabsAttr: function(/*anything*/value){ this.showTabs = value; html.style(this.tabNode, "display", value ? "" : "none"); }, _updatePager: function(){ // summary: // Notify the pager's current and total numbers. var c = this.getChildren(); connect.publish(this.id+"-update", [this._playing, array.indexOf(c, this.selectedChildWidget)+1, c.length]); }, _onMouseOver: function(){ // summary: // Triggered when the mouse is moved over the rotator container. // temporarily suspend the cycling, but don't officially pause it this._resetTimer(); this._over = true; }, _onMouseOut: function(){ // summary: // Triggered when the mouse is moved off the rotator container. this._over = false; // if we were playing, resume playback in 200ms // we need to wait because we may be moused over again right away if(this._playing){ clearTimeout(this._timer); this._timer = setTimeout(lang.hitch(this, "_play", true), 200); } }, _resetTimer: function(){ // summary: // Resets the timer used to start the next transition. clearTimeout(this._timer); this._timer = null; }, _cycle: function(/*boolean or int*/next){ // summary: // Cycles to the next/previous child. // if next is an int, then _cycle() was called via a timer // if next is a boolean, then _cycle() was called via the next/prev buttons, stop playing and reset cycles if(next instanceof Boolean || typeof next == "boolean"){ this._manualChange(); } var c = this.getChildren(), len = c.length, i = array.indexOf(c, this.selectedChildWidget) + (next === false || (next !== true && this.reverse) ? -1 : 1); this.selectChild(c[(i < len ? (i < 0 ? len-1 : i) : 0)]); this._updatePager(); }, _manualChange: function(){ // summary: // This function is only called when a manual change occurs in which // case we may need to stop playing and we need to reset the cycle counter if(this.pauseOnManualChange){ this._playing = false; } this.cycles = -1; }, _play: function(skip){ // summary: // Schedules the next transition. this._playing = true; this._resetTimer(); if(skip !== true && this.cycles>0){ this.cycles--; } if(this.cycles==0){ this._pause(); }else if((!this.suspendOnHover || !this._over) && this.transitionDelay){ // check if current pane has a delay this._timer = setTimeout(lang.hitch(this, "_cycle"), this.selectedChildWidget.domNode.getAttribute("transitionDelay") || this.transitionDelay); } this._updatePager(); }, _pause: function(){ // summary: // Clears the transition timer and pauses the rotator. this._playing = false; this._resetTimer(); }, _state: function(playing){ // summary: // Fired when the play/pause pager button is toggled. if(playing){ // since we were manually changed, disable the cycle counter this.cycles = -1; this._play(); }else{ this._pause(); } }, _transition: function(/*dijit._Widget*/ next, /*dijit._Widget*/ prev){ // summary: // Dispatches the appropriate transition. this._resetTimer(); // check if we have anything to transition if(prev && this.transitionDuration){ switch(this.transition){ case "fade": this._fade(next, prev); return; } } this._transitionEnd(); this.inherited(arguments); }, _transitionEnd: function(){ if(this._playing){ this._play(); }else{ this._updatePager(); } }, _fade: function(/*dijit._Widget*/ next, /*dijit._Widget*/ prev){ // summary: // Crossfades two children. this._styleNode(prev.domNode, 1, 1); this._styleNode(next.domNode, 0, 2); // show the next child and make sure it's sized properly this._showChild(next); if(this.doLayout && next.resize){ next.resize(this._containerContentBox || this._contentBox); } // create the crossfade animation var args = { duration:this.transitionDuration }, anim = coreFx.combine([ baseFx["fadeOut"](lang.mixin({node:prev.domNode}, args)), baseFx["fadeIn"](lang.mixin({node:next.domNode}, args)) ]); this.connect(anim, "onEnd", lang.hitch(this,function(){ this._hideChild(prev); this._transitionEnd(); })); anim.play(); }, _styleNode: function(/*DomNode*/node, /*number*/opacity, /*int*/zIndex){ // summary: // Helper function to style the children. html.style(node, "opacity", opacity); html.style(node, "zIndex", zIndex); html.style(node, "position", "absolute"); } }); declare("dojox.layout.RotatorPager", [Widget, Templated, Contained], { // summary: // Defines controls used to manipulate a RotatorContainer // // description: // A pager can be defined one of two ways: // // - Externally of the RotatorContainer's template and tell the // RotatorPager the rotatorId of the RotatorContainer // - As a direct descendant of the RotatorContainer (i.e. inside the // RotatorContainer's template) // // The pager can contain the following components: // // - Previous button // - Must be a dijit.form.Button // - dojoAttachPoint must be named "previous" // - Next button // - Must be a dijit.form.Button // - dojoAttachPoint must be named "next" // - Play/Pause toggle button // - Must be a dijit.form.ToggleButton // - dojoAttachPoint must be named "playPause" // - Use iconClass to specify toggled state // - Current child # // - dojoAttachPoint must be named "current" // - Total # of children // - dojoAttachPoint must be named "total" // // You can choose to exclude specific controls as well as add elements // for styling. // // Should you need a pager, but don't want to use Dijit buttons, you can // write your own pager widget and just wire it into the topics. The // topic names are prefixed with the widget ID of the RotatorContainer. // Notifications are received from and sent to the RotatorContainer as // well as other RotatorPagers. // // ####published topics: // // [widgetId]-cycle - Notify that the next or previous button was pressed. // Parameters: // // - /*boolean*/ next - true if next, false if previous // // [widgetId]-state - Notify that the play/pause button was toggled. // Parameters: // // - /*boolean*/ playing - true if playing, false if paused // // example: // A pager with the current/total children and previous/next buttons. // | <div dojoType="dojox.layout.RotatorPager" rotatorId="myRotator"> // | <button dojoType="dijit.form.Button" dojoAttachPoint="previous">Prev</button> // | <span dojoAttachPoint="current"></span> / <span dojoAttachPoint="total"></span> // | <button dojoType="dijit.form.Button" dojoAttachPoint="next">Next</button> // | </div> // // example: // A pager with only a play/pause toggle button. // | <div dojoType="dojox.layout.RotatorPager" rotatorId="myRotator"> // | <button dojoType="dijit.form.ToggleButton" dojoAttachPoint="playPause"></button> // | </div> // // example: // A pager styled with iconClass. // | <div dojoType="dojox.layout.RotatorPager" class="rotatorIcons" rotatorId="myRotator"> // | <button dojoType="dijit.form.Button" iconClass="previous" dojoAttachPoint="previous">Prev</button> // | <button dojoType="dijit.form.ToggleButton" iconClass="playPause" dojoAttachPoint="playPause"></button> // | <button dojoType="dijit.form.Button" iconClass="next" dojoAttachPoint="next">Next</button> // | <span dojoAttachPoint="current"></span> / <span dojoAttachPoint="total"></span> // | </div> widgetsInTemplate: true, // rotatorId: int // The ID of the rotator this pager is tied to. // Only required if defined outside of the RotatorContainer's container. rotatorId: "", postMixInProperties: function(){ this.templateString = "<div>" + this.srcNodeRef.innerHTML + "</div>"; }, postCreate: function(){ var p = manager.byId(this.rotatorId) || this.getParent(); if(p && p.declaredClass == "dojox.layout.RotatorContainer"){ if(this.previous){ connect.connect(this.previous, "onClick", function(){ connect.publish(p.id+"-cycle", [false]); }); } if(this.next){ connect.connect(this.next, "onClick", function(){ connect.publish(p.id+"-cycle", [true]); }); } if(this.playPause){ connect.connect(this.playPause, "onClick", function(){ this.set('label', this.checked ? "Pause" : "Play"); connect.publish(p.id+"-state", [this.checked]); }); } this._subscriptions = [ connect.subscribe(p.id+"-state", this, "_state"), connect.subscribe(p.id+"-update", this, "_update") ]; } }, destroy: function(){ // Unsubscribe to all of our topics array.forEach(this._subscriptions, connect.unsubscribe); this.inherited(arguments); }, _state: function(/*boolean*/playing){ // summary: // Updates the display of the play/pause button if(this.playPause && this.playPause.checked != playing){ this.playPause.set("label", playing ? "Pause" : "Play"); this.playPause.set("checked", playing); } }, _update: function(/*boolean*/playing, /*int*/current, /*int*/total){ // summary: // Updates the pager's play/pause button, current child, and total number of children. this._state(playing); if(this.current && current){ this.current.innerHTML = current; } if(this.total && total){ this.total.innerHTML = total; } } }); return RotatorContainer; });