478 lines
15 KiB
JavaScript
478 lines
15 KiB
JavaScript
|
dojo.provide("dojox.layout.RotatorContainer");
|
||
|
|
||
|
dojo.require("dojo.fx");
|
||
|
dojo.require("dijit.layout.StackContainer");
|
||
|
dojo.require("dijit.layout.StackController");
|
||
|
dojo.require("dijit._Widget");
|
||
|
dojo.require("dijit._Templated");
|
||
|
dojo.require("dijit._Contained");
|
||
|
|
||
|
dojo.declare("dojox.layout.RotatorContainer",
|
||
|
[dijit.layout.StackContainer, dijit._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,
|
||
|
|
||
|
// _timer: int
|
||
|
// The timer used for controlling the transitions.
|
||
|
_timer: null,
|
||
|
|
||
|
// _over: Boolean
|
||
|
// Flag to quick check if the mouse is over the rotator.
|
||
|
_over: false,
|
||
|
|
||
|
// _playing: Boolean
|
||
|
// Flag to track transition state.
|
||
|
_playing: false,
|
||
|
|
||
|
// 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
|
||
|
dojo.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();
|
||
|
var sc = new dijit.layout.StackController({ containerId:id }, this.tabNode);
|
||
|
this.tabNode = sc.domNode;
|
||
|
this._stackController = sc;
|
||
|
dojo.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 = [
|
||
|
dojo.subscribe(this.id+"-cycle", this, "_cycle"),
|
||
|
dojo.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(dojo.hitch(this, "_play"), 10);
|
||
|
}else{
|
||
|
// update the pagers with the initial state
|
||
|
this._updatePager();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
destroy: function(){
|
||
|
// summary: Unsubscribe to all of our topics
|
||
|
dojo.forEach(this._subscriptions, dojo.unsubscribe);
|
||
|
this.inherited(arguments);
|
||
|
},
|
||
|
|
||
|
setAttribute: function(/*String*/attr, /*anything*/value){
|
||
|
// summary: Exposes attributes to be changed.
|
||
|
this.inherited(arguments);
|
||
|
switch(attr){
|
||
|
case "showTabs":
|
||
|
this.showTabs = value;
|
||
|
dojo.style(this.tabNode, "display", value ? "" : "none");
|
||
|
break;
|
||
|
case "transitionDelay":
|
||
|
case "transitionDuration":
|
||
|
case "suspendOnHover":
|
||
|
case "pauseOnManualChange":
|
||
|
case "reverse":
|
||
|
this[attr] = value;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_updatePager: function(){
|
||
|
// summary: Notify the pager's current and total numbers.
|
||
|
var c = this.getChildren();
|
||
|
dojo.publish(this.id+"-update", [this._playing, dojo.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);
|
||
|
//var self = this;
|
||
|
//this._timer = setTimeout(function(){self._play(true);}, 200);
|
||
|
this._timer = setTimeout(dojo.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();
|
||
|
var len = c.length;
|
||
|
var i = dojo.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(dojo.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(/*Widget*/next, /*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(/*Widget*/next, /*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 };
|
||
|
var anim = dojo.fx.combine([
|
||
|
dojo["fadeOut"](dojo.mixin({node:prev.domNode},args)),
|
||
|
dojo["fadeIn"](dojo.mixin({node:next.domNode},args))
|
||
|
]);
|
||
|
|
||
|
this.connect(anim, "onEnd", dojo.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.
|
||
|
console.log(arguments);
|
||
|
dojo.style(node, "opacity", opacity);
|
||
|
dojo.style(node, "zIndex", zIndex);
|
||
|
dojo.style(node, "position", "absolute");
|
||
|
}
|
||
|
});
|
||
|
|
||
|
dojo.declare("dojox.layout.RotatorPager", [dijit._Widget, dijit._Templated, dijit._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 = dijit.byId(this.rotatorId) || this.getParent();
|
||
|
if(p && p.declaredClass == "dojox.layout.RotatorContainer"){
|
||
|
if(this.previous){
|
||
|
dojo.connect(this.previous, "onClick", function(){
|
||
|
dojo.publish(p.id+"-cycle", [false]);
|
||
|
});
|
||
|
}
|
||
|
if(this.next){
|
||
|
dojo.connect(this.next, "onClick", function(){
|
||
|
dojo.publish(p.id+"-cycle", [true]);
|
||
|
});
|
||
|
}
|
||
|
if(this.playPause){
|
||
|
dojo.connect(this.playPause, "onClick", function(){
|
||
|
this.attr('label', this.checked ? "Pause" : "Play");
|
||
|
dojo.publish(p.id+"-state", [this.checked]);
|
||
|
});
|
||
|
}
|
||
|
this._subscriptions = [
|
||
|
dojo.subscribe(p.id+"-state", this, "_state"),
|
||
|
dojo.subscribe(p.id+"-update", this, "_update")
|
||
|
];
|
||
|
}
|
||
|
},
|
||
|
|
||
|
destroy: function(){
|
||
|
// summary: Unsubscribe to all of our topics
|
||
|
dojo.forEach(this._subscriptions, dojo.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.attr('label', playing ? "Pause" : "Play");
|
||
|
this.playPause.setAttribute("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;
|
||
|
}
|
||
|
}
|
||
|
});
|