In the drag-and-drop folder, create a file named config.php, and add the database configuration code to it:... Create a new file named drag-and-drop.js, and add this code to it: // hold
Trang 1Let's take a look at how this is going to look:
Figure 10.1: Add, Reorder, and Delete Tasks, in a Simple Visual Interface
Dragging items around the screen makes the other items switch positions
Trang 2Chapter 10
When dropping a task on the DROP HERE TO DELETE area, a confirmation is required before the application proceeds with the actual deletion; as shown in the following figure:
Figure 10.2: Confirmation Required Before Deleting a Task
Time for Action—Task Management Application with AJAX
1 Connect to the ajax database, and create a table named tasks with the following code:
CREATE TABLE tasks (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
order_no INT UNSIGNED NOT NULL default '0',
description VARCHAR(100) NOT NULL default '',
PRIMARY KEY (id)
);
2 In your ajax folder, create a new folder named drag-and-drop
3 In the drag-and-drop folder, create a file named config.php, and add the database configuration code to it:
<?php
// defines database connection data
define('DB_HOST', 'localhost');
define('DB_USER', 'ajaxuser');
define('DB_PASSWORD', 'practical');
define('DB_DATABASE', 'ajax');
?>
Trang 34 Now add the standard error-handling file, error_handler.php Feel free to copy this file from previous chapters Anyway, here's the code for it:
<?php
// set the user error handler method to be error_handler
set_error_handler('error_handler', E_ALL);
// error handler function
function error_handler($errNo, $errStr, $errFile, $errLine)
{
// clear any output that has already been generated
if(ob_get_length()) ob_clean();
// output the error message
$error_message = 'ERRNO: ' $errNo chr(10)
'TEXT: ' $errStr chr(10)
'LOCATION: ' $errFile
', line ' $errLine;
echo $error_message;
// prevent processing any more PHP scripts
exit;
}
?>
5 Download the script.aculo.us library from http://script.aculo.us/downloads
and unzip/untar the downloaded archive to your drag-and-drop folder Change the script.aculo.us folder name from something like scriptaculous-js-x.y.z to
simply scriptaculous
6 Create a new file named index.php, and add this code to it:
<?php
require_once ('taskslist.class.php');
?>
<!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=utf-8" /> <title>AJAX Drag and Drop Sortable List</title>
<link href="drag-and-drop.css" rel="stylesheet" type="text/css" /> <script src="drag-and-drop.js" type="text/javascript"></script>
<script src="scriptaculous/lib/prototype.js" type="text/javascript"> </script>
<script src="scriptaculous/src/scriptaculous.js" type="text/javascript"> </script>
</head>
<body onload="startup()">
<h1>Task Management</h1>
<h2>Add a new task</h2>
<div>
<input type="text" id="txtNewTask" name="txtNewTask"
size="30" maxlength="100" onkeydown="handleKey(event)"/> <input type="button" name="submit" value="Add this task"
onclick="process('txtNewTask', 'addNewTask')" />
</div>
<br />
<h2>All tasks</h2>
<ul id="tasksList" class="sortableList"
onmouseup="process('tasksList', 'updateList')">
<?php
$myTasksList = new TasksList();
echo $myTasksList->BuildTasksList();
?>
Trang 4Chapter 10 </ul>
<br /><br />
<div id="trash">
DROP HERE TO DELETE
<br /><br />
</div>
</body>
</html>
7 Create a new file named taskslist.class.php, and add this code to it:
<?php
// load error handler and database configuration
require_once ('error_handler.php');
require_once ('config.php');
// This class builds a tasks list and
// performs add/delete/reorder actions on it
class TasksList
{
// stored database connection
private $mMysqli;
// constructor opens database connection
function construct()
{
// connect to the database
$this->mMysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD,
DB_DATABASE);
}
// destructor closes database connection
public function destruct()
{
$this->mMysqli->close();
}
// Builds the tasks list
public function BuildTasksList()
{
// initialize output
$myList = '';
// build query
$result = $this->mMysqli->query('SELECT * FROM tasks '
'ORDER BY order_no ASC');
// build task list as <li> elements
while ($row = $result->fetch_assoc())
{
$myList = '<li id="' htmlentities($row['id']) '">'
htmlentities($row['description']) '</li>';
}
// return the list
return $myList;
}
// Handles the server-side data processing
public function Process($content, $action)
{
// perform action requested by client
switch($action)
{
// Reorder task list
case 'updateList':
// retrieve update details
$new_order = explode('_', $content);
// update list
Trang 5for ($i=0; $i < count($new_order); $i++)
{
// escape data received from client
$new_order[$i] =
$this->mMysqli->real_escape_string($new_order[$i]); // update task
$result = $this->mMysqli->query('UPDATE tasks SET order_no="' $i '" WHERE id="' $new_order[$i] '"'); }
$updatedList = $this->BuildTasksList();
return $updatedList;
break;
// Add a new task
case 'addNewTask':
// escape input data
$task = trim($this->mMysqli->real_escape_string($content)); // continue only if task name is not null
if ($task)
{
// obtain the highest order_no
$result = $this->mMysqli->query('SELECT (MAX(order_no) + 1) ' 'AS order_no FROM tasks'); $row = $result->fetch_assoc();
// if the table is empty, order_no will be null
$order = $row['order_no'];
if (!$order) $order = 1;
// insert the new task as the bottom of the list
$result = $this->mMysqli->query
('INSERT INTO tasks (order_no, description) ' 'VALUES ("' $order '", "' $task '")'); // return the updated tasks list
$updatedList = $this->BuildTasksList();
return $updatedList;
}
break;
// Delete task
case 'delTask':
// escape input data
$content = trim($this->mMysqli->real_escape_string($content)); // delete the task
$result = $this->mMysqli->query('DELETE FROM tasks WHERE id="' $content '"');
$updatedList = $this->BuildTasksList();
return $updatedList;
break;
}
}
}
?>
8 Create a new file named drag-and-drop.php, and add this code to it:
<?php
// load helper class
require_once ('taskslist.class.php');
// create TasksList object
$myTasksList = new TasksList();
// read parameters
$action = $_GET['action'];
$content = $_GET['content'];
// clear the output
if(ob_get_length()) ob_clean();
// headers are sent to prevent browsers from caching
Trang 6Chapter 10 header('Expires: Fri, 25 Dec 1980 00:00:00 GMT'); // time in the past header('Last-Modified: ' gmdate( 'D, d M Y H:i:s') 'GMT');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
header('Content-Type: text/html');
// execute the client request and return the updated tasks list
echo $myTasksList->Process($content, $action);
?>
9 Create a new file named drag-and-drop.js, and add this code to it:
// holds an instance of XMLHttpRequest
var xmlHttp = createXmlHttpRequestObject();
// when set to true, display detailed error messages
var showErrors = true;
// initialize the requests cache
var cache = new Array();
// creates an XMLHttpRequest instance
function createXmlHttpRequestObject()
{
// will store the reference to the XMLHttpRequest object
var xmlHttp;
// this should work for all browsers except IE6 and older
try
{
// try to create XMLHttpRequest object
xmlHttp = new XMLHttpRequest();
}
catch(e)
{
// assume IE6 or older
var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0",
"MSXML2.XMLHTTP.5.0",
"MSXML2.XMLHTTP.4.0",
"MSXML2.XMLHTTP.3.0",
"MSXML2.XMLHTTP",
"Microsoft.XMLHTTP");
// try every prog id until one works
for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++)
{
try
{
// try to create XMLHttpRequest object
xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
}
catch (e) {} // ignore potential error
}
}
// return the created object or display an error message
if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
}
// function that displays an error message
function displayError($message)
{
// ignore errors if showErrors is false
if (showErrors)
{
// turn error displaying Off
showErrors = false;
// display error message
alert("Error encountered: \n" + $message);
Trang 7}
}
// Scriptaculous-specific code to define a sortable list and a drop zone function startup()
{
// Transform an unordered list into a sortable list with draggable items Sortable.create("tasksList", {tag:"li"});
// Define a drop zone used for deleting tasks
Droppables.add("trash",
{
onDrop: function(element)
{
var deleteTask =
confirm("Are you sure you want to delete this task?")
if (deleteTask)
{
Element.hide(element);
process(element.id, "delTask");
}
}
});
}
// Serialize the id values of list items (<li>s)
function serialize(listID)
{
// count the list's items
var length = document.getElementById(listID).childNodes.length;
var serialized = "";
// loop through each element
for (i = 0; i < length; i++)
{
// get current element
var li = document.getElementById(listID).childNodes[i];
// get current element's id without the text part
var id = li.getAttribute("id");
// append only the number to the ids array
serialized += encodeURIComponent(id) + "_";
}
// return the array with the trailing '_' cut off
return serialized.substring(0, serialized.length - 1);
}
// Send request to server
function process(content, action)
{
// only continue if xmlHttp isn't void
if (xmlHttp)
{
// initialize the request query string to empty string
params = "";
// escape the values to be safely sent to the server
content = encodeURIComponent(content);
// send different parameters depending on action
if (action == "updateList")
params = "?content=" + serialize(content) + "&action=updateList"; else if (action == "addNewTask")
{
// prepare the task for sending to the server
var newTask =
trim(encodeURIComponent(document.getElementById(content).value)); // don't add void tasks
if (newTask)
params = "?content=" + newTask + "&action=addNewTask";
Trang 8Chapter 10 }
else if (action =="delTask")
params = "?content=" + content + "&action=delTask";
// don't add null params to cache
if (params) cache.push(params);
// try to connect to the server
try
{
// only continue if the connection is clear and cache is not empty
if ((xmlHttp.readyState == 4 || xmlHttp.readyState == 0)
&& cache.length>0)
{
// get next set of values from cache
var cacheEntry = cache.shift();
// initiate the request
xmlHttp.open("GET", "drag-and-drop.php" + cacheEntry, true); xmlHttp.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded"); xmlHttp.onreadystatechange = handleRequestStateChange;
xmlHttp.send(null);
}
else
{
setTimeout("process();", 1000);
}
}
// display the error in case of failure
catch (e)
{
displayError(e.toString());
}
}
}
// function that retrieves the HTTP response
function handleRequestStateChange()
{
// when readyState is 4, we also read the server response
if (xmlHttp.readyState == 4)
{
// continue only if HTTP status is "OK"
if (xmlHttp.status == 200)
{
try
{
postUpdateProcess();
}
catch(e)
{
// display error message
displayError(e.toString());
}
}
else
{
displayError(xmlHttp.statusText);
}
}
}
// Processes server's response
function postUpdateProcess()
{
// read the response
var response = xmlHttp.responseText;
// server error?
Trang 9if (response.indexOf("ERRNO") >= 0 || response.indexOf("error") >= 0) alert(response);
// update the tasks list
document.getElementById("tasksList").innerHTML = response;
Sortable.create("tasksList");
document.getElementById("txtNewTask").value = "";
document.getElementById("txtNewTask").focus();
}
/* handles keydown to detect when enter is pressed */
function handleKey(e)
{
// get the event
e = (!e) ? window.event : e;
// get the code of the character that has been pressed
code = (e.charCode) ? e.charCode :
((e.keyCode) ? e.keyCode :
((e.which) ? e.which : 0));
// handle the keydown event
if (e.type == "keydown")
{
// if enter (code 13) is pressed
if(code == 13)
{
// send the current message
process("txtNewTask", "addNewTask");
}
}
}
/* removes leading and trailing spaces from the string */
function trim(s)
{
return s.replace(/(^\s+)|(\s+$)/g, "")
}
10 Create a new file named drag-and-drop.css, and add this code to it:
body
{
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
}
ul.sortableList
{
list-style-type: none;
padding: 0px;
margin: 0px;
width: 300px;
}
ul.sortableList li
{
cursor: move;
padding: 2px 2px;
margin: 2px 0px;
border: 1px solid #00CC00;
background-color: #F4FFF5;
}
h1
{
border-bottom: 1px solid #cccccc;
}
#trash
{
border: 4px solid #ff0000;
width: 270px;
padding: 10px;
}
Trang 10Chapter 10
11 Load http://localhost/ajax/drag-and-drop in your web browser and test its
functionality to make sure it works as expected (see Figures 10.1 and 10.2 for reference)
What Just Happened?
Adding a task is performed as mentioned in the following steps:
1 The user enters task
2 When the user clicks on Add this task button or presses Enter, the data is sent to the
server with an asynchronous HTTP request The server script inserts the new task
into the database, and returns the updated list, which is then injected into the code with JavaScript
When reordering the list, this is what happens:
1 Every task is an XHTML list element: an <li> The user begins dragging an item; on dropping it, an HTTP request is sent to the server This request consists of a
serialized string of IDs, every list element's ID
2 On the client you'll see the list reordered, while the server updates the order of each element in the database
This is how deleting a task works:
1 The user drags an item and drops it on the DROP HERE TO DELETE area
2 An HTTP request is sent to the server, which performs the task deletion from the
database and the XHTML element is instantly destroyed
We include in index.php the JavaScript libraries we'll be using:
<script src="drag-and-drop.js" type="text/javascript"></script>
<script src="scriptaculous/lib/prototype.js" type="text/javascript">
</script>
<script src="scriptaculous/src/scriptaculous.js" type="text/javascript"> </script>
The first line includes our custom functions and AJAX-related tasks The second line includes the Prototype library, while the third line includes the script.aculo.us library
The onload event inside the <body> tag calls the startup() function, which defines the unordered list with id="tasksList" as a sortable element (Sortable.create) This ensures drag-and-drop functionality for <li> elements inside the list The startup() function also defines a droppable element Droppables.add; we use this as an area where we delete tasks
Also, inside the startup() function, we define a behavior for dropping a list item on the drop zone:
onDrop: function(element)
{
var deleteTask = confirm("Are you sure you want to delete this task?")
if (deleteTask == true)
{
Element.hide(element);
process(element, "delTask");
}
}