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…

13 Responses to Layout Methods to Find Page Positions

  1. Keith Gross says:

    This getting kind of scary. Every time I finish adding a new feature to our forms you write a blog posting on related topics.We’ve had a error flagging system in place that required the designers to place flags next to each field which could be shown or hidden to flag errors as well as warnings and flags indicating extra help is available. To make things simpler we had created a set of object pallet controls that designers could drag onto forms with all the fags pre-positioned. The downside was a lot of unused flags that dragged down rendering and tabbing performance.I just finished changing all of this to eliminate the pre-positioned flags and instead creating them using a flag within a repeating subform. I wasn’t able to solve the general case for determining the position of a field but none of our forms have some of the complications you’ve dealt with so I was able to get something that worked. My setup requires a the flags repeating subform appear within each subform that represents a page. I’ll have to see if I can use your design of simply putting one on the master page.On a related note my setup requires holding on to state data which we currently do by turning off strict scoping and then adding Javascript objects as attributes to fields and subforms. I’ve been told I should move towards strict scoping so we’ve been moving somethings into nodes within extras. Rather then having multiple extras nodes it would be nice if we could store a Javascript object within a extras node. Is this possible?

  2. Keith:I’m pretty sure I’ve seen samples of your work. You’ve crafted a very impressive forms experience. I’m pretty sure you could teach me a thing or two about Javascript.Placing the markers on the master page certainly has some advantages. But it has challenges as well:Adding the markers requires re-computing the layout (xfa.layout.relayout()) which can be expensiveWhen you add/remove subforms, the layout changes and you need to re-position your markers. I didn’t try to solve that problem in my sample. I took the easy way out and had my add/remove buttons simply clear the markers.The design patten I found came after trying about 10 other patterns that didn’t work — and these patterns are very difficult to debug. (I made extensive use of the form dom debugger to figure it out.)If you’re able to incrementally update your markers without doing a relayout() (or if the performance of relayout() isn’t a problem) then this could be a viable pattern for you.As for the rest of your state data — you can’t store a Javascript object in your extras. You have a couple options:To group a series of properties under extras, you can create a nested extras elementStore a text element in your extras in object literal format and parse/eval it to create a JavaScript object when you access itStore a single id element under your extras and use that id to index your properties from Javascript objects back in a script objectThe advantage of the first two approaches is that your data remains encapsulated.Good luck!John

  3. I updated one of the samples: LayoutExtents.pdfSmall bug in the script.

  4. Steve Fletcher says:

    Thanks for the article. I’m using this to get positions of hidden marker fields in my forms.The problem I’m having is how to extract the position information from the form after I render it with LiveCycle ES Forms. I need to pass this back to a client application that’s calling my LiveCycle ES process. I’ve tried putting the position information in a hidden field and the document metadata but cannot extract it with any of the LiveCycle ES services. Any thoughts on this would be appreciated.Thanks,Steve Fletcher

  5. Steve:It will be a while before I’m back in the office to look at this. My first guess would be that you would start with an XDP and have a layout:ready event populate a field value with this information. Then render to an interactive PDF (might have to be a static PDF) and you’d end up with a PDF with the data you want inside.John

  6. Steve Fletcher says:

    John,No problem getting the data set up in the PDF. I can set both doc metadata or a hidden field with the position information.The problem is getting the metadata or hidden field out of the PDF. All the LC ES services I’ve used do not get the correct data. They seem to pull the metadata and hidden field in the state they were in before the JavaScript in the layout:ready event gets run.I’ve tried FormDataIntegration->exportData, FormsService->ProcessFormSubmission, PDFUtility->XMPUtilityService->exportXMP, and PDFUtility->XMPUtilityService->exportMetadata and none of them are pulling the correct data.I can open the rendered PDF in Acrobat and save the metadata or export the form data and the positional data is in the XML. So I know it’s there but I don’t know how to get it out with LC ES.Thanks,Steve Fletcher

  7. Steve:Sorry for the delay (yep, I had a good vacation:)A couple things to try:1. Set the form’s script to either run on server or both (server and client).2. Set the PDFFormRenderSpec.setRenderAtClient(RenderAtClient.No)This will force LiveCycle to completely render the form server side ratherthan allowing the form to finish rendering at the client.3. Make sure the fields you are populating have a binding definition i.e. not binding=”none” — may be a challenge if you’re using a schema.Once the form is rendered, there are a couple services you can use to extract data:- In LiveCycle Forms there is a processFormSubmission operation- In Common/Form Data integration there is an exportData operationThe exportData service is the easiest to use as you simply pass it the PDF and a document variable to hold the extracted data. Once you get the doc var populated you can cast it to an XML var by using a setValue operation and now you can load it into a DOM and xPath to your heart’s content.I got much of this information internally from Paul Guerette — if you’re still having issues, you can contact him for additional help.good luck!John

  8. Bruce says:

    Hi John,Should it be possible to find the position information for objects that have been added to the form using an instanceManager.I have tried but things like xfa.layout.page() always seem to return zero.ThanksBruce

  9. Bruce:Good question. Yes it’s possible. The problem is that as soon as you add a new subform via script, the XFA processor hasn’t yet updated the layout. Your script will cause the layout to be marked as dirty, and when the script completes, we’ll do an incremental re-layout. Once the re-layout is complete we’ll know for sure what page the subform lands on.The re-layout will trigger a layout:ready event, which is where you’d need to put your logic to calculate the subform position.John

  10. Tarek Faham says:

    I am very happy to find this getExtents() great function.

    But, I have tested the function getExtents() and I think there is a bug in calculate hte absolute y position of the element.

    The calculated y position is not correct.

    I appreciate it if you could find the bug and correct it.

    • John Brinkman says:

      Tarek:

      I suspect there is a problem calculating the Y position when there are margins defined. I’ll have a look.

      John

    • John Brinkman says:

      Tarek:
      I’ve found a problem and made a fix. Hope it’s the same problem you found :-)

      John

  11. tarekahf says:

    I was searching for a solution to get absolute coordinates of a field on XFA form. I almost forgot I already found this page several weeks ago, until I found myself again here via google. Why don’t your blog notify subscribers if there is new comment? Anyway, when I am back in the office I will try it again, because now I need it.

    Why I need it, check this post:
    http://forums.adobe.com/message/4312468#4312468
    I appreciete it if you could check the above post, and let me know if you have any feedback.

    Tarek