Posts Tagged JSONwithPadding
RESTful Mobile: Consuming Java EE RESTful Web Services Using jQuery Mobile
Posted by Gary A. Stafford in Java Development, Mobile HTML Development, Software Development on October 18, 2012
Use jQuery Mobile to build a mobile HTML website, capable of calling Jersey-specific Java EE RESTful web services and displaying JSONP in a mobile web browser.
Both NetBeans projects used in this post are available on DropBox. If you like DropBox, please use this link to sign up for a free 2 GB account. It will help me post more files to DropBox for future posts.
Background
In the previous two-part series, Returning JSONP from Java EE RESTful Web Services Using jQuery, Jersey, and GlassFish, we created a Jersey-specific RESTful web service from a database using EclipseLink (JPA 2.0 Reference Implementation), Jersey (JAX-RS Reference Implementation), JAXB, and Jackson Java JSON-processor. The service and associated entity class mapped to a copy of Microsoft SQL Server’s Adventure Works database. An HTML and jQuery-based client called the service, which returned a JSONP response payload. The JSON data it contained was formatted and displayed in a simple HTML table, in a web-browser.
Objectives
In this post, we will extend the previous example to the mobile platform. Using jQuery and jQuery Mobile JavaScript libraries, we will call two RESTful web services and display the resulting JSONP data using the common list/detail UX design pattern. We will display a list of Adventure Works employees. When the end-user clicks on an employee in the web-browser, a new page will display detailed demographic information about that employee.
Similar to the previous post, when the client website is accessed by the end-user in a mobile web browser, the client site’s HTML, CSS, and JavaScript files are downloaded and cached on the end-users machine. The JavaScript file, using jQuery and Ajax, makes a call to the RESTful web service, which returns JSON (or, JSONP in this case). This simulates a typical cross-domain situation where a client needs to consume RESTful web services from a remote source. This is not allowed by the same origin policy, but overcome by returning JSONP to the client, which wraps the JSON payload in a function call.
We will extend both the ‘JerseyRESTfulServices’ and ‘JerseyRESTfulClient’ projects we built in the last series of posts. Here are the high-level steps we will walk-through in this post:
- Create a second view (virtual table) in the Adventure Works database;
- Create a second entity class that maps to the new database view;
- Modify the existing entity class, adding JAXB and Jackson JSON annotations;
- Create a second Jersey-specific RESTful web service from the new entity using Jersey and Jackson;
- Modify the existing Jersey-specific RESTful web service, adding one new methods;
- Modify the web.xml file to allow us to use natural JSON notation;
- Implement a JAXBContext resolver to serialize the JSON using natural JSON notation;
- Create a simple list/detail two-page mobile HTML5 website using jQuery Mobile;
- Use jQuery, Ajax, and CSS to call, parse, and display the JSONP returned by the service.
RESTful Web Services Project
When we are done, the final RESTful web services projects will look like the screen-grab, below. It will contain (2) entity classes, (2) RESTful web service classes, (1) JAXBContext resolver class, and the web.xml configuration file:
1: Create the Second Database View
Create a new database view, vEmployeeNames
, in the Adventure Works database:
USE [AdventureWorks] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE VIEW [HumanResources].[vEmployeeNames] AS SELECT TOP (100) PERCENT BusinessEntityID, REPLACE(RTRIM(LastName + COALESCE (' ' + Suffix + '', N'') + COALESCE (', ' + FirstName + ' ', N'') + COALESCE (MiddleName + ' ', N'')), ' ', ' ') AS FullName FROM Person.Person WHERE (PersonType = 'EM') ORDER BY FullName GO
2: Create the Second Entity
Add the new VEmployeeNames.java
entity class, mapped to the vEmployeeNames
database view, using NetBeans’ ‘Entity Classes from Database…’ wizard. Then, modify the class to match the code below.
package entities; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @Entity @Table(name = "vEmployeeNames", catalog = "AdventureWorks", schema = "HumanResources") @XmlRootElement(name = "vEmployeeNames") @NamedQueries({ @NamedQuery(name = "VEmployeeNames.findAll", query = "SELECT v FROM VEmployeeNames v"), @NamedQuery(name = "VEmployeeNames.findByBusinessEntityID", query = "SELECT v FROM VEmployeeNames v WHERE v.businessEntityID = :businessEntityID"), @NamedQuery(name = "VEmployeeNames.findByFullName", query = "SELECT v FROM VEmployeeNames v WHERE v.fullName = :fullName")}) public class VEmployeeNames implements Serializable { private static final long serialVersionUID = 1L; @Basic(optional = false) @NotNull @Id @Column(name = "BusinessEntityID") private int businessEntityID; @Basic(optional = false) @NotNull @Size(min = 1, max = 102) @Column(name = "FullName") private String fullName; public VEmployeeNames() { } public int getBusinessEntityID() { return businessEntityID; } public void setBusinessEntityID(int businessEntityID) { this.businessEntityID = businessEntityID; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } }
3: Modify the Existing Entity
Modify the existing VEmployee.java
entity class to use JAXB and Jackson JSON Annotations as shown below (class code abridged). Note the addition of the @XmlType(propOrder = { "businessEntityID"... })
to the class, the @JsonProperty(value = ...)
tags to each member variable, and the @Id
tag to the businessEntityID
, which serves as the entity’s primary key. We will see the advantages of the first two annotations later in the post when we return the JSON to the client.
package entities; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import org.codehaus.jackson.annotate.JsonProperty; @Entity @Table(name = "vEmployee", catalog = "AdventureWorks", schema = "HumanResources") @XmlRootElement @NamedQueries({ @NamedQuery(name = "VEmployee.findAll", query = "SELECT v FROM VEmployee v"), ...}) @XmlType(propOrder = { "businessEntityID", "title", "firstName", "middleName", "lastName", "suffix", "jobTitle", "phoneNumberType", "phoneNumber", "emailAddress", "emailPromotion", "addressLine1", "addressLine2", "city", "stateProvinceName", "postalCode", "countryRegionName", "additionalContactInfo" }) public class VEmployee implements Serializable { private static final long serialVersionUID = 1L; @Basic(optional = false) @NotNull @Id @JsonProperty(value = "Employee ID") private int businessEntityID; @Size(max = 8) @JsonProperty(value = "Title") private String title; @Basic(optional = false) @NotNull @Size(min = 1, max = 50) @JsonProperty(value = "First Name") private String firstName; @Size(max = 50) @JsonProperty(value = "Middle Name") private String middleName; @Basic(optional = false) @NotNull @Size(min = 1, max = 50) @JsonProperty(value = "Last Name") private String lastName; @Size(max = 10) @JsonProperty(value = "Suffix") private String suffix; @Basic(optional = false) @NotNull @Size(min = 1, max = 50) @JsonProperty(value = "Job Title") private String jobTitle; @Size(max = 25) @JsonProperty(value = "Phone Number") private String phoneNumber; @Size(max = 50) @JsonProperty(value = "Phone Number Type") private String phoneNumberType; @Size(max = 50) @JsonProperty(value = "Email Address") private String emailAddress; @Basic(optional = false) @NotNull @JsonProperty(value = "Email Promotion") private int emailPromotion; @Basic(optional = false) @NotNull @Size(min = 1, max = 60) @JsonProperty(value = "Address Line 1") private String addressLine1; @Size(max = 60) @JsonProperty(value = "Address Line 2") private String addressLine2; @Basic(optional = false) @NotNull @Size(min = 1, max = 30) @JsonProperty(value = "City") private String city; @Basic(optional = false) @NotNull @Size(min = 1, max = 50) @JsonProperty(value = "State or Province Name") private String stateProvinceName; @Basic(optional = false) @NotNull @Size(min = 1, max = 15) @JsonProperty(value = "Postal Code") private String postalCode; @Basic(optional = false) @NotNull @Size(min = 1, max = 50) @JsonProperty(value = "Country or Region Name") private String countryRegionName; @Size(max = 2147483647) @JsonProperty(value = "Additional Contact Info") private String additionalContactInfo; public VEmployee() { } ... }
4: Create the New RESTful Web Service
Add the new VEmployeeNamesFacadeREST.java
RESTful web service class using NetBean’s ‘RESTful Web Services from Entity Classes…’ wizard. Then, modify the new class, adding the new findAllJSONP()
method shown below (class code abridged). This method call the same super.findAll()
method from the parent AbstractFacade.java
class as the default findAll({id})
method. However, the findAllJSONP()
method returns JSONP instead of XML or JSON, as findAll({id})
does. This is done by passing the results of super.findAll()
to a new instance of Jersey’s JSONWithPadding()
class (com.sun.jersey.api.json.JSONWithPadding
).
package service; import com.sun.jersey.api.json.JSONWithPadding; import entities.VEmployeeNames; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.GenericEntity; @Stateless @Path("entities.vemployeenames") public class VEmployeeNamesFacadeREST extends AbstractFacade<VEmployeeNames> { ... @GET @Path("jsonp") @Produces({"application/javascript"}) public JSONWithPadding findAllJSONP(@QueryParam("callback") String callback) { CriteriaBuilder cb = getEntityManager().getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(); Root empRoot = cq.from(VEmployeeNames.class); cq.select(empRoot); cq.orderBy(cb.asc(empRoot.get("fullName"))); javax.persistence.Query q = getEntityManager().createQuery(cq); List<VEmployeeNames> employees = q.getResultList(); return new JSONWithPadding( new GenericEntity<Collection<VEmployeeNames>>(employees) { }, callback); } ... }
5: Modify the Existing Service
Modify the existing VEmployeeFacadeREST.java
RESTful web service class, adding the findJSONP()
method shown below (class code abridged). This method calls the same super.find({id})
in the AbstractFacade.java
parent class as the default find({id})
method, but returns JSONP instead of XML or JSON. As with the previous service class above, this is done by passing the results to a new instance of Jersey’s JSONWithPadding()
class (com.sun.jersey.api.json.JSONWithPadding
). There are no changes required to the default AbstractFacade.java
class.
package service; import com.sun.jersey.api.json.JSONWithPadding; import entities.VEmployee; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.GenericEntity; @Stateless @Path("entities.vemployee") public class VEmployeeFacadeREST extends AbstractFacade<VEmployee> { ... @GET @Path("{id}/jsonp") @Produces({"application/javascript"}) public JSONWithPadding findJSONP(@PathParam("id") Integer id, @QueryParam("callback") String callback) { List<VEmployee> employees = new ArrayList<VEmployee>(); employees.add(super.find(id)); return new JSONWithPadding( new GenericEntity<Collection<VEmployee>>(employees) { }, callback); } ... }
6: Allow POJO JSON Support
Add the JSONConfiguration.FEATURE_POJO_MAPPING
servlet init parameter to web.xml, as shown below (xml abridged). According to the Jersey website, this will allow us to use POJO support, the easiest way to convert our Java Objects to JSON. It is based on the Jackson library.
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <servlet> <servlet-name>ServletAdaptor</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <init-param> <description>Multiple packages, separated by semicolon(;), can be specified in param-value</description> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>service</param-value> </init-param> <init-param> <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> <param-value>true</param-value> </init-param> ...
7: Implement a JAXBContext Resolver
Create the VEmployeeFacadeREST.java
JAXBContext resolver class, shown below. This allows us to serialize the JSON using natural JSON notation. A good explanation of the use of a JAXBContext resolver can be found on the Jersey website.
package config; import com.sun.jersey.api.json.JSONConfiguration; import com.sun.jersey.api.json.JSONJAXBContext; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import javax.xml.bind.JAXBContext; @Provider public class JAXBContextResolver implements ContextResolver<JAXBContext> { JAXBContext jaxbContext; private Class[] types = {entities.VEmployee.class, entities.VEmployeeNames.class}; public JAXBContextResolver() throws Exception { this.jaxbContext = new JSONJAXBContext(JSONConfiguration.natural().build(), types); } @Override public JAXBContext getContext(Class<?> objectType) { for (Class type : types) { if (type == objectType) { return jaxbContext; } } return null; } }
What is Natural JSON Notation?
According to the Jersey website, “with natural notation, Jersey will automatically figure out how individual items need to be processed, so that you do not need to do any kind of manual configuration. Java arrays and lists are mapped into JSON arrays, even for single-element cases. Java numbers and booleans are correctly mapped into JSON numbers and booleans, and you do not need to bother with XML attributes, as in JSON, they keep the original names.”
What does that mean? Better yet, what does that look like? Here is an example of an employee record, first as plain old JAXB JSON in a JSONP wrapper:
callback({"vEmployee":{"businessEntityID":"211","firstName":"Hazem","middleName":"E","lastName":"Abolrous","jobTitle":"Quality Assurance Manager","phoneNumberType":"Work","phoneNumber":"869-555-0125","emailAddress":"hazem0@adventure-works.com","emailPromotion":"0","addressLine1":"5050 Mt. Wilson Way","city":"Kenmore","stateProvinceName":"Washington","postalCode":"98028","countryRegionName":"United States"}})
And second, JSON wrapped in JSONP, using Jersey’s natural notation. Note the differences in the way the parent vEmployee node, numbers, and nulls are handled in natural JSON notation.
callback([{"Employee ID":211,"Title":null,"First Name":"Hazem","Middle Name":"E","Last Name":"Abolrous","Suffix":null,"Job Title":"Quality Assurance Manager","Phone Number Type":"Work","Phone Number":"869-555-0125","Email Address":"hazem0@adventure-works.com","Email Promotion":0,"Address Line 1":"5050 Mt. Wilson Way","Address Line 2":null,"City":"Kenmore","State or Province Name":"Washington","Postal Code":"98028","Country or Region Name":"United States","Additional Contact Info":null}])
Mobile Client Project
When we are done with the mobile client, the final RESTful web services mobile client NetBeans projects should look like the screen-grab, below. Note the inclusion of jQuery Mobile 1.2.0. You will need to download the library and associated components, and install them in the project. I chose to keep them in a separate folder since there were several files included with the library. This example requires a few new features introduced in jQuery Mobile 1.2.0. Make sure to get this version or later.
8: Create a List/Detail Mobile HTML Site
The process to display the data from the Adventure Works database in the mobile web browser is identical to the process used in the last series of posts. We are still using jQuery with Ajax, calling the same services, but with a few new methods. The biggest change is the use of jQuery Mobile to display the employee data. The jQuery Mobile library, especially with the release of 1.2.0, makes displaying data, quick and elegant. The library does all the hard work under the covers, with the features such as the listview control. We simply need to use jQuery and Ajax to retrieve the data and pass it to the control.
We will create three new files. They include the HTML, CSS, and JavaScript files. We add a ‘.m’ to the file names to differentiate them from the normal web browser files from the last post. As with the previous post, the HTML page and CSS file are minimal. The HTML page uses the jQuery Mobile multi-page template available on the jQuery Mobile website. Although it appears as two different web pages to the end-user, it is actually a single-page site.
Source code for employee.m.html:
<!DOCTYPE html> <html> <head> <title>Employee List</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" href="jquery.mobile-1.2.0/jquery.mobile-1.2.0.min.css" /> <link type="text/css" rel="stylesheet" href="employees.m.css" /> <script src="jquery-1.8.2.min.js" type="text/javascript"></script> <script src="jquery.mobile-1.2.0/jquery.mobile-1.2.0.min.js" type="text/javascript"></script> <script src="employees.m.js" type="text/javascript"></script> </head> <body> <!-- Start of first page: #one --> <div data-role="page" id="one" data-theme="b"> <div data-role="header" data-theme="b"> <h1>Employee List</h1> </div><!-- /header --> <div data-role="content"> <div id="errorMessage"></div> <div class="ui-grid-solo"> <form> <ul data-role="listview" data-filter="true" id="employeeList" data-theme="c" data-autodividers="true"> </ul> </form> </div> </div><!-- /content --> <div data-role="footer" data-theme="b"> <h4>Programmatic Ponderings, 2012</h4> </div><!-- /footer --> </div><!-- /page --> <!-- Start of second page: #two --> <div data-role="page" id="two" data-theme="c"> <div data-role="header" data-theme="b"> <a href="#one" data-icon="back">Return</a> <h1>Employee Detail</h1> </div><!-- /header --> <div data-role="content" data-theme="c"> <div id="employeeDetail"></div> </div><!-- /content --> <div data-role="footer" data-theme="b"> <h4>Programmatic Ponderings, 2012</h4> </div><!-- /footer --> </div><!-- /page two --> </body> </html>
Source code for employee.m.css:
#employeeList { clear:both; } #employeeDetail div { padding-top: 2px; white-space: nowrap; } .field { margin-bottom: 0px; font-size: smaller; color: #707070; } .value { font-weight: bolder; padding-bottom: 12px; border-bottom: 1px #d0d0d0 solid; } .ui-block-a{ padding-left: 6px; padding-right: 6px; } .ui-grid-a{ padding-bottom: 12px; padding-top: -6px; }
8: Retrieve, Parse, and Display the Data
The mobile JavaScript file below is identical in many ways to the JavaScript file used in the last series of posts for a non-mobile browser. One useful change we have made is the addition of two arguments to the function that calls jQuery.Ajax()
. The address of the service (URI) that the jQuery.Ajax()
method requests, and the function that Ajax calls after successful completion, are both passed into the callService(Uri, successFunction)
function as arguments. This allows us to reuse the Ajax method for different purposes. In this case, we call the function once to populate the Employee List with the full names of the employees. We call it again to populate the Employee Detail page with demographic information of a single employee chosen from the Employee List. Both calls are to different URIs representing the two different RESTful web services, which in turn are associated with the two different entities, which in turn are mapped to the two different database views.
callService = function (uri, successFunction) { $.ajax({ cache: true, url: uri, data: "{}", type: "GET", contentType: "application/javascript", dataType: "jsonp", error: ajaxCallFailed, failure: ajaxCallFailed, success: successFunction }); };
The rest of the functions are self-explanatory. There are two calls to the jQuery Ajax method to return data from the service, two functions to parse and format the JSONP for display in the browser, and one jQuery method that adds click events to the Employee List. We perform a bit of string manipulation to imbed the employee id into the id
property of each list item (li
element. Later, when the end-user clicks on the employee name in the list, the employee id is extracted from the id property of the selected list item and passed back to the service to retrieve the employee detail. The HTML snippet below shows how a single employee row in the jQuery listview. Note the id
property of the li
element, id="empId_121"
, for employee id 121.
<li id="empId_121" class="ui-btn ui-btn-icon-right ui-li-has-arrow ui-li ui-btn-up-c" data-corners="false" data-shadow="false" data-iconshadow="true" data-wrapperels="div" data-icon="arrow-r" data-iconpos="right" data-theme="c"> <div class="ui-btn-inner ui-li"> <div class="ui-btn-text"> <a class="ui-link-inherit" href="#">Ackerman, Pilar G</a> </div> <span class="ui-icon ui-icon-arrow-r ui-icon-shadow"> </span> </div> </li>
To make this example work, you need to change the restfulWebServiceBaseUri
variable to the server and port of the GlassFish domain running your RESTful web services. If you are testing the client locally on your mobile device, I suggest using the IP address for the GlassFish server versus a domain name, which your phone will be able to connect to in your local wireless environment. At least on the iPhone, there is no easy way to change the hosts file to provide local domain name resolution.
Source code for employee.m.js:
// =========================================================================== // // Author: Gary A. Stafford // Website: http://www.programmaticponderings.com // Description: Call RESTful Web Services from mobile HTML pages // using jQuery mobile, Jersey, Jackson, and EclipseLink // // =========================================================================== // Immediate function (function () { "use strict"; var restfulWebServiceBaseUri, employeeListFindAllUri, employeeByIdUri, callService, ajaxCallFailed, getEmployeeById, displayEmployeeList, displayEmployeeDetail; // Base URI of RESTful web service restfulWebServiceBaseUri = "http://your_server_name_or_ip:8080/JerseyRESTfulServices/webresources/"; // URI maps to service.VEmployeeNamesFacadeREST.findAllJSONP employeeListFindAllUri = restfulWebServiceBaseUri + "entities.vemployeenames/jsonp"; // URI maps to service.VEmployeeFacadeREST.findJSONP employeeByIdUri = restfulWebServiceBaseUri + "entities.vemployee/{id}/jsonp"; // Execute after the page one dom is fully loaded $(".one").ready(function () { // Retrieve employee list callService(employeeListFindAllUri, displayEmployeeList); // Attach onclick event to each row of employee list on page one $("#employeeList").on("click", "li", function(event){ getEmployeeById($(this).attr("id").split("empId_").pop()); }); }); // Call a service URI and return JSONP to a function callService = function (Uri, successFunction) { $.ajax({ cache: true, url: Uri, data: "{}", type: "GET", contentType: "application/javascript", dataType: "jsonp", error: ajaxCallFailed, failure: ajaxCallFailed, success: successFunction }); }; // Called if ajax call fails ajaxCallFailed = function (jqXHR, textStatus) { console.log("Error: " + textStatus); console.log(jqXHR); $("form").css("visibility", "hidden"); $("#errorMessage").empty(). append("Sorry, there was an error."). css("color", "red"); }; // Display employee list on page one displayEmployeeList = function (employee) { var employeeList = ""; $.each(employee, function(index, employee) { employeeList = employeeList.concat( "<li id=empId_" + employee.businessEntityID.toString() + ">" + "<a href='#'>" + employee.fullName.toString() + "</a></li>"); }); $('#employeeList').empty(); $('#employeeList').append(employeeList).listview("refresh", true); }; // Display employee detail on page two displayEmployeeDetail = function(employee) { $.mobile.loading( 'show', { text: '', textVisible: false, theme: 'a', html: "" }); window.location = "#two"; var employeeDetail = ""; $.each(employee, function(key, value) { $.each(value, function(key, value) { if(!value) { value = " "; } employeeDetail = employeeDetail.concat( "<div class='detail'>" + "<div class='field'>" + key + "</div>" + "<div class='value'>" + value + "</div>" + "</div>"); }); }); $("#employeeDetail").empty().append(employeeDetail); }; // Retrieve employee detail based on employee id getEmployeeById = function (employeeID) { callService(employeeByIdUri.replace("{id}", employeeID), displayEmployeeDetail); }; } ());
The Final Result
Viewed in Google’s Chrome for Mobile web browser on iOS 6, the previous project’s Employee List looks pretty bland and un-mobile like:
However, with a little jQuery Mobile magic you get a simple yet effective and highly functional mobile web presentation. Seen below on page one, the Employee List is displayed in Safari on an iPhone 4 with iOS 6. It features some of the new capabilities of jQuery Mobile 1.2.0’s improved listview, including autodividers.
Here again is the Employee List using the jQuery Mobile 1.2.0’s improved listview search filter bar:
Here is the Employee Detail on page 2. Note the order and names of the fields. Remember previously when we annotated the VEmployeeNames.java
entity with the @XmlType(propOrder = {"businessEntityID", ...})
to the class and the @JsonProperty(value = ...)
tags to each member variable. This is the results of those efforts; our JSON is delivered pre-sorted and titled the way we want. No need to handle those functions on the client-side. This allows the client to be loosely-coupled to the data. The client simply displays whichever key/value pairs are delivered in the JSONP response payload.
Returning JSONP from Java EE RESTful Web Services Using jQuery, Jersey, and GlassFish – Part 2 of 2
Posted by Gary A. Stafford in Java Development, Software Development on October 4, 2012
Create a Jersey-specific Java EE RESTful web service, and an HTML-based client to call the service and display JSONP. Test and deploy the service and the client to different remote instances of GlassFish.
Background
In part 1 of this series, we created a Jersey-specific RESTful web service from a database using NetBeans. The service returns JSONP in addition to JSON and XML. The service was deployed to a GlassFish domain, running on a Windows box. On this same box is the SQL Server instance, running the Adventure Works database, from which the service obtains data, via the entity class.
Objectives
In part two of this series, we will create a simple web client to consume and display the JSONP returned by the RESTful web service. There are many options available for creating a service consumer (client) depending on your development platform and project requirements. We will keep it simple, no complex, complied code, just HTML and JavaScript with jQuery, the well-known JavaScript library.
We will host the client on a separate GlassFish domain, running on an Ubuntu Linux VM using Oracle’s VM VirtualBox. This is a different machine than the service was installed on. When opened by the end-user in a web browser, the client files, including the JavaScript file that calls the service, are downloaded to the end-users machine. This will simulate a typical cross-domain situation where a client application needs to consume RESTful web services from a remote source. This is not allowed by the same origin policy, but overcome by returning JSONP to the client, which wraps the JSON payload in a function call.
Here are the high-level steps we will walk-through in part two:
- Create a simple HTML client using jQuery and ajax to call the RESTful web service;
- Add jQuery functionality to parse and display the JSONP returned by the service;
- Deploy the client to a separate remote instance of GlassFish using Apache Ant;
- Test the client’s ability to call the service across domains and display JSONP.
Creating the RESTful Web Service Client
New NetBeans Web Application Project
Create a new Java Web Application project in NetBeans. Name the project ‘JerseyRESTfulClient’. The choice of GlassFish server and domain where the project will be deployed is unimportant. We will use Apache Ant to deploy the client when we finish the building the project. By default, I chose my local instance of GlassFish, for testing purposes.
Adding Files to Project
The final client project will contains four new files:
- employees.html – HTML web page that displays a list of employees;
- employees.css – CSS information used to by employees.html;
- employees.js – JavaScript code used to by employees.html;
- jquery-1.8.2.min.js – jQuery 1.8.2 JavaScript library, minified.
First, we need to download and install jQuery. At the time of this post, jQuery 1.8.2 was the latest version. I installed the minified version (jquery-1.8.2.min.js
) to save space.
Next, we will create the three new files (employees.html
, employees.css
, and employees.js
), using the code below. When finished, we need to place all four files into the ‘Web Pages’ folder. The final project should look like:
HTML
The HTML file is the smallest of the three files. The HTML page references the CSS file, the JavaScript file, and the jQuery library file. The CSS file provides the presentation (look and feel) and JavaScript file, using jQuery, dynamically provides much of the content that the HTML page normally would contain.
<!DOCTYPE html> <html> <head> <title>Employee List</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link type="text/css" rel="stylesheet" href="employees.css" /> <script src="jquery-1.8.2.min.js" type="text/javascript"></script> <script src="employees.js" type="text/javascript"></script> </head> <body> <div id="pageTitle">Employee List</div> <div id="employeeList"></div> </body> </html>
Cascading Style Sheets (CSS)
The CSS file is also pretty straight-forward. The pageTitle
and employeeList
id selectors and type selectors are used directly by the HTML page. The class selectors are all applied to the page by jQuery, in the JavaScript file.
body { font-family: sans-serif; font-size: small; padding-left: 6px; } span { padding: 6px; display: inline-block; } div { border-bottom: lightgray solid 1px; } #pageTitle { font-size: medium; font-weight: bold; padding: 12px 0px 12px 0px; border: none; } #employeeList { float: left; border: gray solid 1px; } .empId { width: 50px; text-align: center; border-right: lightgray solid 1px; } .name { width: 200px; border-right: lightgray solid 1px; } .jobTitle { width: 250px; } .header { font-weight: bold; border-bottom: gray solid 1px; } .even{ background-color: rgba(0, 255, 128, 0.09); } .odd { background-color: rgba(0, 255, 128, 0.05); } .last { border-bottom: none; }
jQuery and JavaScript
The JavaScript file is where all the magic happens. There are two primary functions. First, getEmployees
, which calls the jQuery.ajax()
method. According jQuery’s website, the jQuery Ajax method performs an asynchronous HTTP (Ajax) request. In this case, it calls our RESTful web service and returns JSONP. The jQuery Ajax method uses an HTTP GET method to request the following service resource (URI):
http://[your-service's-glassfish-server-name]:[your-service's-glassfish-domain-port]/JerseyRESTfulService/webresources/entities.vemployee/{from}/{to}/jsonp?callback={callback}
.
The base (root) URI of the service in the URI above is as follows:
http://[server]:[port]/JerseyRESTfulService/webresources/entities.vemployee/
This is followed by a series of elements (nodes), {from}/{to}/jsonp
, which together form a reference to a specific method in our service. As explained in the first post of this series, we include the /jsonp
element to indicate we want to call the new findRangeJsonP
method to return JSONP, as opposed to findRange
method that returns JSON or XML. We pass the {from}
path parameter a value of ‘0’ and the {to}
path parameter a value of ‘10’.
Lastly, the method specifies the callback function name for the JSONP request, parseResponse
, using the jsonpCallback
setting. This value will be used instead of the random name automatically generated by jQuery. The callback function name is appended to the end of the URI as a query parameter. The final URL is as follows:
http://[server]:[port]/JerseyRESTfulService/webresources/entities.vemployee/0/10/jsonp?callback=parseResponse
.
Note the use of the jsonpCallback
setting is not required, or necessarily recommended by jQuery. Without it, jQuery generate a unique name as it will make it easier to manage the requests and provide callbacks and error handling. This example will work fine if you exclude the jsonpCallback: "parseResponse"
setting.
getEmployees = function () { $.ajax({ cache: true, url: restfulWebServiceURI, data: "{}", type: "GET", jsonpCallback: "parseResponse", contentType: "application/javascript", dataType: "jsonp", error: ajaxCallFailed, failure: ajaxCallFailed, success: parseResponse }); };
Once we have successfully returned the JSONP, the jQuery Ajax method calls the parseResponse(data)
function, passing the JSON to the data
argument. The parseResponse
function iterates through the employee objects using the jQuery.each()
method. Each field of data is surrounding with span
and div
tags, and concatenated to the employeeList
string variable. The string is appended to the div tag with the id of ‘employeeList’, using jQuery’s .append()
method. The result is an HTML table-like grid of employee names, ids, and job title, displayed on the employees.html
page.
Lastly, we call the colorRows()
function. This function uses jQuery’s .addClass(className)
to assign CSS classes to objects in the DOM. The classes are added to stylize the grid with alternating row colors and other formatting.
parseResponse = function (data) { var employee = data.vEmployee; var employeeList = ""; employeeList = employeeList.concat("<div class='header'>" + "<span class='empId'>Id</span>" + "<span class='name'>Employee Name</span>" + "<span class='jobTitle'>Job Title</span>" + "</div>"); $.each(employee, function(index, employee) { employeeList = employeeList.concat("<div class='employee'>" + "<span class='empId'>" + employee.businessEntityID + "</span><span class='name'>" + employee.firstName + " " + employee.lastName + "</span><span class='jobTitle'>" + employee.jobTitle + "</span></div>"); }); $("#employeeList").empty(); $("#employeeList").append(employeeList); colorRows(); };
Here are the complete JavaScript file contents:
// Immediate function (function () { "use strict"; var restfulWebServiceURI, getEmployees, ajaxCallFailed, colorRows, parseResponse; restfulWebServiceURI = "http://[your-service's-server-name]:[your-service's-port]/JerseyRESTfulService/webresources/entities.vemployee/0/10/jsonp"; // Execute after the DOM is fully loaded $(document).ready(function () { getEmployees(); }); // Retrieve Employee List as JSONP getEmployees = function () { $.ajax({ cache: true, url: restfulWebServiceURI, data: "{}", type: "GET", jsonpCallback: "parseResponse", contentType: "application/javascript", dataType: "jsonp", error: ajaxCallFailed, failure: ajaxCallFailed, success: parseResponse }); }; // Called if ajax call fails ajaxCallFailed = function (jqXHR, textStatus) { console.log("Error: " + textStatus); console.log(jqXHR); $("#employeeList").empty(); $("#employeeList").append("Error: " + textStatus); }; // Called if ajax call is successful parseResponse = function (data) { var employee = data.vEmployee; var employeeList = ""; employeeList = employeeList.concat("<div class='header'>" + "<span class='empId'>Id</span>" + "<span class='name'>Employee Name</span>" + "<span class='jobTitle'>Job Title</span>" + "</div>"); $.each(employee, function(index, employee) { employeeList = employeeList.concat("<div class='employee'>" + "<span class='empId'>" + employee.businessEntityID + "</span><span class='name'>" + employee.firstName + " " + employee.lastName + "</span><span class='jobTitle'>" + employee.jobTitle + "</span></div>"); }); $("#employeeList").empty(); $("#employeeList").append(employeeList); colorRows(); }; // Styles the Employee List colorRows = function(){ $("#employeeList .employee:odd").addClass("odd"); $("#employeeList .employee:even").addClass("even"); $("#employeeList .employee:last").addClass("last"); }; } ());
Deployment to GlassFish
To deploy the RESTful web service client to GlassFish, run the following Apache Ant target. The target first calls the clean
and dist
targets to build the .war file, Then, the target calls GlassFish’s asadmin
deploy
command. It specifies the remote GlassFish server, admin port, admin user, admin password (in the password file), secure or insecure connection, the name of the container, and the name of the .war file to be deployed. Note that the server is different for the client than it was for the service in part 1 of the series.
<target name="glassfish-deploy-remote" depends="clean, dist" description="Build distribution (WAR) and deploy to GlassFish"> <exec failonerror="true" executable="cmd" description="asadmin deploy"> <arg value="/c" /> <arg value="asadmin --host=[your-client's-glassfish-server-name] --port=[your-client's-glassfish-domain-admin-port] --user=admin --passwordfile=pwdfile --secure=false deploy --force=true --name=JerseyRESTfulClient --contextroot=/JerseyRESTfulClient dist\JerseyRESTfulClient.war" /> </exec> </target>
Although the client application does not require any Java code, JSP pages, or Servlets, I chose to use NetBeans’ Web Application project template to create the client and chose to create a .war file to make deployment to GlassFish easier. You could just install the four client files (jQuery, HTML, CSS, and JavaScript) on Apache, IIS, or any other web server as a simple HTML site.
Once the application is deployed to GlassFish, you should see the ‘JerseyRESTfulClient’ listed under the Applications tab within the remote server domain.
We will call the client application from our browser. The client application, whose files are downloaded and are now local on our machine, will in turn will call the service. The URL to call the client is: http://[your-client's-glassfish-server-name]:[your-client's-glassfish-domain-port]/JerseyRESTfulClient/employees.html
(see call-out 1, in the screen-grab, below).
Using Firefox with Firebug, we can observe a few important items once the results are displayed (see the screen-grab, below):
- The four client files (jQuery, HTML, CSS, and JavaScript) are cached after the first time the client URL loads, but the jQuery Ajax service call is never cached (call-out 2);
- All the client application files are loaded from one domain, while the service is called from another domain (call-out 3);
- The ‘parseRequest’ callback function in the JSONP response payload, wraps the JSON data (call-out 4).
The JSONP returned by the service to the client (abridged for length):
parseResponse({"vEmployee":[{"addressLine1":"4350 Minute Dr.","businessEntityID":"1","city":"Newport Hills","countryRegionName":"United States","emailAddress":"ken0@adventure-works.com","emailPromotion":"0","firstName":"Ken","jobTitle":"Chief Executive Officer","lastName":"Sánchez","middleName":"J","phoneNumber":"697-555-0142","phoneNumberType":"Cell","postalCode":"98006","stateProvinceName":"Washington"},{"addressLine1":"7559 Worth Ct.","businessEntityID":"2","city":"Renton","countryRegionName":"United States","emailAddress":"terri0@adventure-works.com","emailPromotion":"1","firstName":"Terri","jobTitle":"Vice President of Engineering","lastName":"Duffy","middleName":"Lee","phoneNumber":"819-555-0175","phoneNumberType":"Work","postalCode":"98055","stateProvinceName":"Washington"},{...}]})
The JSON passed to the parseResponse(data)
function’s data
argument (abridged for length):
{"vEmployee":[{"addressLine1":"4350 Minute Dr.","businessEntityID":"1","city":"Newport Hills","countryRegionName":"United States","emailAddress":"ken0@adventure-works.com","emailPromotion":"0","firstName":"Ken","jobTitle":"Chief Executive Officer","lastName":"Sánchez","middleName":"J","phoneNumber":"697-555-0142","phoneNumberType":"Cell","postalCode":"98006","stateProvinceName":"Washington"},{"addressLine1":"7559 Worth Ct.","businessEntityID":"2","city":"Renton","countryRegionName":"United States","emailAddress":"terri0@adventure-works.com","emailPromotion":"1","firstName":"Terri","jobTitle":"Vice President of Engineering","lastName":"Duffy","middleName":"Lee","phoneNumber":"819-555-0175","phoneNumberType":"Work","postalCode":"98055","stateProvinceName":"Washington"},{...}]}
Firebug also allows us to view the JSON in a more structured and object-oriented view:
Conclusion
We have successfully built and deployed a RESTful web service to one GlassFish domain, capable of returning JSONP. We have also built and deployed an HTML client to another GlassFish domain, capable of calling the service and displaying the JSONP. The service and client in this example have very minimal functionality. However, the service can easily be scaled to include multiple entities and RESTful services. The client’s capability can be expanded to perform a full array of CRUD operations on the database, through the RESTful web service(s).
Returning JSONP from Java EE RESTful Web Services Using jQuery, Jersey, and GlassFish – Part 1 of 2
Posted by Gary A. Stafford in Java Development, Software Development on October 1, 2012
Create a Jersey-specific Java EE RESTful web service and an HTML-based client to call the service and display JSONP. Test and deploy the service and the client to different remote instances of GlassFish.
Background
According to Wikipedia, JSONP (JSON with Padding) is a complement to the base JSON (JavaScript Object Notation) data format. It provides a method to request data from a server in a different domain, something prohibited by typical web browsers because of the same origin policy.
Jersey is the open source, production quality, JAX-RS (JSR 311) Reference Implementation for building RESTful Web services on the Java platform according to jersey.java.net. Jersey is a core component of GlassFish.
What do these two things have in common? One of the key features of Jersey is its ability to return JSONP. According to Oracle’s documentation, using Jersey, if an instance is returned by a resource method and the most acceptable media type is one of application/javascript, application/x-javascript, text/ecmascript, application/ecmascript or text/jscript then the object that is contained by the instance is serialized as JSON (if supported, using the application/json media type) and the result is wrapped around a JavaScript callback function, whose name by default is “callback”. Otherwise, the object is serialized directly according to the most acceptable media type. This means that an instance can be used to produce the media types application/json, application/xml in addition to application.
There is plenty of opinions on the Internet about the pros and cons of using JSONP over other alternatives to get around the same origin policy. Regardless of the cons, JSONP, with the help of Jersey, provides the ability to call a RESTful web service from a remote server, without a lot of additional coding or security considerations.
Objectives
Similar to GlassFish, Jersey is also tightly integrated into NetBeans. NetBeans provides the option to use Jersey-specific features when creating RESTful web services. According to documentation, NetBeans will generate a web.xml deployment descriptor and to register the RESTful services in that deployment descriptor instead of generating an application configuration class. In this post, we will create Jersey-specific RESTful web service from a database using NetBeans. The service will return JSONP in addition to JSON and XML.
In addition to creating the RESTful web service, in part 2 of this series, we will create a simple web client to display the JSONP returned by the service. There are many options available for creating clients, depending on your development platform and project requirements. We will keep it simple – no complex compiled code, just simple JavaScript using Ajax and jQuery, the well-known JavaScript library.
We will host the RESTful web service on one GlassFish domain, running on a Windows box, along with the SQL Server database. We will host the client on a second GlassFish domain, running on an Ubuntu Linux VM using Oracle’s VM VirtualBox. This is a different machine than the service was installed on. When opened by the end-user in a web browser, the client files, including the JavaScript file that calls the service, are downloaded to the end-users machine. This will simulate a typical cross-domain situation where a client application needs to consume RESTful web services from a remote source. This is not allowed by the same origin policy, but overcome by returning JSONP to the client, which wraps the JSON payload in a function call.
Demonstration
Here are the high-level steps we will walk-through in this two-part series of posts:
- In a new RESTful web service web application project,
- Create an entity class from the Adventure Works database using EclipseLink;
- Create a Jersey-specific RESTful web service using the entity class using Jersey and JAXB;
- Add a new method to service, which leverages Jersey and Jackson’s abilities to return JSONP;
- Deploy the RESTful web service to a remote instance of GlassFish, using Apache Ant;
- Test the RESTful web service using cURL.
- In a new RESTful web service client web application project,
- Create a simple HTML client using jQuery and Ajax to call the RESTful web service;
- Add jQuery functionality to parse and display the JSONP returned by the service;
- Deploy the client to a separate remote instance of GlassFish using Apache Ant;
- Test the client’s ability to call the service across domains and display JSONP.
To demonstrate the example in this post, I have the follow applications installed, configured, and running in my development environment:
- Microsoft SQL Server 2008 R2 (www.microsoft.com/sqlserver)
- Microsoft’s Adventure Works 2008 R2 Sample Database (msftdbprodsamples.codeplex.com)
- Microsoft JDBC Driver 4.0 for SQL Server (msdn.microsoft.com/en-us/sqlserver/aa937724.aspx)
- NetBeans 7.2 Open Source IDE (netbeans.org)
- Apache Any 1.82 (installed with NetBeans or downloaded separately from ant.apache.org)
- GlassFish 3.2.2.2 Open Source Edition Application Server (installed with NetBeans or downloaded separately from glassfish.java.net)
For the database we will use the Microsoft SQL Server 2008 R2 Adventure Works database I’ve used in the past few posts. For more on the Adventure Works database, see my post, ‘Convert VS 2010 Database Project to SSDT and Automate Publishing with Jenkins – Part 1/3’. Not using SQL Server? Once you’ve created your data source, most remaining steps in this post are independent of the database you choose, be it MySQL, Oracle, Microsoft SQL Server, Derby, etc.
For a full explanation of the use of Jersey and Jackson JSON Processor, for non-Maven developers, as this post demonstrates, see this link to the Jersey 1.8 User Guide. It discusses several relevant topics to this article: Java Architecture for XML Binding (JAXB), JSON serialization, and natural JSON notation (or, convention). See this link from the User Guide, for more on natural JSON notation. Note this example does not implement natural JSON notation functionality.
Creating the RESTful Web Service
New NetBeans Web Application Project
Create a new Java Web Application project in NetBeans. Name the project. I named mine ‘JerseyRESTfulService’. The choice of GlassFish server and domain where the project will be deployed is unimportant. We will use Apache Ant to deploy the service when we finish the building the project. By default, I chose my local instance of GlassFish, for testing purposes.
Create Entity Class from Database
Right-click on the project again and select ‘New’ -> ‘Other…’. From the list of Categories, select ‘Persistence’. From the list of Persistence choices, choose ‘Entity Classes from Database’. Click Next.
Before we can choose which database table we want from the Adventure Works database to create entity class, we must create a connection to the database – a SQL Server Data Source. Click on the Data Source drop down and select ‘New Data Source…’. Give a Java Naming and Directory Interface (JNDI) name for the data source. I called mine ‘AdventureWorks_HumanResources’. Click on the ‘Database Connection’ drop down menu, select ‘New Database Connection…’.
This starts the ‘New Connection Wizard’. The first screen, ‘Locate Driver’, is where we point NetBeans to the Microsoft JDBC Driver 4.0 for SQL Server Driver. Locate the sqljdbc4.jar file.
On the next screen, ‘Customize the Connection’, input the required SQL Server information. The host is the machine your instance of SQL Server is installed on, such as ‘localhost’. The instance is the name of the SQL Server instance in which the Adventure Works database is installed, such as ‘Development’. Once you complete the form, click ‘Test Connection’. If it doesn’t succeed, check your settings, again. Keep in mind, ‘localhost’ will only work if your SQL Server instance is local to your GlassFish server instance where the service will be deployed. If it is on a separate server, make sure to use that server’s IP address or domain name.
As I mentioned in an earlier post, the SQL Server Data Source forces you to select a single database schema. On the ‘Choose Database Schema’ screen, select the ‘HumanResources’ schema. The database tables you will be able to reference from you entity classes are limited to just this schema, when using this data source. To reference other schemas, you will need to create more data sources.
Back in the ‘New Entity Classes from Database’ window, you will now have the ‘AdventureWorks’ data source selected as the Data Source. After a few seconds of processing, all ‘Available Tables’ within the ‘HumanResources’ schema are displayed. Choose the ‘vEmployee(view)’. A database view is a virtual database table. Note the Entity ID message. We will need to do an extra step later on, to use the entity class built from the database view.
On the next screen, ‘Entity Classes’, in the ‘New Entity Classes from Database’ window, select or create the Package to place the individual entity classes into. I chose to call mine ‘entities’.
On the next screen, ‘Mapping Options’, choose ‘Fully Qualified Database Table Names’. Without this option selected, I have had problems trying to make the RESTful web services function properly. This is also the reason I chose to create the entity classes first, and then create the RESTful web services, separately. NetBeans has an option that combines these two tasks into a single step, by choosing ‘RESTful Web Services from Database’. However, the ‘Fully Qualified Database Table Names’ option is not available on the equivalent screen, using that process (at least in my version of NetBeans 7.2). I prefer the two-step approach.
Click finished. You have successfully created the SQL Server data source and entity classes.
If you recall, I mentioned a problem with the entity class we created from the database view. To avoid an error when you build and deploy your project to GlassFish, we need to make a small change to the VEmployee.java entity class. Entity classes need a unique identifier, a primary key (or, Entity ID) identified. Since this entity class was built from database view, as opposed to database table, it lacks a primary key. To fix, annotate the businessEntityID
field with @Id
. This indicates that businessEntityID
is the primary key (Entity ID) for this class. The field, businessEntityID
, must contain unique values, for this to work properly. NetBeans will make the suggested correction for you, if you allow it.
The JPA Persistence Unit is found in the ‘persistence.xml’ file in the ‘Configuration Files’ folder. This file describes the Persistence Unit (PU). The PU serves to register the project’s persistable entity class, which are referred to by JPA as ‘managed classes’.
The data source we created, which will be deployed to GlassFish, is referred to as a JDBC Resource and JDBC Connection Pool. This information is stored in the ‘glassfish-resources.xml’.
Create RESTful Web Service
Now that have a SQL Server Data Source and our entity class, we will create the RESTful web service. Right-click on the project and select ‘New’ -> ‘Other…’ -> ‘Persistence’ -> ‘RESTful Web Services from ‘Entity Classes’. You will see the entity class we just created, from which to choose. Add the entity class.
On the next screen, select or create the Resource Package to store the service class in; I called mine ‘service’. Select the ‘Use Jersey Specific Features’ option.

Select/Create the Service’s Package Location and Select the Option to ‘Use Jersey Specific Features’
That’s it. You now have a Jersey-specific RESTful web service and the corresponding Enterprise Bean and Façade service class in the project.
NetBeans provides an easy way to test the RESTful web services, locally. Right-click on the ‘RESTful Web Services’ project folder within the main project, and select ‘Test RESTful Web Services’. Select the first option, ‘Locally Generated Test Client’, in the ‘Configure REST Test Client’ pop-up window. NetBeans will use the locally configured GlassFish instance to deploy and test the service.
NetBeans opens a web browser window and display the RESTful URIs (Universal Resource Identifier) for the service in a tree structure. There is a parent URI, ‘entities.vemployee’. Selecting this URI will return all employees from the vEmployee database view. The ‘entities.vemployee’ URI has additional children URIs grouped under it, including ‘{id}’, ‘count’, and ‘{from/to}’, each mapped to separate methods in the service class.
Click on the ‘{id}’ URI. Choose the HTTP ‘GET()’ request method from the drop-down, enter ‘1’ for ‘id’, and click the ‘Test’ button. The service should return a status of ‘200 (OK)’, along with xml output containing information on all the Adventure Works employees. Change the MIME type to ‘application/json’. This should return the same result, formatted as JSON. Congratulation, the RESTful web services have just returned data to your browser from the SQL Server Adventure Works database, using the entity classes and data source you created.
Are they URIs or URLs? I found this excellent post that does a very good job explaining the difference between the URL (how to get there) and the URI (the resource), which is part of the URL.
Using Jersey for JSONP
GlassFish comes with the jersey-core.jar installed. In order to deliver JSONP, we also need to import and use com.sun.jersey.api.json.JSONWithPadding
package from jersey-json.jar. I downloaded and installed version 1.8. You can download the jar from several locations. I chose to download it from www.java2.com. You can also download from the download.java.net Maven2 repository.
The com.sun.jersey.api.json.JSONWithPadding package has dependencies two Jackson JSON Processor jars. You will also need to download the necessary Jackson JSON Processor jars. They are the jackson-core-asl-1.9.8.jar and jackson-mapper-asl-1.9.8.jar. At the time of this post, I downloaded the latest 1.9.8 versions from the grepcode.com Maven2 repository.
Create New JSONP Method
NetBeans creates several default methods in the VEmployeeFacadeREST
class. One of those is the findRange
method. The method accepts two integer parameters, from
and to
. The parameter values are extracted from the URL (JAX-RS @Path
annotation). The parameters are called path parameters (@PathParam
). The method returns a List of VEmployee objects (List<VEmployee>
). The findRange
method can return two MIME types, XML and JSON (@Produces
). The List<VEmployee>
is serialized in either format and returned to the caller.
@GET @Path("{from}/{to}") @Produces({"application/xml", "application/json"}) public List<VEmployee> findRange(@PathParam("from") Integer from, @PathParam("to") Integer to) { return super.findRange(new int[]{from, to}); }
Neither XML nor JSON will do, we want to return JSONP. Well, using the JSONWithPadding
class we can do just that. We will copy and re-write the findRange
method to return JSONP. The new findRangeJsonP
method looks similar to the findRange
. However instead of returning a List<VEmployee>
, the new method returns an instance of the JSONWithPadding
class. Since List<E>
extends Collection<E>
, we make the same call as the first method, then cast the List<VEmployee>
to Collection<VEmployee>
. We then wrap the Collection in a GenericEntity<T>
, which extends Object. The GenericEntity<T>
represents a response entity of a generic type T. This is used to instantiate a new instance of the JSONWithPadding
class, using the JSONWithPadding(Object jsonSource, String callbackName)
constructor. The JSONWithPadding
instance, which contains serialized JSON wrapped with the callback function, is returned to the client.
@GET @Path("{from}/{to}/jsonp") @Produces({"application/javascript"}) public JSONWithPadding findRangeJsonP(@PathParam("from") Integer from, @PathParam("to") Integer to, @QueryParam("callback") String callback) { Collection<VEmployee> employees = super.findRange(new int[]{from, to}); return new JSONWithPadding(new GenericEntity<Collection<VEmployee>>(employees) { }, callback); }
We have added a two new parts to the ‘from/to’ URL. First, we added ‘/jsonp’ to the end to signify the new findRangeJsonP
method is to be called, instead of the original findRange
method. Secondly, we added a new ‘callback’ query parameter (@QueryParam
). The ‘callback’ parameter will pass in the name of the callback function, which will then be returned with the JSONP payload. The new URL format is as follows:
http://[your-service's-glassfish-server-name]:[your-service's-glassfish-domain-port]/JerseyRESTfulService/webresources/entities.vemployee/{from}/{to}/jsonp?callback={callback}
Deployment to GlassFish
To deploy the RESTful web service to GlassFish, run the following Apache Ant target. The target first calls the clean
and dist
targets to build the .war file, Then, the target calls GlassFish’s asadmin
deploy
command. It specifies the remote GlassFish server, admin port, admin user, admin password (in the password file), secure or insecure connection, the name of the container, and the name of the .war file to be deployed. Note that the server is different for the service than it will be for the client in part 2 of the series.
<target name="glassfish-deploy-remote" depends="clean, dist" description="Build distribution (WAR) and deploy to GlassFish"> <exec failonerror="true" executable="cmd" description="asadmin deploy"> <arg value="/c" /> <arg value="asadmin --host=[your-service's-glassfish-server-name] --port=[your-service's-glassfish-domain-admin-port] --user=admin --passwordfile=pwdfile --secure=false deploy --force=true --name=JerseyRESTfulService --contextroot=/JerseyRESTfulServicedist\JerseyRESTfulService.war" /> </exec> </target>
In GlassFish, you should see the several new elements: 1) JerseyRESTfulService Application, 2) AdventureWorks_HumanResources JDBC Resource, 3) microsoft_sql_AdventureWorks_aw_devPool JDBC Connection Pool. These are the elements that were deployed by Ant. Also note, 4) the RESTful web service class, VEmployeeFacadeREST, is an EJB StatelessSessionBean.
Test the Service with cURL
What is the easiest way to test our RESTful web service without a client? Answer, cURL, the free open-source URL tool. According to the website, “curl is a command line tool for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP. curl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, cookies, user+password authentication (Basic, Digest, NTLM, Negotiate, kerberos…), file transfer resume, proxy tunneling and a busload of other useful tricks.”
To use cURL, download and unzip the cURL package to your system’s Programs directory. Add the cURL directory path to your system’s PATH environmental variable. Better yet, create a CURL_HOME
environmental variable and add that reference to the PATH
variable, as I did. Adding the the cURL directory path to PATH
allows you to call the cURL.exe application, directly from the command line.
With cURL installed, we can call the RESTful web service from the command line. To test the service’s new method, call it with the following cURL command:
curl -i -H "Accept: application/x-javascript" -X GET http://[your-service's-glassfish-server-name]:[your-service's-glassfish-domain-port]/JerseyRESTfulService/webresources/entities.vemployee/1/3/jsonp?callback=parseResponse
Using cURL is great for testing the RESTful web service. However, the command line results are hard to read. I recommend copy the cURL results into NotePad++ with the JSON Viewer Plugin. Like the NotePad++ XML plugin, the JSON plugin will format the JSONP and provide a tree view of the data structure.
Conclusion
Congratulations! You have created and deployed a RESTful web service with a method capable of returning JSONP. In part 2 of this series, we will create a client to call the RESTful web service and display the JSONP response payload. There are many options available for creating clients, depending on your development platform and project requirements. We will keep it simple – no complex, compiled code, just simple JavaScript using Ajax and jQuery, the well-known JavaScript library.
Consuming Cross-Domain WCF REST Services with jQuery using JSONP
Posted by Gary A. Stafford in .NET Development, Software Development, SQL Server Development on September 25, 2011
Introduction
In a previous article, Interactive Form Functionality on the Client-Side Using jQuery, I demonstrated the use of HTML, JavaScript, jQuery, and jQuery’s AJAX API to create a simple restaurant menu/order form. Although the previous article effectively demonstrated the use of these client-side technologies, the source of the restaurant’s menu items, a static XML file, was not intended to represent a true ‘production-class’ data source. Nowadays, to access data and business logic across the Enterprise or across the Internet, developers are more apt to build service-oriented applications that expose RESTful web services, and client applications that consume those services. RESTful services are services which conform to the REST (Representational State Transfer) architectural pattern. More information on REST can be obtained by reading Chapter 5 and 6 of REST’s author Roy Fielding’s Doctoral Dissertation. Most modern web technologies communicate with RESTful web services, including Microsoft’s Silverlight, Web Forms, and MVC, JavaFX, Adobe Flash, PHP, Python, and Ruby on Rails.
This article will expand on the restaurant menu/order form example from the previous article, replacing the static XML file with a WCF Service. The article will demonstrate the following:
- Use of jQuery’s AJAX API to bi-bidirectionally communicate with WCF Services
- Cross-domain communication with WCF Services using JSONP
- Serialization of complex, nested .NET objects into JSONP-format HTTP Response Messages
- Deserialization of JSONP-format HTTP Request Messages into complex, nested .NET objects
- Optimization of JavaScript and the use of caching to maximize the speed of content delivery to the Client
Source code is now available on GitHub. As of May 2014, there is a revised version of the project on the ‘rev2014′ branch, on GitHub. The below post describes the original code on the ‘Master’ branch. All details are posted on GitHub.
Background
WCF
For .NET developers, Windows Communication Foundation (WCF), Microsoft’s platform for Service Oriented Architecture (SOA), is the current preferred choice for building service-oriented applications. According to Microsoft, WCF is part of the .NET Framework that provides a unified programming model for rapidly building service-oriented applications that communicate across the web and the enterprise.
Prior to WCF, Microsoft offered ASP.NET XML Web Service, or ASP.NET Web Services for short. ASP.NET Web Services send and receive messages using Simple Object Access Protocol (SOAP) via HTTP. Data is serialized from instances of .NET objects into XML-format SOAP messages (or, ‘XML in a SOAP envelop’ as they are also known), and vice-versus. Metadata about the ASP.NET Web Services is contained in the Web Services Description Language (WSDL). Although still prevalent, ASP.NET Web Services is now considered a legacy technology with the advent of WCF, according to Microsoft. SOAP, a protocol for accessing a Web Service, does not conform to REST architecture guidelines.
Hosted on Microsoft’s IIS (Internet Information Services) Web Server, WCF is a complex, yet robust and flexible service-oriented framework. By properly configuring WCF Services, developers can precisely expose business logic and data sources to clients in a variety of ways. WCF Services can send and receive messages as XML in a SOAP envelop, as well as RESTful formats, including POX (plain old XML), ATOM (an XML language used for web feeds), and JSON (JavaScript Object Notation).
JSON/JSONP
The example in this article uses JSON, more specifically JSONP (JSON with Padding), a specialized type of JSON, to exchange information with WCF Services. JSON is an open and text-based data exchange format that provides a standardized data exchange format better suited for AJAX-style web applications. Compared to XML, JSON-formatted messages are smaller in size. For example, the restaurant menu used in this article, formatted as XML, is 927 bytes. The same message, formatted in JSONP is only 311 bytes, about one-third the size. The savings when transmitting JSON-format messages over slow connections, to mobile devices, or to potentially millions of simultaneous web-browsers, is significant.
Since the WCF Service will be hosted in a different domain (a different port in the example) than the web site with the restaurant menu and order form, we must use JSONP. JSONP, based on JSON, that allows pages to request data from a server in a different domain, normally disallowed, due to ‘same origin policy’. The same origin policy is an important security concept for browser-side programming languages, such as JavaScript. According to Wikipedia, same origin policy permits scripts running on pages originating from the same site to access each others methods and properties with no specific restrictions, but prevents access to most methods and properties across pages on different sites. JSONP takes advantage of the open policy for HTML <script>
elements.
Below is an example of the article’s restaurant menu formatted in JSONP, and returned by the WCF Service as part of the HTTP Response to the client’s HTTP Request’s GET
method.
RestaurantMenu([ {"Description":"Cheeseburger","Id":1,"Price":3.99}, {"Description":"Chicken Sandwich","Id":4,"Price":4.99}, {"Description":"Coffee","Id":7,"Price":0.99},{"Description":"French Fries", "Id":5,"Price":1.29},{"Description":"Hamburger","Id":2,"Price":2.99}, {"Description":"Hot Dog","Id":3,"Price":2.49}, {"Description":"Ice Cream Cone","Id":9,"Price":1.99}, {"Description":"Soft Drink","Id":6,"Price":1.19},{"Description":"Water", "Id":8,"Price":0}]);
AJAX (well, not really…)
AJAX (Asynchronous JavaScript and XML) asynchronously exchanges data between the browser and web server, avoiding page reloads, using object. Despite the name, XMLHttpRequest
, AJAX can work with JSON in addition to XML message formatting. Other formats include JSONP, JavaScript, HTML, and text. Using jQuery’s AJAX API, we will make HTTP Requests to the server using the GET
method. Other HTTP methods include POST
, PUT
, and DELETE
. To access cross-domain resources, in this case the WCF Service, the client makes a HTTP Request using the GET
method.
Writing this article, I discovered that using JSONP technically isn’t AJAX because it does not use the XMLHttpRequest
object, a primary requirement of AJAX. JSONP-format HTTP Requests are made by inserting the HTML <script>
tag into the DOM, dynamically. The Content-Type
of the HTTP Response from the WCF Service, as seen with Firebug, is application/x-javascript
, not application/json
, as with regular JSON. I’m just happy if it all works, AJAX or not.
Using the Code
The Visual Studio 2010 Solution used in this article contains (3) projects shown below. All code for this article is available for download at on The Code Project.
- Restaurant – C# Class Library
- RestaurantWcfService – C# WCF REST Service Application
- RestaurantDemoSite – Existing Web Site
Restaurant Class Library
The C# Class Library Project, Restaurant, contains the primary business objects and business logic. Classes that will be instantiated to hold the restaurant menu and restaurant orders include RestaurantMenu
, MenuItem
, RestaurantOrder
, and OrderItem
. Both RestaurantMenu
and RestaurantOrder
inherit from System.Collections.ObjectModel.Collection<T>
. RestaurantMenu
contains instances of MenuItem
, while RestaurantOrder
contains instances of OrderItem
.
The business logic for deserializing the JSON-format HTTP Request containing the restaurant order is handled by the ProcessOrder
class. I struggled with deserializing the JSONP-formatted HTTP Request into an instance of RestaurantOrder
with the standard .NET System.Web.Script.Serialization.JavaScriptSerializer
class. I solved the deserialization issue by using Json.NET
. This .NET Framework, described as a flexible JSON serializer to convert .NET objects to JSON and back again, was created by James Newton-King. It was a real lifesaver. Json.NET is available on Codeplex. Before passing the RAW JSONP-format HTTP Request to Json.NET, I still had to clean it up using the NormalizeJsonString
method I wrote.
Lastly, ProcessOrder
includes the method WriteOrderToFile
, which writes the restaurant order to a text file. This is intended to demonstrate how orders could be sent from the client to the server, stored, and then reloaded and deserialized later, as needed. In order to use this method successfully, you need to create the ‘c:\RestaurantOrders‘ folder path and add permissions for the IUSR
user account to read and write to the RestaurantOrders folder.
The ProcessOrder
class (note the reference to Json.NET: Newtonsoft.Json
):
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Restaurant { public class ProcessOrder { public const string STR_JsonFilePath = @"c:\RestaurantOrders\"; public string ProcessOrderJSON(string restaurantOrder) { if (restaurantOrder.Length < 1) { return "Error: Empty message string..."; } try { var orderId = Guid.NewGuid(); NormalizeJsonString(ref restaurantOrder); //Json.NET: http://james.newtonking.com/projects/json-net.aspx var order = JsonConvert.DeserializeObject <restaurantorder>(restaurantOrder); WriteOrderToFile(restaurantOrder, orderId); return String.Format( "ORDER DETAILS{3}Time: {0}{3}Order Id: {1}{3}Items: {2}", DateTime.Now.ToLocalTime(), Guid.NewGuid(), order.Count(), Environment.NewLine); } catch (Exception ex) { return "Error: " + ex.Message; } } private void NormalizeJsonString(ref string restaurantOrder) { restaurantOrder = Uri.UnescapeDataString(restaurantOrder); int start = restaurantOrder.IndexOf("["); int end = restaurantOrder.IndexOf("]") + 1; int length = end - start; restaurantOrder = restaurantOrder.Substring(start, length); } private void WriteOrderToFile(string restaurantOrder, Guid OrderId) { //Make sure to add permissions for IUSR to folder path var fileName = String.Format("{0}{1}.txt", STR_JsonFilePath, OrderId); using (TextWriter writer = new StreamWriter(fileName)) { writer.Write(restaurantOrder); } } } }
Restaurant WCF Service
If you’ve built WCF Services before, you’ll be familiar with the file structure of this project. The RestaurantService.svc, the WCF Service file, contains no actual code, only a pointer to the code-behind RestaurantService.cs file. This file contains each method which will be exposed to the client through the WCF Service. The IRestaurantService.cs Interface file, defines the Service Contract between the RestaurantService
class and the WCF Service. The IRestaurantService
Interface also defines each Operational Contract with the class’s methods. The Operational Contract includes Operational Contract Attributes, which define how the Service Operation (a method with an Operational Contract) will operate as part of the WCF Service. Operational Contract Attributes in this example include the required invocation (HTTP method – GET
), format of the HTTP Request and Response (JSON), and caching (for the restaurant menu). The WFC Service references (has a dependency on) the Restaurant Class Library.
The WCF Web Service Project, RestaurantWcfService
, contains two methods that are exposed to the client. The first, GetCurrentMenu
, serializes an instance of RestaurantMenu
, containing nested instances of MenuItem
. It returns the JSONP-format HTTP Response to the client. There are no parameters passed to the method by the HTTP Request.
The second method, SendOrder
, accepts the JSONP-format order, through an input parameter of the string
data type, from the client’s HTTP Request. SendOrder
then passes the order to the ProcessOrderJSON
method, part of the Restaurant.ProcessOrder
class. ProcessOrderJSON
returns a string
to SendOrder
, containing some order information (Order Id, date/time, and number of order items). This information is serialized and returned in the JSONP-format HTTP Response to the client. The Response verifies that the order was received and understood.
Lastly, the web.config file contains the WCF bindings, behaviors, endpoints, and caching configuration. I always find configuring this file properly to be a challenge due to the almost-infinite number of WCF configuration options. There are many references available on configuring WCF, but be careful, many were written prior to .NET Framework 4. Configuring WCF for REST and JSONP became much easier with .NET Framework 4. Make sure you refer to the latest materials from MSDN on WCF for .NET Framework 4.
The IRestaurantService.cs Interface:
using Restaurant; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.ServiceModel; using System.ServiceModel.Web; namespace RestaurantWcfService { [ServiceContract] public interface IRestaurantService { [OperationContract] [Description("Returns a copy of the restaurant menu.")] [WebGet(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] [AspNetCacheProfile("CacheFor10Seconds")] RestaurantMenu GetCurrentMenu(); [OperationContract] [Description("Accepts a menu order and return an order confirmation.")] [WebGet(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "SendOrder?restaurantOrder={restaurantOrder}")] string SendOrder(string restaurantOrder); } }
The RestaurantService.cs Class (inherits from IRestaurantService.cs):
using Restaurant; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.ServiceModel.Activation; namespace RestaurantWcfService { [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class RestaurantService : IRestaurantService { public RestaurantMenu GetCurrentMenu() { //Instantiates new RestaurantMenu object and //sorts MeuItem objects by byDescription using LINQ var menuToReturn = new RestaurantMenu(); var menuToReturnOrdered = ( from items in menuToReturn orderby items.Description select items).ToList(); menuToReturn = new RestaurantMenu(menuToReturnOrdered); return menuToReturn; } public string SendOrder(string restaurantOrder) { //Instantiates new ProcessOrder object and //passes JSON-format order string to ProcessOrderJSON method var orderProcessor = new ProcessOrder(); var orderResponse = orderProcessor.ProcessOrderJSON(restaurantOrder); return orderResponse; } } }
The WCF Service’s web.config File:
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="false" targetFramework="4.0" /> <caching> <outputCacheSettings> <outputCacheProfiles> <add name="CacheFor10Seconds" duration="10" varyByParam="none" /> </outputCacheProfiles> </outputCacheSettings> </caching> </system.web> <system.serviceModel> <bindings> <webHttpBinding> <binding name="webHttpBindingWithJsonP" crossDomainScriptAccessEnabled="true" /> </webHttpBinding> </bindings> <behaviors> <endpointBehaviors> <behavior name="webHttpBehavior"> <webHttp helpEnabled="true"/> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" /> <services> <service name="RestaurantWcfService.RestaurantService"> <endpoint address="" behaviorConfiguration="webHttpBehavior" binding="webHttpBinding" bindingConfiguration="webHttpBindingWithJsonP" contract="RestaurantWcfService.IRestaurantService" /> </service> </services> </system.serviceModel> <system.webServer> <modules runAllManagedModulesForAllRequests="true"/> </system.webServer> </configuration>
WCF Web HTTP Service Help
Once you have the article’s code installed and running, you can view more details about the WCF Service’s operations (methods) using the new .NET Framework 4 WCF Web HTTP Service Help Page feature. Depending on your IIS configuration, the local address should be similar to: http://localhost/MenuWcfRestService/RestaurantService.svc/Help.
Restaurant Demo Site
RestaurantDemoSite
is a non-ASP.NET website, just HTML and JavaScript. For this article, I chose to host the RestaurantDemoSite
web site on a different port (2929) than the WCF Service on default port 80. I did this to demonstrate the necessity of JSONP for cross-domain scripting. Hosting them on two different ports is considered hosting on two different domains. Port 2929 is a randomly-selected open port on my particular development machine. Both the WCF Service and the website were setup as Virtual Directories in IIS, and then added to the Visual Studio 2010 Solution, along with the Restaurant Class Library.
Following the format of the first article, the website contains two identical pages, each with the same restaurant menu/order form. The ‘Development’ version is optimized for debugging and demonstration. The other, ‘Production’, with the JavaScript and CSS files minified and packed, is optimized for use in production. The demo uses the latest available jQuery JavaScript Library (jquery-1.6.3.js) and the jQuery plug-in, Format Currency (jquery.formatCurrency-1.4.0.js).
The page contains the new HTML5 <!DOCTYPE>
declaration. I used HTML5’s new numeric input type for inputting the number of items to order. I defined a min and max value, also a new HTML5 feature. You can these HTML features working in the latest version of Google Chrome.
All of the client-side business logic is contained in the restaurant.js JavaScript file. This file makes calls to jQuery and Format Currency. I chose the sometimes controversial, static code analysis tool JSLint to help debug and refactor my JavaScript code. Even if you don’t agree with all of JSLint’s warnings, understanding the reason for them will really enhance your overall knowledge of JavaScript. A good alternative to JSLint, which I’ve also tried, is JSHint, a fork of the JSLint project. JSHint advertises itself as a more configurable version of JSLint.
The restaurant.js JavaScript file:
var addMenuItemToOrder, calculateSubtotal, clearForm, clickRemove, formatRowColor, formatRowCurrency, getRestaurantMenu, handleOrder, orderTotal, populateDropdown, tableToJson, sendOrder, wcfServiceUrl; // Populate drop-down box with JSON data (menu) populateDropdown = function () { var id, price, description; id = this.Id; price = this.Price; description = this.Description; $("#select_item") .append($("<option></option>") .val(id) .html(description) .attr("title", price)); }; // Use strict for all other functions // Based on post at: // http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ (function () { "use strict"; wcfServiceUrl = "http://localhost/MenuWcfRestService/RestaurantService.svc/"; // Execute when the DOM is fully loaded $(document).ready(function () { getRestaurantMenu(); }); // Add selected item to order $(function () { $("#add_btn").click(addMenuItemToOrder); }); // Place order if it contains items $(function () { $("#order_btn").click(handleOrder); }); // Retrieve JSON data (menu) and loop for each menu item getRestaurantMenu = function () { $.ajax({ cache: true, url: wcfServiceUrl + "GetCurrentMenu", data: "{}", type: "GET", jsonpCallback: "RestaurantMenu", contentType: "application/javascript", dataType: "jsonp", error: function () { alert("Menu failed!"); }, success: function (menu) { $.each(menu, populateDropdown); // must call function as var } }); }; // Add selected menu item to order table addMenuItemToOrder = function () { var order_item_selected_quantity, selected_item, order_item_selected_id, order_item_selected_description, order_item_selected_price, order_item_selected_subtotal; // Limit order quantity to between 1-99 order_item_selected_quantity = parseInt($("#select_quantity").val(), 10); if (order_item_selected_quantity < 1 || order_item_selected_quantity > 99 || isNaN(order_item_selected_quantity)) { return; } // Can't add 'Select an Item...' to order if ($("#select_item").get(0).selectedIndex === 0) { return; } // Get values selected_item = $("#select_item option:selected"); order_item_selected_id = parseInt(selected_item.val(), 10); order_item_selected_description = selected_item.text(); order_item_selected_price = parseFloat(selected_item.attr("title")); // Calculate subtotal order_item_selected_subtotal = calculateSubtotal(order_item_selected_price, order_item_selected_quantity); // Write out menu selection to table row $("<tr class='order_row'></tr>").html("<td>" + order_item_selected_quantity + "</td><td class='order_item_id'>" + order_item_selected_id + "</td><td class='order_item_name'>" + order_item_selected_description + "</td><td class='order_item_price'>" + order_item_selected_price + "</td><td class='order_item_subtotal'>" + order_item_selected_subtotal + "</td><td><input type='button' value='remove' /></td>") .appendTo("#order_cart").hide(); // Display grand total of order_item_selected_id $("#order_cart tr.order_row:last").fadeIn("medium", function () { // Callback once animation is complete orderTotal(); }); formatRowCurrency(); formatRowColor(); clickRemove(); clearForm(); }; // Calculate subtotal calculateSubtotal = function (price, quantity) { return price * quantity; }; // Create alternating colored rows in order table formatRowColor = function () { $("#order_cart tr.order_row:odd").css("background-color", "#FAF9F9"); $("#order_cart tr.order_row:even").css("background-color", "#FFF"); }; // Format new order item values to currency formatRowCurrency = function () { $("#order_cart td.order_item_price:last").formatCurrency(); $("#order_cart td.order_item_subtotal:last").formatCurrency(); }; // Bind a click event to the correct remove button clickRemove = function () { $("#order_cart tr.order_row:last input").click(function () { $(this).parent().parent().children().fadeOut("fast", function () { $(this).parent().slideUp("slow", function () { // the row (tr) $(this).remove(); // the row (tr) orderTotal(); }); }); }); }; // Clear order input form and re-focus cursor clearForm = function () { $("#select_quantity").val(""); $("#select_item option:first-child").attr("selected", "selected"); $("#select_quantity").focus(); }; // Calculate new order total orderTotal = function () { var order_total = 0; $("#order_cart td.order_item_subtotal").each(function () { var amount = ($(this).html()).replace("$", ""); order_total += parseFloat(amount); }); $("#order_total").text(order_total).formatCurrency(); }; // Call functions to prepare order and send to WCF Service handleOrder = function () { if ($("#order_cart tr.order_row:last").length === 0) { alert("No items selected..."); } else { var data = tableToJson(); sendOrder(data); } }; // Convert HTML table data into an array // Based on code from: // http://johndyer.name/post/table-tag-to-json-data.aspx tableToJson = function () { var data, headers, orderCartTable, myTableRow, rowData, i, j; headers = ["Quantity", "Id"]; data = []; orderCartTable = document.getElementById("order_cart"); // Go through cells for (i = 1; i < orderCartTable.rows.length - 1; i++) { myTableRow = orderCartTable.rows[i]; rowData = {}; for (j = 0; j < 2; j++) { rowData[headers[j]] = myTableRow.cells[j].innerHTML; } data.push(rowData); } return data; }; // Convert array to JSON and send to WCF Service sendOrder = function (data) { var jsonString = JSON.stringify({ restaurantOrder: data }); $.ajax({ url: wcfServiceUrl + "SendOrder?restaurantOrder=" + jsonString, type: "GET", contentType: "application/javascript", dataType: "jsonp", jsonpCallback: "OrderResponse", error: function () { alert("Order failed!"); }, success: function (confirmation) { alert(confirmation.toString()); } }); }; } ());
Using Firebug to Look Behind the Scenes
In real life, a restaurant’s menu changes pretty infrequently. Therefore, to speed page delivery, I chose to cache the restaurant’s menu on the client-side. Caching is configured as part of the Operational Contract in IRestaurantService
, as well as in the jQuery AJAX call to GetCurrentMenu
in restaurant.js. In this example, I set the cache to 10 seconds, which can be confirmed by looking at the Cache-Control
property in the HTTP Response Header of the call to GetCurrentMenu
, using Firebug.
Below is a screen grab of initial load of the restaurant menu/order form page in Firefox with Firebug running. Note the ‘Domain’ of the AJAX call is different than the page and associated files. Also, both the ‘Status’ and ‘Remote IP’ indicate the HTTP Response to GetCurrentMenu
(the restaurant’s menu) is cached, along with the page and associated files. Firebug is an invaluable tool in the development and debugging of JavaScript, especially when working with AJAX.
Points of Interest
Several things stood out to me as a result of writing this article:
- WCF – No matter how many times I work with WCF Services, getting them configured properly seems like 90% technical knowledge and 10% luck. Ok, maybe 20% luck! Seriously, there are a lot of great resources on the web regarding WCF configuration issues. If you have a specific problem with WCF, odds are someone else already had it and has published a solution. Make sure the information is current to the .NET Framework you are working with.
- Third-party Libraries, Plug-ins, and Frameworks – Don’t confine yourself to using the out-of-the-box .NET Framework, JavaScript, or jQuery to solve all your coding challenges. There are an endless variety of Frameworks, JavaScript Libraries, and jQuery Plug-ins, available. Being a good developer is about providing the best solution to a problem, not necessarily writing each and every line of code, yourself. A few minutes of research can be worth hours of coding!
- Refactoring – Refactoring your code is critical. Just making it work is not good enough. Added bonus? I’ve personally gained a considerable amount of knowledge about software development through refactoring. Forcing yourself to go back and optimize code can be a tremendous learning opportunity. Using third-party refactoring tools such JSLint/JSHint, FxCop, RefactorPro!, CodeRush, ReSharper, and others is a great way to improve both your refactoring and coding skills. I use all these tools as much as possible.
- Cross-Domain with JSONP – Using JSONP is one technique to get around the limitations imposed by the same origin policy. JSONP has its pros and cons. Spend some time to research other methods that might better benefit your project requirements.