mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-31 00:48:21 +01:00
Improve the implementation of Notifications
Summary: Currently, you can't change a notification that's already shown. There's no reason for this. (I'm planning to put file upload progress/errors in notifications.) - Make `setContent()` and `setDuration()` immediately affect the notification. - When there are more than 5 notifications, queue them up instead of dropping them. - Allow arbitrarily many classes to be added/removed. - Make the examples in the UIExamples tests more rich. Test Plan: - Verified normal notifications continue to function as expected. - Played with the UIExamples notifications: - Verified the "update every second" notification udpated every second. - Verified the permanent alert notification was yellow and requires a click to dismiss. - Verified the interactive notification responds correctly to "OK" / "Cancel". - Verified the "click every 2 seconds" notification doesn't vanish until not clicked for 2 seconds. Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran Differential Revision: https://secure.phabricator.com/D3653
This commit is contained in:
parent
3440839c99
commit
e00d3b72fe
4 changed files with 125 additions and 74 deletions
|
@ -888,7 +888,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'javelin-behavior-aphlict-listen' =>
|
'javelin-behavior-aphlict-listen' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/0743d3f3/rsrc/js/application/aphlict/behavior-aphlict-listen.js',
|
'uri' => '/res/6dde3f43/rsrc/js/application/aphlict/behavior-aphlict-listen.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
@ -1522,7 +1522,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'javelin-behavior-phabricator-notification-example' =>
|
'javelin-behavior-phabricator-notification-example' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/df97e4b3/rsrc/js/application/uiexample/notification-example.js',
|
'uri' => '/res/a6d51998/rsrc/js/application/uiexample/notification-example.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
@ -2499,7 +2499,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'phabricator-notification' =>
|
'phabricator-notification' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/cacd79f1/rsrc/js/application/core/Notification.js',
|
'uri' => '/res/c604fbbe/rsrc/js/application/core/Notification.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -35,7 +35,7 @@ JX.behavior('aphlict-listen', function(config) {
|
||||||
|
|
||||||
new JX.Notification()
|
new JX.Notification()
|
||||||
.setContent('(Aphlict) [' + type + '] ' + details)
|
.setContent('(Aphlict) [' + type + '] ' + details)
|
||||||
.setClassName('jx-notification-debug')
|
.alterClassName('jx-notification-debug', true)
|
||||||
.setDuration(0)
|
.setDuration(0)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ JX.behavior('aphlict-listen', function(config) {
|
||||||
!showing_reload) {
|
!showing_reload) {
|
||||||
var reload = new JX.Notification()
|
var reload = new JX.Notification()
|
||||||
.setContent('Page updated, click to reload.')
|
.setContent('Page updated, click to reload.')
|
||||||
.setClassName('jx-notification-alert')
|
.alterClassName('jx-notification-alert', true)
|
||||||
.setDuration(0);
|
.setDuration(0);
|
||||||
reload.listen('activate', function(e) { JX.$U().go(); })
|
reload.listen('activate', function(e) { JX.$U().go(); })
|
||||||
reload.show();
|
reload.show();
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a notification. Usage:
|
* Show a notification popup on screen. Usage:
|
||||||
*
|
*
|
||||||
* var n = new JX.Notification()
|
* var n = new JX.Notification()
|
||||||
* .setContent('click me!');
|
* .setContent('click me!');
|
||||||
|
@ -21,49 +21,78 @@ JX.install('Notification', {
|
||||||
events : ['activate', 'close'],
|
events : ['activate', 'close'],
|
||||||
|
|
||||||
members : {
|
members : {
|
||||||
|
_container : null,
|
||||||
|
_visible : false,
|
||||||
|
_hideTimer : null,
|
||||||
|
_duration : 12000,
|
||||||
|
|
||||||
show : function() {
|
show : function() {
|
||||||
var self = JX.Notification;
|
if (!this._visible) {
|
||||||
self._show(this);
|
this._visible = true;
|
||||||
|
|
||||||
if (this.getDuration()) {
|
var self = JX.Notification;
|
||||||
setTimeout(JX.bind(self, self._hide, this), this.getDuration());
|
self._show(this);
|
||||||
|
this._updateTimer();
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
},
|
},
|
||||||
_render : function() {
|
|
||||||
return JX.$N(
|
|
||||||
'div',
|
|
||||||
{
|
|
||||||
className: 'jx-notification ' + this.getClassName(),
|
|
||||||
sigil: 'jx-notification'
|
|
||||||
},
|
|
||||||
this.getContent());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
properties : {
|
hide : function() {
|
||||||
|
if (this._visible) {
|
||||||
|
this._visible = false;
|
||||||
|
|
||||||
|
var self = JX.Notification;
|
||||||
|
self._hide(this);
|
||||||
|
this._updateTimer();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
alterClassName : function(name, enable) {
|
||||||
|
JX.DOM.alterClass(this._getContainer(), name, enable);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
setContent : function(content) {
|
||||||
|
JX.DOM.setContent(this._getContainer(), content);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional class name(s) to add to the rendered notification.
|
* Set duration before the notification fades away, in milliseconds. If set
|
||||||
*
|
* to 0, the notification persists until dismissed.
|
||||||
* @param string Class name(s).
|
|
||||||
*/
|
|
||||||
className : null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notification content.
|
|
||||||
*
|
|
||||||
* @param mixed Content.
|
|
||||||
*/
|
|
||||||
content : null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Duration before the notification fades away, in milliseconds. If set to
|
|
||||||
* 0, the notification persists until dismissed.
|
|
||||||
*
|
*
|
||||||
* @param int Notification duration, in milliseconds.
|
* @param int Notification duration, in milliseconds.
|
||||||
|
* @return this
|
||||||
*/
|
*/
|
||||||
duration : 12000
|
setDuration : function(milliseconds) {
|
||||||
|
this._duration = milliseconds;
|
||||||
|
this._updateTimer(false);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateTimer : function() {
|
||||||
|
if (this._hideTimer) {
|
||||||
|
clearTimeout(this._hideTimer);
|
||||||
|
this._hideTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._visible && this._duration) {
|
||||||
|
this._hideTimer = setTimeout(JX.bind(this, this.hide), this._duration);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_getContainer : function() {
|
||||||
|
if (!this._container) {
|
||||||
|
this._container = JX.$N(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
className: 'jx-notification',
|
||||||
|
sigil: 'jx-notification'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._container;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
statics : {
|
statics : {
|
||||||
|
@ -74,23 +103,14 @@ JX.install('Notification', {
|
||||||
var self = JX.Notification;
|
var self = JX.Notification;
|
||||||
|
|
||||||
self._installListener();
|
self._installListener();
|
||||||
self._active.push({
|
self._active.push(notification);
|
||||||
object: notification,
|
|
||||||
render: notification._render()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Don't show more than a few notifications at once because it's silly.
|
|
||||||
while (self._active.length > 5) {
|
|
||||||
self._hide(self._active[0].object);
|
|
||||||
}
|
|
||||||
|
|
||||||
self._redraw();
|
self._redraw();
|
||||||
},
|
},
|
||||||
_hide : function(notification) {
|
_hide : function(notification) {
|
||||||
var self = JX.Notification;
|
var self = JX.Notification;
|
||||||
|
|
||||||
for (var ii = 0; ii < self._active.length; ii++) {
|
for (var ii = 0; ii < self._active.length; ii++) {
|
||||||
if (self._active[ii].object === notification) {
|
if (self._active[ii] === notification) {
|
||||||
notification.invoke('close');
|
notification.invoke('close');
|
||||||
self._active.splice(ii, 1);
|
self._active.splice(ii, 1);
|
||||||
break;
|
break;
|
||||||
|
@ -113,17 +133,17 @@ JX.install('Notification', {
|
||||||
'jx-notification',
|
'jx-notification',
|
||||||
function(e) {
|
function(e) {
|
||||||
// NOTE: Don't kill the event since the user might have clicked a
|
// NOTE: Don't kill the event since the user might have clicked a
|
||||||
// link, and we want to follow the link if they did. Istead, invoke
|
// link, and we want to follow the link if they did. Instead, invoke
|
||||||
// the activate event for the active notification and dismiss it if it
|
// the activate event for the active notification and dismiss it if it
|
||||||
// isn't handled.
|
// isn't handled.
|
||||||
|
|
||||||
var target = e.getNode('jx-notification');
|
var target = e.getNode('jx-notification');
|
||||||
for (var ii = 0; ii < self._active.length; ii++) {
|
for (var ii = 0; ii < self._active.length; ii++) {
|
||||||
var n = self._active[ii];
|
var n = self._active[ii];
|
||||||
if (n.render === target) {
|
if (n._getContainer() === target) {
|
||||||
var activation = n.object.invoke('activate');
|
var activation = n.invoke('activate');
|
||||||
if (!activation.getPrevented()) {
|
if (!activation.getPrevented()) {
|
||||||
self._hide(n.object);
|
n.hide();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -151,9 +171,15 @@ JX.install('Notification', {
|
||||||
document.body.appendChild(self._container);
|
document.body.appendChild(self._container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show only a limited number of notifications at once.
|
||||||
|
var limit = 5;
|
||||||
|
|
||||||
var notifications = [];
|
var notifications = [];
|
||||||
for (var ii = 0; ii < self._active.length; ii++) {
|
for (var ii = 0; ii < self._active.length; ii++) {
|
||||||
notifications.push(self._active[ii].render);
|
notifications.push(self._active[ii]._getContainer());
|
||||||
|
if (!(--limit)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JX.DOM.setContent(self._container, notifications);
|
JX.DOM.setContent(self._container, notifications);
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
JX.behavior('phabricator-notification-example', function(config) {
|
JX.behavior('phabricator-notification-example', function(config) {
|
||||||
|
|
||||||
|
var sequence = 0;
|
||||||
|
|
||||||
JX.Stratcom.listen(
|
JX.Stratcom.listen(
|
||||||
'click',
|
'click',
|
||||||
'notification-example',
|
'notification-example',
|
||||||
|
@ -14,29 +17,51 @@ JX.behavior('phabricator-notification-example', function(config) {
|
||||||
e.kill();
|
e.kill();
|
||||||
|
|
||||||
var notification = new JX.Notification();
|
var notification = new JX.Notification();
|
||||||
if (Math.random() > 0.1) {
|
switch (sequence % 4) {
|
||||||
notification.setContent('It is ' + new Date().toString());
|
case 0:
|
||||||
|
var update = function() {
|
||||||
|
notification.setContent('It is ' + new Date().toString());
|
||||||
|
};
|
||||||
|
|
||||||
notification.listen(
|
update();
|
||||||
'activate',
|
setInterval(update, 1000);
|
||||||
function(e) {
|
|
||||||
if (!confirm("Close notification?")) {
|
|
||||||
e.kill();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification
|
|
||||||
.setContent('Alert! Click to reload!')
|
|
||||||
.setDuration(0)
|
|
||||||
.setClassName('jx-notification-alert');
|
|
||||||
|
|
||||||
notification.listen(
|
break;
|
||||||
'activate',
|
case 1:
|
||||||
function(e) {
|
notification
|
||||||
new JX.$U().go();
|
.setContent('Permanent alert notification (until clicked).')
|
||||||
});
|
.setDuration(0)
|
||||||
|
.alterClassName('jx-notification-alert', true);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
notification
|
||||||
|
.setContent('This notification reacts when you click it.');
|
||||||
|
|
||||||
|
notification.listen(
|
||||||
|
'activate',
|
||||||
|
function() {
|
||||||
|
if (!confirm("Close notification?")) {
|
||||||
|
JX.Stratcom.context().kill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
notification
|
||||||
|
.setDuration(2000)
|
||||||
|
.setContent('This notification will close after 2 seconds ' +
|
||||||
|
'unless you keep clicking it!');
|
||||||
|
|
||||||
|
notification.listen(
|
||||||
|
'activate',
|
||||||
|
function() {
|
||||||
|
notification.setDuration(2000);
|
||||||
|
JX.Stratcom.context().kill();
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
notification.show()
|
|
||||||
|
notification.show();
|
||||||
|
sequence++;
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue