Posts in Category "Transpromo"

Layout Methods to Find Page Positions

This post will be in the deep end for many of you.  But for those who design forms that extract position information from the xfa.layout object, you might well find some answers to longstanding problems — especially if you make use of multiple content areas on your master pages.

This blog entry deals specifically with the functions:
xfa.layout.x(), xfa.layout.y(), xfa.layout.h(), xfa.layout.w().

Return Value

The description for layout.x() says: "Determines the x coordinate of a given form design object".  This is true.  Incomplete, but true.  In fact, the x() and y() methods return a coordinate relative to the parent of the object.  The offset of the root subform is relative to the content area.  To find an absolute page coordinate, you could (in theory) traverse up the hierarchy and add the x coordinate of each ancestor plus the x coordinate of the content area.  But the APIs don’t make this easy.  First of all, it’s not obvious which content area contains your object.  And if your ancestor subform originated on a different page, it will have a different offset parameter.  Finding an absolute page position is complicated.  But not impossible.

Parameters

The layout position methods all take the same set of parameters.
e.g. xfa.layout.x(<object>, <units>, <offset>);

object is self evident.  You pass in a reference to some container (field, subform, draw etc).

units is also easy. "in" will cause the function to return a numeric value representing inches.

offset is … complicated.  The documentation for xfa.layout.x() says:

"An integer representing the number of pages to offset the x coordinate of the object, beginning with the first page the object occurs on. If left blank, the default value is 0."

This is partially true.  It is true only in the case where there is one content area per page.  The full truth is more complicated.  The documentation for xfa.layout.h() says:

"An integer representing the amount to offset the height value of a form design object, beginning with the first page the object occurs on. If left blank, the default value is 0."

Hmm.  Not true at all.  It’s not an offset of the height value.  Someone forgot to copy/paste the part of the description that says "number of pages".  We’ll work toward a more accurate description in the next section…

Multiple Content Areas

For forms with multiple content areas, here is the actual behaviour of the offset parameter:

The offset parameter indicates which relative content area the object appears in.  e.g. If a subform spans 2 pages and 5 content areas, you can call xfa.layout.h with offset values of 0, 1, 2, 3, 4 to return the the height of the object in each of the 5 content areas. 

This is great but, now the problem becomes: how do we find out which actual content area the object is in?  You can’t assume that the object appears in each content area on a page.  A subform with an explicit overflow target might skip a content area on any given page.

Fortunately, there is a heuristic that allows us to infer the content area in which an object appears. 
When you make a call to: xfa.layout.pageContent(0, "", false); you get a list of all objects on a page — including the content area objects.  If a field or subform appears in more than one content area on a page, that object will appear multiple times in the returned list.  The list appears in layout-order. Content area objects appear before the objects that have been placed in that content area.  To find out which content area an object appears in, look back up the list to find the most recent content area.

Finding Absolute Coordinates

Finding the x coordinate of an object involves adding the x coordinates of all the ancestors plus the x coordinate of the content area.  But, as mentioned above, the ancestor subforms may have originated in a different content area.  In order to add up the x coordinates, you have to find out the offset value of your parent object in this content area.  The script code to figure this all out is pretty complex.  I’ve put all the complexity under a script object that you can call:

// This function returns an array of extents for a given object
// There will be one entry in the array for each content area
// an object appears in.
// Each entry is an object with these properties:
//   Extent.page
//   Extent.contentArea  (SOM expression)
//   Extent.x            (absolute page position)
//   Extent.y
//   Extent.w
//   Extent.h  
// All measurements are inches.
function getExtents(vObject)

I have attached a sample form with multiple pages, multiple content areas, and which displays the results of querying layout positions.

Application

Once you’re able to find page positions, how can you use that information? Placing transpromo content is the best example.  I have also developed another sample where I used a different kind of field highlighting.  For each field error, the form places a "highlighter" subform on the master page (the highlighter is an arrow).  When you open the form, click on the "highlight errors" button.

I have another purpose in mind, but that’s the topic for a future blog post…

Form DOM Debugging Tool

One area where a novice form designer often needs help is in figuring out how to bind their form to data.  Ok, scratch that.  Advanced form designers often need help in this area as well.  There is lots of reading you can do in the XFA specification to learn how the template DOM gets merged with the data DOM to produce the form DOM.  Designer does a great job of generating binding SOM expressions for you.  But even still, when you are dealing with a complex schema, it can be hard to figure out where things went wrong.

