Implemented entry creation, pagination. Some restyling and bugfixes.

- Bug fix in ts_api:list_entries/3. Case statement matching on atoms but
  input is a list (string).
- Bug fix in ts_api:put_entry/3. Was expecting the wrong result from
  ts_entry:new/1.
- Bug fix in ts_entry:list/4. Code crashed when the starting offset was
  greater than the total number of elements. Now returns [].
- Fixed ts_json:encode_datetime/1 and ts_json:decode_datetime/1 to handle
  millisecond values in the datetime string (per ISO standard).
- Broke out ``control-links`` style to a top-level class.
- Added showdown.js, a JS Markdown processor. Not hooked up to anything
  yet but intend to display entry notes with Markdown.
- Added code for entry pagination. Loads the most recent 20 entries and
  loads more upon demand in batches of 20.
- Fixed bug in login routine that kept the user edit fields from being
  pre-populated.
- Rewrote the loadEntries function to double for new entries and loading
  more existing entries.
- Commented displayEntries. Also refactored into displayNewerEntries,
  which pushed new entries on to the top of the stack, and
  displayOlderEntries, which tags them onto the bottom.
- Implemented hidden notes field for new entry input.
- Implemented new entry creation.
- Created a helper function to ISO format a Date object.
- Expanded entry template to show control links (edit, show notes, del).
- Activated the 'load more entries' button.
This commit is contained in:
Jonathan Bernard
2011-03-07 16:43:40 -06:00
parent c185c8cd81
commit 1b1e31059b
14 changed files with 1720 additions and 114 deletions

View File

