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.
This commit is contained in:
Jonathan Bernard 2011-03-01 18:00:51 -06:00
parent efab46f167
commit 348c73a36f
4 changed files with 81 additions and 23 deletions

Binary file not shown.

Binary file not shown.

View File

@ -240,16 +240,16 @@ get_user(YArg, Username) ->
list_timelines(YArg, Username) -> list_timelines(YArg, Username) ->
% pull out the POST data % pull out the POST data
PostData = yaws_api:parse_post(YArg), QueryData = yaws_api:parse_query(YArg),
% read or default the Start % 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); {start, StartVal} -> list_to_integer(StartVal);
false -> 0 false -> 0
end, end,
% read or default the Length % read or default the Length
Length = case lists:keyfind(length, 1, PostData) of Length = case lists:keyfind(length, 1, QueryData) of
{length, LengthVal} -> {length, LengthVal} ->
erlang:min(list_to_integer(LengthVal), 50); erlang:min(list_to_integer(LengthVal), 50);
false -> 50 false -> 50
@ -340,35 +340,35 @@ delete_timeline(_YArg, _Username, _TimelineId) -> {status, 405}.
list_entries(YArg, Username, TimelineId) -> list_entries(YArg, Username, TimelineId) ->
% pull out the POST data % 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 % first determine if we are listing by date
case {ts_timeline:lookup(Username, TimelineId), case {ts_timeline:lookup(Username, TimelineId),
lists:keyfind(byDate, 1, PostData)} of lists:keyfind("byDate", 1, QueryData)} of
{no_record, _ByDateField} -> make_json_404( {no_record, _ByDateField} -> make_json_404(
[{status, "no such timeline"}, [{status, "no such timeline"},
{see_docs, "/ts_api_doc/entries.html#LIST"}]); {see_docs, "/ts_api_doc/entries.html#LIST"}]);
% listing by date range % listing by date range
{Timeline, {byDate, "true"}} -> {Timeline, {"byDate", "true"}} ->
% look for the start date; default to the beginning of the timeline % 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 % TODO: error handling if the date is badly formatted
{startDate, StartDateVal} -> ts_json:decode_date(StartDateVal); {startDate, StartDateVal} -> ts_json:decode_date(StartDateVal);
false -> Timeline#ts_timeline.created false -> Timeline#ts_timeline.created
end, end,
% look for end date; default to right now % 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 % TODO: error handling if the date is badly formatted
{endDate, EndDateVal} -> ts_json:decode_date(EndDateVal); {endDate, EndDateVal} -> ts_json:decode_date(EndDateVal);
false -> calendar:now_to_universal_time(erlang:now()) false -> calendar:now_to_universal_time(erlang:now())
end, end,
% read sort order and list entries % 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 % descending sort order
{order, "desc"} -> ts_entry:list_desc( {order, "desc"} -> ts_entry:list_desc(
{Username, TimelineId}, StartDate, EndDate); {Username, TimelineId}, StartDate, EndDate);
@ -391,24 +391,24 @@ list_entries(YArg, Username, TimelineId) ->
_Other -> _Other ->
% read or default the Start % 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); {start, StartVal} -> list_to_integer(StartVal);
false -> 0 false -> 0
end, end,
% read or default the Length % read or default the Length
Length = case lists:keyfind(length, 1, PostData) of Length = case lists:keyfind("length", 1, QueryData) of
{length, LengthVal} -> {length, LengthVal} ->
erlang:min(list_to_integer(LengthVal), 500); erlang:min(list_to_integer(LengthVal), 500);
false -> 50 false -> 50
end, end,
% read sort order and list entries % read sort order and list entries
Entries = case lists:keyfind(order, 1, PostData) of Entries = case lists:keyfind("order", 1, QueryData) of
{order, "desc"} -> ts_entry:list_desc( {"order", "desc"} -> ts_entry:list_desc(
{Username, TimelineId}, Start, Length); {Username, TimelineId}, Start, Length);
_Other -> ts_entry:list_asc( _UnknownOrder -> ts_entry:list_asc(
{Username, TimelineId}, Start, Length) {Username, TimelineId}, Start, Length)
end, end,

View File

