tiistai 30. kesäkuuta 2009

Using jQueryUI & Ajax to create asychrnonous applications

Asynchronous Java Script is everywhere. Google apps, Microsoft Live, Flickr! and Facebook are just a whef of the bigger sites relying on AJAX. Today we'll create our own AJAX application with the help of jQuery and PHP.

Introduction


I will show you how to create your own Web 2.0 AJAX web application with the help of jQuery and PHP.
I'll also introduce you to the very handy jQueryUI library.
What we will be creating is a book database where you can store information on all of your books and the information on who's borrowed your books.
You can easily modify this sample application into a movie collection database or pretty much anything similar.
Here's a picture of the main page. The content is loaded through AJAX and this page newer needs to be refreshed.



Here's the jQueryUI dialog for adding more books to the database.
Notice that the jQueryUI is styleable through themes.
I'm using here the UI Darkness theme.
A lot of other themes can be downloaded from the jQueryUI page and you can modify them and even create your own.



A word of a warning: the jQueryUI is not fully compatible with the Internet Explorer.
It will degrade nicely and function as it should but techniques like rounded corner don't yet work.


Here's the second dialog. If you are borrowing a book to someone, just enter here to whom you are borrowing it to and the database will remember it for you.



The jQueryUI offers quite a lot of settings for the dialogs and even those icons are included in the themes.



Step 1 - Create the database


Let's start by creating our database. I'm using a single mySql table for this application.



I'm storing the borrowing information in the status field.
If the field is empty, the application assumes that the book is in the shelf.
In the other case this is where the name of the borrower is stored.


This very simple piece of sql in what the phpMyAdmin generated for me when I added the table.



CREATE TABLE `books`.`book` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`title` VARCHAR( 100 ) NOT NULL ,
`author` VARCHAR( 100 ) NOT NULL ,
`genre` VARCHAR( 100 ) NOT NULL ,
`status` VARCHAR( 100 ) NOT NULL
) ENGINE = MYISAM ;

I've set the id field to be auto incrementing primary key.
This means that whenever I add a new row to the table, it get's the next available int as id.
That will come in handy later on when we need to different the lines from one another.
We could use the title as the primary key, but that way we couldn't add more than one book with the same name.



Step 2 - Get the jQuery and jQueryUI libraries


Fire up your browser and surf to http://jqueryui.com/


Click the link Build custom download on the frontpage and you're directred to a custom build form.



You only need a whew of these components to complete this tutorial, but I suggest you download the whole packet now so you can play around with it.


On other projects it important to download only the components you really need as the whole library is very large in size and can make your application really slow to load on slower connections.


The .zip file contains a lot example code.
The index.html shows you the components and icons and the development bundle folder has example application.
They are all worth looking through but for this tutorial we'll only need the js and css folders.
Extract them into your project folder.


Notice that the jQueryUI package also contains a late version of the jQuery library.




Step 3 - The basic html page



<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Virtual bookshelf</title>
</head>
<body>

<h1>Bookshelf</h1>

<div id="container">
<p id="top-bar"> </p>

<ul id="menu">
<li class="ui-state-default ui-corner-all" id="button-load-all">All books</li>
<li class="ui-state-default ui-corner-all" id="button-load-dystopia">Dystopias</li>
<li class="ui-state-default ui-corner-all" id="button-load-fantasy">Fantasy</li>
<li class="ui-state-default ui-corner-all" id="button-load-scifi">Science Fiction</li>
</ul>

<fieldset class="ui-corner-all">
<legend>List of books</legend>
<table id="books" class="ui-widget ui-widget-content">
<thead>
<tr class="ui-widget-header ">
<th>ID</th>
<th width="25%">Title</th>
<th>Author</th>
<th>Genre</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>

</tbody>
</table>
</fieldset>

<p><a href="#" id="dialog_link" class="ui-state-default ui-corner-all button"><span class="ui-icon ui-icon-newwin"></span>Add a new book</a></p>

</div>
</body>
</html>


I'm using the ISO-8859-1 charset so that I wouldn't escape the whew special characters in Finnish language, but you can change this to UTF-8 if you prefer.


I've placed all the content inside a div with the id container.
This is so that I can more easily style the whole thing with css.
The p#top-bar is also just for styling.
I've placed the filter buttons inside an unordered list and given the buttons a whew css classes.
The ui-state-default is a style defined by the jQueryUI themes.
It represents a button in its default state.
The css file also has styles for buttons under hover and onclick.
The ui-corner-all is also from jQueryUI and does exactly what the name suggests, but as it uses CSS3's corner radius, it won't do anything in browser that don't support CSS3 (that's you IE).


Here's our boring plain html page now. Let's add some style to it.




Step 4 - Adding the css


In the top of the html page add the following stylesheet links. Right above the title tag is a good place.



