Toaster.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. define("dojox/widget/Toaster", [
  2. "dojo/_base/declare", // declare
  3. "dojo/_base/lang", // lang.getObject...
  4. "dojo/_base/connect", // connect.connect, connect.subscribe
  5. "dojo/_base/fx", // fx.fadeOut
  6. "dojo/dom-style", // domStyle.set
  7. "dojo/dom-class", // domClass.add
  8. "dojo/dom-geometry", // domGeometry.getMarginBox
  9. "dijit/registry", // registry.getUniqueId()
  10. "dijit/_WidgetBase",
  11. "dijit/_TemplatedMixin",
  12. "dijit/BackgroundIframe",
  13. "dojo/fx",
  14. "dojo/has",
  15. "dojo/_base/window",
  16. "dojo/window"
  17. ], function(declare, lang, connect, baseFx, domStyle, domClass, domGeometry, registry, WidgetBase, Templated, BackgroundIframe, coreFx, has, baseWindow, window){
  18. lang.getObject("dojox.widget", true);
  19. var capitalize = function(/* String */w){
  20. return w.substring(0,1).toUpperCase() + w.substring(1);
  21. };
  22. return declare("dojox.widget.Toaster", [WidgetBase, Templated], {
  23. // summary:
  24. // Message that slides in from the corner of the screen, used for notifications
  25. // like "new email".
  26. templateString: '<div class="dijitToasterClip" dojoAttachPoint="clipNode"><div class="dijitToasterContainer" dojoAttachPoint="containerNode" dojoAttachEvent="onclick:onSelect"><div class="dijitToasterContent" dojoAttachPoint="contentNode"></div></div></div>',
  27. // messageTopic: String
  28. // Name of topic; anything published to this topic will be displayed as a message.
  29. // Message format is either String or an object like
  30. // {message: "hello word", type: "error", duration: 500}
  31. messageTopic: "",
  32. // messageTypes: Enumeration
  33. // Possible message types.
  34. messageTypes: {
  35. MESSAGE: "message",
  36. WARNING: "warning",
  37. ERROR: "error",
  38. FATAL: "fatal"
  39. },
  40. // defaultType: String
  41. // If message type isn't specified (see "messageTopic" parameter),
  42. // then display message as this type.
  43. // Possible values in messageTypes enumeration ("message", "warning", "error", "fatal")
  44. defaultType: "message",
  45. // positionDirection: String
  46. // Position from which message slides into screen, one of
  47. // ["br-up", "br-left", "bl-up", "bl-right", "tr-down", "tr-left", "tl-down", "tl-right"]
  48. positionDirection: "br-up",
  49. // positionDirectionTypes: Array
  50. // Possible values for positionDirection parameter
  51. positionDirectionTypes: ["br-up", "br-left", "bl-up", "bl-right", "tr-down", "tr-left", "tl-down", "tl-right"],
  52. // duration: Integer
  53. // Number of milliseconds to show message
  54. duration: 2000,
  55. // slideDuration: Integer
  56. // Number of milliseconds for the slide animation, increasing will cause the Toaster
  57. // to slide in more slowly.
  58. slideDuration: 500,
  59. //separator: String
  60. // String used to separate messages if consecutive calls are made to setContent before previous messages go away
  61. separator: "<hr></hr>",
  62. postCreate: function(){
  63. this.inherited(arguments);
  64. this.hide();
  65. // place node as a child of body for positioning
  66. baseWindow.body().appendChild(this.domNode);
  67. if(this.messageTopic){
  68. connect.subscribe(this.messageTopic, this, "_handleMessage");
  69. }
  70. },
  71. _handleMessage: function(/*String|Object*/message){
  72. if(lang.isString(message)){
  73. this.setContent(message);
  74. }else{
  75. this.setContent(message.message, message.type, message.duration);
  76. }
  77. },
  78. setContent: function(/*String|Function*/message, /*String*/messageType, /*int?*/duration){
  79. // summary:
  80. // sets and displays the given message and show duration
  81. // message:
  82. // the message. If this is a function, it will be called with this toaster widget as the only argument.
  83. // messageType:
  84. // type of message; possible values in messageTypes enumeration ("message", "warning", "error", "fatal")
  85. // duration:
  86. // duration in milliseconds to display message before removing it. Widget has default value.
  87. duration = duration||this.duration;
  88. // sync animations so there are no ghosted fades and such
  89. if(this.slideAnim){
  90. if(this.slideAnim.status() != "playing"){
  91. this.slideAnim.stop();
  92. }
  93. if(this.slideAnim.status() == "playing" || (this.fadeAnim && this.fadeAnim.status() == "playing")){
  94. setTimeout(lang.hitch(this, function(){
  95. this.setContent(message, messageType, duration);
  96. }), 50);
  97. return;
  98. }
  99. }
  100. // determine type of content and apply appropriately
  101. for(var type in this.messageTypes){
  102. domClass.remove(this.containerNode, "dijitToaster" + capitalize(this.messageTypes[type]));
  103. }
  104. domStyle.set(this.containerNode, "opacity", 1);
  105. this._setContent(message);
  106. domClass.add(this.containerNode, "dijitToaster" + capitalize(messageType || this.defaultType));
  107. // now do funky animation of widget appearing from
  108. // bottom right of page and up
  109. this.show();
  110. var nodeSize = domGeometry.getMarginBox(this.containerNode);
  111. this._cancelHideTimer();
  112. if(this.isVisible){
  113. this._placeClip();
  114. //update hide timer if no sticky message in stack
  115. if(!this._stickyMessage) {
  116. this._setHideTimer(duration);
  117. }
  118. }else{
  119. var style = this.containerNode.style;
  120. var pd = this.positionDirection;
  121. // sets up initial position of container node and slide-out direction
  122. if(pd.indexOf("-up") >= 0){
  123. style.left=0+"px";
  124. style.top=nodeSize.h + 10 + "px";
  125. }else if(pd.indexOf("-left") >= 0){
  126. style.left=nodeSize.w + 10 +"px";
  127. style.top=0+"px";
  128. }else if(pd.indexOf("-right") >= 0){
  129. style.left = 0 - nodeSize.w - 10 + "px";
  130. style.top = 0+"px";
  131. }else if(pd.indexOf("-down") >= 0){
  132. style.left = 0+"px";
  133. style.top = 0 - nodeSize.h - 10 + "px";
  134. }else{
  135. throw new Error(this.id + ".positionDirection is invalid: " + pd);
  136. }
  137. this.slideAnim = coreFx.slideTo({
  138. node: this.containerNode,
  139. top: 0, left: 0,
  140. duration: this.slideDuration});
  141. this.connect(this.slideAnim, "onEnd", function(nodes, anim){
  142. //we build the fadeAnim here so we dont have to duplicate it later
  143. // can't do a fadeHide because we're fading the
  144. // inner node rather than the clipping node
  145. this.fadeAnim = baseFx.fadeOut({
  146. node: this.containerNode,
  147. duration: 1000});
  148. this.connect(this.fadeAnim, "onEnd", function(evt){
  149. this.isVisible = false;
  150. this.hide();
  151. });
  152. this._setHideTimer(duration);
  153. this.connect(this, 'onSelect', function(evt){
  154. this._cancelHideTimer();
  155. //force clear sticky message
  156. this._stickyMessage=false;
  157. this.fadeAnim.play();
  158. });
  159. this.isVisible = true;
  160. });
  161. this.slideAnim.play();
  162. }
  163. },
  164. _setContent: function(message){
  165. if(lang.isFunction(message)){
  166. message(this);
  167. return;
  168. }
  169. if(message && this.isVisible){
  170. message = this.contentNode.innerHTML + this.separator + message;
  171. }
  172. this.contentNode.innerHTML = message;
  173. },
  174. _cancelHideTimer:function(){
  175. if (this._hideTimer){
  176. clearTimeout(this._hideTimer);
  177. this._hideTimer=null;
  178. }
  179. },
  180. _setHideTimer:function(duration){
  181. this._cancelHideTimer();
  182. //if duration == 0 we keep the message displayed until clicked
  183. if(duration>0){
  184. this._cancelHideTimer();
  185. this._hideTimer=setTimeout(lang.hitch(this, function(evt){
  186. // we must hide the iframe in order to fade
  187. // TODO: figure out how to fade with a BackgroundIframe
  188. if(this.bgIframe && this.bgIframe.iframe){
  189. this.bgIframe.iframe.style.display="none";
  190. }
  191. this._hideTimer=null;
  192. //force clear sticky message
  193. this._stickyMessage=false;
  194. this.fadeAnim.play();
  195. }), duration);
  196. }
  197. else
  198. this._stickyMessage=true;
  199. },
  200. _placeClip: function(){
  201. var view = window.getBox();
  202. var nodeSize = domGeometry.getMarginBox(this.containerNode);
  203. var style = this.clipNode.style;
  204. // sets up the size of the clipping node
  205. style.height = nodeSize.h+"px";
  206. style.width = nodeSize.w+"px";
  207. // sets up the position of the clipping node
  208. var pd = this.positionDirection;
  209. if(pd.match(/^t/)){
  210. style.top = view.t+"px";
  211. }else if(pd.match(/^b/)){
  212. style.top = (view.h - nodeSize.h - 2 + view.t)+"px";
  213. }
  214. if(pd.match(/^[tb]r-/)){
  215. style.left = (view.w - nodeSize.w - 1 - view.l)+"px";
  216. }else if(pd.match(/^[tb]l-/)){
  217. style.left = 0 + "px";
  218. }
  219. style.clip = "rect(0px, " + nodeSize.w + "px, " + nodeSize.h + "px, 0px)";
  220. if(has("ie")){
  221. if(!this.bgIframe){
  222. if (!this.clipNode.id) {
  223. this.clipNode.id = registry.getUniqueId("dojox_widget_Toaster_clipNode");
  224. }
  225. this.bgIframe = new BackgroundIframe(this.clipNode);
  226. }
  227. var iframe = this.bgIframe.iframe;
  228. if(iframe){ iframe.style.display="block"; }
  229. }
  230. },
  231. onSelect: function(/*Event*/e){
  232. // summary: callback for when user clicks the message
  233. },
  234. show: function(){
  235. // summary: show the Toaster
  236. domStyle.set(this.domNode, 'display', 'block');
  237. this._placeClip();
  238. if(!this._scrollConnected){
  239. this._scrollConnected = connect.connect(window, "onscroll", this, this._placeClip);
  240. }
  241. },
  242. hide: function(){
  243. // summary: hide the Toaster
  244. domStyle.set(this.domNode, 'display', 'none');
  245. if(this._scrollConnected){
  246. connect.disconnect(this._scrollConnected);
  247. this._scrollConnected = false;
  248. }
  249. domStyle.set(this.containerNode, "opacity", 1);
  250. }
  251. });
  252. });