A good way to debug this problem is to visualize the resulting DOMs.  Since we have full scripting access to the form DOM and to the data DOM, we can add a visual display of the DOMs to our form.  That’s the approach that today’s sample takes.  I took a work-in-progress purchase order form and added a "debugging subform" (domDump) at the end of the form in order to display the DOMs.  When you open the form and look at the last page you will see two side-by-side subforms.  The left side shows a tree view of the form DOM, the right side shows a tree view of the data DOM.  Some of the things you should note:

  • Entries in the tree are color-coded depending on their bind status
  • The display is cut off at one page.  To see more, use the scroll buttons at the top
  • Collapse and expand sections of the trees using the +/- buttons on the rows
  • Set focus in a field on either side of the tree and the display will highlight that entry and the corresponding entry(s) in the other DOM in red.
  • Set focus on a row in the form DOM, and some binding information is displayed in the top right of the display
  • Shift+click on a row in the form DOM will set focus to the corresponding field on the form.
  • By default we display the current state of the form at the docReady event.  If you want to refresh/rebuild the tree views, click the refresh button.

To try this out on your own forms, take the domDump subform and add it to your object library in Designer.  When you want to debug a form, drag the domDump subform onto your form.  (The subform needs to be displayed on a new master page with a content area that is at least 8×10.5 inches.) After debugging, when you are happy with your form, remove domDump.

There are some restrictions on the usage of this tool:

  • It works only on interactive forms
  • It works only on dynamic forms
  • Because of the extensive use of JavaScript objects, it does not work on forms with version 8.1 strict-scoping turned on

There is code in the script to check that these conditions have been met.

I won’t go into an in-depth description of the JavaScript that makes this debugger work.  It’s complicated :-) The intent is that form designers can use it without understanding the internals.  The one area where users might be tempted to tweak the script is to customize the information that gets displayed when a row in the form DOM is highlighted.  If that interests you, look for the script function: debugDisplay().

For interest sake, I’ve included one of my previous samples with the debugging subform added.  When I first added the dompDump subform I had to press the "refresh" button in order to see the completed DOMs.  That’s because the transpromo content and the debugging content are both populated from the docReady event — and the transpromo happens last.

Futures

  1. Admittedly, displaying tree views using dynamic subforms is non-trivial and a bit clunky.  One possible enhancement would be that rather than display the tree view on the form, we could export all the data for the tree views.  Then we could write a cool flash app to load the data give a proper rich user experience.  The only drawbacks with that approach is that a) it becomes a multi-step process and b) you lose the ability to "shift+click" to the corresponding form field.
  2. It would be great to have a similar debugging capability for WSDL connections.

March 19 Update

After using the form DOM debugger with several forms, I’m hooked.  I couldn’t resist making a couple improvements to it.  I’ve lifted most of the restrictions as to what flavour of forms it can be used in.  It now works with a broader range of template versions and strict scoping variations.  It also works for non-interactive documents.  For non-interactive, the tree display will spill over multiple pages and give a full dump — rather than windowing the content on a single page.  The only remaining restriction is that it works only with dynamic forms.

June Update

There is a follow-up debugger effort that supercedes the sample in this entry. Please have a look here.

Working with Data Fragments

Many of you will be familiar with the idea of constructing a form using template fragments. Template fragments are a powerful way to construct a form experience with modular parts. But there are some workflows where template fragments do not (yet) have all the functionality we might like. In cases where the content of the form is determined at runtime, template fragments might not have the flexibility you need. Take the transpromo examples from earlier posts(here and here). In those samples, the advertisement was baked into he template.  But in real life the actual ad that gets inserted will change. On starting a new marketing campaign, a company may want to issue a new set of ads to embed in their output. The actual ad chosen will vary depending on data in the form. Is the client single? Insert the sports car ad. Do they have a new baby? Insert the minivan ad. Some of our customers are building these kinds of applications using what we call “stitching solutions”. They have written Java libraries that assemble XFA templates on-demand. Writing a Java solution is fine for some, but eventually we have to make this easier.

