JoyentJoyent

Day two: Proper HTTP PUT & DELETE with jQuery.

In our previous article we learnt how to create a basic application for the Smart platform, the meaning of some relevant files and added the first couple of actions for our RESTful TODO list application. Now, we’re about to complete our Task Resource by adding the routes and functions to allow for modifications and deletion of Tasks.

Delete Tasks

Let’s start with the easiest one: we will add a delete link to our tasks listing. This link will use jQuery to send a DELETE HTTP Request. On success deletion on the server, we’ll remove the task from the list we’re displaying on the browser. And, in case something nasty happens, we’ll alert our application user about it.

The first thing we need is to load jQuery into our HTML document header. We’re going to use the very convenient Google JS API. Here is the code we should add to our document:

<script type="application/javascript" src="http://www.google.com/jsapi"></script>
<script type="application/javascript">
  google.load("jquery", "1.3.2");
</script>

Then, into our list.html template, we need to add the link and some information about the list element containing the task, so we can remove it later from the browser:

[% SET host="http://$request.headers.Host" %]
<ul id="tasks">
  [% FOREACH task = tasks %]
  <li id="task_[% task.id %]">
    [% SET ud = task.created %]
    <a href="[% host %]/tasks/[% task.id %]">[% task.title %]</a> - <small>Created on: [% ud %]</small>
    <a class="delete" href="/tasks/[% task.id %]" data-remove="task_[% task.id %]">
      delete
    </a>
  </li>
</ul>

Now, we will create a new JavaScript file into our public directory, where we’ll keep our code separated from the HTML, public/js/delete.js:

