Implemented notes UI.

- Switched from a global reset in www/css/ts-screen.scss to a selected
  top-level elements reset to allow default formatting for user notes.
- Restructured the #entry-list and entry displays.
- Restructured notes div, now has a sub-div for text and a textarea
  element for input.
- Added Showdown.js, a JavaScript Markdown library for formatting comments.
- Moved EntryView blur events to the View events map.
- Added images for expansion of notes.
- Added the ability to edit notes.
- Split EntryListView.addOne into renderOne and addOne so that renderOne
  can be called with a new entry (fixes duration glitch)
This commit is contained in:
Jonathan Bernard
2011-05-16 04:09:37 -05:00
parent 65a9a517f9
commit 2cc17b85f1
35 changed files with 1949 additions and 1427 deletions

View File

@ -91,18 +91,30 @@ $(document).ready(function(){
className: 'entry',
notesCache: false,
events: {
"click img.notes-icon" : "toggleNotes",
"click img.expand-entry" : "showNotes",
"click img.collapse-entry" : "hideNotes",
"dblclick div.mark" : "editMark",
"dblclick div.timestamp" : "editTimestamp",
"dblclick div.notes" : "editNotes",
"keypress .mark-input" : "updateOnEnter",
"keypress .timestamp-input" : "updateOnEnter"
"keypress .timestamp-input" : "updateOnEnter",
"keypress .notes-input" : "updateOnCtrlEnter",
"blur .mark-input" : "close",
"blur .timestamp-input" : "close",
"blur .notes-input" : "close"
},
initialize: function(options) {
_.bindAll(this, 'render', 'close', 'editTImestamp',
'editMark', 'updateOnEnter', 'getViewModel', 'toggleNotes');
this.model.bind('change', this.render);
_.bindAll(this, 'render', 'close', 'editTImestamp', 'editMark',
'update', 'updateOnEnter', 'updateOnCtrlEnter', 'getViewModel',
'renderNotes', 'showNotes', 'hideNotes');
this.markdownConverter = options.markdownConverter;
this.model.bind('change', this.update);
this.model.view = this;
this.nextModel = options.nextModel;
@ -113,9 +125,12 @@ $(document).ready(function(){
* HTML content. Add new `blur` listeners to the input fields.
*/
render: function() {
// render the HTML
$(this.el).html(ich.entryTemplate(this.getViewModel()));
this.$(".mark-input").bind('blur', this.close);
this.$(".timestamp-input").bind('blur', this.close);
// invalidate the notes display cache
this.notesCache = false;
return this;
},
@ -130,9 +145,19 @@ $(document).ready(function(){
this.$('.timestamp').text(data.start);
this.$('.timestamp-input').val(data.timestamp);
this.$('.duration').text(data.duration);
this.$('.notes-text').html(this.renderNotes(data.notes));
this.$('.notes-input').val(data.notes);
return this;
},
renderNotes: function(source) {
if (!this.notesCache) {
this.notesCache = this.markdownConverter.makeHtml(source);
}
return this.notesCache
},
editMark: function() {
$(this.el).addClass('edit-mark');
this.$('.mark-input').focus();
@ -145,6 +170,18 @@ $(document).ready(function(){
return this;
},
editNotes: function() {
// invalidate notes HTML cache
this.notesCache = false;
// show notes textarea, hide display
$(this.el).addClass('edit-notes');
// focus input
this.$('.notes-input').focus();
return this;
},
/**
* Translate the model data into a form suitable to be displayed.
* @return a map including display-able `start` and `duration` values.
@ -156,20 +193,21 @@ $(document).ready(function(){
var tsDate = new Date(data.timestamp);
data.start = this.formatStart(tsDate);
data.duration = this.formatDuration(this.model, this.nextModel);
data.notes = data.notes ? data.notes : '*No notes for this entry.*';
return data;
},
/** Close editable fields. */
close: function() {
$(this.el).removeClass('edit-mark edit-timestamp');
$(this.el).removeClass('edit-mark edit-timestamp edit-notes');
},
/** Persist changes in input fields. */
save: function() {
this.model.save({
mark: this.$('.mark-input').val(),
timestamp: this.$('.timestamp-input').val()});
this.update();
timestamp: this.$('.timestamp-input').val(),
notes: this.$('.notes-input').val()});
},
/** Event handler for keypresses on entry input fields. */
@ -177,6 +215,10 @@ $(document).ready(function(){
if(e.keyCode == 13) { this.save(); this.close(); }
},
updateOnCtrlEnter: function(e) {
if (e.keyCode == 10) { this.save(); this.close(); }
},
/**
* Get the display-able start time from the entry timestamp.
* @param startDate a Date object, the entry timestamp.
@ -229,8 +271,19 @@ $(document).ready(function(){
min + "m ";
},
toggleNotes: function() {
this.$('.notes').slideToggle();
showNotes: function() {
if (!this.notesCache) {
this.$('.notes-text').html(
this.renderNotes(this.model.get('notes')))
}
this.$('.notes').slideDown();
$(this.el).addClass('show-notes');
},
hideNotes: function() {
this.$('.notes').slideUp();
$(this.el).removeClass('show-notes');
}
});
@ -243,15 +296,24 @@ $(document).ready(function(){
},
initialize: function() {
_.bindAll(this, 'addOne', 'createNewEntry', 'render');
_.bindAll(this, 'addOne', 'createNewEntry', 'render', 'renderOne');
this.collection.bind('add', this.addOne);
this.collection.bind('refresh', this.render);
this.collection.view = this;
this.entryContainer = this.$("#entries")
this.markdownConverter = new Showdown.converter();
},
addOne: function(entry, nextEntry) {
if (!entry.view) { new TS.EntryView({model: entry}); }
addOne: function(entry) {
var lastEntry = this.collection.at(this.collection.length - 2);
lastEntry.view.nextModel = entry;
lastEntry.view.update();
this.renderOne(entry, null);
},
renderOne: function(entry, nextEntry) {
if (!entry.view) { new TS.EntryView(
{model: entry, markdownConverter: this.markdownConverter}); }
entry.view.nextModel = nextEntry
this.entryContainer.prepend(entry.view.render().el);
},
@ -280,7 +342,7 @@ $(document).ready(function(){
var entry = this.collection.at(i);
var nextEntry = (i + 1 < len ? this.collection.at(i + 1) : null);
this.addOne(entry, nextEntry);
this.renderOne(entry, nextEntry);
}
}
});