You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
470 lines
13 KiB
470 lines
13 KiB
define([ |
|
"dojo/aspect", |
|
"dojo/_base/declare", |
|
"dojo/_base/Deferred", |
|
"dojo/_base/lang", |
|
"dojo/_base/array", |
|
"dojo/_base/fx", |
|
"dojo/dom", |
|
"dojo/dom-attr", |
|
"dojo/dom-construct", |
|
"dojo/dom-geometry", |
|
"dojo/dom-style", |
|
"dojo/topic", |
|
"dojo/on", |
|
"dojo/parser", |
|
"dojo/query", |
|
"dojo/fx/easing", |
|
"dojo/NodeList-dom" |
|
], function(aspect, declare, Deferred, lang, array, fx, dom, domAttr, domConstruct, domGeometry, domStyle, topic, |
|
on, parser, query){ |
|
|
|
// build friendly strings |
|
var _defaultTransition = "dojox.widget.rotator.swap", // please do NOT change |
|
_defaultTransitionDuration = 500, |
|
_displayStr = "display", |
|
_noneStr = "none", |
|
_zIndex = "zIndex"; |
|
|
|
var Rotator = declare("dojox.widget.Rotator", null, { |
|
// summary: |
|
// A widget for rotating through child nodes using transitions. |
|
// |
|
// description: |
|
// A small, fast, extensible, awesome rotator that cycles, with transitions, |
|
// through panes (child nodes) displaying only one at a time and ties into |
|
// controllers used to change state. |
|
// |
|
// The Rotator does not rely on dijit. It is designed to be as lightweight |
|
// as possible. Controllers and transitions have been externalized |
|
// so builds can be as optimized with only the components you want to use. |
|
// |
|
// For best results, each rotator pane should be the same height and width as |
|
// the Rotator container node and consider setting overflow to hidden. |
|
// While the Rotator will accept any DOM node for a rotator pane, a block |
|
// element or element with display:block is recommended. |
|
// |
|
// Note: When the Rotator begins, it does not transition the first pane. |
|
// |
|
// subscribed topics: |
|
// |
|
// [id]/rotator/control - Controls the Rotator |
|
// Parameters: |
|
// |
|
// - /*string*/ action - The name of a method of the Rotator to run |
|
// - /*anything?*/ args - One or more arguments to pass to the action |
|
// |
|
// published topics: |
|
// |
|
// [id]/rotator/update - Notifies controllers that a pane or state has changed. |
|
// Parameters: |
|
// |
|
// - /*string*/ type - the type of notification |
|
// - /*dojox.widget.Rotator*/ rotator - the rotator instance |
|
// - /*object?*/ params - params |
|
// |
|
// declarative dojo/method events (per pane): |
|
// |
|
// - onBeforeIn - Fired before the transition in starts. |
|
// - onAfterIn - Fired after the transition in ends. |
|
// - onBeforeOut - Fired before the transition out starts. |
|
// - onAfterOut - Fired after the transition out ends. |
|
// |
|
// example: |
|
// | <div dojoType="dojox.widget.Rotator"> |
|
// | <div>Pane 1!</div> |
|
// | <div>Pane 2!</div> |
|
// | </div> |
|
// |
|
// example: |
|
// | <script type="text/javascript"> |
|
// | require("dojo/parser", "dojo/domReady!", "dojox/widget/Rotator", "dojox/widget/rotator/Fade"], |
|
// | function(parser) { parser.parse(); }); |
|
// | </script> |
|
// | <div dojoType="dojox/widget/Rotator" transition="dojox/widget/rotator/crossFade"> |
|
// | <div>Pane 1!</div> |
|
// | <div>Pane 2!</div> |
|
// | </div> |
|
|
|
// transition: string |
|
// The name of a function that is passed two panes nodes and a duration, |
|
// then returns a dojo.Animation object. The default value is |
|
// "dojox.widget.rotator.swap". |
|
transition: _defaultTransition, |
|
|
|
// transitionParams: string |
|
// Parameters for the transition. The string is read in and eval'd as an |
|
// object. If the duration is absent, the default value will be used. |
|
transitionParams: "duration:" + _defaultTransitionDuration, |
|
|
|
// panes: array |
|
// Array of panes to be created in the Rotator. Each array element |
|
// will be passed as attributes to a html.create() call. |
|
panes: null, |
|
|
|
constructor: function(/*Object*/params, /*DomNode|string*/node){ |
|
// summary: |
|
// Initializes the panes and events. |
|
lang.mixin(this, params); |
|
|
|
var _t = this, |
|
t = _t.transition, |
|
tt = _t._transitions = {}, |
|
idm = _t._idMap = {}, |
|
tp = _t.transitionParams = eval("({ " + _t.transitionParams + " })"), |
|
node = _t._domNode = dom.byId(node), |
|
cb = _t._domNodeContentBox = domGeometry.getContentBox(node); // we are going to assume the rotator will not be changing size |
|
|
|
// if we don't have an id, then generate one |
|
_t.id = node.id || (new Date()).getTime(); |
|
|
|
// force the rotator DOM node to a relative position and attach the container node to it |
|
if(domStyle.get(node, "position") == "static"){ |
|
domStyle.set(node, "position", "relative"); |
|
} |
|
|
|
// create our object for caching transition objects |
|
tt[t] = lang.getObject(t); |
|
if(!tt[t]){ |
|
this._transitionWarn(t, _defaultTransition); |
|
tt[_t.transition = _defaultTransition] = lang.getObject(_defaultTransition); |
|
} |
|
|
|
// clean up the transition params |
|
if(!tp.duration){ |
|
tp.duration = _defaultTransitionDuration; |
|
} |
|
|
|
// if there are any panes being passed in, add them to this node |
|
array.forEach(_t.panes, function(p){ |
|
domConstruct.create("div", p, node); |
|
}); |
|
|
|
// zero out our panes array to store the real pane instance |
|
_t.panes = []; |
|
|
|
// find and initialize the panes |
|
query("> *", node).forEach(function(n, i){ |
|
_t._initializePane(n, i); |
|
}); |
|
|
|
_t._controlSub = topic.subscribe(_t.id + "/rotator/control", lang.hitch(_t, this.control)); |
|
}, |
|
|
|
insert: function(/*DomNode*/node, /*number?*/index){ |
|
// summary: |
|
// Inserts a new pane into the rotator at a given index. If no index is |
|
// given, the new pane is inserted at the end of the pane list. |
|
var pane, |
|
panes = this.panes, |
|
paneNode; |
|
|
|
if (index == null) { |
|
index = panes.length; |
|
} |
|
|
|
if (index < panes.length) { |
|
pane = panes[index]; |
|
domConstruct.place(node, pane.node, 'before'); |
|
} |
|
else { |
|
domConstruct.place(node, this._domNode, 'last'); |
|
} |
|
|
|
this._initializePane(node, index); |
|
}, |
|
|
|
remove: function(/*DomNode|number*/nodeOrIndex){ |
|
// summary: |
|
// Removes a pane from the rotator. |
|
function removeFromPanes(idx) { |
|
var removed = panes.splice(idx, 1)[0]; |
|
if (removed) { |
|
if (removed.id) { |
|
_t._idMap[removed.id] = undefined; |
|
} |
|
_t._domNode.removeChild(removed.node); |
|
} |
|
if (_t.idx > idx) { |
|
_t.idx--; |
|
} |
|
} |
|
|
|
var index, |
|
_t = this, |
|
panes = this.panes; |
|
|
|
if (typeof nodeOrIndex === "number") { |
|
index = nodeOrIndex; |
|
} |
|
else { |
|
for (var i = 0; i < panes.length; i++) { |
|
if (panes[i].node === nodeOrIndex) { |
|
index = i; |
|
break; |
|
} |
|
} |
|
|
|
if (index == null) { |
|
return; |
|
} |
|
} |
|
|
|
if (index === this.idx) { |
|
var def = this.go(this.idx - 1); |
|
if (def) { |
|
return def.then(function () { |
|
removeFromPanes(index); |
|
}); |
|
} |
|
else { |
|
removeFromPanes(index); |
|
} |
|
} |
|
else { |
|
removeFromPanes(index); |
|
} |
|
}, |
|
|
|
destroy: function(){ |
|
// summary: |
|
// Destroys the Rotator and its DOM node. |
|
array.forEach([this._controlSub, this.wfe], function(wfe) { wfe && wfe.remove() }); |
|
domConstruct.destroy(this._domNode); |
|
this.panes = []; |
|
}, |
|
|
|
next: function(){ |
|
// summary: |
|
// Transitions the Rotator to the next pane. |
|
return this.go(this.idx + 1); |
|
}, |
|
|
|
prev: function(){ |
|
// summary: |
|
// Transitions the Rotator to the previous pane. |
|
return this.go(this.idx - 1); |
|
}, |
|
|
|
go: function(/*int|string?*/p){ |
|
// summary: |
|
// Transitions the Rotator to the specified pane index. |
|
var _t = this, |
|
i = _t.idx, |
|
pp = _t.panes, |
|
len = pp.length, |
|
idm = _t._idMap[p]; |
|
|
|
// we gotta move on, so if the current pane is waiting for an event, just |
|
// ignore it and clean up |
|
_t._resetWaitForEvent(); |
|
|
|
// determine the next index and set it to idx for the next go to |
|
p = idm != null ? idm : (p || 0); |
|
p = p < len ? (p < 0 ? len-1 : p) : 0; |
|
|
|
// if we're already on the requested pane or still transitioning, then return |
|
if(p == i || _t.anim){ |
|
return null; |
|
} |
|
|
|
// get the current and next panes |
|
var current = pp[i], |
|
next = pp[p]; |
|
|
|
// adjust the zIndexes so our animations look good... this must be done before |
|
// the animation is created so the animation could override it if necessary |
|
domStyle.set(current.node, _zIndex, 2); |
|
domStyle.set(next.node, _zIndex, 1); |
|
|
|
// info object passed to animations and onIn/Out events |
|
var info = { |
|
current: current, |
|
next: next, |
|
rotator: _t |
|
}, |
|
|
|
// get the transition |
|
anim = _t.anim = _t._transitions[next.trans](lang.mixin({ |
|
rotatorBox: _t._domNodeContentBox |
|
}, info, next.params)); |
|
|
|
if(anim){ |
|
// create the deferred that we'll be returning |
|
var def = new Deferred(), |
|
ev = next.waitForEvent, |
|
|
|
h = aspect.after(anim, "onEnd", function(){ |
|
// reset the node styles |
|
domStyle.set(current.node, { |
|
display: _noneStr, |
|
left: 0, |
|
opacity: 1, |
|
top: 0, |
|
zIndex: 0 |
|
}); |
|
|
|
h.remove(); |
|
_t.anim = null; |
|
_t.idx = p; |
|
|
|
// fire end events |
|
if(current.onAfterOut){ current.onAfterOut(info); } |
|
if(next.onAfterIn){ next.onAfterIn(info); } |
|
|
|
_t.onUpdate("onAfterTransition"); |
|
|
|
if(!ev){ |
|
// if there is a previous waitForEvent, then we need to make |
|
// sure it gets unsubscribed |
|
_t._resetWaitForEvent(); |
|
|
|
// animation is all done, fire the deferred callback. |
|
def.callback(); |
|
} |
|
}, true); |
|
|
|
// if we're waiting for an event, subscribe to it so we know when to continue |
|
_t.wfe = ev ? topic.subscribe(ev, function(){ |
|
_t._resetWaitForEvent(); |
|
def.callback(true); |
|
}) : null; |
|
|
|
_t.onUpdate("onBeforeTransition"); |
|
|
|
// fire start events |
|
if(current.onBeforeOut){ current.onBeforeOut(info); } |
|
if(next.onBeforeIn){ next.onBeforeIn(info); } |
|
|
|
// play the animation |
|
anim.play(); |
|
|
|
// return the deferred |
|
return def; /*Deferred*/ |
|
} |
|
}, |
|
|
|
onUpdate: function(/*string*/type, /*object?*/params){ |
|
// summary: |
|
// Send a notification to all controllers with the state of the rotator. |
|
topic.publish(this.id + "/rotator/update", type, this, params || {}); |
|
}, |
|
|
|
_initializePane: function(/*node*/node, /*number*/index) { |
|
// summary: |
|
// Initializes a new pane node. |
|
var tp = this.transitionParams, |
|
q = { node: node, idx: index, params: lang.mixin({}, tp, eval("({ " + (domAttr.get(node, "transitionParams") || "") + " })")) }, |
|
r = q.trans = domAttr.get(node, "transition") || this.transition, |
|
tt = this._transitions, |
|
panes = this.panes, |
|
// default styles to apply to all the container node and rotator's panes |
|
p = { |
|
left: 0, |
|
top: 0, |
|
position: "absolute", |
|
display: _noneStr |
|
}; |
|
|
|
// cache each pane's title, duration, and waitForEvent attributes |
|
array.forEach(["id", "title", "duration", "waitForEvent"], function(a){ |
|
q[a] = domAttr.get(node, a); |
|
}); |
|
|
|
if(q.id){ |
|
this._idMap[q.id] = index; |
|
} |
|
|
|
// cache the transition function |
|
if(!tt[r] && !(tt[r] = lang.getObject(r))){ |
|
this._transitionWarn(r, q.trans = this.transition); |
|
} |
|
|
|
// find the selected pane and initialize styles |
|
if(this.idx == null || domAttr.get(node, "selected")){ |
|
if(this.idx != null){ |
|
domStyle.set(panes[this.idx].node, _displayStr, _noneStr); |
|
} |
|
this.idx = index; |
|
p.display = ""; |
|
} |
|
domStyle.set(node, p); |
|
|
|
// check for any declarative script blocks |
|
// TODO: The way declarative scripts are handled here needs to be updated to no conflict with dojo/parser |
|
query("> script[type^='dojo/method']", node).orphan().forEach(function(s){ |
|
var e = domAttr.get(s, "event"); |
|
if(e){ |
|
q[e] = parser._functionFromScript(s); |
|
} |
|
}); |
|
|
|
// add this pane to the array of panes |
|
panes.splice(index, 0, q); |
|
}, |
|
|
|
_resetWaitForEvent: function(){ |
|
// summary: |
|
// If there is a waitForEvent pending, kill it. |
|
if(this.wfe){ |
|
this.wfe.remove(); |
|
delete this.wfe; |
|
} |
|
}, |
|
|
|
control: function(/*string*/action){ |
|
// summary: |
|
// Dispatches an action, first to this engine, then to the Rotator. |
|
var args = lang._toArray(arguments), |
|
_t = this; |
|
args.shift(); |
|
|
|
_t._resetWaitForEvent(); |
|
|
|
if(_t[action]){ |
|
// action exists, so call it and fire deferred if applicable |
|
var def = _t[action].apply(_t, args); |
|
if(def){ |
|
def.addCallback(function(){ |
|
_t.onUpdate(action); |
|
}); |
|
} |
|
|
|
// since this action was triggered by a controller, we assume this was a |
|
// manual action, so check if we should pause |
|
_t.onManualChange(action); |
|
}else{ |
|
console.warn(_t.declaredClass, ' - Unsupported action "', action, '".'); |
|
} |
|
}, |
|
|
|
resize: function(/*int*/width, /*int*/height){ |
|
var b = this._domNodeContentBox = { w: width, h: height }; |
|
domGeometry.setContentSize(this._domNode, b); |
|
array.forEach(this.panes, function(p){ domGeometry.setContentSize(p.node, b); }); |
|
}, |
|
|
|
onManualChange: function(){ |
|
// summary: |
|
// Stub function that can be overriden or connected to. |
|
}, |
|
|
|
_transitionWarn: function(bt, dt){ |
|
console.warn(this.declaredClass, ' - Unable to find transition "', bt, '", defaulting to "', dt, '".'); |
|
} |
|
}); |
|
|
|
lang.setObject(_defaultTransition, function(/*Object*/args){ |
|
// summary: |
|
// The default rotator transition which swaps two panes. |
|
return new fx.Animation({ // dojo.Animation |
|
play: function(){ |
|
domStyle.set(args.current.node, _displayStr, _noneStr); |
|
domStyle.set(args.next.node, _displayStr, ""); |
|
this._fire("onEnd"); |
|
} |
|
}); |
|
}); |
|
|
|
return Rotator; |
|
});
|
|
|