Making Ajax service calls with PHP, jQuery, and JSON

A centralized, standardized, event-based system

In this article, learn about a system for making and responding to Asynchronous JavaScript and XML (Ajax) service calls in a consistent, event-based manner. The system can determine if a remote process call succeeds or fails. Discover how to standardize the result format of objects returned by Ajax service calls, provide event-based responses to Ajax calls, and centralize Ajax result handling. The system uses PHP, jQuery, and JSON technologies, and example code walks you through the construction of the system. The article wraps up with an example Ajax call that shows how the pieces of the system interact.

Jeremy J. Wischusen, Web Application Architect, Binary Neuron L.L.C.

Jeremy Wischusen photoJeremy 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.



28 February 2012

Also available in Japanese Spanish

Introduction

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.

Frequently used acronyms

  • Ajax: Asynchronous JavaScript + XML
  • DOM: Document Object Model
  • HTML: Hypertext Markup Language
  • JSON: JavaScript Serialized Object Notation
  • PHP: Hypertext Preprocessor

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.

Underlying technologies

The system outlined in this article uses the following technologies:

  • Access to a PHP 5 server with a json_encode function 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).

Goals of the system

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 success property, 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 errors array, 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 the ServiceResult object 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 messages property, 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 data property that provides a method for passing arbitrary data back to the result handler. For example, if you're calling a login service, data may contain user profile data. While the data property 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, the data property allows all result handlers to know where to look for such data.
  • The onSuccessEvent and onErrorEvent properties, 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 are userLoggedIn for success or loginFailed for 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 addListener and removeListener functions let you add and remove listeners from the dispatcher object.
  • The eventName parameter is the event to be subscribed to.
  • The listener parameter is the function to be registered.
  • The dispatch function broadcasts the event specified in the eventName parameter.
  • The data parameter provides a method of passing along data for any subscribed listener functions.
  • The dispatcher property 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 event object (the event parameter).
  • The XMLHttpRequest (the request parameter).
  • An object representing the settings used to make the original Ajax call (the settings parameter).

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.


Example Ajax call

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 LoginController is a BaseController. Hence, it also inherits the EventDispatcher methods.
  • Use of the EventDispatcher addListener method in the onLoad function. The event being passed in matches the onSuccessEvent returned by the ServiceResult object that is returned by the login service. The handler function being registered is passed along the result object, which is the JSON-encoded ServiceResult object 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.


Summary

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 PHP object that is returned in a JSON-encoded format.
  • Provides event-based responses to Ajax calls by designing an EventDispatcher JavaScript object that lets all subclasses register and respond to events on a common dispatcher.
  • Centralizes Ajax result handling by using jQuery's onAjaxComplete handler to act as a centralized handler that broadcasts events specified in the ServiceResult object 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.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=795383
ArticleTitle=Making Ajax service calls with PHP, jQuery, and JSON
publish-date=02282012