$(document).ready( function() {

  $('a.delete').click( function() {
    var theAnchor = this;
    if ( confirm('Are you sure you want to delete this?' ) )
      $.ajax({
        type: 'delete',
        url: $(this).attr('href'),
        dataType: 'json',
        success: function(data, textStatus) {
          if (data['ok'] == true) {
            var toRemove = $(theAnchor).attr('data-remove');
            $("#" + toRemove).remove();
          } else {
            alert( "Oooops!, something failed" );
          } 
        },
        error: function (XMLHttpRequest, textStatus, errorThrown) {
          alert("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText);
        }
      });
    return false;
  });

});

Obviously, we need to create the proper HTML script element in order to include this file.

This isn’t a jQuery article, but I’d suggest to look at the jQuery AJAX Options documentation to get a better idea of the detailed meaning of every line, briefly however, this is what this code does:

  • Add an Event Handler to all the links on the page with class delete.
  • Ask for confirmation when somebody clicks into one of those links.
  • After confirmation, send a DELETE HTTP request to the URL at the link href attribute, and expect JSON as the type for the response data.
  • On successful Response – no HTTP error – review if the data contains the JSON {'ok': true}.
  • If so, remove the HTML li element for our link
  • Otherwise, that means something has happened on server and the Tasks hasn’t been deleted. Alert the user about that fact.
  • Finally, if there’s an HTTP Error, alert about the status code and response – something like 404 Not Found

And here comes our first DELETE function!:

Now that we have all our assets in place, let’s write the required DELETE action to make all this stuff work:

system.use("org.json.json2");

DELETE(/\/tasks\/(.+)$/, function( anId ) {
  this.response.mime = 'application/json';
  try {
    this.task = Task.get( anId );
    this.task.remove();
    return JSON.stringify( { ok: true } );
  } catch(e) {
    return JSON.stringify( { ok: false } );
  }
});

As you can see, it’s similar to the function we used to display a single task, but this time it also tries to remove the task from the datastore. Another difference is that, instead of rendering a template, it returns a JSON string, (therefore we added the proper Mime Type to our Response Object).

The response object is accessible within our bootstrap.js file actions using this.response., and the available properties are expected for an HTTP Response: mime, code, headers and body. (Of course, you don’t need to set this.response.body when using Sammy, it will take care of that for you!).

Updating Tasks

Our tiny REST application is close to being complete. The only remaining action is to allow modification of existing Tasks. And we’ll do it properly, using the HTTP PUT method again, with some jQuery help. At the same time we’re going to use some Template tricks to reuse our existing template for Task creation.

Basically, we’re going to proceed in a similar way as we did when we created a new task. The first thing we need is a way to retrieve a form with the data for the task we want to edit – and even provide handy link to be able to access that page!

So, let’s add that link at the bottom of our web/task.html template:

[% SET host="http://$request.headers.Host" %]
<a href="[% host %]/tasks/[% task.id %]/edit">Edit this task &raquo;</a></p>

Now, let’s create the action which will retrieve a nice form to update an existing task:

GET(/\/tasks\/(.+)\/edit\/?$/, function( anId ) {
  try {
    this.task = Task.get( anId );
  } catch(e) {
    this.task = { id: null, title: null, notes: null };
  }
  return template("form.html");
});

Please, note that this function needs to be placed in bootstrap.js before the task show action, otherwise, the Regular Expresion for task show will match the route and we’ll never reach our edit action.

Of course, we need to introduce some modifications into our web/form.html template, in order to display the task properties when we’re editing a task, instead of creating a new one:

[% SET host="http://$request.headers.Host" %]
<form action="[% host %]/tasks[%- "/$task.id" IF task -%]" method="post"[%- ' id="task_form"' IF task -%]>
  <div class="field">
    <label for="title">Task title:</label>
    <input type="text" name="title" id="title" class="text" value="[%- task.title IF task -%]" />
  </div>
  <div class="field">
    <label for="notes">Notes:</label>
    <textarea rows="6" cols="40" name="notes" id="notes">[%- task.notes IF task -%]</textarea>
  </div>
  <div class="field">
    <input type="submit" value="Save" />             
  </div>
</form>

The dash just after opening and before the closing template tags means remove trailing and leading new lines, (see template Chomping Whitespace for the details), and it’s not needed because the Smart Platform Template is configured to remove those lines by default.

The idea is to use the PUT method only when we’re editing a task so, we’re not printing the form id attribute unless we’ve got a task to edit. Likewise, we don’t need to print the tasks properties if we are creating a new task.

Our public/js/update.js file will looks similar to the aforementioned public/js/delete.js:

$(document).ready(function() {
  $('#task_form').bind('submit', function() {
    $.ajax({
      type: 'PUT',
      url: $(this).attr('action'),
      dataType: 'json',
      data: $(this).serialize(),
      processData : false,
      success: function(data, textStatus) {
        if (data['ok'] == true) {
          window.location.replace($('#task_form').attr('action'));
        } else {
          alert( "Oooops!, something failed" );
        } 
      },
      error: function (XMLHttpRequest, textStatus, errorThrown) {
        alert("Ooooops!, request failed with status: " + XMLHttpRequest.status + ' ' + XMLHttpRequest.responseText);
      }
    });
    return false;
  });
});

The logic is pretty much the same one as in the delete action, but it will perform an HTTP PUT Request, sending our form values properly serialized and, on success, it will redirect us to the Task show page. It will pick the proper URL to submit the form to using the action attribute of the form HTML element.

Finally, we need the most important piece: bootstrap.js PUT action:

PUT(/\/tasks\/(.+)/, function( anId ) {
  try {
    this.task = Task.get( anId );
    this.task.notes = this.request.body.notes;
    this.task.title = this.request.body.title;
    this.task.save();

    return JSON.stringify( { ok: true } );
  } catch(e) {
    return JSON.stringify( { ok: false } );
  }
});

Again, similar logic to the DELETE action. Try to update our Task with the provided parameters, return JSON with success or failure. Quite simple, huh?.

Something to try:

As an exercise before the next article, it would be really cool to implement a way to mark the tasks as completed. At the end, that’s mostly the main goal of this application complete the tasks!.

That’s all folks!. We’re done with our attempt to create a RESTful TODO list application using the Smart Platform. Anyway, the application is far to be complete so, the next time we’ll add positions to our Tasks, sort them using that position and learn about Resource hooks and the search method.