Adobe Consulting Public Sector: August 2008 Archives

« July 2008 | Main | November 2008 »

August 19, 2008

Using JSON to Exchange Data with Web Services in XFA Forms

Continuing from my previous post on extending the JavaScript prototype property, another most under-utilized technique is the use of JavaScript Object Notation (JSON) as a data exchange format between a form and web services. Did you ever think of using JSON in form development? No? Me neither. I never thought of it until one of my customers suggested the possibility. It was an elegant solution, as our web services were getting more complex, we were wrestling on reading the data versus implementing solutions. JSON gave us a way to reduce hassle of working with complex objects.

If you are reading, you probably know what JSON is. But if you don't, you can learn more about it at http://www.json.org/.

In this Service Oriented Architecture (SOA) era, I believe that most forms retrieve data from (or submit to) external sources via web services. Web services are great as they allow to a wide variety of clients to consume the services. Web service responses could range from a few pieces of data or a large set of data (e.g. array). Working with latter type of XML response is a lot harder if you need to conditionally hide/show certain item in the list or use the data in another form or fashion. In this situation, JSON is ideal.

So before you do anything to the form, you need to modify your web service to return a JSON formatted string. Instead of returning an ArrayList or Array of objects from the web service operation (Java), you have to convert your array into a JSON string. I use JSON-lib (net.sf.json.JSONArray) but you can any JSON library you prefer. Here is sample code:


public String getSimpleArray() 
{
	Person[] people = { 
                new Person("John", "Doe", "235-235-5466", "john@doe.com"),
                new Person("Tom", "Green", "444-235-2343", "tom@green.com")
  		};
	JSONArray jsonArray = JSONArray.fromObject(people);
	return jsonArray.toString();
}

The web service response should look similar to the following snippet:


<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns1:getSimpleArrayResponse xmlns:ns1="http://consulting.adobe.com/">
<return xmlns="http://consulting.adobe.com/">[{"email":"john@doe.com","firstname":"John","lastname":"Doe","phone":"235-235-5466"},{"email":"tom@green.com","firstname":"Tom","lastname":"Green","phone":"444-235-2343"}]</return>
</ns1:getSimpleArrayResponse>
</soap:Body>
</soap:Envelope>

As you can see, the soap body only contains one element, which is a JSON structured string. To utilize this in your form, simple create a data connection to this web service operation and bind the return element to a text field. Then convert the raw value of the text field to a JSON object by using the eval() function.


// wsResponse is a text field and is bound to the web service return element.
var people = eval('(' + wsResponse.rawValue + ')'); // or wsResponse.rawValue.evalJSON(); if you use the String prototype extension from my previous post.

This gives you an array of JavaScript objects called "people". You can now access this array just like any other native JavaScript arrays. Below are some examples:


// loop through the array
for(var i = 0; i < people.length; i++)
{
	var person = people[i];
	textField.rawValue += person.firstname + " " + person.lastname + "\n";
}

// get the email of the last person in the array
var email = people[people.length - 1].email;


Yeah, it's that simple. Now you can forget about how to access your data and focus on how and where to use the. As a side note, there are some security concerns with JSON regarding code insertions attacks etc. There are some open-source libraries that handle these issues.


I don't have any samples handy. However, you need one, leave a comment and I will post one. Thanks.


Resources


Posted by Mick Lerlop (lerlop) at 08:00 AM | Comments (1) | TrackBack

August 13, 2008

Extending JavaScript with Prototypes in XFA Forms

Do you sometimes wish you had functions that standard JavaScript does not provide? For instance, a trim or strip function that removes leading and trailing white spaces from a string object. In LiveCycle Designer, the traditional way is to create a script object and a function. Then to use it, you would have a function call similar to the following statement:

var newString = ScriptObject.trim(oldString);

These function calls can become lengthy in situations where the script object is located on a different page than your method call. For example:

var newString = form1.page1.subform1.ScriptObject.trim(oldString);

Having said that, the problem with this approach is that, to reuse this component, the script object name and location must stay the same for each form. If you move the location of the script object, since there is no easy way to refactor it, your references will certainly break. A more elegant solution is to use of the prototype property, you can extend the functionality of the classes in the same fashion you can do with HTML JavaScript in a web browsers.

The prototype property is probably one of the most under-utilized scripting features when it comes to LiveCycle form development. However, it is so powerful as it allows you to easily extend and reuse JavaScript intrinsic classes and your own. For instance, the trim function can be rewritten as follows:

String.prototype.trim = function() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); }

And to use it:


var oldString = "    Hello   ";
var newString = oldString.trim();
// newString -> "Hello";

You may also want to have another method called "strip" that performs the same task.

String.prototype.strip = String.prototype.trim;

As you can see, your function is simpler and easy to use. Using the prototype property for custom functions also prevents your code from breaking in case you change the name or location of the script object and provides a natural way to extend objects even if you did not author them.

Based on my past projects where the requirement was to create resuable Designer library components for multiple teams, I usually wrap my script objects in a subform, make it into a library component (xfo) or a form fragment and share it with other form developers.

Also to make the JavaScript interpreter in Acrobat/Reader aware of your prototype extensions, you must load all your prototype definitions when the form loads. You can achieve this by wrapping the prototype function and property definitions inside a function and call that function in the subform initialize event. For instance:


function initialize()
{
	String.prototype.trim...
	String.prototype.strip...
	...
}

In my sample form, once the extensions have been initialized, it will print out the below messages in the debugger console.

Initializing Array extensions...
Array extensions initialized.
Initializing String extensions...
String extensions initialized.

One last note - as you develop these prototype functions, don't forget to include documentation comments to increase readability and ease of use. Feel free to share your experiences or ask any question you may have.

Resources

Posted by Mick Lerlop (lerlop) at 08:34 AM | Comments (0) | TrackBack