Stefan Cameron on Forms
Building intelligent forms using Adobe LiveCycle Designer

Making a Table of Contents

One of the hottest topics on the Designer Forums these days seems to be methods by which one can add a table of contents to their form. Since there are many different ways to achieve this, I thought I would post a little sample to demonstrate how this can be done.

Of primary concern when adding a table of contents to your form is ensuring that the links provided to the form’s various pages/sections remain valid at all times. Static forms whose page set never changes don’t really need to worry about this but dynamic forms do. That is, regardless of whether content pages are added, re-ordered or even deleted from your dynamic form, you need to ensure that when a TOC link is clicked, the user is taken to the correct page pertaining to the topic they selected.

The best way of ensuring that TOC links don’t get broken as a result of changes to the form’s pages is by using layout information provided by the XFA Layout Model. This model provides information such as an object’s actual dimensions (width/height), the page on which it is located as well as a few other interesting pieces of information.

By using the

xfa.layout.absPage

we’re able to get the page number on which an object is currently located, taking into account the various pages which may have been inserted or removed in-between the TOC page and the page in question. Taking that page number and assigning it to the

xfa.host.currentPage

property then sets the current page to the one on which is located the object in quetsion.

Download Sample [pdf]

Minimum Requirements: Designer 7.x, Acrobat 7.x.

page or absPage?

If you’re looking at the Adobe XML Form Object Model Reference (page 188), you’ll notice that there are actually two methods which provide the number of the page on which an object is currently located:

xfa.layout.page // returns a 1-based number

and

xfa.layout.absPage // returns a 0-based number

Since we need to set that information to the

xfa.host.currentPage

property and that it expects a zero-based page number, it’s simpler to use the absPage version.

The Layout:Ready Event

When using the XFA Layout Model, it’s very important to realize that the information it contains is constantly updated whenever something on your form changes that may affect an object’s positional data. Therefore, you must ensure that the form’s layout has been updated before you attempt to obtain information from it about a particular object.

For TOC page fields that display the page number on which a topic is currently located, it’s easier to simply put script in the Layout:Ready event of that field since this event is fired every time the form’s layout process is complete. Therefore, whenever pages get added, re-ordered or even removed, this event is fired and the fields are updated with the most current information.

Alternatively, if you’re writing script in another event and you need to know whether information in the XFA Layout Model is up-to-date, you can use the

xfa.layout.ready

boolean property which returns true if the form’s layout process is complete and false otherwise.

Links in XFA

A lot of customers have asked me how to create links in XFA. The concept of links is essential to a TOC page since it’s sole purpose is to provide links to various pages within a document.

Unfortunately, XFA 2.4 (used by Designer 7.1+ and Acrobat 7.0.5+) doesn’t have the ability to describe the equivalent of an HTML anchor (link) or a PDF Bookmark. Therefore, we’re forced to cheat a little. One way of doing that is by using a transparent button overlaid on top of another object (text or field) which displays information about a link. You could also use a button directly by using its caption to describe what it links to but you’ll most likely run into text alignment problems if you need the button’s caption to be anything but centered.

My sample uses the former solution and places transparent buttons over the fields which display information about the link (title and current page number). You can make a button transparent by removing its caption text and customizing its Appearance property (on the Object palette’s Field tab) to remove its borders and fill.

Finally, the z-order (or top-down order in the Hierarchy palette) of the transparent buttons used as links with respect to the fields over which they’re to be located is very important in that the buttons must be above (higher in z-order or lower in the Hierarchy palette) the fields to which they pertain. One way to do this is by selecting a button and choosing the “Layout | Bring to Front” menu item. This will ensure that when you click on the topic, you’ll click on the link button rather than the field which describes the topic.


Posted by Stefan Cameron on September 4th, 2006
Filed under Scripting,Tutorials
Both comments and pings are currently closed.