<link rel="stylesheet" href="style.css" type="text/css" media="all" />
<link type="text/css" href="css/ui-darkness/jquery-ui-1.7.2.custom.css" rel="stylesheet" />

I downloaded the jQueryUI with the ui-darkness theme. If you chose a different theme this folder name is going to be different.



BODY
{
background: #5f5f5f url('images/bg.jpg') repeat-x;
font-family: Verdana;
font-size: 10px;
}
h1
{
font-family: Verdana;
font-size: 48px;
color: #fff;
font-style: italic;
width: 800px;
margin: 0px auto;
}
div#container
{
width: 800px;
background-color: #ffffff;
margin: 20px auto;
border-bottom: 1px solid #222222;
}
p#top-bar
{
background-color: #000000;
border-top: 1px solid #222222;
}
ul#menu
{
list-style-type: none;
margin-bottom: 50px;
}
ul#menu li
{
float: left;
padding: 4px;
margin-left: 6px;
cursor: pointer;
}
fieldset
{
border: 1px solid #cccccc;
margin: 4px;
}
legend
{
border: 1px solid #cccccc;
background-color: #efefef;
}
a.button
{
padding: .4em 1em .4em 20px;
text-decoration: none;
position: relative;
font-size: 10px;
margin: 4px;
}
a.button span.ui-icon
{
margin: 0 5px 0 0;
position: absolute;
left: .2em;
top: 50%;
margin-top: -8px;
}
#books
{
width: 100%;
}

The bg.jpg image I'm using as the page background is just a 1 px wide gradient going from black (#000000) to gray (#5f5f5f).
The a.button and a.button span.ui-icon are important to place the icon in the right place inside the button.
Without these lines the buttons are going to look like a mess.


Ok, so now we have something like this (that is if you are using a good enought browser. These screenshots are taken from Google Chrome that does a good job with at least the rounded corners).




Step 5 - Preparing the dialogs


We're using three jQueryUI dialogs to interact with the user.
We must define these elements inside the html page to add functionality to them through the jQuery later on.


Add the following divs inside the html page right above the end of the body element (right above the </body> tag).



<div id="dialog" title="Add a new book">
<p id="validateTips">All form fields are required.</p>
<form>
<fieldset>
<label for="title">Title of the book</label><br />
<input type="text" name="title" id="title" class="text ui-widget-content ui-corner-all" /><br />
<label for="author">Author</label><br />
<input type="text" name="author" id="author" value="" class="text ui-widget-content ui-corner-all" /><br />
<label for="genre">Genre</label><br />
<select name="genre" id="genre" class="ui-widget-content ui-corner-all">
<option value="Dystopia">Dystopia</option>
<option value="Fantasy">Fantasy</option>
<option value="Science Fiction">Science Fiction</option>
</select>
</fieldset>
</form>
</div>

Here's the html code for the Add a book dialog.
I've wrapped all the inputs inside an empty form just to make it more standards compliant, but this isn't really necessary.
The validateTips is identified to allow the jQuery code to display validation info to the user.
I've only added a whew fields for this tutorial.
Feel free to add to or edit these fields as needed.



<div id="dialog-borrow" title="Borrow a book">
<form>
<fieldset>
<input type="hidden" name="bookid" id="bookid" value="" />
<label for="name">Name of the borrower</label><br />
<input type="text" name="name" id="name" class="text ui-widget-content ui-corner-all" />
</fieldset>
</form>
</div>

Here's dialog number two. All it takes is a hidden field to keep track on whick book the action is targeted and an input field for the name of the borrower.



<div id="dialog-del" title="Really delete the book?">
<form>
<input type="hidden" name="bookid" id="bookid2" value="" />
<p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>Are you sure you want to delete this book?</p>
</form>
</div>

And finally the confirm delete dialog.
Again a hidden input to keep track of the targeted book.
The buttons for these dialogs are added in the jQuery part.


Now we must include jQuery, jQueryUI and our soon to be made java script controller file to the html page.
Add these lines to the head of the page right under the stylesheets.



<script type="text/javascript" src="js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.7.2.custom.min.js"></script>
<script type="text/javascript" src="js/bookshelf.js"></script>


Step 7 - Create the java script controller


In the js/ folder add a new javascrpit file and name it bookshelf.js.
In this file we will create the functionality of our application.


We want the functionality to be added when the page get's loaded so lets add a new jQuery function that will get fired on DOM load.



$(function() {
});

Inside this function we must now add the code for creating and opening the dialogs and controlling the visual elements such as the button being hovered on or clicked.



$.get("load.php?action=all", function(data){
$('#books tbody').append(data);
UpdateLinks();
});

