RotatorContainer.js 15 KB

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