In this article, learn about a system for making and responding to Ajax service calls in a consistent, event-based manner using PHP, jQuery, and JSON. The system is intended for Ajax calls to services such as login and profile updating (not for simple loading of content, like an HTML page). You can use this system to determine if a remote process call succeeds or fails.
To follow along with this article, it is assumed that you:
- Are familiar with the basis of object-oriented programming in both JavaScript and PHP.
- Are familiar with the DOM Level 2 event model and how to interact with it in JavaScript. For the example system, you interact with this model using the jQuery library.
- Have a basic understanding of Ajax concepts.
- Know how objects are constructed and referenced using the JSON notation.
See Resources for more information on these technologies and concepts.
The system outlined in this article uses the following technologies:
- Access to a PHP 5 server with a
json_encodefunction of PHP (requires PHP 5.2.0 or greater and PHP Extension Community Library, or PECL, JSON 1.2.0 or greater). - The jQuery JavaScript library (Version 1.4.4 or greater).
The scenario involves a company that decided to redo one of their websites. As part of the redesign, the company wanted login and profile editing to be done with Ajax pop-up windows. Because the site was already under renovation, they took the opportunity to implement ideas for standardizing how Ajax service calls were made and responded to.
The new system was created to accomplish three main goals:
- Standardize the result format of objects returned by Ajax service calls.
- Provide event-based responses to Ajax calls .
- Centralize Ajax result handling.
The rest of this article outlines how the system achieves these goals.
Standardize the result format of objects returned by Ajax calls
The PHP class ServiceResult standardizes the resulting object created by making an Ajax call. All Ajax service calls return a JSON-encoded object of this type, giving
all Ajax service calls a consistent and predictable interface.
Listing 1 shows the PHP ServiceResult class.
Listing 1. PHP
ServiceResult class
<?php
class ServiceResult {
/**
*
* @var Boolean
*/
public $success;
/**
*
* @var Array
*/
public $errors;
/**
*
* @var Array
*/
public $messages;
/**
* Holds data returned by a service or passthrough data.
*/
public $data;
/**
*
* @var String - Event name triggered in JavaScript when service call successful.
*/
public $onSuccessEvent;
/**
*
* @var String - Event name triggered in JavaScript when service call fails.
*/
public $onErrorEvent;
}
?>
|
At the top of the class, the listing defines:
- The
successproperty, indicating whether the process being invoked succeeds or fails. This is not an indication of whether the Ajax call itself succeeds. If the Ajax call fails, you won't get the JSON object so you still need to handle Ajax failures with a fault handler of some kind. In the example, success indicates whether the user is found during the login process, or if the user's profile updates successfully. - The
errorsarray, which holds any information about why the call doesn't succeed.For example, if a login process fails, it may contain a message such as "No user with that user name and password failed." By allowing theServiceResultobject to pass back error messages, you don't have to hard code any messages in your JavaScript code. The JavaScript code simply needs to know that any error messages intended for the user are contained in this array. -
The
messagesproperty, which is also an array, passes back non-error related messages to the result handler. The messages can be anything you want the user to know in relation to the service call. You can include success messages, such as "You have been logged in." By keeping errors and generic messages separate, you don't have to determine if a message is error related, or simply informative, in the result handler when displaying it to the user (thus simplifying the logic on the client side). - The
dataproperty that provides a method for passing arbitrary data back to the result handler. For example, if you're calling a login service,datamay contain user profile data. While thedataproperty itself is generic, the result handlers for the specific request must make assumptions about the data contained in this property based on the service call being made. Regardless, thedataproperty allows all result handlers to know where to look for such data. - The
onSuccessEventandonErrorEventproperties, which are strings for defining the event to be broadcast for a successful or unsuccessful service call. Again, this refers to the process and not the Ajax call itself. Examples of such events areuserLoggedInfor success orloginFailedfor failure.
Provide event-based responses to Ajax calls
The second goal of the system is to make responses to Ajax calls event-based using the DOM Level 2
event model. To accomplish this, the example uses some object-oriented design in JavaScript.
For the system to work, individual objects must be able to subscribe to DOM Level 2 events
that are returned by the service call (onSuccessEvent and onErrorEvent events passed back by the
PHP ServiceResult object).
All objects must subscribe to the same object
that dispatches the event.
Create a basic object inheritance chain for your JavaScript object. Create the chain so that if you ever need to switch out the underlying JavaScript library, you can do so without disturbing the interface for interacting with the DOM Level 2 event model in your JavaScript objects.
The EventDispatcher object is at the top level of the inheritance chain,
as shown in Listing 2, and is responsible
for defining the JavaScript interface for interacting with the DOM Level 2
event model. It is also
responsible for ensuring that all of the JavaScript objects are subscribing to the same dispatcher.
Listing 2.
EventDispatcher object
function EventDispatcher() {
}
EventDispatcher.prototype.addListener = function (eventName, listener) {
this.dispatcher.bind(eventName, listener)
}
EventDispatcher.prototype.removeListener = function (eventName, listener) {
this.dispatcher.unbind(eventName, listener)
}
EventDispatcher.prototype.dispatch = function (eventName, data) {
this.dispatcher.trigger(eventName, data);
}
EventDispatcher.prototype.dispatcher = jQuery(document);
|
In Listing 2:
- The
addListenerandremoveListenerfunctions let you add and remove listeners from the dispatcher object. - The
eventNameparameter is the event to be subscribed to. - The
listenerparameter is the function to be registered. - The
dispatchfunction broadcasts the event specified in theeventNameparameter. - The
dataparameter provides a method of passing along data for any subscribed listener functions. - The
dispatcherproperty is set to a jQuery object referencing the document DOM object.
By setting the dispatcher property to a jQuery object referencing the document DOM
object, all objects' events are registered on and broadcast by the document DOM object
that exists on any given page. This is simply a property of the EventDispatcher
object, and all methods reference that dispatcher property. You can change the underlying dispatcher by
changing that property. You can use any object that provides access to the DOM Level 2 event model
by simply changing the dispatcher and updating methods to correspond to that dispatcher interface
(without interrupting any of the objects that inherit from the EventDispatcher object). It's key
that all objects subscribe to the same dispatcher.
The next step is to allow individual scripts to inherit the functions defined by the
EventDispatcher object. The company in this
scenario has a convention that each page on the site has a single
controller object responsible for the function of that page. To bridge the gap between these individual
page controllers and the EventDispatcher object, create a BaseController class that inherits from the
EventDispatcher object.
For the sake of brevity, all functions not specifically related to event dispatching are omitted from the sample code. There are other things that controllers inherit from this class.
Listing 3 shows the BaseController class.
Listing 3.
BaseController class
function BaseController() {
}
BaseController.prototype = new EventDispatcher();
|
Because the BaseController prototype property is set to an EventDispatcher object, any page-specific
controller that inherits from BaseController
includes event dispatcher functions.
Centralize Ajax result handling
The final goal for the system is to centralize the result processing of Ajax
service calls. You can do so using jQuery's ajaxComplete function, which is automatically called any time an Ajax call is completed using jQuery. To ensure this occurs,
place the code in Listing 4 in a script file that you
include in any page that makes Ajax service calls.
Listing 4. jQuery
ajaxComplete function
jQuery(document).ajaxComplete(function(event, request, settings) {
if (settings.dataType == 'json') {
var dispatcher = new EventDispatcher();
var result = jQuery.parseJSON(request.responseText);
if (result) {
if (result.success == true && result.onSuccessEvent) {
dispatcher.dispatch(result.onSuccessEvent, result)
} else if (result.onErrorEvent) {
dispatcher.dispatch(result.onErrorEvent, result)
}
}
}
})
|
The handler is registered against the document object because you know all pages have this object. Each time the function is invoked, it is passed:
- A jQuery Ajax
eventobject (theeventparameter). - The
XMLHttpRequest(therequestparameter). - An object representing the settings used to make the original
Ajax call (the
settingsparameter).
For the example system, you're only interested in the
XMLHttpRequest object and the settings object.
Take a closer look at the code inside the handler in Listing 4. The handler first checks the settings object to see if the result format is set to
JSON. (Recall that the purpose of this system
is not to respond to simple content load requests, but to respond to service-based Ajax calls, all of which
return a JSON-encoded ServiceResult). If the data type returned by the Ajax request is not JSON, you are not interested in responding to it and
the handler bypasses all of the remaining code.
If the data type is JSON, the code creates an instance of the
JavaScript EventDispatcher. Because all
instances of the EventDispatcher class reference the same dispatcher instance, any events broadcast by it
trigger all of the listeners registered on it by any JavaScript object extending the
EventDispatcher class.
The code then grabs a reference to JSON encoded from the request object's
responseText property (using jQuery's
parseJSON function), and stores it in the
result variable.
Assuming the parsing of the responseText succeeds,
the handler checks if the result object indicates that the
procedure is successful by checking the success
property. If it is successful, the handler then checks whether
there is an onSuccessEvent defined. If there is, the event dispatcher
instance rebroadcasts that event along with the result object.
If the result object does not indicate that the process is successful, the code checks for an
onErrorEvent. If this is defined, the dispatcher instance rebroadcasts the event, again
passing along the result object.
By using the jQuery ajaxComplete functions, you can now notify your client-side code to react based
upon events. The client-side objects don't have to register as listeners
on the Ajax call. The client-side code does not even have to know that a service call is being made. It simply
has to know what events to listen for and how to respond to the
data that is passed back to it.
In this section, a simple Ajax login service call shows how the pieces of the system interact. For the sake of simplicity, it is assumed that you've included all the necessary PHP and JavaScript files where necessary.
Create a PHP file that represents the service being invoked and returns a JSON-encoded
ServiceResult object. Listing 5 shows an example.
Listing 5. PHP file
<?php
$ctrl = new LoginController();
$result = new ServiceResult();
$result->success = $ctrl->login($_POST['username'], $_POST['password']);
$result->errors = $ctrl->errors;
$result->onSuccessEvent = 'refreshPage';
$result->onErrorEvent = 'userLoginFailed';
echo json_encode($result);
?>
|
You need a JavaScript object that invokes the service. For this example,
it is assumed that you have already created an HTML login form. Call the object that is responsible for making the service
call LoginController, as in
Listing 6.
Listing 6.
LoginController service call
LoginController.prototype.login = function() {
var self = this;
var username = jQuery('#loginForm .email').val()
var password = jQuery('#loginForm .password').val()
var data = {
username:username,
password:password,
};
jQuery.ajax({
url:'login.php',
async:true,
service:'login',
type:'post',
dataType:'json',
data:data
});
}
|
Because this system responds to Ajax service calls using events, you are not
passing result and fault handlers to the jQuery.ajax call.
Now that you have the service invocation, you want to allow objects to respond to onSucessEvents and
onErrorEvents defined in the ServiceResult object that is returned by the login service. The responding objects need to inherit from EventDispatcher, which is inheritable by
extending BaseController. In this case, the LoginController object displays a message to the user
explaining that
the login attempt failed. If the login attempt succeeds, the AppController object responds
by refreshing the page. Start with the LoginController, as in Listing 7.
Listing 7.
LoginController adding listeners
function LoginController() {
var self = this;
jQuery(document).ready(function() {
self.onLoad();
})
}
LoginController.prototype = new BaseController();
LoginController.prototype.onLoad = function() {
var self = this;
this.addListener('userLoginFailed', function(event, result) {
self.onLoginFailed(result);
})
}
LoginController.prototype.onLoginFailed = function(result) {
jQuery('#errorDisplay').html(result.errors[0]);
}
|
The constructor invokes an onLoad
function. This is not key to the example, but
is included
because this controller may need to reference DOM objects that are not
available until the page is loaded.
Two key items to note here are:
- The prototype object of the
LoginControlleris aBaseController. Hence, it also inherits theEventDispatchermethods. - Use of the
EventDispatcher addListenermethod in theonLoadfunction. The event being passed in matches theonSuccessEventreturned by theServiceResultobject that is returned by the login service. The handler function being registered is passed along theresultobject, which is the JSON-encodedServiceResultobject returned by the login service.
The onLoginFailed method uses the result object error array to display a message to the user. Handling
of a failed login is taken care of.
When the user login is successful, you want the page to refresh so the user's session data is
updated.
However, there are other occasions where you might want to respond to service invocations by refreshing the
page, such as when users update their profiles. Rather than having multiple controllers with result
handlers that refresh the page, keep this function in a higher level
controller. Then, service
calls that want to refresh the page to update session data can do so simply by dispatching the
refreshPage
event type as the onSucessEvent. To do so, repeat the process you used for the LoginController in
the
AppController, as in Listing 8.
Listing 8.
AppController object
function AppController() {
var self = this;
jQuery(document).ready(function() {
self.onLoad();
})
}
AppController.prototype = new BaseController();
AppController.prototype.onLoad = function () {
this.addListener('refreshPage', function(event, user) {
window.location.reload()
})
}
|
Again, the AppController simply sets its prototype to an instance of BaseController and registers a
listener
function using addListener for the refreshPage event.
Now that you've created the responding objects and registered the event listeners, include the script with the onAjaxComplete event handler function. When the service call completes,
the
onAjaxComplete handler dispatches the appropriate event notifying the registered handler function.
The LoginController and AppController need not have any knowledge of each other. They
do
not even know that they're responding to a service call--let alone the same service call.
This article examined a system for making Ajax service calls that:
- Standardizes the result format of objects returned by Ajax service
calls using a standardized
ServiceResult PHPobject that is returned in a JSON-encoded format. - Provides event-based responses to Ajax calls by designing an
EventDispatcherJavaScript object that lets all subclasses register and respond to events on a common dispatcher. - Centralizes Ajax result handling by using jQuery's
onAjaxCompletehandler to act as a centralized handler that broadcasts events specified in theServiceResultobject returned by the service call.
An example Ajax call showed how you can create two JavaScript objects, which have no knowledge of each other, to respond to the same service call.
Learn
-
Classes and Objects: Read more
about object-oriented programming in the PHP manual.
-
"Get
started with object-oriented JavaScript code" (developerWorks,
April 2011): Learn how to create objects using JavaScript.
-
jQuery Documentation: Learn more about using the jQuery library.
-
jQuery ajaxComplete
function: Get more details about how this function works.
-
JavaScript Object Notation: Learn
more about syntax and usage.
-
json_encode:
Read how PHP encodes objects into JSON format.
-
DOM events:
Read this Wikipedia entry to learn more about DOM Level 2 events.
- developerWorks technical events and webcasts: Stay current with technology in these sessions.
- developerWorks on Twitter: Join today to follow developerWorks tweets.
- developerWorks podcasts: Listen to interesting interviews and discussions for software developers.
- developerWorks on-demand demos: Watch demos ranging from product installation and setup for beginners to advanced functionality for experienced developers.
Get products and technologies
-
jQuery JavaScript library: Download version 1.4.4 or greater.
- IBM product evaluation versions: Download or explore the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
- developerWorks profile: Create your profile today and set up a watchlist.
- XML zone discussion forums: Participate in any of several XML-related discussions.
- The developerWorks community: Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Jeremy Wischusen has over 13 years of experience designing websites and applications for clients such as Purple Communications, myYearbook.com, HBO, and others, building both front- and back-end systems with Flash, Flex, jQuery, PHP, MySQL, MSSQL, and PostgreSQL. He has taught web design, Flash, and ActionScript for clients such as Wyeth Pharmaceuticals, The Vanguard Group, Bucks County Community College, and The University of the Arts.




