From 348c73a36fecad463b3a5aa0cb79d626dd2a85ec Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Tue, 1 Mar 2011 18:00:51 -0600 Subject: [PATCH] Bugfix and documentation. - Fixed a bug in ts_api:list_timelines/2 and ts_api:list_entries/3, which respond only to GET requests but were looking for POST data. - Added documentation for ts.js. --- db/test/DECISION_TAB.LOG | Bin 153 -> 153 bytes db/test/LATEST.LOG | Bin 92 -> 92 bytes src/ts_api.erl | 28 +++++++-------- www/js/ts.js | 76 ++++++++++++++++++++++++++++++++++----- 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/db/test/DECISION_TAB.LOG b/db/test/DECISION_TAB.LOG index adcec600b28414be674f715cf4627090f13f1f49..27497cf9786be5ce8e5e308aa6527f2a43b0b72a 100644 GIT binary patch delta 29 kcmbQqIFoULEu-l~J27tIBnAc_?<5BHnZXm|EttZy0B+$2bN~PV delta 29 lcmbQqIFoULEu+yyJ27sdBnD2QJxL7A$F5I|w_x(l0swX{2vGn4 diff --git a/db/test/LATEST.LOG b/db/test/LATEST.LOG index b000dae73fe217ecd070c233ae565dda580fb133..c6413418e65e977fc37b65581e72b423d9c8f1c7 100644 GIT binary patch delta 39 scma!vnP8=4n!>=Fm!FrPlIoC`pOIf&lEIwBz`!aDBz(M+7&ycN0PBYeR{#J2 delta 39 ucma!vnP8=4l)}K9m!FrPlIoC`pOIf&lEIwBz`!b$#K0-ECy9aiK{^2L3JY%l diff --git a/src/ts_api.erl b/src/ts_api.erl index a880c4a..2aeda64 100644 --- a/src/ts_api.erl +++ b/src/ts_api.erl @@ -240,16 +240,16 @@ get_user(YArg, Username) -> list_timelines(YArg, Username) -> % pull out the POST data - PostData = yaws_api:parse_post(YArg), + QueryData = yaws_api:parse_query(YArg), % read or default the Start - Start = case lists:keyfind(start, 1, PostData) of + Start = case lists:keyfind(start, 1, QueryData) of {start, StartVal} -> list_to_integer(StartVal); false -> 0 end, % read or default the Length - Length = case lists:keyfind(length, 1, PostData) of + Length = case lists:keyfind(length, 1, QueryData) of {length, LengthVal} -> erlang:min(list_to_integer(LengthVal), 50); false -> 50 @@ -340,35 +340,35 @@ delete_timeline(_YArg, _Username, _TimelineId) -> {status, 405}. list_entries(YArg, Username, TimelineId) -> % pull out the POST data - PostData = yaws_api:parse_post(YArg), + QueryData = yaws_api:parse_query(YArg), % first determine if we are listing by date case {ts_timeline:lookup(Username, TimelineId), - lists:keyfind(byDate, 1, PostData)} of + lists:keyfind("byDate", 1, QueryData)} of {no_record, _ByDateField} -> make_json_404( [{status, "no such timeline"}, {see_docs, "/ts_api_doc/entries.html#LIST"}]); % listing by date range - {Timeline, {byDate, "true"}} -> + {Timeline, {"byDate", "true"}} -> % look for the start date; default to the beginning of the timeline - StartDate = case lists:keyfind(startDate, 1, PostData) of + StartDate = case lists:keyfind("startDate", 1, QueryData) of % TODO: error handling if the date is badly formatted {startDate, StartDateVal} -> ts_json:decode_date(StartDateVal); false -> Timeline#ts_timeline.created end, % look for end date; default to right now - EndDate = case lists:keyfind(endDate, 1, PostData) of + EndDate = case lists:keyfind("endDate", 1, QueryData) of % TODO: error handling if the date is badly formatted {endDate, EndDateVal} -> ts_json:decode_date(EndDateVal); false -> calendar:now_to_universal_time(erlang:now()) end, % read sort order and list entries - Entries = case lists:keyfind(order, 1, PostData) of + Entries = case lists:keyfind("order", 1, QueryData) of % descending sort order {order, "desc"} -> ts_entry:list_desc( {Username, TimelineId}, StartDate, EndDate); @@ -391,24 +391,24 @@ list_entries(YArg, Username, TimelineId) -> _Other -> % read or default the Start - Start = case lists:keyfind(start, 1, PostData) of + Start = case lists:keyfind("start", 1, QueryData) of {start, StartVal} -> list_to_integer(StartVal); false -> 0 end, % read or default the Length - Length = case lists:keyfind(length, 1, PostData) of + Length = case lists:keyfind("length", 1, QueryData) of {length, LengthVal} -> erlang:min(list_to_integer(LengthVal), 500); false -> 50 end, % read sort order and list entries - Entries = case lists:keyfind(order, 1, PostData) of - {order, "desc"} -> ts_entry:list_desc( + Entries = case lists:keyfind("order", 1, QueryData) of + {"order", "desc"} -> ts_entry:list_desc( {Username, TimelineId}, Start, Length); - _Other -> ts_entry:list_asc( + _UnknownOrder -> ts_entry:list_asc( {Username, TimelineId}, Start, Length) end, diff --git a/www/js/ts.js b/www/js/ts.js index f6d5625..67cca3b 100644 --- a/www/js/ts.js +++ b/www/js/ts.js @@ -1,121 +1,179 @@ +var user = []; +var timelines = []; +var activeTimeline = []; +var entries = []; + +var lastEntryBar; + +/* Setup after the document is ready for manipulation. */ $(document).ready(function(){ + lastEntryBar = $("#last-entry"); + + // wire the login dialog using jQuery UI $("#login-dialog").dialog({ autoOpen: false, height: 300, width: 300, modal: true, - buttons: { - Login: function(){login()} - } + buttons: { Login: function(){login()} } }); + // try to load user information for an authenticated user $.ajax({ url: "/ts_api/users/", type: "GET", + error: function(jqXHR, textStatus, error) { + // assume there is no authenticated user, show login dialog $("#login-dialog").dialog("open"); }, + success: function(data, textStatus, jqXHR) { + // load the user information loadUser(data.user.username); }}); }) -var user = [] -var timelines = [] -var activeTimeline = [] - +/* Read the user's credentials from the login form and perform + * an AJAX request to the API to set the session cookie. */ function login() { + // lookup the login dialog elements var name = $("#login-name"); var pwd = $("#login-password"); + // call the API via AJAX $.ajax({ url: "/ts_api/login", processData: false, data: JSON.stringify({username: name.val(), password: pwd.val()}), type: "POST", + error: function(jqXHR, textStatus, error) { + // assuming bad credentials (possible server error or bad request, + // we should check that, FIXME var tips = $(".validate-tips"); tips.text("Incorrect username/password combination."); tips.addClass("ui-state-error"); tips.slideDown(); }, + success: function(data, textStatus, jqXHR) { + // load the user information and hide the login dialog loadUser(name.val()); $("#login-dialog").dialog("close"); }}); } +/* End the current user session and expire any session credentials we + * have aquired. */ function logout(event) { alert("TODO: log user out via AJAX."); + // TODO: wipe username, timeline, entry variables and displays event.preventDefault(); } +/* Load and display the user's information and timelines. */ function loadUser(username) { + + // call the user_summary API function $.ajax({ url: "/ts_api/app/user_summary/" + username, type: "GET", + success: function(data, textStatus, jqXHR) { + // set the user variable user = data.user; + + // set the timelines variable timelines = data.timelines; + + // update the user id display $("#fullname").text(user.name); $("#username").text("- " + user.username); + // pre-populate the editable user-info fields // TODO: not working $("#fullname-input").text(user.name); $("#email-input").text(user.email); + // 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. activeTimeline = timelines[0]; + // update the timeline display $("#timeline-name").text(activeTimeline.timeline_id + " |"); $("#timeline-desc").text(activeTimeline.description); - loadTimeline(user, activeTimeline) + // TODO: populate the drop-down list for the available timeline + // choices + + // load the entries for this timeline + loadEntries(user, activeTimeline) }, + error: function(jqXHR, textStatus, error) { + // TODO alert("TODO: handle error for user load.") + alert(jqXHR.responseText) } }); } -function loadTimeline(user, timeline) { +/* Read the first 50 entries for a timeline. */ +function loadEntries(user, timeline) { + + // call the API list_entries function via AJAX $.ajax({ url: "/ts_api/entries/" + user.username + "/" + timeline.timeline_id, type: "GET", + success: function(data, textStatus, jqXHR) { + entries = data.entries; + // push the entries onto the page + displayEntries(entries) } }); } +/* Show/hide the editable user-info panel. */ function toggleUserInfo(event) { $("#user-info").slideToggle("slow"); event.preventDefault(); } +/* Show/hide the password change controls. */ function showChangePwd(event) { $("#change-pwd").slideToggle("slow"); } +/* Update the user information based on the editable user-info panel. */ function updateUser(event) { alert("TODO: update user via AJAX."); event.preventDefault(); } +/* Show/hide the editable timeline-info panel. */ function toggleTimelineInfo(event) { $("#timeline-info").slideToggle("slow"); event.preventDefault(); } +/* Show the change timeline menu. */ function showTimelineMenu(event) { alert("TODO: show other timelines via a popup menu"); event.preventDefault(); } +/* Update the timeline details based on the editable timeline-info panel. */ function updateTimeline(event) { alert("TODO: update timeline via AJAX."); event.preventDefault(); } +/* 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"); event.preventDefault();