Adobe Consulting Public Sector: LiveCycle Archives

July 27, 2009

Updating Hostname of WSDL Connections in XDP with LiveCycle Form Rendering Process

A lot of times form developers develop form designs locally on their machines or on development servers. When dealing with web services, they would use the local or development instances of the web services by creating data connections pointing to WSDL URLs on localhost or dev.server.com. This is perfectly fine until the forms need to be migrated to other systems such as system test, stating or productions. The form developers would need to revisit the forms and update the data connections to point to the appropriate URLs depending on which environment the forms are being migrated to.

This becomes a big time consuming issue on projects dealing with a vast amount of form designs. So recently what I have been recommending my customers is to use a form rendering process that updates the WSDL data connections in the form design automatically on the fly as the form is requested by a user.


How does this work? (Please also refer to the process map below.)

  • The process reads the requested form design resource from the LiveCycle repository.
  • It then copies the content of the form design as XML to process variable and counts the number of WSDL data connections in the form.
  • Then it loops through every WSDL connections and update the domain name including the port with a new domain name extracted from a process variable called targetURL. (This targetURL variable is passed in automatically when you set up your the Render Service setting in your form variable.)
  • It then updates the form design variable with the new XML that has new WSDL information.
  • Finally LiveCycle renders the form design into a PDF. 
wsdl-updating-form-rendering-process.png

