Implemented edit and update for entries.

- Added ts_entry:delete/1 to delete an entry from the database.
- Implemented ts_api:delete_entry/3.
- Added a form to facilitate editing individual entries.
- Moved the small show/hide functions directly into the HTML.
- Wired up the update timeline form.
- Wired up the edit and update entry form.
This commit is contained in:
Jonathan Bernard 2011-03-08 18:02:33 -06:00
parent 1b1e31059b
commit 39c3b83d3f
12 changed files with 150 additions and 47 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -482,7 +482,22 @@ post_entry(YArg, Username, TimelineId, EntryId) ->
_Error -> make_json_500(YArg) _Error -> make_json_500(YArg)
end. end.
delete_entry(_YArg, _Username, _TimelineId, _EntryId) -> todo. delete_entry(YArg, Username, TimelineId, EntryId) ->
% find the record to delete
case ts_entry:lookup(Username, TimelineId, EntryId) of
Record ->
% try to delete
case ts_entry:delete(Record) of
ok -> {status, 200};
Error ->
io:format("Error occurred deleting entry record: ~p", [Error]),
make_json_500(YArg)
end;
no_record -> make_json_404(YArg)
end.
% ============================== % % ============================== %
% ======== UTIL METHODS ======== % % ======== UTIL METHODS ======== %

View File

