February 12, 2009
Dynamically creating a table with data in LiveCycle form using JavaScript
Stefan Cameron wrote an article on scripting table columns a while back. Taking that idea, how would you dynamically create a table based on some data? Well, there are a couple of ways and this blog post will show you one way of achieving it.
To begin, first you need some data. In my example, I have two sets of strings in JSON format. Your data could be XML or comma delimited strings coming from a web service, ODBC connection, etc. It does not matter what format it is in as long as it is tabular data and your script can handle it property so that you can populate the table cells.
In addition to the data, I have a hidden table with one column and one text cell. The idea is that the script will use this hidden 1x1 table as a template to create multiple row and column instances. The text cell instances will be assigned with values from your data.
The following JavaScript function will take care of the table creation:
/**
* Create rows and columns based on the number of data items and number of attributes
*/
function createTable(jsonString, table)
{
// Evaluate JSON string into a array of JavaScript objects
// See the StringSO script object for the prototype function
var data = jsonString.evalJSON();
var attributes = new Array();
var attributeCounter = 0;
var row = table.somExpression + ".Row";
var pageWidth = 8; // inches;
var cellWidth;
var columnWidthsString = "";
// Get all attributes
for(var a in data[0])
{
attributes[attributeCounter++] = a;
}
// Calculate the cell width based on number of columns and page width
cellWidth = pageWidth/attributes.length;
// Create first column for all rows
for(i = 0; i < data.length; i++)
{
table._Row.addInstance(0);
}
// creating columns
for(i = 0; i < attributes.length; i++)
{
for(var n = 0; n <= data.length; n++)
{
var column = row + "[" + n + "].Column[" + i + "]";
var cell = column + ".Cell";
// Set the value in the cell
if(n == 0)
{
xfa.resolveNode(cell).rawValue = attributes[i];
}
else
{
xfa.resolveNode(cell).rawValue = data[n-1][attributes[i]] + "";
}
if(i < attributes.length - 1)
{
var t = row + "[" + n + "]";
xfa.resolveNode(t)._Column.addInstance(0);
}
// Set cell width
xfa.resolveNode(cell).w = cellWidth + "in";
}
// Building column widths string
columnWidthsString += cellWidth + "in ";
}
// Set column widths
table.columnWidths = columnWidthsString;
// Make the table visible
table.presence = "visible";
}
What the function does is that it figures out how many columns there will be, calculates the optimum cell and column width. With that information, it loop through the data array to create rows and columns, and finally adjust the column widths accordingly.
Hopefully this is useful for those who are looking for a way to display data returned from a web service without knowing how many columns there are going to be. Feel free to ask any questions and let’s keep the discussion going.

Posted by Mick Lerlop (lerlop) at 02:29 PM | Comments (0) | TrackBack
November 14, 2008
Generating JavaScript Documentation for Script Objects
One of the things many developers often fail to do is to document their code. Code and documentation comments are not only useful to yourself but also to other developers who may have to contribute to your work or use your work as part of their work. They help increase maintainability, code readability and ease of use. As previously posted, you can create script objects in Adobe LiveCycle Designer to extend standard JavaScript classes with prototype functions and properties. Your script objects can then be distributed among teams for reuse. However, if your script objects contain a large number of functions and properties, your end users may request for documentation.
I have created a Java library that generates Javadoc-styled JavaScript documentation from script objects available in an XDP form. As always, the library can be invoked three ways - command-line, ANT and API call. It is simply a wrapper around the jsdoc-toolkit application. Therefore my util requires jsdoc-toolkit (at least version 2), which can be downloaded from http://code.google.com/p/jsdoc-toolkit/.
The util requires the following four parameters in the exact order:
- Path the root of the jsdoc-toolkit folder (e.g. /Users/lerlop/resources/Java/jsdoc-toolkit/2.0.2)
- Path the jsdoc template folder you'd like to use (e.g. /Users/lerlop/resources/Java/jsdoc-toolkit/2.0.2/templates/jsdoc)
- Path to the XDP file
- Path to the output directory
Using ANT
<?xml version="1.0" encoding="UTF-8"?>
<project name="test-ant" basedir="."><taskdef
name="xdp-jsdoc-gen"
classname="ac.xfa.jsdoc.ant.Task"
classpath="dist/xdp-jsdoc-gen.jar"/><target name="GenerateJSDoc">
<xdp-jsdoc-gen
jsdocToolkitPath="/Users/lerlop/resources/Java/jsdoc-toolkit/2.0.2"
templatePath="/Users/lerlop/resources/Java/jsdoc-toolkit/2.0.2/templates/jsdoc"
xdpPath="test.xdp"
outputPath="/Users/lerlop/Desktop/output"
/>
</target>
</project>
Using command-line
java -jar xdp-jsdoc-gen.jar /Users/lerlop/resources/Java/jsdoc-toolkit/2.0.2 /Users/lerlop/resources/Java/jsdoc-toolkit/2.0.2/templates/jsdoc ../test.xdp /Users/lerlop/Desktop/output
Resulting output
----- Being processing -----
Finished writing /Users/lerlop/Desktop/output/SO.js
Finished writing /Users/lerlop/Desktop/output/ArraySO.js
Finished writing /Users/lerlop/Desktop/output/StringSO.js
Generating jsdoc at /Users/lerlop/Desktop/output/jsdocs
JSDocs created successfully in /Users/lerlop/Desktop/output/jsdocs
----- End -----
Using API
// Instantiate a generator object
JSDocGenerator generator = new JSDocGenerator(jsdocToolkitPath,templatePath, xdpPath, outputPath);// run
generator.run();
The result
The generated files should look similar to this.

And the documentation itself should look similar to this.

As a note, the input form design needs to be in an XDP format. So if your form is in PDF, you need to save it as XDP in LiveCycle Designer or programmatically call LiveCycle Forms to convert PDF to XDP. Moreover, if you have a lot of forms in a LiveCycle repository (e.g. database or Documentum), you can write a Java application that loops through the forms in the repository using the LiveCycle repository API.
Downloads
- Java library - xdp-jsdoc-gen.jar
- jsdoc-toolkit - http://code.google.com/p/jsdoc-toolkit/
- Sample XDP form (optional)
Posted by Mick Lerlop (lerlop) at 11:37 AM | Comments (0) | TrackBack
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
- JSON http://www.json.org/
- JSON Security concerns
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
- Sample
- For more information about the prototype property, click here.
- LiveCycle Designer ES Scripting Basics
- LiveCycle Designer ES Scripting Reference
- For more information about Form Fragments, click here.
Posted by Mick Lerlop (lerlop) at 08:34 AM | Comments (0) | TrackBack