One of the solutions available using today’s LiveCycle products leverages the notion of data fragments (instead of template fragments). I use the term “data fragments” to refer to the idea of embedding rich content in data. You might be surprised at how much you can customize the look of your document via XML instance data. You can add images, rich text, hyperlinks, positioned text and even floating fields.

A solution that uses data fragments to place ads in statements might look like this:

  1. An application for authoring data fragments representing the advertisements
  2. An application for adding metadata to the ads and storing them in a repository
  3. A rules engine for selecting ads from the repository based on correlating transaction data with ad metadata
  4. Print engine to render the statements with the ads

I can’t give you the whole solution, but can offer a sample that would help you get started with parts 1 and 4:

  • AdGenerator.pdf: This is a PDF for generating a data fragment and adding it to statement data. (Ideally we’d define something fancier for designing data fragments – maybe a slick flash app side-by-side with the PDF.)
  • statement.xdp: A sample credit card statement that includes a placeholder subform to render the ad data fragment.

Here is a copy of AdGenerator.pdf, populated and ready to export data. (Please give special notice to the image artwork that I worked so hard on.)

Here is a copy of the resulting statement generated with the ad.

How AdGenerator.pdf works

The advertisement is a growable subform that holds:

  • An image field
  • A repeating subform housing rich text

The form has various field and button controls to add the image and to add and position the rich text.  To understand how to use the form, read the instructions on the form itself. The scripts are also well documented and a good source for discovering the techniques used.

Dynamic properties

The (x,y) placement of rich text and the size of the ad subform are controlled by dynamic properties. Authoring these means going into XML Source view and adding the appropriate <setProperty> elements. E.g.:

<field name="Image" w="203.2mm" h="25.4mm">
   
<setProperty target="h" ref="height"/>
   
<setProperty target="w" ref="width"/>

Repeating, positioned text

Another case where we had to use XML source mode: Having added the TextData subform as a positioned child of advertisement, add an <occur/> element to allow it to repeat:

<occur min="0" max="-1"/>

The buttons and fields that position the text will update both the text coordinates as well as the data that the text coordinates are dynamically bound to.

Data References

You can personalize the text on the ad by injecting data values inline with the text.

On loading transaction data, we populate a listbox with all the data values found in the instance data. Adding the data reference uses the same mechanism as floating fields in Designer. We inject a special <span/> element into the xhtml with an embedded data reference. E.g.:

<span xfa:embed="$data.Statement[0].AccountNumber[0]" />

Styling Rich Text

In order to style your rich text in Acrobat, you need to bring up the properties editor (ctl + e). To add a hyperlink, select some text and choose “Hyperlink…” from the context menu. (By the way, there seems to be a bug here.  Rich text editing never works for me in Designer preview.  I use standalone Acrobat — with Designer shut down).

Transpromo: the sequel

Before the break I wrote an entry describing how to place transpromo content on your form.  The sample was fairly restricted in that the advertisement could be inserted only in specific spots in the subform hierarchy.  Today’s sample allows the advertisement to be placed anywhere on the form. 
Note that this sample is not an interactive PDF, but is a print form — as you’d expect for a transpromo appication.  Here is the XDP form, the sample data and the result of a print operation.  In order to generate some interesting white space to work fill up, I’ve added a conditional break on the form.  Every time the date field value changes, I force a new page.

The strategy behind this sample is that we use a dynamic subform to place the advertisement on the master page rather than in the form hierarchy.  This isn’t as easy as it sounds, so I’ll explain the steps to build the form.

In a nutshell, the strategy involves:

  1. find the place on the page where the ad will fit
  2. create an instance of the advertisement subform
  3. modify the (x,y) coordinate to position the ad correctly. 

However, there are some challenges to make this work.  The main problem is that when we re-paginate, we might throw away any existing master page instances (pageAreas) and re-create them.  Any subform instances and (x,y) positions would be lost in the process.  The solution involves persisting the ad placement information in the form data and binding the advertisement subforms to the data.  The specific mechanics need a fair bit of explaining…

Processing Steps

In the previous post I described these processing steps:

  1. Loading content
  2. Merging data with template (create Form DOM)
  3. Executing calculations and validations
  4. Layout (pagination)
  5. Render