@ -1,5 +1,5 @@
-module(ts_entry). -module(ts_entry).
-export([create_table/1, new/1, update/1, lookup/3, list_asc/3, list_desc/3]). -export([create_table/1, new/1, update/1, delete/1, lookup/3, list_asc/3, list_desc/3]).
-include("ts_db_records.hrl"). -include("ts_db_records.hrl").
-include_lib("stdlib/include/qlc.hrl"). -include_lib("stdlib/include/qlc.hrl").
@ -35,6 +35,9 @@ lookup(Username, TimelineId, EntryId) ->
[Entry] -> Entry [Entry] -> Entry
end. end.
delete(ER = #ts_entry{}) -> mnesia:dirty_delete_object(ER).
list({Username, Timeline}, Start, Length, OrderFun) list({Username, Timeline}, Start, Length, OrderFun)
when is_integer(Start) and is_integer(Length) -> when is_integer(Start) and is_integer(Length) ->

View File

@ -211,7 +211,7 @@ body {
background: #b34c2b; background: #b34c2b;
color: #c5c5b9; color: #c5c5b9;
font-weight: bold; font-weight: bold;
min-width: 2em; width: 2em;
text-align: right; } text-align: right; }
.entry-bar .details { .entry-bar .details {
float: left; } float: left; }
@ -223,6 +223,13 @@ body {
.entry-bar .details .entry-notes { .entry-bar .details .entry-notes {
display: none; display: none;
padding-left: 1.5em; } padding-left: 1.5em; }
.entry-bar .entry-edit {
display: none; }
.entry-bar .entry-edit .id {
width: 2em;
padding: 0.2em 0.5em 0.2em 0.5em; }
.entry-bar .entry-edit .entry-notes {
padding: 0; }
.top-entry { .top-entry {
-moz-border-radius-topleft: 0.5em; -moz-border-radius-topleft: 0.5em;

View File

@ -264,7 +264,7 @@ body {
background: $obor; background: $obor;
color: lighten($greyTxt, 40%); color: lighten($greyTxt, 40%);
font-weight: bold; font-weight: bold;
min-width: 2em; width: 2em;
text-align: right; text-align: right;
} }
@ -283,6 +283,19 @@ body {
padding-left: 1.5em; padding-left: 1.5em;
} }
} }
.entry-edit {
display: none;
.id {
width: 2em;
padding: 0.2em 0.5em 0.2em 0.5em;
}
.entry-notes {
padding: 0;
}
}
} }
.top-entry { .top-entry {

View File

@ -25,6 +25,9 @@ $(document).ready(function(){
buttons: { Login: function(){login()} } buttons: { Login: function(){login()} }
}); });
// TODO: add a hook to AJAX requests to check for 401 unauth
// and re-display the login dialog.
// try to load user information for an authenticated user // try to load user information for an authenticated user
$.ajax({ $.ajax({
url: "/ts_api/users/", url: "/ts_api/users/",
@ -109,6 +112,7 @@ function loadUser(username) {
// update the timeline display // 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);
$("#timeline-desc-input").val(activeTimeline.description);
// TODO: populate the drop-down list for the available timeline // TODO: populate the drop-down list for the available timeline
// choices // choices
@ -205,27 +209,12 @@ function displayOlderEntries(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. */ /* 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) {
$("#timeline-info").slideToggle("slow");
event.preventDefault();
}
/* Show the change timeline menu. */ /* 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");
@ -234,22 +223,24 @@ function showTimelineMenu(event) {
/* Update the timeline details based on the editable timeline-info panel. */ /* Update the timeline details based on the editable timeline-info panel. */
function updateTimeline(event) { function updateTimeline(event) {
alert("TODO: update timeline via AJAX."); var desc = $("#timeline-desc-input").val();
event.preventDefault();
}
/* Show/hide the add notes panel. */ $.ajax({url: "/ts_api/timelines/" + user.username
function showNewNotes(event) { $("#add-notes").slideToggle("slow"); } + "/" + activeTimeline.timeline_id,
type: "POST",
data: JSON.stringify({desc: desc, created: activeTimeline.created}),
function toggleEntryNotes(event, entryId) { error: function(jqXHR, textStatus, error) {
var selector = "#" + entryId + " .entry-notes"; // TODO: better error handling
$(selector).slideToggle("slow"); alert("Error updating timeline: \n" + jqXHR.responseText); },
event.preventDefault();
} success: function(data, testStatus, jqXHR) {
// TODO: check for appropriate data.status value
// update display
$("#timeline-desc").text(data.timeline.description);
}});
function editEntry(event, entryId) {
var selector = "#" + entryId;
alert("TODO: implement edit entry. Called for '" + selector + "'");
event.preventDefault(); event.preventDefault();
} }
@ -293,9 +284,59 @@ function newEntry(event) {
event.preventDefault(); event.preventDefault();
} }
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);
}
/* Delete an entry. */ /* Delete an entry. */
function deleteEntry(event, entryId) { function deleteEntry(event, entryId) {
$.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); },
success: function(data, textStatus, jqXHR) {
$("#entry-" + entryId).slideUp('slow',
function() {$("#entry-" + entryId).remove(); });
}});
event.preventDefault();
} }
/* Generate a UTC timestamp string in ISO format. /* Generate a UTC timestamp string in ISO format.

View File

@ -16,20 +16,41 @@
<script id="entry" type="text/html"> <script id="entry" type="text/html">
<div class="entry-bar" id="entry-{{entry_id}}"> <div class="entry-bar" id="entry-{{entry_id}}">
<div class="entry-display">
<span class="id">{{entry_id}}</span> <span class="id">{{entry_id}}</span>
<div class="details"> <div class="details">
<div class="entry-mark">{{mark}}</div> <div class="entry-mark">{{mark}}</div>
<div class="entry-notes">{{notes}}</div> <div class="entry-notes">{{notes}}</div>
</div> </div>
<div class="control-links"> <div class="control-links">
<a onclick="toggleEntryNotes(event, 'entry-{{entry_id}}')" <a onclick="$('#entry-{{entry_id}} .entry-display .entry-notes').slideToggle('slow');"
href="#">show notes</a> href="#">show notes</a>
<a onclick="editEntry(event, 'entry-{{entry_id}}')" <a onclick="toggleEditEntry(event, {{entry_id}})"
href="#">edit</a> href="#">edit</a>
<a onclick="deleteEntry(event, 'entry-{{entry_id}}')" <a onclick="deleteEntry(event, {{entry_id}})"
href="#">del</a> href="#">del</a>
</div> </div>
</div> </div>
<div class="entry-edit">
<form action="/ts/update-entry.yaws"
onsubmit="updateEntry(event, {{entry_id}})">
<input type="text" id="entry-{{entry_id}}-id-input"
class="id" value="{{entry_id}}"/>
<div class="details">
<input type="text" id="entry-{{entry_id}}-mark-input"
class="entry-mark" value="{{mark}}"/></br>
<textarea id="entry-{{entry_id}}-notes-input"
class="entry-notes" rows="8" cols="40" >{{notes}}</textarea>
</div>
</form>
<div class="control-links">
<a onclick="$('#entry-{{entry_id}} .entry-edit .entry-notes').slideToggle('slow');"
href="#">show notes</a>
<a onclick="updateEntry(event, {{entry_id}})"
href="#">save changes</a>
</div>
</div>
</div>
</script> </script>
</head> </head>
@ -40,7 +61,8 @@
<span id="username">- no_user</span> <span id="username">- no_user</span>
<div class="control-links"> <div class="control-links">
<a href="/ts/edit-user.yaws" <a href="/ts/edit-user.yaws"
onclick="toggleUserInfo(event)">user info</a> onclick="$('#user-info').slideToggle('slow'); return false;">
user info</a>
<a href="/ts/logout.yaws" onclick="logout(event)">logout</a> <a href="/ts/logout.yaws" onclick="logout(event)">logout</a>
</div> </div>
@ -74,7 +96,7 @@
<label for="enable-pwd-change-input"> <label for="enable-pwd-change-input">
<input name="enable-pwd-change" type="checkbox" <input name="enable-pwd-change" type="checkbox"
id="enable-pwd-change-input" id="enable-pwd-change-input"
onclick="showChangePwd(event)"/> onclick="$('#change-pwd').slideToggle('slow');"/>
change password change password
</label> </label>
</div> </div>
@ -93,7 +115,8 @@
<span id="timeline-desc">timeline description</span> <span id="timeline-desc">timeline description</span>
<div class="control-links"> <div class="control-links">
<a href="/ts/edit-timeline.yaws" <a href="/ts/edit-timeline.yaws"
onclick="toggleTimelineInfo(event)">timeline info</a> onclick="$('#timeline-info').slideToggle('slow'); return false;">
timeline info</a>
<a href="/ts/select-timeline.yaws" <a href="/ts/select-timeline.yaws"
onclick="showTimelineMenu(event)">change timelines</a> onclick="showTimelineMenu(event)">change timelines</a>
</div> </div>
@ -122,7 +145,8 @@
class="form-submit" type="submit" value="create entry"/> class="form-submit" type="submit" value="create entry"/>
<div class="control-links"> <div class="control-links">
<a id="show-notes" href="#" <a id="show-notes" href="#"
onclick="showNewNotes(event)">add notes</a> onclick="$('#add-notes').slideToggle('slow');">
add notes</a>
</div> </div>
<div id="add-notes" class="form-col"> <div id="add-notes" class="form-col">
<label for="new-notes-input">notes:</label> <label for="new-notes-input">notes:</label>