Feb 28, 2012 ... event-based responses to Ajax calls, and centralize Ajax result handling. The
system uses PHP, jQuery, and JSON technologies, and example ...
Making Ajax service calls with PHP, jQuery, and JSON A centralized, standardized, event-based system Skill Level: Intermediate Jeremy J. Wischusen (
[email protected]) Web Application Architect Binary Neuron L.L.C.
28 Feb 2012 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.
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
Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 1 of 12
developerWorks®
ibm.com/developerWorks
•
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. Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 2 of 12
ibm.com/developerWorks
developerWorks®
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
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
Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 3 of 12
developerWorks®
ibm.com/developerWorks
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
Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 4 of 12
ibm.com/developerWorks
developerWorks®
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
Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 5 of 12
developerWorks®
ibm.com/developerWorks
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:
Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 6 of 12
ibm.com/developerWorks
developerWorks®
• 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
Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 7 of 12
developerWorks®
ibm.com/developerWorks
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
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. Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 8 of 12
ibm.com/developerWorks
developerWorks®
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 Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 9 of 12
developerWorks®
ibm.com/developerWorks
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.
Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 10 of 12
ibm.com/developerWorks
developerWorks®
Resources 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.
Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 11 of 12
developerWorks®
ibm.com/developerWorks
About the author Jeremy J. Wischusen 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.
Making Ajax service calls with PHP, jQuery, and JSON © Copyright IBM Corporation 2012
Trademarks Page 12 of 12