20 Responses to “Making a Table of Contents”

  1. Mac on September 4th, 2006

    Just to say that this kind of quick how-to article is a real help – thanks!

  2. andrea on February 24th, 2007

    While looking for help regarding a problem I encountered, I was reading your article and wondering if you might have a solution for the following problem:

    I’m working on a dynamic form with eight different pages (as subforms). When you open the form only two pages are visible, the others are hidden. After the user clicks on a button a new page gets added (visible) to the form. Now I want the new page to become the current page. I tried to set xfa.host.currentPage to the new page in layout:ready event for the new page. But at this point the Host Model doesn’t know about the new page (xfa.host.numPages has still value 2), so I have no chance to set xfa.host.currentPage to 3. The Layout Model knows the new page already (xfa.layout.pageCount has value 3).

    Do you have any idea at which point I might be able to set xfa.host.currentPage to the new page?

    Thanks for your help!

  3. Stefan Cameron on March 8th, 2007

    Andrea,

    The problem you’ve run into is similar to the problem others have when attempting to set focus to a particular field after validating the field from which the focus came.

    As you said, the Host Model doesn’t know about the new page that you just made visible immediately after you’ve set the page’s presence to “visible”. Therefore, you need to delay your call to xfa.host.currentPage until the host has been updated.

    You can do this by using the following line of JavaScript code in the button’s Click event (the button which sets the presence of the page):

    app.setTimeOut(“xfa.host.currentPage = 2;”, 10);

    This script will delay the call to set the current page to the third page (page indexes are zero-based) by 10 milliseconds, just long enough for the host (Acrobat) to react to the change in presence and update the Host Model.

  4. andrea on March 14th, 2007

    Thanks Stefan, this works quite well.

    But trying to set the focus to a field on the new page still doesn’t work (I’m using xfa.host.setFocus(“field name”)). Do I have to put the call to setFocus in a TimeOut as well? Or is there another way to set the focus to a new field, so that the user can start typing immediately?

  5. Stefan Cameron on March 20th, 2007

    Andrea,

    Your latest question sent me on a bug hunt! You are correct in thinking that you need to place the call to “xfa.host.setFocus” in “app.setTimeOut” as well. There’s a bug in Acrobat 8.0 which prevents “xfa.host.setFocus” from functioning properly when setting focus to a field immediately after showing the hidden page on which it is located.

    Note that when using “xfa.host.setFocus” in an “app.setTimeOut” call, you’ll have to give the full SOM expression, not just the field’s name:

    app.setTimeOut(“xfa.host.setFocus(‘” + Page2SF.TextField1.somExpression + “‘);”, 10);

    Also note that if you’ll always be setting the focus to a field after showing a page, you don’t have to set the current page since setting focus will cause Acrobat to scroll to the appropriate page automatically.

  6. andrea on April 3rd, 2007

    Hi Stefan,

    setting the focus to a field using app.setTimeOut(…) is working for my form as long as I’m not using a paper form barcode.
    But my originally form contains a paper form barcode. When eliminating everything belonging to the paper form barcode the timeout-call is working perfectly (the correct page and field gains the focus). As soon as the paper form barcode is added to the form, the timeout is not working anymore.

    Now my problem is: how can I get both to work (paper form barcode and setting the focus)?

  7. Stefan Cameron on April 4th, 2007

    Andrea,

    Unfortunately, I’m having a difficult time reproducing the behaviour you’re seeing when you include a Paper Forms Barcode on your form.

    Have you setup the barcode to encode fields in a collection? Is the encoding in XML or tab-delimited? Are you doing this with Designer 7.1 or 8.0?

  8. Ken on April 5th, 2007

    I’ve been able to get the focus to work when adding an instance like this, but do you know of a way to get the focus to go to the previous instance when deleting a subform? For example, if I added three addresses and didn’t want the third one, I can delete it, but the focus goes nowhere. I would like the focus to go to the name field of the second address. Thanks, Ken

  9. Stefan Cameron on April 9th, 2007

    Ken,

    Assuming you have a “delete” button on each subform instance and you want focus to go to the “name” field of the previous instance when the user clicks on the “delete” button to remove a particular instance, you simply need to get the instance number (i.e. the index) of the previous instance, get a reference to that instance and set focus to the “name” field which it contains.

    Here’s an example in JavaScript (you would place this script on the “delete” button’s Click event):

    // index of the previous instance (“parent” is the repeatable subform)
    var nPrevIndex = this.parent.index – 1;

    if (nPrevIndex >= 0)
    {
        // we can safely set focus on a previous instance
        var oPrevInstName =
          xfa.form.form1.resolveNode(“AddressSF[” + nPrevIndex + “].Name”);

        // set focus to previous instance’s “name” field
        xfa.host.setFocus(oPrevInstName.somExpression);
    }

    // remove the current instance
    xfa.form.form1._AddressSF.removeInstance(this.parent.index);

    Note that this script assumes the repeatable subform’s name is “AddressSF”, that it contains a field named “Name” and that it’s being executed using Acrobat 8.0.

    If you’re doing this in Acrobat 7.0.x, you may need to delay the “xfa.host.setFocus” call with an “app.setTimeOut” call if the focus doesn’t go to the “name” field:

    app.setTimeOut(“this.getField(‘” + GetFQSOMExp(oPrevInstName) + “‘).setFocus();”, 10);

  10. andrea on April 12th, 2007

    Hi Stefan,

    sorry for getting back so late, but I just came back from vacation…

    Back to your questions:
    I’m working with Designer 7.1.
    In the “value” tab of object “PaperFormsBarcode” I’ve checked “Automatic Scripting” with Format: Tab-Delimited. Then I’ve checked “Include Label” apply to “Collection Data”. I’ve created a collection wich includes different fields (check-boxes, textfields. list-boxes, radio buttons) from my form. The barcode is visible.

    Adding the barcode to my form is really slowing down user interaction and the timeout-call is not working anymore even if I set the timeout to 10000.

    Any ideas?

  11. Stefan Cameron on April 14th, 2007

    Andrea,

    I’m sorry: I should have also asked you what version of Acrobat you are using.

    I set the same settings on the paper forms barcode in my test form but still couldn’t reproduce what you’re seeing.

    In order to investigate the problem a little more, you could also try removing some, but not all, of the objects from the collection being used by the barcode.

    It’s not surprising that performance drops once you start using the barcode, especially if the collection includes a large number of fields to encode: Everytime a field’s value changes, the barcode’s script must re-encode values from all fields in the collection since it can’t change only the value of the field whose value changed (and neither does it know the specified field whose value has changed). If there are a large number of fields to encode, it’s possible that this drop in performance is causing a timing issue which is preventing the “app.setTimeOut” call from functioning correctly.

    Let me know what you find and we’ll take it from there.

  12. andrea on April 20th, 2007

    Stefan,

    Your are right, my barcode includes a large number of fields, which is preventing the timeOut call from functioning correctly. If I reduce the number of fields to encode the timeOut call is working fine.

    I think I have to play around with the paper form barcode a bit to get my problem fixed.

    So thank your for your hints. They really helped my a lot.

    Andrea

  13. Ron on October 13th, 2008

    Stefan,

    Quick question. I saw some setFocus comments here so thought I’d try.

    In my Validate Event using Adobe Reader 8.1.0 or higher, I’m using:

    xfa.host.setFocus(); // Clear focus operation
    xfa.host.setFocus(this); // Set cursor back to current field

    This sets the cursor back to the current field when bad data is entered and works for both the Tab and Enter key. It highlights the bad data.

    But this does not work correctly when using Adobe Reader 8.0 and 7.0.x.
    Problems I had with these older Readers regarding setFocus:

    1. Enter key does not fire setFocus, only Tab works
    2. setFocus goes into loop when using a Date/Time field and Reader 7.0.x
    3. xfa.host.setFocus() does not clear focus operation like Reader 8.1.0+

    Anybody else had similar problems or know a workaround?

    Thanks,
    Ron

  14. Ron on October 14th, 2008

    I looked at this again today and discovered a difference:

    READER 7.0.x:

    On Date/Time field, Validate fires on exit even if no change was made to the data.

    On other type fields, Validate fires on exit when data was changed.

    READER 8.0 and up:

    On Date/Time field and other type fields, Validate fires on exit when data was changed.

    I read in the Livecycle reference manual that the Validate event is supposed to fire on exit only when there is a change to the data. So does the older Acrobat Reader versions have a bug with regard to this?

    Any ideas and how to get around this bug?

  15. Stefan Cameron on October 19th, 2008

    Ron,

    In my experience, the Validate events are unreliable and in need of fixing. We’re already thinking about solutions for this in a future release.

    As for the focus issues, the behaviour of xfa.host.setFocus() changed in a recent version of Acrobat/Reader — it may have been the 8.1 release. The major change is that the call to setFocus() used to occur after the current event script was finished executing (i.e. it was postponed until later). This was causing problems such as the inability to set focus back to a field on exit if it contained invalid data (which sounds like what you want to do) because by the time the setFocus(this.somExpression) was executed, the focus had already changed to the next field in tab order. Now, the setFocus() call is executed immediately which means you get the behaviour you expect.

    I would recommend you check for invalid data in the field’s Exit event which is fired whether the user presses the Enter or Tab key or clicks away from a field. If the value entered is invalid, just call xfa.host.setFocus(this.somExpression). The Validate event fires any time a dependent field’s data changes which could cause you to set focus to the wrong field in certain cases.

  16. Dawid on October 24th, 2008

    Hi Stefan,

    Unfortunatelly the field’s Exit event has exactly the same behaviour as you described (I’m using LC Designer 8.2 with Acrobat and Adobe Reader 7.0.5 target version, AR 9 and AR 7.0.8 for testing).
    I’m also trying to prohibit the user to leave the field in (the simplest) case of no data using Exit event with code like this:

    if (this.rawValue == null || this.rawValue.length < 1) {
    xfa.host.messageBox(“Field is empty!”,”Error”);
    xfa.host.setFocus(this.somExpression);
    }

    Without any luck, because clicking on another field the Exit event of the next field is trigered and in case this field is also mandatory, the user gets two messages. I witnessed a really lot of Designer crashes just trying to make work this simple task.

    Please help or enlighten us, what is the best practice for forcing the user to enter a valid data in field.

    Thanks for your answer,
    Dawid

  17. Dawid on November 3rd, 2008

    Hi Stefan,

    I’d like to ask you, please, explain how you are forcing the user to enter a valid data in a field without leaving it.

    Thank you very much,
    Dawid

  18. Stefan Cameron on November 3rd, 2008

    Dawid,

    In my experience, it’s best not to try to keep the user in a particular field but rather to perform validations when the user doesn’t another action like clicking on the submit button. To do this nicely, you might want to try my two-button submit technique combined with invalid flashing fields.

  19. Saran on December 19th, 2008

    Hi Stefan,

    I need to place a Save button on my PDF form which saves the Form when any field is filled. I get articles related to SaveAs button option. But i want just Save option button. Please help me out in fixing this issue..

  20. Stefan Cameron on December 20th, 2008

    Saran,

    It sounds like you’re talking about an auto-save feature where you would programmatically (through script) save the form whenever a user would exit a field, having entered a value.

    Unfortunately, I don’t believe it can be done. The only method available on the Acrobat Doc object is the “saveAs” method and even then, it can only be executed during a batch or console event.