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