This isn’t the complete story.  There are some intermediate steps as well.  Specifically, when doing the pagination, we create pageArea objects — along with their associated content.  We then attempt to merge this page content with form data.  The expanded processing steps are:

  1. Loading content
  2. Merging data with template (create Form DOM)
  3. Executing calculations and validations
  4. Layout (pagination)
    4a. Merge data with page content
    4b. Execute calculations and validations on page content
  5. Render

Settings stored in Data

The way to customize page content at runtime is to put all the page settings in the form data and have the page content subform bind to the data.  You need to find a place in the data that will not interfere with the rest of the form data.  This is especially important if your form is based on an XML schema.  Any page data intermixed with form data would violate the schema — or could inadvertently merge with other elements in the template.  The solution is to place the page data in a separate dataset.  The default location for form data is under <xfa:data>.  We’ll place the page data under <pageData>:

<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
   <xfa:data>
       … form data goes here …
   </xfa:data>
   <pageData>  <!– data for transpromo ads goes here –> 
      <advertisement>  <!– placement data for one ad –>  
         <x>0.25in</x> <!– x,y position –> 
         <y>2.89in</y>

                       <!– bind to presence property –>   
         <presence>visible</presence>  
         <Subform7in>  <!– this will select the right sized ad –> 
                       <!– populate info/debug field –> 
            <available>7.85in</available>  
         </Subform7in>  
      </advertisement>  
      <advertisement> … next page ad … </advertisement>
   </pageData>
</xfa:datasets>

Once we generate the data, we need to make sure that the subforms on the master pages bind to the data accordingly.  The advertisement subform uses the binding expression: "!pageData.advertisement[*]". (The "!" character is a shortcut in SOM that brings you to a child of xfa.datasets).  The rest of the bindings are "Normal" (based on a name match).

Now any time a repagination happens, the page content will re-bind to the data and all the settings will be restored.

Use Dynamic Properties

Setting properties such as the x and y coordinates via data is done using dynamic properties.  Designer supports setting properties such as caption, error messages, choice list contents from from data.  While Designer exposes the set of commonly used properties, in reality almost all properties can be set this way.  e.g. To populate the x and y properties from data, I used XML source view to add the necessary <setProperty> elements:

<subform name="advertisement" w="203.2mm" layout="tb">
   <occur min="0" max="-1"/>
   <setProperty target="x" ref="$.x"/>
   <setProperty target="y" ref="$.y"/> 
   <setProperty target="presence" ref="$.presence"/>

   …
</subform>

The presence property is set to "hidden" by default so that the advertisement subforms do not clutter up the design view.  In addition to setting the x and y properties via data, we also set the subform presence property to "visible".

Note: There was one other case where I needed to use XML source view to design this form.  In order to make "advertisement" subform optional, I added the <occur> element:

<subform name="advertisement">
   <occur min="0" max="1"/>

Building the data in script

Adding data can be done using createNode() as we’ve done in previous samples.  The code to add specify the x property would look something like:

var vPageData = xfa.datasets.createNode("dataGroup","pageData");
var vAds      = xfa.datasets.createNode("dataGroup","ads");
var vAdvert   = xfa.datasets.createNode("dataGroup","advertisement");
var vX        = xfa.datasets.createNode("dataValue","x");

xfa.datasets.nodes.append(vPageData);
vPageData.nodes.append(vAds);
vAds.nodes.append(vAdvert);
vAdvert.nodes.append(vX);
vX.value = vCA.x;

Or we can use the assignNode() method and do the whole thing in one command:

xfa.datasets.assignNode("pageData.ads.advertisement.x", vCA.x, 0);

The way assignNode() works is that as it traverses the SOM expression and creates any intermediate nodes that don’t exist. In this example it would create "advertisement" as a dataGroup (since it can tell that it’s a grouping node) and then creates "x" as a dataValue (since it can tell that it is a leaf node).  Once the nodes are created, the second parameter is the value to assign to the leaf node.  The last parameter dictates how to create nodes (0 == "create/replace").

The other benefit to using assignNode() is that if your method inadvertently gets called twice, it won’t create another instance of the data.  It will overwrite the data added previously.

Triggering re-layout

