Merge lp:~frankban/juju-gui/bug-1076404-growl into lp:juju-gui/experimental
- bug-1076404-growl
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 248 |
Proposed branch: | lp:~frankban/juju-gui/bug-1076404-growl |
Merge into: | lp:juju-gui/experimental |
Diff against target: |
543 lines (+406/-4) 12 files modified
app/app.js (+2/-1) app/index.html (+1/-0) app/models/models.js (+2/-0) app/modules-debug.js (+4/-0) app/store/notifications.js (+1/-1) app/templates/notifier.handlebars (+3/-0) app/views/notifications.js (+30/-2) app/widgets/notifier.js (+148/-0) lib/views/stylesheet.less (+50/-0) test/index.html (+1/-0) test/test_notifications.js (+61/-0) test/test_notifier_widget.js (+103/-0) |
To merge this branch: | bzr merge lp:~frankban/juju-gui/bug-1076404-growl |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+134660@code.launchpad.net |
Commit message
Description of the change
Growl-style notifications.
This branch introduces growl-style notifications
in the top-right corner of the window.
Implemented a notifier widget: it is a reusable
piece of code that just need a title, a message
and a timeout to display a notification. It is used
to render notification outside view containers, so
that notifications are preserved when the user changes
the current view.
A notifier is created when a notification is added,
following these rules:
- the notification is an error;
- the notification is local (it's not related to the
delta stream).
The last point involves adding a new field "isDelta"
to the notification model, defaulting to false.
Notifications created when the delta stream arrives are
marked as "isDelta: true".
UI: new notifications appear on top, and disappear after
8 seconds. Mouse over prevents a notification to hide,
mouse click destroys a notification.
Also fixed a bug in the unit view: a callable was undefined
when trying to "retry" or "resolve" a unit.
Francesco Banconi (frankban) wrote : | # |
Thiago Veronezi (tveronezi) wrote : | # |
Thanks, Francesco.
I like it! I have just one really minor comment.
[]s,
Thiago.
https:/
File app/views/
https:/
app/views/
I think will you always have the 'notifier-box' element. It is
hard-coded in index.html. So probably you can remove this check.
Francesco Banconi (frankban) wrote : | # |
Thanks for the review Thiago! A reply to your comment follows.
https:/
File app/views/
https:/
app/views/
On 2012/11/16 14:39:29, thiago wrote:
> I think will you always have the 'notifier-box' element. It is
hard-coded in
> index.html. So probably you can remove this check.
The check is here to make tests work. The element is not present in the
test page, and it's created by tests' setUp and tearDown only when
needed, so that notifications are not created as side effects by
non-relevant test cases.
Madison Scott-Clary (makyo) wrote : | # |
This all looks pretty good to me, Francesco! Thanks for the branch.
Works well, tests pass.
https:/
File lib/views/
https:/
lib/views/
Less is so neat, sometimes :)
Francesco Banconi (frankban) wrote : | # |
*** Submitted:
Growl-style notifications.
This branch introduces growl-style notifications
in the top-right corner of the window.
Implemented a notifier widget: it is a reusable
piece of code that just need a title, a message
and a timeout to display a notification. It is used
to render notification outside view containers, so
that notifications are preserved when the user changes
the current view.
A notifier is created when a notification is added,
following these rules:
- the notification is an error;
- the notification is local (it's not related to the
delta stream).
The last point involves adding a new field "isDelta"
to the notification model, defaulting to false.
Notifications created when the delta stream arrives are
marked as "isDelta: true".
UI: new notifications appear on top, and disappear after
8 seconds. Mouse over prevents a notification to hide,
mouse click destroys a notification.
Also fixed a bug in the unit view: a callable was undefined
when trying to "retry" or "resolve" a unit.
R=thiago, matthew.scott
CC=
https:/
Preview Diff
1 | === modified file 'app/app.js' |
2 | --- app/app.js 2012-11-07 12:35:06 +0000 |
3 | +++ app/app.js 2012-11-16 13:56:21 +0000 |
4 | @@ -280,7 +280,8 @@ |
5 | 'unit', |
6 | // The querystring is used to handle highlighting relation rows in |
7 | // links from notifications about errors. |
8 | - { unit: unit, db: this.db, env: this.env, |
9 | + { getModelURL: Y.bind(this.getModelURL, this), |
10 | + unit: unit, db: this.db, env: this.env, |
11 | querystring: req.query }); |
12 | }, |
13 | |
14 | |
15 | === added file 'app/assets/images/notifier-error.png' |
16 | Binary files app/assets/images/notifier-error.png 1970-01-01 00:00:00 +0000 and app/assets/images/notifier-error.png 2012-11-16 13:56:21 +0000 differ |
17 | === modified file 'app/index.html' |
18 | --- app/index.html 2012-11-13 18:18:47 +0000 |
19 | +++ app/index.html 2012-11-16 13:56:21 +0000 |
20 | @@ -18,6 +18,7 @@ |
21 | |
22 | <body> |
23 | |
24 | + <div id="notifier-box"></div> |
25 | <div id="viewport-wrapper"> |
26 | <div id="vp-left-border"></div> |
27 | <div id="viewport"> |
28 | |
29 | === modified file 'app/models/models.js' |
30 | --- app/models/models.js 2012-10-19 01:44:45 +0000 |
31 | +++ app/models/models.js 2012-11-16 13:56:21 +0000 |
32 | @@ -309,6 +309,8 @@ |
33 | [model.name, |
34 | (model instanceof Y.Model) ? model.get('id') : model.id]); |
35 | }}, |
36 | + // Whether or not the notification is related to the delta stream. |
37 | + isDelta: {value: false}, |
38 | link: {}, |
39 | link_title: { |
40 | value: 'View Details' |
41 | |
42 | === modified file 'app/modules-debug.js' |
43 | --- app/modules-debug.js 2012-11-13 16:46:36 +0000 |
44 | +++ app/modules-debug.js 2012-11-16 13:56:21 +0000 |
45 | @@ -24,6 +24,10 @@ |
46 | modules: { |
47 | // Primitives |
48 | |
49 | + 'notifier': { |
50 | + fullpath: '/juju-ui/widgets/notifier.js' |
51 | + }, |
52 | + |
53 | 'svg-layouts': { |
54 | fullpath: '/juju-ui/assets/javascripts/svg-layouts.js' |
55 | }, |
56 | |
57 | === modified file 'app/store/notifications.js' |
58 | --- app/store/notifications.js 2012-10-15 10:07:49 +0000 |
59 | +++ app/store/notifications.js 2012-11-16 13:56:21 +0000 |
60 | @@ -156,7 +156,7 @@ |
61 | var change_type = change[0], |
62 | change_op = change[1], |
63 | change_data = change[2], |
64 | - notify_data = {}, |
65 | + notify_data = {isDelta: true}, |
66 | rule = rules[change_type], |
67 | model; |
68 | |
69 | |
70 | === added file 'app/templates/notifier.handlebars' |
71 | --- app/templates/notifier.handlebars 1970-01-01 00:00:00 +0000 |
72 | +++ app/templates/notifier.handlebars 2012-11-16 13:56:21 +0000 |
73 | @@ -0,0 +1,3 @@ |
74 | +<i class="sprite notifier-error"></i> |
75 | +<h3>{{title}}</h3> |
76 | +<div>{{message}}</div> |
77 | |
78 | === modified file 'app/views/notifications.js' |
79 | --- app/views/notifications.js 2012-10-05 13:08:43 +0000 |
80 | +++ app/views/notifications.js 2012-11-16 13:56:21 +0000 |
81 | @@ -3,10 +3,11 @@ |
82 | YUI.add('juju-notifications', function(Y) { |
83 | |
84 | var views = Y.namespace('juju.views'), |
85 | + widgets = Y.namespace('juju.widgets'), |
86 | Templates = views.Templates; |
87 | |
88 | /* |
89 | - * Abstract Base class used to view a ModelList of notifications |
90 | + * Abstract Base class used to view a ModelList of notifications |
91 | */ |
92 | var NotificationsBaseView = Y.Base.create('NotificationsBaseView', |
93 | Y.View, [views.JujuBaseView], { |
94 | @@ -24,10 +25,36 @@ |
95 | notifications.after('create', this.slowRender, this); |
96 | notifications.after('remove', this.slowRender, this); |
97 | notifications.after('reset', this.slowRender, this); |
98 | + // Bind new notifications to the notifier widget. |
99 | + notifications.after('add', this.addNotifier, this); |
100 | |
101 | // Env connection state watcher |
102 | env.on('connectedChange', this.slowRender, this); |
103 | + }, |
104 | |
105 | + /** |
106 | + * Create and display a notifier widget when a notification is added. |
107 | + * The notifier is created only if: |
108 | + * - the notifier box exists in the DOM; |
109 | + * - the notification is a local one (not related to the delta stream); |
110 | + * - the notification is an error. |
111 | + * |
112 | + * @method addNotifier |
113 | + * @param {Object} ev An event object (with a "model" attribute). |
114 | + * @return {undefined} Mutates only. |
115 | + */ |
116 | + addNotifier: function(ev) { |
117 | + var notification = ev.model, |
118 | + notifierBox = Y.one('#notifier-box'); |
119 | + // Show error notifications only if the DOM contain the notifier box. |
120 | + if (notifierBox && |
121 | + !notification.get('isDelta') && |
122 | + notification.get('level') === 'error') { |
123 | + new widgets.Notifier({ |
124 | + title: notification.get('title'), |
125 | + message: notification.get('message') |
126 | + }).render(notifierBox); |
127 | + } |
128 | }, |
129 | |
130 | /* |
131 | @@ -251,6 +278,7 @@ |
132 | 'view', |
133 | 'juju-view-utils', |
134 | 'node', |
135 | - 'handlebars' |
136 | + 'handlebars', |
137 | + 'notifier' |
138 | ] |
139 | }); |
140 | |
141 | === added directory 'app/widgets' |
142 | === added file 'app/widgets/notifier.js' |
143 | --- app/widgets/notifier.js 1970-01-01 00:00:00 +0000 |
144 | +++ app/widgets/notifier.js 2012-11-16 13:56:21 +0000 |
145 | @@ -0,0 +1,148 @@ |
146 | +'use strict'; |
147 | + |
148 | +YUI.add('notifier', function(Y) { |
149 | + |
150 | + var widgets = Y.namespace('juju.widgets'); |
151 | + |
152 | + /** |
153 | + * Display a notification. |
154 | + * This is the constructor for the notifier widget. |
155 | + * |
156 | + * @class Notifier |
157 | + * @namespace widgets |
158 | + */ |
159 | + function Notifier(config) { |
160 | + Notifier.superclass.constructor.apply(this, arguments); |
161 | + } |
162 | + |
163 | + Notifier.NAME = 'Notifier'; |
164 | + Notifier.ATTRS = { |
165 | + title: {value: ''}, |
166 | + message: {value: ''}, |
167 | + timeout: {value: 8000} |
168 | + }; |
169 | + |
170 | + /** |
171 | + * Define the widget class extending Y.Widget. |
172 | + * |
173 | + * @class Notifier |
174 | + * @namespace widgets |
175 | + */ |
176 | + Y.extend(Notifier, Y.Widget, { |
177 | + |
178 | + CONTENT_TEMPLATE: null, |
179 | + TEMPLATE: Y.namespace('juju.views').Templates.notifier, |
180 | + |
181 | + /** |
182 | + * Attach the widget bounding box to the DOM. |
183 | + * Override to insert new instances before existing ones. |
184 | + * This way new notification are displayed on top of the page. |
185 | + * The resulting rendering process is also very simplified. |
186 | + * |
187 | + * @method _renderBox |
188 | + * @protected |
189 | + * @param {Y.Node object} parentNode The node containing this widget. |
190 | + * @return {undefined} Mutates only. |
191 | + */ |
192 | + _renderBox: function(parentNode) { |
193 | + parentNode.prepend(this.get('boundingBox')); |
194 | + }, |
195 | + |
196 | + /** |
197 | + * Create the nodes required by this widget and attach them to the DOM. |
198 | + * |
199 | + * @method renderUI |
200 | + * @return {undefined} Mutates only. |
201 | + */ |
202 | + renderUI: function() { |
203 | + var content = this.TEMPLATE({ |
204 | + title: this.get('title'), |
205 | + message: this.get('message') |
206 | + }); |
207 | + this.get('contentBox').append(content); |
208 | + }, |
209 | + |
210 | + /** |
211 | + * Attach event listeners which bind the UI to the widget state. |
212 | + * The mouse enter event on a notification node pauses the timer. |
213 | + * The mouse click event on a notification destroys the widget. |
214 | + * |
215 | + * @method bindUI |
216 | + * @return {undefined} Mutates only. |
217 | + */ |
218 | + bindUI: function() { |
219 | + var contentBox = this.get('contentBox'); |
220 | + contentBox.on( |
221 | + 'hover', |
222 | + function() { |
223 | + if (this.timer) { |
224 | + this.timer.pause(); |
225 | + } |
226 | + }, |
227 | + function() { |
228 | + if (this.timer) { |
229 | + this.timer.resume(); |
230 | + } |
231 | + }, |
232 | + this |
233 | + ); |
234 | + contentBox.on('click', function(ev) { |
235 | + this.hideAndDestroy(); |
236 | + ev.halt(); |
237 | + }, this); |
238 | + }, |
239 | + |
240 | + /** |
241 | + * Create and start the timer that will destroy the widget after N seconds. |
242 | + * |
243 | + * @method syncUI |
244 | + * @return {undefined} Mutates only. |
245 | + */ |
246 | + syncUI: function() { |
247 | + this.timer = new Y.Timer({ |
248 | + length: this.get('timeout'), |
249 | + repeatCount: 1, |
250 | + callback: Y.bind(this.hideAndDestroy, this) |
251 | + }); |
252 | + this.timer.start(); |
253 | + }, |
254 | + |
255 | + /** |
256 | + * Hide this widget using an animation and destroy the widget at the end. |
257 | + * |
258 | + * @method hideAndDestroy |
259 | + * @return {undefined} Mutates only. |
260 | + */ |
261 | + hideAndDestroy: function() { |
262 | + this.timer.stop(); |
263 | + this.timer = null; |
264 | + if (this.get('boundingBox').getDOMNode()) { |
265 | + // Animate and destroy the notification if it still exists in the DOM. |
266 | + var anim = new Y.Anim({ |
267 | + node: this.get('boundingBox'), |
268 | + to: {opacity: 0}, |
269 | + easing: 'easeIn', |
270 | + duration: 0.2 |
271 | + }); |
272 | + anim.on('end', this.destroy, this); |
273 | + anim.run(); |
274 | + } else { |
275 | + // Otherwise, just destroy the notification. |
276 | + this.destroy(); |
277 | + } |
278 | + } |
279 | + |
280 | + }); |
281 | + |
282 | + widgets.Notifier = Notifier; |
283 | + |
284 | +}, '0.1.0', {requires: [ |
285 | + 'anim', |
286 | + 'event', |
287 | + 'event-hover', |
288 | + 'handlebars', |
289 | + 'gallery-timer', |
290 | + 'juju-templates', |
291 | + 'node', |
292 | + 'widget' |
293 | +]}); |
294 | |
295 | === modified file 'lib/views/stylesheet.less' |
296 | --- lib/views/stylesheet.less 2012-11-15 14:33:54 +0000 |
297 | +++ lib/views/stylesheet.less 2012-11-16 13:56:21 +0000 |
298 | @@ -565,6 +565,56 @@ |
299 | } |
300 | |
301 | /* |
302 | + * Notifier widget. |
303 | + */ |
304 | +#notifier-box { |
305 | + position: absolute; |
306 | + right: 0px; |
307 | + top: 0px; |
308 | + z-index: 9999; |
309 | + .yui3-notifier-content { |
310 | + @background-color: black; |
311 | + @margin-left: 41px; |
312 | + @margin-top: 13px; |
313 | + @opacity: 0.8; |
314 | + .create-border-radius(8px); |
315 | + // Using a variable here because LESS strips commas in mixin args. |
316 | + @box-shadow: 0 2px 4px lighten(@background-color, 10%), |
317 | + 0 1px 2px lighten(@background-color, 20%) inset; |
318 | + .create-box-shadow(@box-shadow); |
319 | + background-color: @background-color; |
320 | + margin: 6px; |
321 | + opacity: @opacity; |
322 | + overflow: hidden; |
323 | + width: 277px; |
324 | + &:hover { |
325 | + cursor: pointer; |
326 | + opacity: @opacity - 0.1; |
327 | + } |
328 | + h3 { |
329 | + color: #FDF6E3; |
330 | + font-size: 16px; |
331 | + font-weight: normal; |
332 | + margin-top: @margin-top; |
333 | + margin-left: @margin-left; |
334 | + } |
335 | + div { |
336 | + color: #DDD7C6; |
337 | + font-size: 12px; |
338 | + line-height: 16px; |
339 | + margin-left: @margin-left; |
340 | + margin-bottom: 12px; |
341 | + } |
342 | + .sprite { |
343 | + float: left; |
344 | + margin-top: @margin-top + 5px; |
345 | + margin-left: 12px; |
346 | + opacity: 1; |
347 | + } |
348 | + } |
349 | +} |
350 | + |
351 | +/* |
352 | * Overview Module |
353 | */ |
354 | #overview { |
355 | |
356 | === modified file 'test/index.html' |
357 | --- test/index.html 2012-11-13 13:55:16 +0000 |
358 | +++ test/index.html 2012-11-16 13:56:21 +0000 |
359 | @@ -33,6 +33,7 @@ |
360 | <script src="test_endpoints.js"></script> |
361 | <script src="test_application_notifications.js"></script> |
362 | <script src="test_charm_store.js"></script> |
363 | + <script src="test_notifier_widget.js"></script> |
364 | |
365 | <script> |
366 | YUI().use('node', 'event', function(Y) { |
367 | |
368 | === modified file 'test/test_notifications.js' |
369 | --- test/test_notifications.js 2012-10-18 15:12:38 +0000 |
370 | +++ test/test_notifications.js 2012-11-16 13:56:21 +0000 |
371 | @@ -414,3 +414,64 @@ |
372 | 'Relation with endpoint1 (relation type "relation1") was created'); |
373 | }); |
374 | }); |
375 | + |
376 | +describe('notification visual feedback', function() { |
377 | + var env, models, notifications, notificationsView, notifierBox, views, Y; |
378 | + |
379 | + before(function(done) { |
380 | + Y = YUI(GlobalConfig).use('juju-env', 'juju-models', 'juju-views', |
381 | + function(Y) { |
382 | + var juju = Y.namespace('juju'); |
383 | + env = new juju.Environment(); |
384 | + models = Y.namespace('juju.models'); |
385 | + views = Y.namespace('juju.views'); |
386 | + done(); |
387 | + }); |
388 | + }); |
389 | + |
390 | + // Instantiate the notifications model list and view. |
391 | + // Also create the notifier box and attach it as first element of the body. |
392 | + beforeEach(function() { |
393 | + notifications = new models.NotificationList(); |
394 | + notificationsView = new views.NotificationsView({ |
395 | + env: env, |
396 | + notifications: notifications |
397 | + }); |
398 | + notifierBox = Y.Node.create('<div id="notifier-box"></div>'); |
399 | + notifierBox.setStyle('display', 'none'); |
400 | + Y.one('body').prepend(notifierBox); |
401 | + }); |
402 | + |
403 | + // Destroy the notifier box created in beforeEach. |
404 | + afterEach(function() { |
405 | + notifierBox.remove(); |
406 | + notifierBox.destroy(true); |
407 | + }); |
408 | + |
409 | + // Assert the notifier box contains the expectedNumber of notifiers. |
410 | + var assertNumNotifiers = function(expectedNumber) { |
411 | + assert.equal(expectedNumber, notifierBox.get('children').size()); |
412 | + }; |
413 | + |
414 | + it('should appear when a new error is notified', function() { |
415 | + notifications.add({title: 'mytitle', level: 'error'}); |
416 | + assertNumNotifiers(1); |
417 | + }); |
418 | + |
419 | + it('should only appear when the DOM contains the notifier box', function() { |
420 | + notifierBox.remove(); |
421 | + notifications.add({title: 'mytitle', level: 'error'}); |
422 | + assertNumNotifiers(0); |
423 | + }); |
424 | + |
425 | + it('should not appear when the notification is not an error', function() { |
426 | + notifications.add({title: 'mytitle', level: 'info'}); |
427 | + assertNumNotifiers(0); |
428 | + }); |
429 | + |
430 | + it('should not appear when the notification comes form delta', function() { |
431 | + notifications.add({title: 'mytitle', level: 'error', isDelta: true}); |
432 | + assertNumNotifiers(0); |
433 | + }); |
434 | + |
435 | +}); |
436 | |
437 | === added file 'test/test_notifier_widget.js' |
438 | --- test/test_notifier_widget.js 1970-01-01 00:00:00 +0000 |
439 | +++ test/test_notifier_widget.js 2012-11-16 13:56:21 +0000 |
440 | @@ -0,0 +1,103 @@ |
441 | +'use strict'; |
442 | + |
443 | +describe('notifier widget', function() { |
444 | + var Notifier, notifierBox, Y; |
445 | + |
446 | + before(function(done) { |
447 | + Y = YUI(GlobalConfig).use('notifier', 'node-event-simulate', |
448 | + function(Y) { |
449 | + Notifier = Y.namespace('juju.widgets').Notifier; |
450 | + done(); |
451 | + }); |
452 | + }); |
453 | + |
454 | + // Create the notifier box and attach it as first element of the body. |
455 | + beforeEach(function() { |
456 | + notifierBox = Y.Node.create('<div id="notifier-box"></div>'); |
457 | + notifierBox.setStyle('display', 'none'); |
458 | + Y.one('body').prepend(notifierBox); |
459 | + }); |
460 | + |
461 | + // Destroy the notifier box created in beforeEach. |
462 | + afterEach(function() { |
463 | + notifierBox.remove(); |
464 | + notifierBox.destroy(true); |
465 | + }); |
466 | + |
467 | + // Factory rendering and returning a notifier instance. |
468 | + var makeNotifier = function(title, message, timeout) { |
469 | + var notifier = new Notifier({ |
470 | + title: title || 'mytitle', |
471 | + message: message || 'mymessage', |
472 | + timeout: timeout || 10000 |
473 | + }); |
474 | + notifier.render(notifierBox); |
475 | + return notifier; |
476 | + }; |
477 | + |
478 | + // Assert the notifier box contains the expectedNumber of notifiers. |
479 | + var assertNumNotifiers = function(expectedNumber) { |
480 | + assert.equal(expectedNumber, notifierBox.get('children').size()); |
481 | + }; |
482 | + |
483 | + it('should be able to display a notification', function() { |
484 | + makeNotifier(); |
485 | + assertNumNotifiers(1); |
486 | + }); |
487 | + |
488 | + it('should display the given title and message', function() { |
489 | + makeNotifier('mytitle', 'mymessage'); |
490 | + var notifierNode = notifierBox.one('*'); |
491 | + assert.equal('mytitle', notifierNode.one('h3').getContent()); |
492 | + assert.equal('mymessage', notifierNode.one('div').getContent()); |
493 | + }); |
494 | + |
495 | + it('should be able to display multiple notifications', function() { |
496 | + var number = 10; |
497 | + for (var i = 0; i < number; i += 1) { |
498 | + makeNotifier(); |
499 | + } |
500 | + assertNumNotifiers(number); |
501 | + }); |
502 | + |
503 | + it('should display new notifications on top', function() { |
504 | + makeNotifier('mytitle1', 'mymessage1'); |
505 | + makeNotifier('mytitle2', 'mymessage2'); |
506 | + var notifierNode = notifierBox.one('*'); |
507 | + assert.equal('mytitle2', notifierNode.one('h3').getContent()); |
508 | + assert.equal('mymessage2', notifierNode.one('div').getContent()); |
509 | + }); |
510 | + |
511 | + it('should destroy notifications after N milliseconds', function(done) { |
512 | + makeNotifier('mytitle', 'mymessage', 1); |
513 | + // A timeout of 250 milliseconds is used so that we ensure the destroying |
514 | + // animation can be completed. |
515 | + setTimeout(function() { |
516 | + assertNumNotifiers(0); |
517 | + done(); |
518 | + }, 250); |
519 | + }); |
520 | + |
521 | + it('should destroy notifications on click', function(done) { |
522 | + makeNotifier(); |
523 | + notifierBox.one('*').simulate('click'); |
524 | + // A timeout of 250 milliseconds is used so that we ensure the destroying |
525 | + // animation can be completed. |
526 | + setTimeout(function() { |
527 | + assertNumNotifiers(0); |
528 | + done(); |
529 | + }, 250); |
530 | + }); |
531 | + |
532 | + it('should prevent notification removal on mouse enter', function(done) { |
533 | + makeNotifier('mytitle', 'mymessage', 1); |
534 | + notifierBox.one('*').simulate('mouseover'); |
535 | + // A timeout of 250 milliseconds is used so that we ensure the node is not |
536 | + // preserved by the destroying animation. |
537 | + setTimeout(function() { |
538 | + assertNumNotifiers(1); |
539 | + done(); |
540 | + }, 250); |
541 | + }); |
542 | + |
543 | +}); |
Reviewers: mp+134660_ code.launchpad. net,
Message:
Please take a look.
Description:
Growl-style notifications.
This branch introduces growl-style notifications
in the top-right corner of the window.
Implemented a notifier widget: it is a reusable
piece of code that just need a title, a message
and a timeout to display a notification. It is used
to render notification outside view containers, so
that notifications are preserved when the user changes
the current view.
A notifier is created when a notification is added,
following these rules:
- the notification is an error;
- the notification is local (it's not related to the
delta stream).
The last point involves adding a new field "isDelta"
to the notification model, defaulting to false.
Notifications created when the delta stream arrives are
marked as "isDelta: true".
UI: new notifications appear on top, and disappear after
8 seconds. Mouse over prevents a notification to hide,
mouse click destroys a notification.
Also fixed a bug in the unit view: a callable was undefined
when trying to "retry" or "resolve" a unit.
This branch is intended to be a first implementation proposal
of the growl notifications. It doesn't solve open issues like:
- how to handle/aggregate multiple notifications;
- how to correctly generate and format titles and messages;
- where notifications should appear in the window.
https:/ /code.launchpad .net/~frankban/ juju-gui/ bug-1076404- growl/+ merge/134660
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/6851058/
Affected files: images/ notifier- error.png models. js debug.js notifications. js notifier. handlebars notifications. js notifier. js stylesheet. less notifications. js notifier_ widget. js
A [revision details]
M app/app.js
A app/assets/
M app/index.html
M app/models/
M app/modules-
M app/store/
A app/templates/
M app/views/
A app/widgets/
M lib/views/
M test/index.html
M test/test_
A test/test_