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.

471 lines (405 loc) 13.4 kB
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; });