Stefan Cameron on Forms
Building intelligent forms using Adobe LiveCycle Designer

Instantiating Subforms at Runtime

Something that’s often desired, when working with dynamic forms, is to let the user specify, at run time (in Acrobat), how many lines of information are required to specify what they want.

The classic example is a Purchase Order form on which there are objects (usually buttons) which let you add and remove item lines from the section of the form which calculates the total cost of the order based on how many items are being ordered and the quantity and cost of each item.

In order to achieve this, the use of the Instance Manager is required. The Instance Manager is an object which exists only on repeatable subforms (this could be a subform which is parented to a flowed subform and set to repeat for each data item or it could be a row in a table — since rows and tables are, essentially, subforms). With this special object, you can modify the set of instances of a repeatable (dynamic) subform.

You can access the Instance Manager in two ways. The first is by accessing the object from the dynamic (repeatable) subform:

// append a new instance of DynSubform within its parent container
DynSubform.instanceManager.addInstance(true);

// ensure that the new instance is included in
//  subsequent calculations
xfa.form.recalculate(true);

The second is by using the shortcut “underscore” syntax (note that this syntax is supported if the form is rendered to PDF):

// remove the third instance (zero-based) of DynSubform
_DynSubform.removeInstance(2);

// work-around for Acrobat 7.x bug (reported) where number of
//  instances isn't properly updated after calling removeInstance
xfa.form.remerge();

The drawback to using the first syntax is that there must always be at least one instance of the dynamic subform which exists. If you have a form which must initially not have any instances of a particular dynamic subform, then you need to use the second syntax with the underscore since that special object is always “available” to you regardless of the current number of instances of the dynamic subform in question.

It’s also important to pay special attention to the minimum, maximum and initial number of instances of a dynamic subform. This is set on the Object palette’s Binding tab once you’ve specified that the subform is to Repeat for Each Data Item. The min and max limits must be repected at all times when using the Instance Manager. For example, if you set a min count of 1 and attempt to remove the last instance of a dynamic subform, an exception will be thrown which will result in a script error. You can check the current number of instances by using the Instance Manager’s count property and compare it to, for example,

// get the minimum number of instances that can be instantiated
_DynSubform.min

There are other things you can do with the Instance Manager such as setting the number of instances (using its setInstance(int) method), move instances around (using its moveInstance(int, int) method) etc. These are covered in detail in the Adobe XML Form Object Model Reference (located in the XML section). Check-out the Manipulating instances of a subform section on page 675.


Posted by Stefan Cameron on May 18th, 2006
Filed under Instance Manager,Scripting
Both comments and pings are currently closed.