@ -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(){ $(document).ready(function(){
lastEntryBar = $("#last-entry");
// wire the login dialog using jQuery UI
$("#login-dialog").dialog({ $("#login-dialog").dialog({
autoOpen: false, autoOpen: false,
height: 300, height: 300,
width: 300, width: 300,
modal: true, modal: true,
buttons: { buttons: { Login: function(){login()} }
Login: function(){login()}
}
}); });
// try to load user information for an authenticated user
$.ajax({ $.ajax({
url: "/ts_api/users/", url: "/ts_api/users/",
type: "GET", type: "GET",
error: function(jqXHR, textStatus, error) { error: function(jqXHR, textStatus, error) {
// assume there is no authenticated user, show login dialog
$("#login-dialog").dialog("open"); }, $("#login-dialog").dialog("open"); },
success: function(data, textStatus, jqXHR) { success: function(data, textStatus, jqXHR) {
// load the user information
loadUser(data.user.username); }}); loadUser(data.user.username); }});
}) })
var user = [] /* Read the user's credentials from the login form and perform
var timelines = [] * an AJAX request to the API to set the session cookie. */
var activeTimeline = []
function login() { function login() {
// lookup the login dialog elements
var name = $("#login-name"); var name = $("#login-name");
var pwd = $("#login-password"); var pwd = $("#login-password");
// call the API via AJAX
$.ajax({ $.ajax({
url: "/ts_api/login", url: "/ts_api/login",
processData: false, processData: false,
data: JSON.stringify({username: name.val(), password: pwd.val()}), data: JSON.stringify({username: name.val(), password: pwd.val()}),
type: "POST", type: "POST",
error: function(jqXHR, textStatus, error) { error: function(jqXHR, textStatus, error) {
// assuming bad credentials (possible server error or bad request,
// we should check that, FIXME
var tips = $(".validate-tips"); var tips = $(".validate-tips");
tips.text("Incorrect username/password combination."); tips.text("Incorrect username/password combination.");
tips.addClass("ui-state-error"); tips.addClass("ui-state-error");
tips.slideDown(); tips.slideDown();
}, },
success: function(data, textStatus, jqXHR) { success: function(data, textStatus, jqXHR) {
// load the user information and hide the login dialog
loadUser(name.val()); loadUser(name.val());
$("#login-dialog").dialog("close"); $("#login-dialog").dialog("close");
}}); }});
} }
/* End the current user session and expire any session credentials we
* have aquired. */
function logout(event) { function logout(event) {
alert("TODO: log user out via AJAX."); alert("TODO: log user out via AJAX.");
// TODO: wipe username, timeline, entry variables and displays
event.preventDefault(); event.preventDefault();
} }
/* Load and display the user's information and timelines. */
function loadUser(username) { function loadUser(username) {
// call the user_summary API function
$.ajax({ $.ajax({
url: "/ts_api/app/user_summary/" + username, url: "/ts_api/app/user_summary/" + username,
type: "GET", type: "GET",
success: function(data, textStatus, jqXHR) { success: function(data, textStatus, jqXHR) {
// set the user variable
user = data.user; user = data.user;
// set the timelines variable
timelines = data.timelines; timelines = data.timelines;
// update the user id display
$("#fullname").text(user.name); $("#fullname").text(user.name);
$("#username").text("- " + user.username); $("#username").text("- " + user.username);
// pre-populate the editable user-info fields
// TODO: not working // TODO: not working
$("#fullname-input").text(user.name); $("#fullname-input").text(user.name);
$("#email-input").text(user.email); $("#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]; activeTimeline = timelines[0];
// update the timeline display
$("#timeline-name").text(activeTimeline.timeline_id + " |"); $("#timeline-name").text(activeTimeline.timeline_id + " |");
$("#timeline-desc").text(activeTimeline.description); $("#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) { error: function(jqXHR, textStatus, error) {
// TODO
alert("TODO: handle error for user load.") 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({ $.ajax({
url: "/ts_api/entries/" + user.username + "/" + timeline.timeline_id, url: "/ts_api/entries/" + user.username + "/" + timeline.timeline_id,
type: "GET", type: "GET",
success: function(data, textStatus, jqXHR) { 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) { function toggleUserInfo(event) {
$("#user-info").slideToggle("slow"); $("#user-info").slideToggle("slow");
event.preventDefault(); event.preventDefault();
} }
/* Show/hide the password change controls. */
function showChangePwd(event) { $("#change-pwd").slideToggle("slow"); } function showChangePwd(event) { $("#change-pwd").slideToggle("slow"); }
/* Update the user information based on the editable user-info panel. */
function updateUser(event) { function updateUser(event) {
alert("TODO: update user via AJAX."); alert("TODO: update user via AJAX.");
event.preventDefault(); event.preventDefault();
} }
/* Show/hide the editable timeline-info panel. */
function toggleTimelineInfo(event) { function toggleTimelineInfo(event) {
$("#timeline-info").slideToggle("slow"); $("#timeline-info").slideToggle("slow");
event.preventDefault(); event.preventDefault();
} }
/* Show the change timeline menu. */
function showTimelineMenu(event) { function showTimelineMenu(event) {
alert("TODO: show other timelines via a popup menu"); alert("TODO: show other timelines via a popup menu");
event.preventDefault(); event.preventDefault();
} }
/* Update the timeline details based on the editable timeline-info panel. */
function updateTimeline(event) { function updateTimeline(event) {
alert("TODO: update timeline via AJAX."); alert("TODO: update timeline via AJAX.");
event.preventDefault(); event.preventDefault();
} }
/* Show/hide the add notes panel. */
function showNewNotes(event) { $("#add-notes").slideToggle("slow"); } 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) { function newEntry(event) {
alert("TODO: create entry vi AJAX"); alert("TODO: create entry vi AJAX");
event.preventDefault(); event.preventDefault();