2011-03-01 18:00:51 -06:00
|
|
|
var user = [];
|
|
|
|
var timelines = [];
|
|
|
|
var activeTimeline = [];
|
|
|
|
var entries = [];
|
|
|
|
|
2011-03-03 17:05:30 -06:00
|
|
|
var newEntryBar;
|
2011-03-07 16:43:40 -06:00
|
|
|
var moreEntriesbar;
|
|
|
|
|
|
|
|
var currentEntryOffset = 0;
|
|
|
|
var loadLength = 20;
|
2011-03-01 18:00:51 -06:00
|
|
|
|
|
|
|
/* Setup after the document is ready for manipulation. */
|
2011-03-01 08:23:51 -06:00
|
|
|
$(document).ready(function(){
|
2011-02-24 07:29:30 -06:00
|
|
|
|
2011-03-07 16:43:40 -06:00
|
|
|
// looked up once and remembered
|
2011-03-03 17:05:30 -06:00
|
|
|
newEntryBar = $("#new-entry");
|
2011-03-07 16:43:40 -06:00
|
|
|
moreEntriesBar = $("#more-entries");
|
2011-03-01 18:00:51 -06:00
|
|
|
|
|
|
|
// wire the login dialog using jQuery UI
|
2011-03-01 08:23:51 -06:00
|
|
|
$("#login-dialog").dialog({
|
|
|
|
autoOpen: false,
|
|
|
|
height: 300,
|
|
|
|
width: 300,
|
|
|
|
modal: true,
|
2011-03-01 18:00:51 -06:00
|
|
|
buttons: { Login: function(){login()} }
|
2011-03-01 08:23:51 -06:00
|
|
|
});
|
|
|
|
|
2011-03-08 18:02:33 -06:00
|
|
|
// TODO: add a hook to AJAX requests to check for 401 unauth
|
|
|
|
// and re-display the login dialog.
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
// try to load user information for an authenticated user
|
2011-03-01 08:23:51 -06:00
|
|
|
$.ajax({
|
|
|
|
url: "/ts_api/users/",
|
|
|
|
type: "GET",
|
2011-03-01 18:00:51 -06:00
|
|
|
|
2011-03-01 08:23:51 -06:00
|
|
|
error: function(jqXHR, textStatus, error) {
|
2011-03-01 18:00:51 -06:00
|
|
|
// assume there is no authenticated user, show login dialog
|
2011-03-01 08:23:51 -06:00
|
|
|
$("#login-dialog").dialog("open"); },
|
2011-03-01 18:00:51 -06:00
|
|
|
|
2011-03-01 08:23:51 -06:00
|
|
|
success: function(data, textStatus, jqXHR) {
|
2011-03-01 18:00:51 -06:00
|
|
|
// load the user information
|
2011-03-01 08:23:51 -06:00
|
|
|
loadUser(data.user.username); }});
|
|
|
|
|
|
|
|
})
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
/* Read the user's credentials from the login form and perform
|
|
|
|
* an AJAX request to the API to set the session cookie. */
|
2011-03-01 08:23:51 -06:00
|
|
|
function login() {
|
2011-03-01 18:00:51 -06:00
|
|
|
// lookup the login dialog elements
|
2011-03-01 08:23:51 -06:00
|
|
|
var name = $("#login-name");
|
|
|
|
var pwd = $("#login-password");
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
// call the API via AJAX
|
2011-03-01 08:23:51 -06:00
|
|
|
$.ajax({
|
|
|
|
url: "/ts_api/login",
|
|
|
|
processData: false,
|
|
|
|
data: JSON.stringify({username: name.val(), password: pwd.val()}),
|
|
|
|
type: "POST",
|
2011-03-01 18:00:51 -06:00
|
|
|
|
2011-03-01 08:23:51 -06:00
|
|
|
error: function(jqXHR, textStatus, error) {
|
2011-03-01 18:00:51 -06:00
|
|
|
// assuming bad credentials (possible server error or bad request,
|
|
|
|
// we should check that, FIXME
|
2011-03-01 08:23:51 -06:00
|
|
|
var tips = $(".validate-tips");
|
|
|
|
tips.text("Incorrect username/password combination.");
|
|
|
|
tips.addClass("ui-state-error");
|
|
|
|
tips.slideDown();
|
|
|
|
},
|
2011-03-01 18:00:51 -06:00
|
|
|
|
2011-03-01 08:23:51 -06:00
|
|
|
success: function(data, textStatus, jqXHR) {
|
2011-03-01 18:00:51 -06:00
|
|
|
// load the user information and hide the login dialog
|
2011-03-01 08:23:51 -06:00
|
|
|
loadUser(name.val());
|
|
|
|
$("#login-dialog").dialog("close");
|
|
|
|
}});
|
2011-02-24 07:29:30 -06:00
|
|
|
}
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
/* End the current user session and expire any session credentials we
|
|
|
|
* have aquired. */
|
2011-02-24 07:29:30 -06:00
|
|
|
function logout(event) {
|
|
|
|
alert("TODO: log user out via AJAX.");
|
2011-03-01 18:00:51 -06:00
|
|
|
// TODO: wipe username, timeline, entry variables and displays
|
2011-02-24 07:29:30 -06:00
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
/* Load and display the user's information and timelines. */
|
2011-03-01 08:23:51 -06:00
|
|
|
function loadUser(username) {
|
2011-03-01 18:00:51 -06:00
|
|
|
|
|
|
|
// call the user_summary API function
|
2011-03-01 08:23:51 -06:00
|
|
|
$.ajax({
|
|
|
|
url: "/ts_api/app/user_summary/" + username,
|
|
|
|
type: "GET",
|
2011-03-01 18:00:51 -06:00
|
|
|
|
2011-03-01 08:23:51 -06:00
|
|
|
success: function(data, textStatus, jqXHR) {
|
2011-03-01 18:00:51 -06:00
|
|
|
// set the user variable
|
2011-03-01 08:23:51 -06:00
|
|
|
user = data.user;
|
2011-03-01 18:00:51 -06:00
|
|
|
|
|
|
|
// set the timelines variable
|
2011-03-01 08:23:51 -06:00
|
|
|
timelines = data.timelines;
|
2011-03-01 18:00:51 -06:00
|
|
|
|
|
|
|
// update the user id display
|
2011-03-01 08:23:51 -06:00
|
|
|
$("#fullname").text(user.name);
|
|
|
|
$("#username").text("- " + user.username);
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
// pre-populate the editable user-info fields
|
2011-03-07 16:43:40 -06:00
|
|
|
$("#fullname-input").val(user.name);
|
|
|
|
$("#email-input").val(user.email);
|
2011-03-01 08:23:51 -06:00
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
// set the active timeline to the first in the list
|
|
|
|
// TODO: implement a mechanism to remember the last used timeline
|
|
|
|
// on the server side and respond to that here.
|
2011-03-01 08:23:51 -06:00
|
|
|
activeTimeline = timelines[0];
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
// update the timeline display
|
2011-03-01 08:23:51 -06:00
|
|
|
$("#timeline-name").text(activeTimeline.timeline_id + " |");
|
|
|
|
$("#timeline-desc").text(activeTimeline.description);
|
2011-03-08 18:02:33 -06:00
|
|
|
$("#timeline-desc-input").val(activeTimeline.description);
|
2011-03-01 08:23:51 -06:00
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
// TODO: populate the drop-down list for the available timeline
|
|
|
|
// choices
|
|
|
|
|
|
|
|
// load the entries for this timeline
|
2011-03-07 16:43:40 -06:00
|
|
|
loadEntries(user, activeTimeline, "new")
|
2011-03-01 08:23:51 -06:00
|
|
|
},
|
2011-03-01 18:00:51 -06:00
|
|
|
|
2011-03-01 08:23:51 -06:00
|
|
|
error: function(jqXHR, textStatus, error) {
|
2011-03-01 18:00:51 -06:00
|
|
|
// TODO
|
2011-03-01 08:23:51 -06:00
|
|
|
alert("TODO: handle error for user load.")
|
2011-03-01 18:00:51 -06:00
|
|
|
alert(jqXHR.responseText)
|
2011-03-01 08:23:51 -06:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2011-03-07 16:43:40 -06:00
|
|
|
/* Read entries for a timeline. */
|
|
|
|
function loadEntries(user, timeline, order) {
|
2011-03-01 18:00:51 -06:00
|
|
|
|
|
|
|
// call the API list_entries function via AJAX
|
2011-03-01 08:23:51 -06:00
|
|
|
$.ajax({
|
2011-03-07 16:43:40 -06:00
|
|
|
url: "/ts_api/entries/" + user.username + "/"
|
|
|
|
+ timeline.timeline_id + "?order=asc&start="
|
|
|
|
+ currentEntryOffset + "&length=" + loadLength,
|
2011-03-01 08:23:51 -06:00
|
|
|
type: "GET",
|
2011-03-01 18:00:51 -06:00
|
|
|
|
2011-03-01 08:23:51 -06:00
|
|
|
success: function(data, textStatus, jqXHR) {
|
2011-03-01 18:00:51 -06:00
|
|
|
entries = data.entries;
|
2011-03-07 16:43:40 -06:00
|
|
|
|
|
|
|
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;
|
2011-03-03 17:05:30 -06:00
|
|
|
},
|
|
|
|
|
|
|
|
error: function(jqXHR, textStatus, error) {
|
|
|
|
alert(jqXHR.responseText);
|
2011-03-01 08:23:51 -06:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2011-03-07 16:43:40 -06:00
|
|
|
/* 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) {
|
2011-03-03 17:05:30 -06:00
|
|
|
|
2011-03-07 16:43:40 -06:00
|
|
|
// for each entry
|
2011-03-03 17:05:30 -06:00
|
|
|
_.each(entries, function(entry) {
|
2011-03-07 16:43:40 -06:00
|
|
|
|
|
|
|
// 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()
|
2011-03-03 17:05:30 -06:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
/* Update the user information based on the editable user-info panel. */
|
2011-02-12 07:48:19 -06:00
|
|
|
function updateUser(event) {
|
|
|
|
alert("TODO: update user via AJAX.");
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
/* Show the change timeline menu. */
|
2011-02-12 14:57:29 -06:00
|
|
|
function showTimelineMenu(event) {
|
|
|
|
alert("TODO: show other timelines via a popup menu");
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
/* Update the timeline details based on the editable timeline-info panel. */
|
2011-02-12 14:57:29 -06:00
|
|
|
function updateTimeline(event) {
|
2011-03-08 18:02:33 -06:00
|
|
|
var desc = $("#timeline-desc-input").val();
|
|
|
|
|
|
|
|
$.ajax({url: "/ts_api/timelines/" + user.username
|
|
|
|
+ "/" + activeTimeline.timeline_id,
|
|
|
|
type: "POST",
|
|
|
|
data: JSON.stringify({desc: desc, created: activeTimeline.created}),
|
2011-02-12 14:57:29 -06:00
|
|
|
|
2011-03-08 18:02:33 -06:00
|
|
|
error: function(jqXHR, textStatus, error) {
|
|
|
|
// TODO: better error handling
|
|
|
|
alert("Error updating timeline: \n" + jqXHR.responseText); },
|
2011-02-13 11:38:50 -06:00
|
|
|
|
2011-03-08 18:02:33 -06:00
|
|
|
success: function(data, testStatus, jqXHR) {
|
|
|
|
// TODO: check for appropriate data.status value
|
|
|
|
|
|
|
|
// update display
|
|
|
|
$("#timeline-desc").text(data.timeline.description);
|
|
|
|
}});
|
2011-03-07 16:43:40 -06:00
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
2011-03-01 18:00:51 -06:00
|
|
|
/* Create a new entry based on the user's input in the new-entry panel. */
|
2011-02-14 09:01:32 -06:00
|
|
|
function newEntry(event) {
|
2011-03-07 16:43:40 -06:00
|
|
|
|
|
|
|
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("");
|
|
|
|
}});
|
|
|
|
|
2011-02-14 09:01:32 -06:00
|
|
|
event.preventDefault();
|
|
|
|
}
|
2011-03-07 16:43:40 -06:00
|
|
|
|
2011-03-08 18:02:33 -06:00
|
|
|
function toggleEditEntry(event, entryId) {
|
|
|
|
$("#entry-" + entryId + " .entry-display").toggle();
|
|
|
|
$("#entry-" + entryId + " .entry-edit").toggle();
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateEntry(event, entryId) {
|
|
|
|
|
|
|
|
var mark = $("#entry-" + entryId + "-mark-input").val();
|
|
|
|
var notes = $("#entry-" + entryId + "-notes-input").val();
|
|
|
|
var timestamp = getUTCTimestamp(); // TODO: define and read from input element
|
|
|
|
|
|
|
|
var payload = JSON.stringify(
|
|
|
|
{ mark: mark, notes: notes, timestamp: timestamp });
|
|
|
|
|
|
|
|
$.ajax({url: "/ts_api/entries/" + user.username
|
|
|
|
+ "/" + activeTimeline.timeline_id
|
|
|
|
+ "/" + entryId,
|
|
|
|
type: "POST",
|
|
|
|
data: payload,
|
|
|
|
|
|
|
|
error: function(jqXHR, textStatus, error) {
|
|
|
|
// TODO: error handling
|
|
|
|
alert("Error updating entry: \n" + jqXHR.responseText); },
|
|
|
|
|
|
|
|
success: function(data, textStatus, jqXHR) {
|
|
|
|
// TODO: check that data.status is appropriate
|
|
|
|
|
|
|
|
// update the entry display
|
|
|
|
$("#entry-" + entryId + " .entry-mark").text(data.entry.mark);
|
|
|
|
$("#entry-" + entryId + " .entry-notes").text(data.entry.notes);
|
|
|
|
}});
|
|
|
|
|
|
|
|
toggleEditEntry(event, entryId);
|
|
|
|
}
|
|
|
|
|
2011-03-07 16:43:40 -06:00
|
|
|
/* Delete an entry. */
|
|
|
|
function deleteEntry(event, entryId) {
|
2011-03-08 18:02:33 -06:00
|
|
|
$.ajax({url: "/ts_api/entries/" + user.username
|
|
|
|
+ "/" + activeTimeline.timeline_id
|
|
|
|
+ "/" + entryId,
|
|
|
|
type: "DELETE",
|
|
|
|
|
|
|
|
error: function(jqXHR, textStatus, error) {
|
|
|
|
// TODO: error handling
|
|
|
|
alert("Error updating entry: \n" + jqXHR.responseText); },
|
2011-03-07 16:43:40 -06:00
|
|
|
|
2011-03-08 18:02:33 -06:00
|
|
|
success: function(data, textStatus, jqXHR) {
|
|
|
|
$("#entry-" + entryId).slideUp('slow',
|
|
|
|
function() {$("#entry-" + entryId).remove(); });
|
|
|
|
}});
|
|
|
|
|
|
|
|
event.preventDefault();
|
2011-03-07 16:43:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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';
|
|
|
|
}
|