37 Responses to “Instantiating Subforms at Runtime”

  1. Manuel on December 1st, 2006

    Thanks! Your example form helped clear some questions I had. The LiveCycle Dev. Center is great, but your content here is definitely helpful.

  2. dave on December 2nd, 2006

    Where can I find help designing a form using a script to validate the date and apply the tax if the form is files after a certain day of the month. In other words, if it is past the 20th of the month, a 10% penalty is applied to the total.

  3. Stefan Cameron on December 4th, 2006

    Dave,

    You can achieve this simply by using the following JavaScript code in the total field’s Calculate script:

    var oToday = new Date();

    if (oToday.getDate() > 20)
      SubTotal.rawValue * 1.10;
    else
      SubTotal.rawValue;

    where “SubTotal.rawValue” either retrieves the sub total of the order or is the summation equation itself.

    This script sets the Total field’s value to 10% more if the current day of the month is greater than the 20th.

  4. Iulia on March 28th, 2007

    Hi Stefan,

    I have a question related to how we can save such a form (after it was filled by the user).

    If you design a form with Adobe Acrobat Professional there is a setting you can do to enable users to save the form (Advanced menu > Enable User Rights in Acrobat Reader). But since creating dynamic forms is only possible in LiveCycle Designer I cannot make the same setting and buying licenses of Acrobat Reader Extensions for all users is not an option.

    I tried to add a Submit button on the form which sends PDF (connected to an ASP.NET application). I click on it but nothing happens. I also tried to put a simple button which runs the following Javascript on mouse up:

    form1.#subform[0].Button1::mouseUp: – (JavaScript, client)

    // Saving the form is done at the application level, so you need to invoke the
    // Acrobat app model.
    App.executeMenuItem(“SaveAs”); // The end user will be prompted to specify a
    // file name.
    // However, you must save the form silently if the form needs to be certified
    // and the certificate must be trusted for privileged JavaScript.
    var mydoc = event.target;
    mydoc.saveAs();

    Nothing happens either.

    Can you tell me please if there is another solution? Please note I need to save the entire pdf, not only the data entered by the user.

    Thanks in advance,
    Iulia

  5. Stefan Cameron on March 30th, 2007

    Iulia,

    The command you’re referring to (“Advanced | Enable Usage Rights in Adobe Reader”) in Acrobat Pro 8.0 can still be used with dynamic forms created in Designer (you can even use an earlier version of Designer like 7.1 if you wish).

    Simply create the form in Designer, save it as a Dynamic PDF form and open it in Acrobat Pro 8.0. That command will then be available to you and you’ll be able to save the form with Usage Rights enabled (e.g. data save) such that the user will be able to save the entire PDF along with the data using the Adobe Reader.

  6. Iulia on April 2nd, 2007

    Hi Stefan,

    You are so right! Thank you very much.

    I succeeded to save the form using the Acrobat Reader “Save” button, but I now have another question: I wanted to add a “Save” button to the form and when it is clicked to hide it and save the form (with the “Save” button hidden).

    For starters, I tried to put app.execMenuItem(“Save”) on the button click event but again, nothing happens.

    Can you shed some light on this, too? And can you indicate a help file or something similar to describe the pdf form structure objects (like xfa)?

    Thank you,
    Iulia

  7. Iulia on April 3rd, 2007

    Hi,

    Never mind about the help file; I found it on
    http://partners.adobe.com/public/developer/en/xml/Adobe_XML_Form_Object_Model_Reference.pdf

    However, the question related to adding a Save button to the form still remains.

    Many thanks,
    Iulia

  8. Stefan Cameron on April 3rd, 2007

    Iulia,

    Unfortunately, I don’t think you’ll be able to save the form silently using a button on a form.

    The problem with a silent save (i.e. just “save” instead of “save as”) is that it’s a security risk to the person using the form since they don’t know that it’s happening.

    You can, however, trigger a “save as” action from a button like this (in JavaScript only):

    app.execMenuItem(“SaveAs”);

    If you want to hide the save button prior to the save, you would just need to add this line prior to executing the menu item:

    this.presence = “invisible”;

    You can find more information on app.execMenuItem and its restrictions in the JavaScript for Acrobat API Reference, page 120.

  9. Meng on October 1st, 2007

    Hi Stefan,

    I have established a database connection in my forms such that whenever an user selects a value from a drop-down list, all the related fields will be updated with corresponding data records from the database. Besides, data records in database will be updated whenever an exit event is triggered as users modify/edit field data.

    Since this solution is only possible when there’s a connection to the database (on-line), it would be much more convenient for my users to have an off-line version which enables them to input any data and save it locally before doing the update to database when they have access to database again.

    So i have tried to save the forms with populated values from the database. But when I open the pdf again all the data are gone. Why is this happening?

    Thanks in advance!

  10. Stefan Cameron on October 14th, 2007

    Meng,

    Sounds like a cool form you’ve put together!

    The only thing that comes to mind as to why the fields go blank when you open the form with saved data is that the data connection you’ve defined is being opened when the form is initialized (opened in Acrobat). If a connection to the database can’t be established, the resulting dataset is empty and that’s what gets merged into the form, resulting in the fields becoming empty as well.

    You may need to add a button or two on the form which users can use to control when data is retrieved from and sent to the database using the data connection. You could, for example, have a “retrieve” button whose Click event opens the data connection in order to get the data from the database. At that point, the user can save the form with the data and continue filling it offline. Later, when online again with the updated data, the user can click on the second button, the “update” button, and that can open the data connection in order to send the updated data to the database.

    Have a look at the various ODBC data connection articles in my Data Binding section for some examples of the various things you may need to do.

  11. CF on October 6th, 2008

    Hi Stefan,

    I am trying to cascade the values in a dropdown box based on the value selected in another dropdown box within a repeating sub-form. In my case, the repeating sub-form contains a group of address fields, include dropdown boxes for State and City. Once the user selects a State, I want to limit the list of selectable Cities. I was able to make it work with a script in the validate event of the State field, but only for the first instance of the sub-form. Even though the event is executed for new instances of the subform, I couldn’t manipulate the City list in the new instance. I have a feeling the solution has something to do with InstanceManager, but couldn’t find any examples of how this is done.

  12. Stefan Cameron on October 10th, 2008

    CF,

    The Validate event isn’t the right event to use for what you’re trying to do, which isn’t really a validation. What you should be doing is using the Change event in the State drop down list to look at the new selection and then add the appropriate items to the City drop down list. The following script is what you would put in the State field’s Change event, in JavaScript:

    var newSelection = this.boundItem(xfa.event.newText);
    City.clearItems(); // remove all city field items that may have already been added from a previous State selection
    switch (newSelection)
    {
        case "NY":
            City.addItem("New York");
            City.addItem("Other city");
            break;
    
        case "AZ":
            City.addItem("Phoenix");
            break;
    }

    You shouldn’t need to worry about the instances of the repeatable subform in order to get this to work in all the instances, assuming the State field selection is done by the user.

  13. BAJ on October 20th, 2008

    CF has the same issue I am. The instance Manager is not holding Focus on the row (or column) that the user is editing.

    If you have a row that is generated by the Manager and it has code for a cell tied to a method it will work on the first instance on the sub form.
    If rows are added using the Manager, the code will continue to reference the information contained that same first instance (row) instead of changing focuse to the current instance that is being edited.

    How do you tell the instance Manager that you are editing (x)row ?

  14. Michael Murphy on January 5th, 2009

    I’m creating a form in Designer 8.2 for users with Reader 7. I’ve been building my tables with repeating subform controls instead of tables. They work beautifully except for one very important issue. The dynamically added subform “rows” are being tacked on to the very end of the tab order. Do you know if there’s some way I can address this?

  15. Stefan Cameron on January 7th, 2009

    Michael Murphy,

    I suspect the problem here is the older Reader which didn’t get the tab order bug fixes which went into Acrobat/Reader 9 along with the improvements in Designer 8.2.

  16. [...] count is 1) and if so, will remove the last row that was added to the table. Note the use of the underscore prefix to access the Item row’s Instance [...]

  17. Douglas on May 27th, 2009

    I’m trying to add a page with a script using Javascript. Do you have an example of this??

    Douglas

  18. Stefan Cameron on June 5th, 2009

    Douglas,

    Typically, pages are subforms parented to the root subform (named, “form1″, by default at the top of the Hierarchy tree) that have the same dimensions as the content area on the master page to which they pertain. Adding them via script is easy if you name them.

    Set the name of the page subform using the Hierarchy palette or “Object palette > Binding tab > Name property” and then, in script, use the Instance Manager to add another instance of the page subform:

    // JavaScript:
    xfa.form.form1._myPage.addInstance(0); // assuming you called it "myPage"
  19. Alex on August 12th, 2009

    Hello.
    I’m new… begginer… dummy at creating forms. I have a table and only need to add rows clicking a button, just that.
    How do i access the Instance Manager? where should i write all that code you wrote to access the instance manager?
    thank you in advance for your help.

    Alex

  20. Stefan Cameron on August 23rd, 2009

    Alex,

    My tutorial on creating an expandable table with totals has what you’re looking for.

  21. Hephzibah Israel on September 17th, 2009

    Hi: I am in need of adding a “Copy Row” button that would add an instance of a repeating subform along with the data from the previous instance of the same subform. Kindly advice if this is even possible to do with Adobe Designer 7.0

    Thank you in advance,
    Hephzibah Israel

  22. Stefan Cameron on September 22nd, 2009

    Hephzibah Israel,

    The Instance Manager‘s addInstance() method returns the new instance that is added. From there, you can access the form objects within the new instance and set their values. In order to duplicate the values from the previous instance, you would have to decide what qualifies as the “previous instance” and then get that instance and use it as a reference for setting values into the new instance.

    For example, if the “previous instance” was the instance of the subform containing the “copy row” button that was clicked, then the script might look like this (assuming that the button is directly parented to the repeating subform):

    // JavaScript:
    // create new row instance
    var newRow = this.parent.parent._RepeatingRow.addInstance(0);
    // copy values from this row (the row being copied) to the new one
    newRow.someField.rawValue = this.someField.rawValue;
  23. Hephzibah Israel on October 14th, 2009

    Thank you so much Stefan. It worked!

  24. Julie Cooke on October 19th, 2009

    How do I specify the the added instance to add to the next page? The subform is approx 1 page long, so I want each added instance to have its own page. (the 1st page has company header & 1st instance of subform). Currently the added instances run over other static text & off end of page. Thanks.

  25. Stefan Cameron on October 22nd, 2009

    Julie Cooke,

    Use the “Object palette > Pagination tab > Placement property” to specify that the repeating subform should be placed on “top of next page”. This should ensure that each new instance ends-up on a page of its own.

  26. [...] a little something that very important to know when working with dynamic subforms and using their Instance Managers (IMs) to add instances of them to a form: New subform instances aren’t automatically [...]

  27. silentkiller_125 on March 13th, 2010

    I also have a state drop down list based on the previous field’s selection.That page is in a subform section when i add an instance of that subform section the state dropdowns dont work.I have the script in change event , but why doesnt it work ??

  28. Stefan Cameron on March 21st, 2010

    @silentkiller_125,

    Are you using relative expressions to access the form objects within the subform instance? If not, you might be always accessing the same instance, not the one that was just added.

  29. Kerry Steven on April 26th, 2010

    I am creating form in which user first chooses from a selection by pressing one of a plurality of buttons. Each click of button creates a new instance of different subforms depending on button clicked on a new page. However, the screen won’t move to the newly created page! Do you know a script to add so that the screen moves to the newly created page? I’ve included page down, but that won’t work if the new instance is on a third page.

  30. Stefan Cameron on May 1st, 2010

    @Kerry Steven,

    Usually, setting input focus on a form object will cause Acrobat/Reader to scroll to that location. You can set the focus like this:

    xfa.host.setFocus({formObject}.somExpression);

    where “{formObject}” is the name of the subform you have instantiated after the user clicked one of the buttons.

  31. Kerry Steven on May 20th, 2010

    Thanks Stefan,

    I tried the setFocus command, but the screen still does not move when the button is clicked. I’m using version 8.0. Any assistance would be appreciated.

    —– UnifiedFormat.page1.ChooseRejectionType.Button2::click: – (JavaScript, client) —————

    _Rejection102.addInstance(1);
    xfa.host.setFocus(Rejection102.somExpression);

  32. Stefan Cameron on May 31st, 2010

    @Kerry Steven,

    The issue could be that you’re setting focus to a table/subform. I think the scroll will only happen if setting the focus causes the insertion pointer (“I-beam”) to move to some field that is currently out of view.

    Trying setting the focus on a field inside the instance of the subform/row you add.

  33. helary on August 3rd, 2010

    Dear Stefan,

    Time to visit your wonderful site again.

    I have developed a pdf which will import data from excel file (around 60 fields per row). I need to add instance to a page dynamically for each input record read and then do some rendering before filling in the fields.

    That’s ok just for a few records but Reader will crash when no. of input records are more than 70-80, I have read some documents on how to improve performance in dynamic pdf forms but the problem lies on the complication of my form as it needs to according to requirement.

    I wanna know if there is any limit on adding instance of a page or subform on runtime and any tips that I can get rid of the problem.

    Rgds

  34. Stefan Cameron on August 13th, 2010

    @helary,

    That’s strange behaviour. I’m not aware of a limit to the number of instances of subforms that a form can create at runtime.

    The only thing I can suggest is that you structure your code such that minimum processing is required to generate the instances.

  35. Shishir P on October 2nd, 2010

    Hello,
    I was facing a similar problem and was hoping if you could help me, I have 3 dimensional array on the form, I have “+” and ” – ” buttons on the first level array, for every 1 record of top level array, the underlying arrays can have multple records. On the click of the button I am creating an instance and also require to replicate the number of records and along with data, so far I have managed to create an instance, this creates a single blank record in the underlying arrays as well, the script used is as below:-

    /*
    This button will insert one instance of the repeating subform or table row.

    errMsg: The error message displayed.
    */
    var errMsg = “You have reached the maximum number of items allowed.”;

    var oWrapper = this.parent.parent; // The outer subform built in to the object, enclosing the button group.
    var oTargetSubform = oWrapper.parent.parent.parent; // The subform or table row the controls are intended to manipulate.
    var oManager = oTargetSubform.instanceManager; // Get the instance manager.
    var nMaxCount = oManager.occur.max; // Get the maximum number of subform occurrences allowed.
    var nSubCount = oManager.count; // Get the current number of instances.

    //—————————————————————————————————-
    // Current Index of the tables

    //var index_T_ELEMENTS = this.parent.parent.parent.parent.parent.parent.parent.T_ELEMENTS.DATA.index;
    //var index_T_COLL_CELL = this.parent.parent.parent.parent.parent.parent.parent.T_ELEMENTS.DATA.T_COLL_CELL.index;
    //var index_T_FIELD = this.parent.parent.parent.parent.parent.parent.parent.T_ELEMENTS.DATA.T_COLL_CELL.DATA.T_FIELD.index;

    // Current number of Records

    var len_T_ELEMENTS = this.parent.parent.parent.parent.parent.parent.parent.T_ELEMENTS.nodes.length;
    var len_T_COLL_CELL = this.parent.parent.parent.parent.parent.parent.parent.T_ELEMENTS.DATA.T_COLL_CELL.nodes.length;
    var len_T_FIELD = this.parent.parent.parent.parent.parent.parent.parent.T_ELEMENTS.DATA.T_COLL_CELL.DATA.T_FIELD.nodes.length;

    // Proceed if the maximum number of subform occurrences has not been reached.
    if ((nMaxCount == “-1″) || (nSubCount < nMaxCount)) {

    // Invoke the Instance Manager.
    var oNewInstance = oManager.addInstance(1);

    //——————————————————————————————————–

    //——————————————————————————————————–
    // Execute all the form calculations.
    xfa.form.recalculate(1);

    // Move the new subform below the current one.
    var nIndexFrom = oNewInstance.index;
    var nIndexTo = oTargetSubform.index + 1;
    oManager.moveInstance(nIndexFrom, nIndexTo);

    } else {

    xfa.host.messageBox(errMsg,"Insert Item", 3);

  36. Stefan Cameron on October 8th, 2010

    @Shishir P,

    What exactly is the problem you’re experiencing?

  37. R.L.E on November 26th, 2010

    My form has a constant set of dynamic and static textFields for page 1 and 2, pg.2 having a header block that will need to be called to repeat to the top of a variable number of subsequent pages.

    I need to create for the 3rd (+ pages) a set of data input fields that will create repeatable new subForm instances to the body of the page below, insert sentences/paragraphs of variable length from a dataBase (based on item codes) into each new instance and clear itself with each submission (button or enter key).

    Could you give example/explain how one would code into this submit event the method to detect the bottom margin of the page (or other page position/formatting) and if nextInstance will excede margin, add a newPage with headerBlock and dataEntryControls repeating first at top of page followed by the nextInstance?

    Thanks in advance