|
|
|
To do list part II - REST and AJAX
Now that we have created a simple todo application with very little effort, we can enhance it and introduce the use of RESTful resources and AJAX. If you are not familiar with REST, you can get more information here. If you are not familiar with AJAX, you can get more information here where there are also links to tutorials at the bottom of the page.
The following concepts are covered in this tutorial:
- Representational State Transfer (REST)
- JavaScript Object Notation (JSON)
- Asynchronous JavaScript and XML (AJAX)
Stop the previous application
To avoid any port conflicts, lets stop the running application. To do this, use the following steps:
- Select the simpletodo project as shown on the following screen.
- Switch to the Console view.
- Click the red Terminate button to stop the application as shown on the following screen.
Creating an empty Zero application
- Create a new Zero project as explained in To do list part I - Simple todo and name it
simpletodo2.
Using REST
So far, we created a simple Groovy template that handled the HTTP requests. Now, we will introduce some more Zero conventions and make our application RESTful. For any entity that we want to expose as a RESTful resource in a Zero application, we create handlers under the app/resources directory. To use this resource-based programming model, perform the following steps:
- Select the simpletodo2 project.
- Click File -> New -> File.
- Select the app/resources folder under the simpletodo2 project and name the new file todos.groovy
- Click Finish.
- Click Yes if a dialog box appears, as shown on the following screen, asking you to add runtime Groovy support.
- Add the following text as the content of the
todos.groovy file:
/*
* ============================================================================
* (C) Copyright IBM Corporation 2006, 2007. All Rights Reserved.
*
* You may only copy, modify, and distribute these samples internally.
* These samples have not been tested under all conditions and are provided
* to you by IBM without obligation of support of any kind.
*
* IBM PROVIDES THESE SAMPLES "AS IS" SUBJECT TO ANY STATUTORY WARRANTIES THAT
* CANNOT BE EXCLUDED. IBM MAKES NO WARRANTIES OR CONDITIONS, EITHER EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR
* CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
* NON-INFRINGEMENT REGARDING THESE SAMPLES OR TECHNICAL SUPPORT, IF ANY.
* ============================================================================
*/
// In the GC, app.todos is a Map[index : [id:index, value:todoItem]] that is, each
// index is a map of 2 properties whose keys are id and value.
// The app.index keeps track of the last index used
def onList() {
logger.INFO {"onGETcalled"}
def todos = app.todos[]
if (todos == null) {
todos = []
}
else {
todos = todos.values()
}
outputJson(todos)
}
def onCreate() {
logger.INFO {"onPOSTcalled"}
def item = request.params.item[]
request.status = HttpURLConnection.HTTP_BAD_REQUEST
if (item != null && item.length() > 0) {
def todos = app.todos[]
def index = app.index[]
if (todos == null) {
todos = [:]
index = 0
}
index++
todos.put(index, [id:index, value:item])
app.todos = todos
app.index = index
request.status = HttpURLConnection.HTTP_CREATED
request.headers.out.Location = request.uri[] + '/' + index
}
}
def onDeleteCollection() {
logger.INFO {"onDELETE called"}
app.todos = null
}
def onRetrieve() {
logger.INFO {"onGETcalled"}
def id = request.params.todosId[]
if (id != null) {
def todos = app.todos[]
def value = todos.get(id.toInteger())
if (value != null) {
outputJson(value)
return
}
}
send404()
}
def onDelete() {
logger.INFO {"onDELETE called"}
def id = request.params.todosId[]
if (id != null) {
def todos = app.todos[]
if (todos.remove(id.toInteger())) {
app.todos = todos
return
}
}
send404()
}
def send404() {
request.status = HttpURLConnection.HTTP_NOT_FOUND
request.view = "error"
render()
}
def outputJson(data) {
logger.INFO {"todo data =$data"}
request.headers.out.'Content-Type'='application/json'
request.view = 'JSON'
request.json.output = data
render()
}
- Save the
todos.groovy file.
Working with the todos.groovy file
First, let's talk about the logger.* statements that you see in each of the methods. Zero has built in support for logging and tracing and uses the java.util.logging package. For more information, see the logging and tracing article.
The todos.groovy file consists of the handler methods that get called based on the HTTP method of the incoming request, and another method names outputJson for providing the data in JSON format. JSON is a lightweight data-interchange format that you can easily understand. For more information on JSON, visit the JSON Web site.
The following methods are used:
- onList
- accesses the todos object in the app zone of the global context, creates a list from all the to do list items, and calls the outputJson method. The outputJson method sets a required field in the ViewHandler renderer before calling the render method to produce the output data. So what is a renderer? Zero provides a library of renderers for common output patterns. The common case of serializing a Java object to the response as a JSON representation is represented as a renderer. For more information on how this all works, see the JSON support article.
- onCreate
- accesses the posted data using the request zone, retrieves the existing to do list from the app zone, and adds the posted item to the to do list. Then it updates the todos object in the app zone with the updated to do list that contains the new item.
- onDeleteCollection
- resets the entire to do list by accessing the todos object of the app zone, which we use to store the to do list.
- onRetrieve
- To access a specific item of the collection RESTfully, its id is specified in the URL using Zero conventions. (/resources/<collectionName>[/<memberId>[/ <pathInfo>]]). We access the id of the specific item by using request.params.todosId[] where todosId is created by adding Id to the collection name todos. Once we know what the item id is, we use todos.get(id) to get the actual item and return is as JSON.
- onDelete
- accesses the specific item using the same format as the onRetrieve method and removes it from the to do list.
For more information, see the resource oriented design article.
Using AJAX
We will use a Groovy template again as our primary web resource.
- Select the public folder of our current project named simpletodo2 as shown on the following screen.
- Click File -> New -> File and name the new file
todo.gt
- Click Finish.
- Right click todo.gt and select Open With -> Text Editor.
- Add the following text as the content of the
todo.gt file.
<!--
/*
* ============================================================================
* (C) Copyright IBM Corporation 2006, 2007. All Rights Reserved.
*
* You may only copy, modify, and distribute these samples internally.
* These samples have not been tested under all conditions and are provided
* to you by IBM without obligation of support of any kind.
*
* IBM PROVIDES THESE SAMPLES "AS IS" SUBJECT TO ANY STATUTORY WARRANTIES THAT
* CANNOT BE EXCLUDED. IBM MAKES NO WARRANTIES OR CONDITIONS, EITHER EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR
* CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
* NON-INFRINGEMENT REGARDING THESE SAMPLES OR TECHNICAL SUPPORT, IF ANY.
* ============================================================================
*/
-->
<html>
<head>
<title>Todo List</title>
<script type="text/javascript">
var todoURL = '<%= getRelativeUri("/resources/todos") %>';
function createXHR() {
try { return new XMLHttpRequest(); } catch(e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {}
alert("XHR not supported");
return null;
}
function displayItems(text) {
if (text != null) {
//alert(text);
var data = eval('(' + text + ')');
var list = "";
//alert(typeof(data));
for (var i=0; i<data.length; i++) {
list += '<input type="checkbox" name="todos" value="'+data[i].id+'">' +data[i].value+ ' <br>';
}
document.getElementById("todoList").innerHTML = list;
}
}
function addTodo() {
var xhr = createXHR();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 201) {
getTodos();
} else {
alert("Error getting data from the server");
}
}
}
xhr.open("POST", todoURL, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("item="+encodeURI(document.todoForm.item.value));
document.todoForm.reset();
}
function deleteTodos() {
var todos = document.todoForm.todos;
if (null != todos) {
if (todos instanceof NodeList) {
// build a list of items to delete
var toDelete = [];
var j = 0;
for (var i=0; i<todos.length; i++) {
if (todos[i].checked) {
toDelete[j] = todos[i];
j++;
}
}
for (var k=0; k<toDelete.length; k++) {
if (k == (toDelete.length - 1)) {
deleteTodo(toDelete[k].value, true);
} else {
deleteTodo(toDelete[k].value, false);
}
}
} else {
// todos is not a nodelist, so it only contains a single
// todo
deleteTodo(todos.value, true);
}
}
}
function deleteTodo(itemId, refresh) {
var xhr = createXHR();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (refresh) {
getTodos();
}
} else {
alert("Error getting data from the server");
}
}
}
//xhr.open("DELETE", "/resources/todos", true);
//We need to send the server which items to delete, so we use POST
//and override with DELETE to invoke the onDELETE event on the server
xhr.open("POST", todoURL + "/" + itemId, true);
xhr.setRequestHeader('X-Method-Override', 'DELETE');
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(null);
}
window.onload = function() {
getTodos()
}
function getTodos() {
var xhr = createXHR();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
displayItems(xhr.responseText);
} else {
alert("Error getting data from the server");
}
}
}
xhr.open("GET", todoURL, true);
xhr.send(null);
}
</script>
</head>
<body>
<b>Your todo items:</b>
<form name="todoForm">
<div id="todoList"></div>
<br>
<input type="text" id="item">
<input type="button" onClick="addTodo();" value="Add">
<input type="button" onClick="deleteTodos();" value="Delete">
</form>
</body>
</html>
- Save the
todo.gt file.
Working with the todo.gt file
Javascript is a core component of AJAX communication. The todo.gt file uses Javascript methods to create an XMLHttpRequest object and communicate with the application. The following methods are involved:
- createXHR
- A helper method that returns an XMLHttpRequest object and works across different browser versions. If you are familiar with AJAX, this is probably one of the first methods you became familiar with.
- displayItems
- Accepts data in the form of JSON text and generates the HTML for displaying this data as a to do list. The important statements to notice in this method are:
-
- var data = eval('(' + text + ')');
- Uses the ability of Javascript to convert data in the JSON format as text to an object.
-
- document.getElementById("todoList").innerHTML = list;
- Uses the DOM API to dynamically add the to do list to the page.
- addTodo
- Retrieves the to do item that you are trying to add using document.todoForm.item.value and posts it to the application to be added to the to do list. The important Zero convention here is the URL used for posting to the application. Previously, we introduced the concept of RESTful resources and added our to do list as a folder under the
/app/resources folder. When we need to access this resource in a Zero application, we use the /resources/* format where * matches the contents of the /app/resources folder. So in our case, we would use /resources/todos to post to our handlers in the /app/resources/todos.groovy file.
- deleteTodo
- Uses the URL format previously discussed to delete specific to do items. It calls the
getTodos method to get the list of selected to do items on the page before sending this information and calling the DELETE method to invoke the onDELETE handler defined in the /app/resources/todos.groovy file.
Running the simpletodo2 application
To run the simpletodo2 application, do the following:
- Right-click the simpletodo2 project and click Run As -> Project Zero Application.
- Using your browser, go to the http://localhost:8080/todo.gt site.
<<< Previous | Next >>>
|