In this sample we’ve placed our code in the docReady event of the adControl field.  The docReady event fires after layout and render are complete.  Once we have generated form data for each page, we add an advertisement subform (merging it with the new data) and then call xfa.layout.relayout(). I encountered a bug along the way — the relayout() call ought to have remerged without an explicit call to add the advertisement subform. Since it didn’t, the workaround was to add the subform explicitly.

Limits

1. As mentioned already, this form is intended for print.  It is not intended for (nor will it work in) an interactive environment where subforms are added or removed.  In interactive forms we can count on more layout:ready events firing.  In fact, the first sample transpromo form I made the mistake for relying too much on the layout:ready event.  Consequently, that form works only in interactive mode and not for print.  But given the greater flexibility of this second sample, I recommend staying with this approach for transpromo form design.

2. This form currently works only on page areas that have a single content area. All object positions are computed relative to the content area, but it isn’t possible to determine which content area you are currently in.

Re-Use

In order to re-use the code from this sample, simply take the content from page1 (the adholder subform) and place it on each master page where you’d like to add content.  A cautionary note: if you copy/paste the adHolder subform or if you create a custom object from it, Designer will remove the binding information. You will need to re-specify it when you bring it into a new form.

Next Steps

The sample could be extended to search for other kinds of white space.  Currently we find only the leftover vertical white space at the end of each page.  There should be enough information in the layout tree for us to also discover white space inside positioned content.

Transpromo for your forms

If you are in the business of producing statements for your customers, you are likely interested in adding some targeted advertising. Just adding an advertisement to a form is not hard – the hard part is figuring out how to add the advertisement without causing more pages to be added to your printed output. i.e. how do we place ads in the available whitespace.

There are a couple of different design patterns where you can use JavaScript to discover (and use) available whitespace on dynamic XFA/PDF forms. I will cover the first pattern in today’s post.

To see what the end result looks like, open this sample form. On open we have determined that there is enough room for a very large ad, and we place a “flyer” that is 8 inches tall. Try adding more detail rows by pressing the “+” button. This causes the available white space to shrink and forces us to choose smaller advertisement subforms. After adding two rows, the ad shrinks to a 7 inch flyer. This continues until there is no room for any ads. But then after adding one or two more rows we spill over to a new page. Now once again there is room for a large ad on the second page.

Let’s take a closer look at how this form was designed.

Do nothing until layout:ready

Rendering a dynamic form goes through several processing stages:

  1. Loading content
  2. Merging data with template (create Form DOM)
  3. Executing calculations and validations
  4. Layout (pagination)
  5. Render

There a couple of script events that tell us when some of these processing stages are complete. The form:ready event fires when step 3 is complete. The layout:ready event fires after step 4.

Discovering whitespace is dependent on having a completed layout. Once all our content has been placed on pages we can examine the resulting placements. Consequently, we put the logic for placing our ad in a layout:ready event. But there is a problem: changing the form after layout:ready causes … another layout and another layout:ready event. Unless we are careful, we will cause an infinite loop of constant [re]layout. To guard against this, the form creates a form variable: vEvalTranspromo to indicate that we are ready for evaluation. Once we have placed our ad, we set it to false. Any time we change the structure of the form (add/remove a subform) we set it to true. (Note that normally we would do this on the server with a print form and would not worry about changes to the structure after the initial layout.)

Structuring the form

This example requires placing your ad content in your form at the spot where a page break will occur. At this location we place an ad container subform which is a wrapper that holds a series of [optional] child subforms of varying sizes. These child subforms hold the ad content.  Initially there is no content in the ad container, so it has a height of zero and does not impact layout. Our whitespace calculation is based on taking the Y position of the ad container, and subtracting it from the height of the content area.

When we have discovered how much space is available, we create the largest possible child ad subform to fit.

Next Steps

This form looks at only one aspect of the transpromo problem: discovery/usage of whitespace. Another key component is to select ads based on context. If this were a credit card statement, we would select the ads based on the transaction and customer data. This level of logic requires some sort of integration with a rules engine. This could happen as a pre-processing step or as a web-service request during layout.

Hopefully this will have whetted your appetite for transpromo. The sample is not overly complex (less than 100 lines of JavaScript). There is another Design pattern that involves more script, but also allows greater flexibility in ad placement. But that will have to wait until January.

Until then, enjoy your holidays. ‘Peace on earth, goodwill toward men’.