var title = $("#title"),
author = $("#author"),
genre = $("#genre"),
allFields = $([]).add(title).add(author).add(genre),
tips = $("#validateTips"),
name = $("#name"),
bookid = $("#bookid"),
bookid2 = $("#bookid2"),
loadDystopias = $("#button-load-dystopia"),
loadFantasy = $("#button-load-fantasy"),
loadScifi = $("#button-load-scifi"),
loadAll = $("#button-load-all");

When the page is loaded we want to fill the table with the data we've stored in the database (if any).
So we make an Ajax request in which we send a querystring action=all through the GET into our PHP file that will handle the retrieving of data from the database.
Then we append the returned data into our table's tbody. I've also defined here some variables to avoid having to get them through the $("#") markup every time we need them.
Also as we now add buttons that we want to have event's bind onto, we must call for a function to update the links.


So add this function into the end of the java script file.



function UpdateLinks()
{
$(".del").click(function(){
$("#bookid2").val(this.name);
$("#dialog-del").dialog('open');
return false;
});
$(".borrow").click(function(){
$("#bookid").val(this.name);
$("#dialog-borrow").dialog('open');
return false;
});
$(".return").click(function(){
$.get("load.php?action=return&id=" + this.name, function(data){
returnId = data;
});
$.get("load.php?action=all", function(data){
$('#books tbody').html(data);
UpdateLinks();
});
});
}

What it does is it adds event onto our newly created buttons.
The delete links opens the confirm delete dialog and the borrow link opens the borrow dialog.
Clicking on the return button runs another Ajax query to mark the book as returned in the database.
After altering the data in the database, we must reload the page contents and once again update the links.


Add the following code inside the first function. I.e. the one that gets fired on DOM load.



$('#dialog_link').click(function(){
$('#dialog').dialog('open');
return false;
});
$('#dialog_link, ul#menu li').hover(
function() { $(this).addClass('ui-state-hover'); },
function() { $(this).removeClass('ui-state-hover'); }
);
loadAll.click(function(){
$.get("load.php?action=all", function(data){
$('#books tbody').html(data);
UpdateLinks();
});
});
loadDystopias.click(function(){
$.get("load.php?action=dystopia", function(data){
$('#books tbody').html(data);
UpdateLinks();
});
});
loadFantasy.click(function(){
$.get("load.php?action=fantasy", function(data){
$('#books tbody').html(data);
UpdateLinks();
});
});
loadScifi.click(function(){
$.get("load.php?action=scifi", function(data){
$('#books tbody').html(data);
UpdateLinks();
});
});

Here we add functionality to the onclick event of the filter buttons and add the button for opening the add a book dialog.
Also I've added some visual candy with making the buttons to toggle their hover style on mouse enter and mouse leave events.


Ok, now onto making the dialogs work.


Here's the dialog for borrowing a book. Add all the rest of the java script inside the first function.



$("#dialog-borrow").dialog({
bgiframe: true,
autoOpen: false,
height: 140,
modal: true,
buttons: {
'Borrow book': function() {
$.get("load.php?action=borrow&id=" + bookid.val() + "&name=" + name.val(), function(data){
returnId = data;
});
$.get("load.php?action=all", function(data){
$('#books tbody').html(data);
UpdateLinks();
});
$(this).dialog('close');
},
Cancel: function() {
$(this).dialog('close');
}
}
});

Every one of these dialogs will follow this same pattern.
First we set the dialog options, then we add the buttons and add their actions.
The Borrow book button sends another Ajax call and like before after altering the data in the database we refresh the data on the page to match it.
When writing normal GET querystrings it is important to escape the ampersands with the &amp;, but in jQuery's Ajax calls we can't escape those special chars.



$("#dialog-del").dialog({
bgiframe: true,
resizable: false,
autoOpen: false,
height:140,
modal: true,
buttons: {
'Delete book': function() {
$.get("load.php?action=del&id=" + bookid2.val(), function(data){
returnId = data;
});
$.get("load.php?action=all", function(data){
$('#books tbody').html(data);
UpdateLinks();
});
$(this).dialog('close');
},
Cancel: function() {
$(this).dialog('close');
}
}
});

There's nothing special in the confirm delete dialog.
If the user confirms, we run yet another jQuery Ajax call and update the page contents.



$("#dialog").dialog({
bgiframe: true,
autoOpen: false,
height: 250,
modal: true,
buttons: {
'Add book': function() {
var bValid = true;
allFields.removeClass('ui-state-error');

bValid = bValid && checkLength(title,"title",3,80);
bValid = bValid && checkLength(author,"author",3,80);

if (bValid) {
$.get("load.php?action=add&title=" + title.val() + "&author=" + author.val() + "&genre=" + genre.val(), function(data){
returnId = data;
});
$.get("load.php?action=all", function(data){
$('#books tbody').html(data);
UpdateLinks();
});
$(this).dialog('close');
}
},
Cancel: function() {
$(this).dialog('close');
}
},
close: function() {
allFields.val('').removeClass('ui-state-error');
}
});

