Added message log UI
Change-Id: If446e95e301ee218a6aaf49d4f141e6b77236733
diff --git a/src/components/messages.js b/src/components/messages.js
index 6d862a2..0b39920 100644
--- a/src/components/messages.js
+++ b/src/components/messages.js
@@ -8,42 +8,144 @@
var message = require('./message');
var Messages = defineClass({
+ statics: {
+ SLIDE_DOWN: 150,
+ TTL: 9000,
+ FADE: 1000,
+ SLIDE_UP: 300,
+ OPEN_CLOSE: 500
+ },
+
publics: {
+ close: function() {
+ var self = this;
+
+ if (this.isOpen()) {
+ if (this.$messages.children().length) {
+ var scrollOffset =
+ this.$messages.scrollTop() + self.$messages.height();
+
+ this.$messages
+ .addClass('animating')
+ .animate({ height: 0 }, {
+ duration: this.OPEN_CLOSE,
+ progress: function() {
+ self.$messages.scrollTop(
+ scrollOffset - self.$messages.height());
+ },
+ complete: function() {
+ self.$messages.removeClass('animating');
+ self.$.addClass('headlines');
+ self.$messages.attr('style', null);
+ }
+ });
+ } else {
+ this.$.addClass('headlines');
+ }
+ }
+ },
+
+ isClosed: function() {
+ return this.$.hasClass('headlines') &&
+ !this.$messages.hasClass('animating');
+ },
+
+ isOpen: function() {
+ return !this.$.hasClass('headlines') &&
+ !this.$messages.hasClass('animating');
+ },
+
+ open: function() {
+ var self = this;
+
+ if (!this.isOpen()) {
+ var $animating = this.$.find('.animating');
+ $animating.stop(true);
+ $animating.removeClass('animating');
+ $animating.attr('style', null);
+
+ this.$.removeClass('headlines');
+ if (this.$messages.children().length) {
+ var goalHeight = this.$messages.height();
+ this.$messages
+ .addClass('animating')
+ .height(0)
+ .animate({ height: goalHeight }, {
+ duration: this.OPEN_CLOSE,
+ progress: function() {
+ self.$messages.scrollTop(self.$messages.prop('scrollHeight'));
+ },
+ complete: function() {
+ self.$messages.removeClass('animating');
+ self.$messages.attr('style', null);
+ }
+ });
+ }
+ }
+ },
+
push: function(messageData) {
var messageObject = new message.Message(messageData);
- /*
- * Implementation notes: slideDown won't work properly (won't be able to
- * calculate goal height) unless the element is in the DOM tree prior
- * to the call, so we hide first, attach, and then animate. slideDown
- * implicitly shows the element.
- *
- * Similarly, we use animate rather than fadeIn because fadeIn implicitly
- * hides the element upon completion, resulting in an abrupt void in the
- * element flow. Instead, we want to keep the element taking up space
- * while invisible until we've collapsed the height via slideUp.
- */
- messageObject.$.hide();
- this.$.append(messageObject.$);
- messageObject.$
- .slideDown(Messages.slideDown)
- .delay(Messages.ttl)
- .animate({ opacity: 0 }, Messages.fade)
- .slideUp(Messages.slideUp, function() {
- messageObject.$.remove();
- });
+ this.$messages.append(messageObject.$);
+
+ if (this.isOpen()) {
+ this.$messages.scrollTop(this.$messages.prop('scrollHeight'));
+ } else {
+ /*
+ * Implementation notes: slideDown won't work properly (won't be able to
+ * calculate goal height) unless the element is in the DOM tree prior
+ * to the call, so we hide first, attach, and then animate. slideDown
+ * implicitly shows the element. Furthermore, it won't run unless the
+ * element starts hidden.
+ *
+ * Similarly, we use animate rather than fadeIn because fadeIn
+ * implicitly hides the element upon completion, resulting in an abrupt
+ * void in the element flow. Instead, we want to keep the element taking
+ * up space while invisible until we've collapsed the height via
+ * slideUp.
+ *
+ * It would be best to use CSS animations, but at this time that would
+ * mean sacrificing either auto-height or flow-affecting sliding.
+ */
+ messageObject.$.addClass('animating');
+ messageObject.$
+ .slideDown(this.SLIDE_DOWN)
+ .delay(this.TTL)
+ .animate({ opacity: 0 }, this.FADE)
+ .slideUp(this.SLIDE_UP, function() {
+ messageObject.$
+ .removeClass('animating')
+ .attr('style', null);
+ });
+ }
+ },
+
+ toggle: function() {
+ /* If this were pure CSS, we could just toggle, but we need to do some
+ * JS housekeeping. */
+ if (this.isOpen()) {
+ this.close();
+ } else if (this.isClosed()) {
+ this.open();
+ }
}
},
constants: ['$'],
init: function() {
- this.$ = $('<ul>').addClass('messages');
+ this.$handle = $('<div>')
+ .addClass('handle')
+ .click(this.toggle.bind(this));
+
+ this.$messages = $('<ul>');
+
+ this.$ = $('<div>')
+ .addClass('messages')
+ .addClass('headlines')
+ .append(this.$handle)
+ .append(this.$messages);
}
});
-Messages.slideDown = 150;
-Messages.ttl = 9000;
-Messages.fade = 1000;
-Messages.slideUp = 300;
-
module.exports = Messages;
\ No newline at end of file
diff --git a/src/static/index.css b/src/static/index.css
index 416663c..f62f541 100644
--- a/src/static/index.css
+++ b/src/static/index.css
@@ -63,22 +63,77 @@
height: 100%;
}
-ul.messages {
+.messages {
width: 30%;
- min-width: 10em;
+ border: 1px solid #aaa;
+ border-radius: 2px;
+}
+
+.messages.headlines {
+ border: initial;
+}
+
+.messages .handle {
+ background-color: rgba(192, 192, 192, .95);
+ border: 1px solid #aaa;
+ border-radius: 2px;
+ color: #444;
+ cursor: pointer;
+ text-align: center;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.messages.headlines .handle {
+ background-color: rgba(255, 255, 255, .5);
+ border: initial;
+}
+
+.handle:before {
+ content: "=";
+ font-stretch: expanded;
+}
+
+.handle:after {
+ content: "=";
+ font-stretch: expanded;
+}
+
+.messages ul {
+ background-color: rgba(255, 255, 255, .95);
list-style: none;
+ margin: 0;
+ max-height: 20em;
+ overflow: auto;
+ min-width: 10em;
+ padding: 0;
+ width: 100%;
+}
+
+.messages.headlines ul {
+ background-color: initial;
+ max-height: initial;
}
.messages li {
- color: #FFF;
- background-color: rgba(0, 0, 0, .6);
font-size: 10pt;
padding: 3px 3px 3px 1em;
- border-radius: 4px;
- margin-bottom: 3px;
text-indent: -.5em;
}
+.messages.headlines li {
+ background-color: rgba(0, 0, 0, .6);
+ border-radius: 4px;
+ color: white;
+ display: none;
+ margin-top: 3px;
+}
+
.messages li:before {
font-weight: bold;
}
diff --git a/test/components/map.js b/test/components/map.js
index 63ea0b8..62f483a 100644
--- a/test/components/map.js
+++ b/test/components/map.js
@@ -16,7 +16,7 @@
maps: mockMaps
});
- var $messages = $('.messages', map.$);
+ var $messages = $('.messages ul', map.$);
t.ok($messages.length, 'message display exists');
t.equals($messages.children().length, 0, 'message display is empty');
diff --git a/test/travel.js b/test/travel.js
index 6999b71..2aca67c 100644
--- a/test/travel.js
+++ b/test/travel.js
@@ -30,7 +30,7 @@
maps: mockMaps
});
- var $messages = $('.messages');
+ var $messages = $('.messages ul');
t.ok($messages.length, 'message display exists');
t.equals($messages.children().length, 0, 'message display is empty');