@ -4,11 +4,17 @@ var activeTimeline = [];
var entries = [];
var newEntryBar;
var moreEntriesbar;
var currentEntryOffset = 0;
var loadLength = 20;
/* Setup after the document is ready for manipulation. */
$(document).ready(function(){
// looked up once and remembered
newEntryBar = $("#new-entry");
moreEntriesBar = $("#more-entries");
// wire the login dialog using jQuery UI
$("#login-dialog").dialog({
@ -92,9 +98,8 @@ function loadUser(username) {
$("#username").text("- " + user.username);
// pre-populate the editable user-info fields
// TODO: not working
$("#fullname-input").text(user.name);
$("#email-input").text(user.email);
$("#fullname-input").val(user.name);
$("#email-input").val(user.email);
// set the active timeline to the first in the list
// TODO: implement a mechanism to remember the last used timeline
@ -109,7 +114,7 @@ function loadUser(username) {
// choices
// load the entries for this timeline
loadEntries(user, activeTimeline)
loadEntries(user, activeTimeline, "new")
},
error: function(jqXHR, textStatus, error) {
@ -120,19 +125,31 @@ function loadUser(username) {
});
}
/* Read the first 50 entries for a timeline. */
function loadEntries(user, timeline) {
/* Read entries for a timeline. */
function loadEntries(user, timeline, order) {
// call the API list_entries function via AJAX
$.ajax({
url: "/ts_api/entries/" + user.username + "/" + timeline.timeline_id + "?order=desc",
url: "/ts_api/entries/" + user.username + "/"
+ timeline.timeline_id + "?order=asc&start="
+ currentEntryOffset + "&length=" + loadLength,
type: "GET",
success: function(data, textStatus, jqXHR) {
entries = data.entries;
// push the entries onto the page
displayEntries(entries)
if (order == "new") {
// reverse the list so that the oldest are first
entries.reverse()
// push the entries onto the page
displayNewerEntries(entries)
} else {
displayOlderEntries(entries)
}
// update our current offset
currentEntryOffset += loadLength;
},
error: function(jqXHR, textStatus, error) {
@ -141,11 +158,50 @@ function loadEntries(user, timeline) {
});
}
/* Push the entries onto the top of the entry display. */
function displayEntries(entries) {
/* Push the entries onto the top of the entry display. This function assumes
* that the entries are sorted by id, ascending (0, 1, 2). Each new entry goes
* on top, pushing down all existing entries. So the first entry in the
* argument list ends up at the bottom of the new entries. */
function displayNewerEntries(entries) {
// for each entry
_.each(entries, function(entry) {
newEntryBar.after(ich.entry(entry));
// remove the existing top-entry designation
$(".top-entry").removeClass("top-entry");
// create the new entry from the entry template (see ICanHas.js)
var entryElem = ich.entry(entry);
// mark the new entry as the current top-entry
entryElem.addClass("top-entry");
// push the entry on after the new-entry bar (on top of existing
// entries).
newEntryBar.after(entryElem);
// TODO: animate? If so, may need to pause after each to allow
// some animation to take place. Going for a continuous push-down
// stack effect.
});
}
/* Push the entries onto the bottom of the entry display. The function assumes
* that the entries are sorted by id, descending (2, 1, 0). Each new entry goes
* at the bottom of the stack of entries, extending the stack downwards. So the
* last entry in the list is at the very bottom of the stack. */
function displayOlderEntries(entries) {
// for each entry
_.each(entries, function(entry) {
// create the new entry from the template (ICanHas.js)
var entryElem = ich.entry(entry);
// place the entry on top of the bottom of the page
moreEntriesBar.before(entryElem);
// perhaps animate, see comments at the end of displayNewerEntries()
});
}
@ -185,8 +241,75 @@ function updateTimeline(event) {
/* Show/hide the add notes panel. */
function showNewNotes(event) { $("#add-notes").slideToggle("slow"); }
/* Create a new entry based on the user's input in the new-entry panel. */
function newEntry(event) {
alert("TODO: create entry vi AJAX");
function toggleEntryNotes(event, entryId) {
var selector = "#" + entryId + " .entry-notes";
$(selector).slideToggle("slow");
event.preventDefault();
}
function editEntry(event, entryId) {
var selector = "#" + entryId;
alert("TODO: implement edit entry. Called for '" + selector + "'");
event.preventDefault();
}
/* Create a new entry based on the user's input in the new-entry panel. */
function newEntry(event) {
var mark = $("#new-entry-input").val();
var notes = $("#new-notes-input").val();
var timestamp = getUTCTimestamp(); // timestamp is handled on client.
// This is important. It means that the timestamps are completely in the
// hands of the user. Timestamps from this service can not be considered
// secure. This service is not a punch-card, but a time-tracker, voluntary
// and editable by the user.
var payload = JSON.stringify(
{ mark: mark, notes: notes, timestamp: timestamp });
$.ajax({url: "/ts_api/entries/" + user.username
+ "/" + activeTimeline.timeline_id,
type: "PUT",
data: payload,
error: function(jqXHR, textStatus, error) {
// TODO: better error handling.
alert("Error creating entry: \n" + jqXHR.responseText); },
success: function(data, textStatus, jqXHR) {
// TODO: check that data.status == "ok"
// grab the entry data
var newEntry = data.entry;
// push the entry on top of the stack
displayNewerEntries([newEntry]);
// clear the input
$("#new-entry-input").val("");
$("#new-notes-input").val("");
}});
event.preventDefault();
}
/* Delete an entry. */
function deleteEntry(event, entryId) {
}
/* Generate a UTC timestamp string in ISO format.
* Javascript 1.8.5 has Date.toJSON(), which is equivalent to this function,
* but is only supported in Firefox 4. */
function getUTCTimestamp() {
var d = new Date();
function pad(n){return n<10 ? '0'+n : n}
return d.getUTCFullYear()+'-'
+ pad(d.getUTCMonth()+1)+'-'
+ pad(d.getUTCDate())+'T'
+ pad(d.getUTCHours())+':'
+ pad(d.getUTCMinutes())+':'
+ pad(d.getUTCSeconds())+'Z';
}