The dialog for adding a new book is a bit more complex as it includes a basic validation of form field values.
Well make a new function to check the length of the input values.
You can make this validation better, but here's the basics that should be in every input validation.



function updateTips(t) {
tips.text(t).effect("pulsate");
}

function checkLength(o,n,min,max) {
if ( o.val().length > max || o.val().length < min ) {
o.addClass('ui-state-error');
updateTips("Length of " + n + " must be between "+min+" and "+max+".");
return false;
} else {
return true;
}
}

If the field doesn't validate we add an error class to inform the user.
Also we update the contents of the validation tip paragraph and make it pulsate to better capture the users attention.
And that's all the java script we'll need for this project. Let's move onto the other end of the Ajax call.



Step 8 - The PHP functions


Create a new PHP file and name it load.php.
This is the file we used in the jQuery code to receive the Ajax calls and this is the file we assumed to receive some data from.


Let's make a basic switch controller to interpret the querystring commands.



<?PHP
switch ($_GET['action'])
{
case 'scifi':
ReturnAll("WHERE genre = 'Science Fiction'");
break;
case 'fantasy':
ReturnAll("WHERE genre = 'fantasy'");
break;
case 'dystopia':
ReturnAll("WHERE genre = 'dystopia'");
break;
case 'all':
ReturnAll();
break;
case 'add':
AddEntry($_GET['title'], $_GET['author'], $_GET['genre']);
break;
case 'borrow':
BorrowBook($_GET['id'], $_GET['name']);
break;
case 'return':
ReturnBook($_GET['id']);
break;
case 'del':
DeleteBook($_GET['id']);
break;
}
?>

The only function to touch the database will be the RunQuery function.
All the other functions will use this one with different arguments to access the data.



function RunQuery( $sql, $type )
{
$link = mysql_connect('localhost', 'username', 'password');
if (!$link) {
return false;
}
$db_selected = mysql_select_db('books', $link);
if (!$db_selected) {
mysql_close($link);
return false;
}
$result = mysql_query( $sql );
if (!$result) {
mysql_close($link);
return false;
}
if ( $type == "select")
{
$return = "";
while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
$return .=
'<tr>
<td>' . $row['id'] . '</td>
<td>' . $row['title'] . '</td>
<td>' . $row['author'] . '</td>
<td>' . $row['genre'] . '</td>
<td>' . $row['status'] . '</td>';
if(strlen($row['status']) < 1)
$return .= '<td><a class="borrow" name="' . $row['id'] . '" href="#">Borrow book</a>';
else
$return .= '<td><a class="return" name="' . $row['id'] . '" href="#">Return book</a>';
$return .= ' / <a class="del" name="' . $row['id'] . '" href="#">Delete</a></td>
</tr>';
}
mysql_close($link);
return $return;
}
else if ( $type == "insert" )
{
$id = mysql_insert_id();
mysql_close($link);
return $id;
}

mysql_close($link);
return true;
}

The code should be pretty straight forward.
Open a database link and select the database we created earlier.
Then we run the given query and store it's results in the $result variable.


As we use this one function for all kinds of queries, we need to decide what to return based on the $type variable.
If the user requested a select query we return the query results formatted in a table row.
And if the request was to insert a new row we return the id of the new row.


It's a good practice to return Ajax data as XML, but since we are going to add the data into a table it's just the same to format it as html here as we can then style the table as a whole.



function ReturnAll($where = "")
{
$result = RunQuery("SELECT * FROM book $where ORDER BY id;", "select");
if( !$result )
{
echo '<tr><td colspan="5"><em>Error loading data or no data entered</em></td></tr>';
}
else
{
echo $result;
}
}

Here's the main select query. All the other selects ose this query with a changing $where variable.
If no entries were found we return (echo) a message indicating that nothing was found.



function AddEntry($title, $author, $genre)
{
echo RunQuery("INSERT INTO book VALUES(default, '$title', '$author', '$genre', '');", "insert");
}

function BorrowBook($id, $name)
{
echo RunQuery("UPDATE book SET status = '$name' WHERE id = $id", "update");
}

function ReturnBook($id)
{
echo RunQuery("UPDATE book SET status = '' WHERE id = $id", "update");
}

function DeleteBook($id)
{
echo RunQuery("DELETE FROM book WHERE id = $id;", "delete");
}

The rest of the functionality is pretty simple.
Just run a query in the database with the given arguments.


If we now run the application with the Firebug on we should see GET querystrings being sent to the load.php and data being returned from the server.




Conclusion


That's it for making a simple Ajax application and styling it with the jQueryUI library widgets.
Hope you enjoyed reading this tutorial and learnt something new.
Thanks for reading!

Ei kommentteja:

Lähetä kommentti