Requirements

  • LiveCycle Forms ES server.
  • The form design has to be in an XDP format.
  • The XDP has to be in the LiveCycle repository (e.g. database, Documentum, FileNet, etc.).
  • All web services used in the XDP have to be on the same server as the LiveCycle instance.
  • The only difference between web services on various environments must be the domain name and port. (e.g. http://dev:8080/soap/services/Weather?wsdl and http://staging/soap/services/Weather?wsld - OK but http://dev:8080/soap/services/Weather?wsdl and http:/staging/soap/services/Weather2?wsdl - Not OK)
Download

Download wsdl-updating-form-rendering-process.xml and import the XML into LiveCycle Workbench as a process.

Further thoughts
  • If you need to reader extend your forms, add a Reader Extensions action at the end of the process map.
  • If your form design is in PDF, you could modify the process map to read the PDF resource from the repository and convert it to XDP.
  • What if you only deploy PDFs to your production system and don't want to render them only the fly? You could render XDPs to PDFs as a batch process by creating a watched folder endpoint that invokes this form rendering proess.

Posted by Mick Lerlop (lerlop) at 11:41 AM | Comments (0)

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.


Download sample form


screenshot

Posted by Mick Lerlop (lerlop) at 2:29 PM | Comments (0)

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 8:00 AM | Comments (1)

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 8:34 AM | Comments (0)

June 10, 2008

Zen and the Art of Offline Data Capture

Whenever, I meet with developers on how to use LiveCycle technologies (or PDF in general), I usually run into: the user fills out the data here ... and the PDF is generated here. Generating a PDF for printer friendly output is good, but that’s not all a PDF can do. A PDF can capture data and save you a lot of time and overhead.

So take a traditional web-based application where a user needs to fill-out a form. Look at the over-head that is behind the web form: User Management for the user to save/retrieve a form, Load Balancing during peak-usage, complexity if the user wants to forward the form to another user, and Availability if you want to add a second form to your web-application without down-time.

Enter the world of off-line submission, provide the end user a fillable PDF; this will allow an anonymous user to fill, save, and forward the PDF to another individual (via e-mail or USB drive). When the user is done, have the user e-mail the filled PDF back to your server. The e-mail might take a few hours before reaching your server, but who cares? The end-user isn't waiting on a spinning hourglass to stop. If you need another form, then create a new PDF and publish it. A new PDF doesn’t necessarily have to be created by the developer of your Web Application team. Another group can create the PDF as long as both of you agree to an XML Schema. See, a nice decoupled system, when was the last time you can leverage a User Interface from another team?

Not all clients implement this solution in exactly the same way as above, some have the need to submit a 200 Megabyte PDF. In this case e-mail would not work and would have to revert back to an HTTP submission, they will loose some benefits but also gain some as well. Hopefully, the reader gets some new ideas in solving some old problems.

So, if you want to prototype the solution, then the LiveCycle products you would need: LiveCycle Forms to pre-populate and extract data, LiveCycle Reader Extensions, this will allow (amongst other things) a PDF to be saved locally with Adobe Reader. And finally LiveCycle Designer that helps you create your PDF.

Posted by Venkata Adidam (vadidam) at 3:32 PM | Comments (1)

May 15, 2008

Using Image in Acrobat JavaScript Dialog

I have had customers asking about how to brand Acrobat JavaScript dialog boxes with images in Adobe LiveCycle Designer. I did some research and found that it was not as simple as defining an <img href=”image.png”/> tag (I wish it was that easy though). As an overview, images used in a Acrobat JavaScript dialog has to be in a icon stream format represented by a hex-encoded string. The data string also needs to be 32 bits per pixel with 4 channels (ARGB) or 8 bits per channel with the channels interleaved. The hex-encoded string looks something like this, "fffffffffff...efdf8fff0e3beffd3b". Beautiful. Anyway, moving on.

There are a number of free and commercial third party tools you could use to convert images to hex-encoded strings. However, none of the tools fits my workflow in terms of flexibility and extensibility. So I have created a Java utility library, called Acrobat Dialog Image Generator (ADIG), which allows you to generate a hex-encoded string or a skeleton Acrobat dialog box with an embedded image.

You can invoke ADIG via a command-line interface, ANT or an API call. Here are some sample invocations:

Command-line

java -jar adig.jar /Users/lerlop/Pictures/test.jpg /Users/lerlop/Desktop/

This will reads in test.jpg and generates a JavaScript dialog file called test.jpg.txt on my desktop. Here is a sample.

ANT

ant -buildfile adig.xml

This will also produce the same result as the command-line option but everything is defined in the following build file.

    
<?xml version="1.0" encoding="UTF-8"?>
<project name="MyProject" default="GeneratorAcrobatDialogImage" basedir=".">
	<taskdef
		name="adig"
		classname="com.adobe.consulting.ant.tasks.GenerateAcrobatDialogImage"
		classpath="../../build/adig.jar"/>

	<target name="GeneratorAcrobatDialogImage">
		<adig imagePath="/Users/lerlop/Pictures/test.jpg" outputPath="/Users/lerlop/Desktop"/>
	</target>
</project>
    

API Call

String hexString = AcrobatDialogImageGenerator. generateHexEncodedString(imagePath);

By calling this static method in your Java code, you can generate a hex-encoded string from an image and assign it to a String object. This particular option is very useful when you try to perform any kind of batch operations. For instance, I can call this method to dynamically insert an image to a JavaScript dialog defined in an XDP before calling LiveCycle Forms to render it as a PDF.

To use the generated dialog JavaScript code in your form design, you can follow these simple steps:

  1. Run ADIG via command-line or ANT to get a generated file. The generate code should like this.
  2. Open your form in LiveCycle Designer (note: XFA form not AcroForm).
  3. Create and name a new script object. It does not matter where the script object is located as long as you can reference it later. For simplicity, create it on page 1 and let’s call it DialogSO.
  4. Copy all the code from the generated file and paste it into the script object.
  5. Now create a button object so you can use it to launch the dialog box. Note that you could launch the dialog box in any event such as form::docReady or form::initialize.
  6. The last thing is to make the button launch the dialog box. In the click event of the button, type in the following function call: DialogSO.launchDialog();

There you have it. You can extend the dialog box in any way you like by adding dialog elements to the dialog body. Please refer to the JavaScript for Acrobat API Reference.

I want to note that there are commercial tools out there that will let you design and extend Acrobat dialog boxes far more than just adding an image and generating dialog JavaScript template. If you are looking for a WYSIWYG tool to design dialog boxes, WindJack’s AcroDialogs may be more suitable for your needs.

Downloads:

Posted by Mick Lerlop (lerlop) at 4:33 AM | Comments (1)