This page gives an overview of the Jaxer server and the benefits of using Jaxer.
Contents |
Introduction
Note: Jaxer, the Aptana Server, is currently at alpha stage. Jaxer and all associated documentation and other information are currently only being made available subject to a confidentiality agreement. For more information, contact: jaxer@aptana.com.
Rich, "Ajax-style" web sites and applications offer tremendous advantages over both static web sites and desktop applications. But they can be difficult to develop, maintain, and expand. Many of the difficulties on the client side are alleviated by Ajax libraries such as jQuery, EXT, and Dojo, and by development environments such as Aptana Studio. Jaxer, the Aptana server, completes the picture by offering a powerful "Ajax server." With Jaxer, you can develop your entire site or application – end to end – using only standards-based DHTML, JavaScript, and CSS. You can populate the HTML DOM on the server before the page is served using the same techniques and libraries you would use on the client; call server-side JavaScript functions directly from client-side JavaScript; validate your data with the same code on both the server and the client; and do it all in a completely standards-based, flexible, open-source environment.
Benefits of using Jaxer
Let's take a closer look at what it takes to put together a modern web site or application.
Client-side paradigms
The key to developing modern, richly interactive web sites and applications is to start with the desired user experience and interface. Web application developers need to determine how information will be presented to the user and how will the user interact with it. Once a developer sketches out the basic information flow, several other high-level decisions become apparent:
- Will this be a single-page app or will there be page transitions?
- How much data will be available at any given time?
- How can the user control this data?
After defining the user interface, a developer can create a mockup to walk through the design. This becomes imperative for web applications with rich interfaces, which require dynamic HTML mockups with working controls and realistic data. Increasingly, a developer will use one or more "Ajax libraries" to create a rich UI, styled with CSS. As a developer refines these client-side mock-ups, the web page that the user will see takes shape.
Server-side development paradigms
Developing a client-side web page is only part of the work involved in developing a rich internet application, however. Developers encounter a new set of questions as they complete the development of their application:
- Where does the data for the client come from?
- How will changes to that data be persisted?
- How will multiple users share data?
Solving these issues requires a whole new set of technologies: the server side. For example, a server using PHP, Java, or Ruby, and often a templating framework, emits the HTML and the JavaScript needed for the client as one or more documents. The data needed by the client is then packaged into the page, usually by the templating framework writing JavaScript-format text. For rich web controls such as grids or calendars, the server-side templates cannot always emit exactly the HTML needed by the client-side Ajax library, so often the server will only emit an empty HTML container, and the client-side Ajax library must return to the server to fetch the data it needs to create the HTML for the control.
Client communication back to the server presents more issues. Ajax libraries simplify sending the data from the client, but then the server side must be set up to accept a large variety of requests and return the data in some format that the client can then read. Frequently, a templating framework writes (and escapes) PHP data into XML or JSON for transmission back to the client. Data validation on both the client and the server present even more issues – for example, how do you keep them in sync?
Eliminating the gap between client and server
Jaxer eliminates the complexity of using different technologies for client and server. With Jaxer, developers can use a single set of technologies—the ones closest to their users: HTML, JavaScript, and CSS.
As always, the page the user will see starts on the server, but now this page can just be the same static page that was mocked up in the design phase. As a developer, you can designate some JavaScript to run on the server before the page is sent to the client, such as code that accesses a database to fill up a grid. You can also designate some of the JavaScript to remain on the server but be accessible from the client, such as code to update the database with new data from the client, or retrieve database data for refreshing the client. In this case, when the user interacts with the page to fire events, these events trigger client-side JavaScript that can directly call the functions you designated to stay behind on the server.
Jaxer enables you to use Ajax-style development end-to-end—you can even use your favorite Ajax libraries for all of your development! This provides you with several key development benefits:
- The same code can validate data on both the client (for immediate user feedback) and on the server (for security), so validations never get out of sync.
- The same code can prepare both the HTML DOM server side (for better perceived responsiveness) and modify it client-side (when the user changes the data or it’s refreshed from the server).
- Using the same code on both the client and the server, developers have fewer technologies to learn and stay on top of, and fewer parts of the application or site to maintain.
Architecture
Jaxer is pre-configured for use as a plug-in to the Apache 2.x web server. Future versions will support more configurations. To provide world-class, standards-compliant JavaScript and DOM capabilities server-side, Jaxer is built on the Mozilla engine, which is the same engine used in the popular Firefox browser. Jaxer is layered into Apache as an input and output filter so that you can also use Jaxer to modify dynamic pages created by other languages, such as PHP or Ruby.
Jaxer Core vs. Framework
Jaxer itself is a combination of C/C++ "Core" code and a server-side JavaScript "Framework." The Core provides the JavaScript parser and runtime, HTML parser and DOM engine, and an event architecture that calls the Framework as the document is being processed on the server. The Framework provides the Jaxer logic itself, for example deciding which code to run on the server and which on the client, creating proxies on the client for callable server-side functions, serializing and deserializing data, and so on.
Because the Framework is written in JavaScript, it is easily extensible by Aptana or other developers. Jaxer is an open-source, free, and extensible platform for end-to-end Ajax development and for building rich presentation layers on top of existing back-end platforms.
Jaxer page lifecycle
Let's examine the lifecycle of a typical web page built with Jaxer:
- The HTML document starts life on the server, either as a static HTML file read from disk, or as a dynamic page generated by PHP, Ruby, Java, etc.
- Jaxer receives the document as an output (post-process) filter from Apache, and starts to parse and execute it, just as a browser would. Jaxer creates and populates the DOM, executes the JavaScript code designated to run on the server, and so on until the entire document is consumed.
- The result is a DOM modified by the Framework and by the developer: in particular, proxies automatically replace server-side client-callable functions. Some important side effects include storing designated JavaScript functions as callbacks and persisting session-type data.
- The new DOM is serialized as an HTML document and streamed out to the client as usual.
- The client receives the HTML document and the processing continues, recreating the DOM from HTML and this time executing the client-side JavaScript left in the page.
- When one of the client-side proxy functions is called, its parameters are automatically serialized into a JSON (JavaScript Object Notation) format, and an XMLHttpRequest is sent to the server to invoke the original function with these parameters.
- When the server receives this special request, the parameters are deserialized, the function to be invoked is restored and called with these parameters, and the results (or any exception) are serialized back into JSON.
- The data is returned to the client, where it is deserialized and returned as the result of the proxy (or a corresponding client-side exception is thrown).
On the server side, the developer's JavaScript environment is enhanced by the Jaxer Framework, which provides access to the database (MySQL only at this time), file system, network (coming soon), the HTTP Request and Response data, and in the future also external server-side platforms such as Java, PHP, and Ruby.
Running code on Jaxer
To define and/or execute any code server-side, add a runat attribute to a <script> block. This attribute has several possible values, and they determine where the code will execute (whenever this page is served) and what other actions will automatically take place:
value | description |
---|---|
client |
The functions and code contained in the script block will run in the client browser only. This functions exactly as a regular script block. This is the default value of the runat attribute, so usually you'll omit it for script blocks intended for the client. Its main use is to override the runat attribute of a specific function within a server-side script block. Note: if a script block has runat = "client" (or no runat attribute), it will not run at all server-side, so you cannot override the runat behaviors of individual functions from within this block. |
server |
The functions and code contained in the script will run on the server only. Any functions defined within the script block will be cached in association with this page. These functions are not directly callable from the client, but they can be called during callback processing by other server-side functions. These script blocks will not be presented to the client browser. |
both |
The functions and code contained in the script will run on both the client and the server. Any functions defined within the script block will be cached in association with this page. The server-side functions are not directly callable from the client, but they can be called during callback processing by other server-side functions. |
Although most use cases will be covered by the basic attributes shown above, You can use the following runat values on <script>tags:
value | description |
---|---|
server-proxy | Same as the basic 'server' target except ALL the functions will be proxied by default |
server-nocache | Same as the basic 'server' target except NONE of the functions will be cached by default |
both-proxy | Same as the basic 'both' target except ALL the functions will be proxied by default |
both-nocache | Same as the basic 'both' target except NONE of the functions will be cached by default |
Programmatic runat configuration
Jaxer is aware of some special function object properties that can be declared on individual function objects to control how they are managed. When these are specified the property value will override the containing script block runat setting for the individual function. This allows more granular control and prevents the need to break scripts out into separate files depending on their runat target.
property | description |
---|---|
proxy |
Server-side functions can be declared to be proxied so they are callable from the client side. This is achieved by specifying a proxy property on the function object. The possible values for this property are true or false. This is only required for enabling the proxying of the function. By default, in a <script runat="server"> block, the functions are not proxied. Note that if a function is not proxied, it isn't just that proxies are not inserted into the client to facilitate calling it: it's actually marked as not callable on the server, so hacking the client to try to call the function on the server will not work. |
runat |
Takes the same values as the <script> tag runat attributes. |
Recommended style
The following illustrates one simple way of using the runat and proxy options in a typical code scenario. We choose to group all the server-side code in one script block, and explicitly designate a subset of function to be proxied. Then all client-side code goes in a different script block (where there isn't even the option of programatically changing it by setting a different runat or proxy value). Of course you may choose a different way of organizing your code if that makes more sense. And for large amounts of code, it may also make sense to extract the code into (reusable) external JavaScript files.
[html, 1]
<script type="text/javascript" runat="server">
function setPassword(username, newPassword) { // put code in here to directly set the password of a given username // this code should not be callable from the client }
function changePassword(username, oldPassword, newPassword) { // put code in here to first verify the submitted password, // and then -- if successful -- call setPassword to actually make changes // this code should be callable from the client } changePassword.proxy = true;
</script>
<script type="text/javascript">
function submitPassword() { // put client-side code here to grab the username and old and new passwords // and call changePassword on the server }
</script>
Examples
The _login.js file referenced in the example above contains some functions that explicitly override the runat='server' directive specified on the script tag used to load the file.
Proxy example
In the following snippet, the function will proxied:
[javascript, 1]
function checkCredentials(username, password)
{
var rs = Aptana.DB.execute("SELECT * FROM users WHERE username = ? AND password = ?",
[username, password]);
if (rs.rows.length == 0)
{
return "";
}
var user = rs.rows[0];
makeAuthenticated(user);
return user.username;
}
checkCredentials.proxy = true;
Client-side example
In the following snippet the function will run client side.
[javascript, 1]
function login()
{
var username = $('username').value;
var password = $('password').value;
var username = checkCredentials(username, password);
if (username != "")
{
fromTemplate('loginComponent', 'loginAuthenticated');
setTimeout("$('authenticatedUsername').innerHTML = '" + username + "'", 0);
changeAuthentication(true);
}
else
{
$('loginMessage').innerHTML = "Sorry, try again";
}
}
login.runat = "client";
Alternate Syntax
Jaxer provides a useful convenience object inside the Jaxer namespace to allow the proxy functions to be declared in a single group within your JavaScript code:
[javascript, 1]
Jaxer.proxies = [myFunc1, myFunc2, "myFunction"];
// ...
Jaxer.proxies = Aptana.proxies .push[myFunc3, "myFunction4"];
This code must be presented in such a way that it is executed by the server prior to DOM serialization. You can also use this to remove the proxied functions by setting the value to null.
Note: Jaxer.proxies is NOT a complete collection of the functions being proxied by the server it is just a convenient way to express the myFunc.proxy=true; syntax for multiple function references
The runat attribute applies to everything within the <script> block. Individual functions within the <script> block can be changed to a different runat value by adding a runat/proxy property to them and setting it to the appropriate (string) value: for example, myFunction.runat = "both". The one exception is for <script> blocks that don't have a runat attribute (or have runat="client"): since such <script> blocks are not executed at all on the server, setting runat properties within those <script> blocks will not take place on the server, so the behavior of functions within them cannot be changed from within them.
Jaxer by example: Simple "Tasks" application tutorial
This tutorial describes how to create a simple, one-page Ajax-style application to keep track of a list of tasks. The user can create new tasks, check off and delete existing tasks, and all the data should persist and be accessible from any browser. The functionality in this example is extremely simple to focus on the basics (e.g. no logging in step). Although you'll probably use one of the many popular Ajax libraries, such as jQuery, EXT, Dojo, or YUI, in building your own applications, this simple tutorial assumes no specific Ajax library use, and only uses the JavaScript built into any modern browser and keeps all CSS, JavaScript, and HTML in a single document.
Viewing the sample application
Your Jaxer installation (whether as a standalone Jaxer Package or in Aptana Studio) includes this simple task application.
To access the tasks application from within Aptana Studio:
- In the Samples View, expand the Aptana Jaxer folder.
- Select the tasks example.
- Click either the Preview Sample button
to do a quick preview of the sample, or click the Import Sample button
to import the sample as a project into your workspace.
To access the tasks application from a standalone Jaxer Package installation:
- Navigate to your Jaxer installation folder, and double-click the StartServers.bat file.
- In your web browser, navigate to the following URL: http://localhost:8081/aptana/
- From the column on the left, click the Apps and Tools link.
- Click the Tasks link.
Creating the client-side web page
This section describes how to create your own tasks application from scratch.
To create the client-side:
- Create a basic HTML page. The example below contains several example tasks:
[html,N] <html> <head> <title>Tasks</title> </head> <body>
Tasks To Do
New: <input type="text" id="txt_new" size="60"> <input type="button" id="btn_new" value="add">
<input type="checkbox" title="Done"> <input type="text" size="60" title="description" value="Bring home some milk">
<input type="checkbox" title="Done"> <input type="text" size="60" title="description" value="Prepare presentation">
<input type="checkbox" title="Done"> <input type="text" size="60" title="description" value="Make car reservations">
<input type="checkbox" title="Done"> <input type="text" size="60" title="description" value="File taxes">
</body> </html>
- Preview the page in a browser, you should see something similar to the example below:
- To add formatting, add a style block at the bottom of the <head> section:
[html,N]<style> body { font: 9pt Arial; float: left; } .tasks { background-color: #f0f0ff; padding: 8px; } .new-task { padding-bottom: 8px; } .task { padding: 4px; } </style>
- Preview the page again to see the styles added:
This web page is starting to look nice, but it does not have any functionality yet. Next, you will delete the sample data and add some event handlers and a bit of JavaScript.
Scripting the client-side page
To add a simple client-side script to the page:
- Add a helper function to ease DOM access. Add the following script block to the <head> element:
[html] <script type="text/javascript"> function $(id) { return document.getElementById(id); } </script>
When the user types a description into the top textbox and clicks add, a new task line should be created at the top of the list.
- Add an onclick event handler to the button to grab the value of the textbox and call a JavaScript function that will add the task.
[html] <input type="button" value="add" onclick="addTask($('txt_new').value)">
- Remove the rows of sample data from the HTML. The new function addTask(description, id) will insert the new task into the DOM. For future use, allow the id to be specified or else be auto-generated. Add this to the above <script> block:
[javascript] function addTask(description, id) { var newId = id || Math.ceil(1000000000 * Math.random()); var div = document.createElement("div"); div.id = "task_" + newId; div.className = "task"; var checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); checkbox.setAttribute("title", "done"); checkbox.setAttribute("id", "checkbox_" + newId); div.appendChild(checkbox); var input = document.createElement("input"); input.setAttribute("type", "text"); input.setAttribute("size", "60"); input.setAttribute("title", "description"); input.setAttribute("id", "input_" + newId); input.setAttribute("value", description); div.appendChild(input); $("tasks").insertBefore(div, $("tasks").firstChild); }
Note: This addTask uses pure DOM manipulation. You could instead create the HTML as a string or copy a hidden HTML block (acting as a template) and convert it into the new HTML fragment. Usually you would use your favorite Ajax library to achieve this quickly.
- Your application now has some basic functionality. Preview it in your browser again.
You are now ready to add the last bit of functionality—-when you check off a task as done, it should disappear from the list.
- Add another function to the script block, completeTask(taskId), to delete the task and its contents from the DOM, and add a line to addTask that will add a call to completeTask from the checkbox's onclick handler:
[javascript] <script type="text/javascript"> function $(id) { return document.getElementById(id); } function addTask(description) { var newId = Math.ceil(1000000000 * Math.random()); var div = document.createElement("div"); div.id = "task_" + newId; div.className = "task"; var checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); checkbox.setAttribute("title", "done"); checkbox.setAttribute("id", "checkbox_" + newId); Aptana.setEvent(checkbox, "onclick", "completeTask(" + newId + ")"); div.appendChild(checkbox); var input = document.createElement("input"); input.setAttribute("type", "text"); input.setAttribute("size", "60"); input.setAttribute("title", "description"); input.setAttribute("id", "input_" + newId); input.setAttribute("value", description); div.appendChild(input); $("tasks").insertBefore(div, $("tasks").firstChild); } function completeTask(taskId) { var div = $("task_" + taskId); div.parentNode.removeChild(div); } </script>
You can now add tasks, complete and delete them, and modify their descriptions; however you cannot save or retrieve data yet.
Note: to add the onclick event handler programatically to the checkbox, we used the Aptana.setEvent(domElement, eventName, handler) function. There are multiple ways to add event handlers to a DOM element programatically. As you'll soon see, we'll need addTask to work both server-side and client-side. On the server we'll need to add the event handler to the DOM, changing the HTML that's then sent to the client. If we would have used checkbox.onclick = "completeTask(" + newId + ")" that would have added the event handler without changing the DOM, so the handler would not have made it to the client. The Aptana.setEvent function "does the right thing" on both server and client, modifying the DOM on the former and directly setting the onclick property of the checkbox on the latter. The key lesson to remember: only DOM modifications make it from the server to the client.
Adding server-side functionality to tasks
This example uses MySQL to save and retrieve tasks data. (Make that sure you have adjusted config.js in your Jaxer installation directory before continuing – see the installation and configuration instructions.) The Jaxer Framework gives access to MySQL. The Framework is accessible within the Aptana namespace (JavaScript global object) server-side, and a small part of it is also inserted into the Aptana namespace client-side. The Framework, to minimize name collisions, defines no other global variables.
To add server-side functionality to save and retrieve your task data:
- Create the database table to store the tasks, if it isn't already there. Add a runat="server" attribute to your <script> block (See the section about Running code on Jaxer to learn more about runat attributes) and add the following code:
[javascript] var sql = "CREATE TABLE IF NOT EXISTS tasks (" + " id int(11) NOT NULL," + " description varchar(255)," + " created datetime NOT NULL" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 "; Jaxer.DB.execute(sql);
Note: When you call Jaxer.DB.execute(…), you are using our default connection settings as defined in config.js, e.g. using the "demos" database specified in the default config.js that ships with Jaxer. If this database does not yet exist, it will be created automatically for you when Jaxer serves the first page.
- Add code to populate the DOM on the server with any saved tasks, before the page is sent to the client. This code executes after the DOM has been loaded into the server, so add it into a window.onserverload event handler, similar to using a window.onload handler. Use a SQL SELECT query to read the tasks from the database into a resultSet object, and iterate over its rows. For each task row, add a task to the DOM using the same code as on the client:
[javascript] window.onserverload = function() { var resultSet = Jaxer.DB.execute("SELECT * FROM tasks ORDER BY created"); for (var i=0; i<resultSet.rows.length; i++) { var task = resultSet.rows[i]; addTask(task.description, task.id); } }
Note: Usually addTask would use some 3rd-party Ajax library to create and add the DOM fragment for the task. Because both the DOM and JavaScript are on the server, you can use exactly the same addTask with the same Ajax library also on the server-side, and ensure that the HTML emitted from the server is exactly what the client needs. Just set addTask.runat="both" (or put it in a <script runat="both"> block).
- Add a server-side function to save a task, updating or inserting it as necessary, and make it callable from the client:
[javascript] function saveTask(id, description) { var resultSet = Jaxer.DB.execute("SELECT * FROM tasks WHERE id = ?", [id]); if (resultSet.rows.length > 0) // task already exists { Jaxer.DB.execute("UPDATE tasks SET description = ? WHERE id = ?", [description, id]); } else // insert new task { Jaxer.DB.execute("INSERT INTO tasks (id, description, created) " + " VALUES (?, ?, NOW())", [id, description]); } } saveTask.runat = "server-proxy";
Use the capability of DB.execute. For example, instead of embedding input values into the SQL itself, which could expose your code to SQL injection attacks. Use question marks and pass in an array of values to be substituted respectively for the question marks. This also eliminates the need to escape the input values.
- Set up your code to call saveTask within addTask from the client whenever a task is added. Also call this when the task description changes by adding it to the onchange handler on the <input> box of each task. Recall that these too are created by addTask, which now becomes:
[javascript] function addTask(description) { var newId = Math.ceil(1000000000 * Math.random()); var div = document.createElement("div"); div.id = "task_" + newId; div.className = "task"; var checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); checkbox.setAttribute("title", "done"); checkbox.setAttribute("id", "checkbox_" + newId); Jaxer.setEvent(checkbox, "onclick", "completeTask(" + newId + ")"); div.appendChild(checkbox); var input = document.createElement("input"); input.setAttribute("type", "text"); input.setAttribute("size", "60"); input.setAttribute("title", "description"); input.setAttribute("id", "input_" + newId); input.setAttribute("value", description); Jaxer.setEvent(input, "onchange", "saveTask(" + newId + ", this.value)"); div.appendChild(input); $("tasks").insertBefore(div, $("tasks").firstChild); if (!Jaxer.isOnServer) { saveTask(newId, description); } } addTask.runat = "both";
- To use addTask on the server, you’ll need the $(id) helper function on the server, so add $.runat="both"; to your code.
- Finally, when a task is checked off as done, delete it from the client and server. Change completeTask to call a new server-side, client-proxied deleteSavedTask function:
[javascript] function completeTask(taskId) { var div = $("task_" + taskId); div.parentNode.removeChild(div); deleteSavedTask(taskId); } completeTask.runat = "client"; function deleteSavedTask(id) { Jaxer.DB.execute("DELETE FROM tasks WHERE id = ?", [id]); } deleteSavedTask.proxy = true;
This final step completes your application. You now have a simple Ajax tasks manager built entirely in one web page using only standard, web-native paradigms – HTML, JavaScript, and CSS – with a bit of SQL on the server and some new JavaScript server-side functionality. The server modifies the HTML DOM based on some JavaScript code and the database, adds a few proxy functions and sends it to the client. The client allows the user to modify it based on new information using the same code, and seamlessly calls the server back to persist the changes to the database.
Complete Code
This page contains the entire code sample for the simple tasks application.
[html, 1]
<html> <head> <title>Tasks</title> <style> body { font: 9pt Arial; float: left; } .tasks { background-color: #f0f0ff; padding: 8px; } .new-task { padding-bottom: 8px; } .task { padding: 4px; } </style>
<script type="text/javascript" runat="server">
var sql = "CREATE TABLE IF NOT EXISTS tasks " + "( id INTEGER NOT NULL" + ", description VARCHAR(255)" + ", created DATETIME NOT NULL" + ")"; Jaxer.DB.execute(sql);
window.onserverload = function() { var resultSet = Jaxer.DB.execute("SELECT * FROM tasks ORDER BY created"); for (var i=0; i<resultSet.rows.length; i++) { var task = resultSet.rows[i]; addTaskToUI(task.description, task.id); } }
function saveTaskInDB(id, description) { var resultSet = Jaxer.DB.execute("SELECT * FROM tasks WHERE id = ?", [id]); if (resultSet.rows.length > 0) // task already exists { Jaxer.DB.execute("UPDATE tasks SET description = ? WHERE id = ?", [description, id]); } else // insert new task { Jaxer.DB.execute("INSERT INTO tasks (id, description, created) " + "VALUES (?, ?, ?)", [id, description, new Date()]); } } saveTaskInDB.proxy = true;
function deleteSavedTask(id) { Jaxer.DB.execute("DELETE FROM tasks WHERE id = ?", [id]); } deleteSavedTask.proxy = true;
function $(id) { return document.getElementById(id); } $.runat = "both";
function addTaskToUI(description, id) { var newId = id || Math.ceil(1000000000 * Math.random()); var div = document.createElement("div"); div.id = "task_" + newId; div.className = "task"; var checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); checkbox.setAttribute("title", "done"); checkbox.setAttribute("id", "checkbox_" + newId); Jaxer.setEvent(checkbox, "onclick", "completeTask(" + newId + ")"); div.appendChild(checkbox); var input = document.createElement("input"); input.setAttribute("type", "text"); input.setAttribute("size", "60"); input.setAttribute("title", "description"); input.setAttribute("id", "input_" + newId); input.value = description; Jaxer.setEvent(input, "onchange", "saveTaskInDB(" + newId + ", this.value)"); div.appendChild(input); $("tasks").insertBefore(div, $("tasks").firstChild); if (!Jaxer.isOnServer) { saveTaskInDB(newId, description); } } addTaskToUI.runat = "both";
</script>
<script type="text/javascript">
function completeTask(taskId) { var div = $("task_" + taskId); div.parentNode.removeChild(div); deleteSavedTask(taskId); }
function newTask() { var description = $('txt_new').value; if (description != ) { addTaskToUI(description); $('txt_new').value = ; } }
function newKeyDown(evt) { if (evt.keyCode == 13) { newTask(); return false; } }
</script> </head> <body>
Tasks To Do
New: <input type="text" id="txt_new" size="60" onkeydown="newKeyDown(event)"> <input type="button" value="add" onclick="newTask()">
</body> </html>