Stefan Cameron on Forms
Building intelligent forms using Adobe LiveCycle Designer

Creating and Loading Data Nodes

Have you ever needed to create data nodes in the Data DOM on-the-fly? How about a need to parse XML obtained from a web service data connection in order to find a value for some property? If that’s the case, then you’ll find-out how to do it in this article.

Creating Data Nodes

In a similar article, John Brinkman showed how to create form variable nodes at runtime however what’s not obvious about data nodes is that you don’t create them using

a class name of your choice.

For example, say you wanted to create the following data node structure:

<custom>
    <message>Hello World!</message>
</custom>

 

Given the following syntax for the createNode() function (see page 367 in the LC Designer 8.2 Scripting Reference):

{model}.createNode(className [, name, namespace]);

you might try the following, using “custom” as the class name for the first group/container node <custom>:

var n = createNode("custom");

Doing the above would generate an error stating that “custom” is an invalid node type.

The trick here is that only specific XFA class names are allowed, such as “text”, “decimal”, “integer”, etc., as well as two special types for data nodes: “dataGroup” and “dataValue”. When you need a data node that will contain other data nodes, you use the “dataGroup” class. When you need a data node to which you will assign a value, you use the “dataValue” node.

The JavaScript code to create the above XML structure then becomes:

// <custom> data group to contain <message>
var c = xfa.datasets.createNode("dataGroup", "custom");
// <message> data value for "Hello World!"
var m = xfa.datasets.createNode("dataValue", "message");
// set value of data value node
m.value = "Hello World!";
// add <message> into <custom> data group node
c.nodes.append(m);
// print result to JavaScript Console (Ctrl + J in Acrobat)
console.println(c.saveXML("pretty"));

// output from above:

<custom>
    <message>Hello World!</message>
</custom>

Note that the createNode() function must be called on an object of type “model” and since we’re creating nodes in the Data DOM, we use the xfa.datasets object since it is a model object.

At this point, the <custom> node (and the nodes/values it contains) is not part of the Data DOM. To add it to the Data DOM so that it gets submitted with the rest of the form’s data, you would have to append it to a node inside the root subform (the top-most subform in the Hierarchy palette). For example, if your root subform’s name was “form1”, you would do the following to add the <custom> node to the form’s data:

xfa.datasets.data.form1.nodes.append(c);

This can be very useful. In fact, this technique is used in Correspondence Management letter template scripts to flag a selection list’s default selection as having been processed. Since the data merged into the list to print the letter based on the template may not match the default selection in the list, the scripts generate a special node directly in the Data DOM to indicate whether the list’s data value should be processed instead of its default selection (i.e. no need for a hidden checkbox field for each list, keeping the form/PDF file size down).

The “Custom Node Example” section in my sample form demonstrates the above technique.

Loading Data Nodes

Another use for creating data nodes is they give you the ability to load existing XML in order to parse it without having to modify the Data DOM. If you create a new, orphaned (un-parented) data group node, you can use its loadXML() function to load an XML string into it, thereby giving you a handy XML node structure for parsing.

One example use of this would be in a form that retrieves various settings from a web service data connection where the structure of the returned data is determined by the input parameters (i.e. you have a single data connection which you can use to retrieve various data). You can return the XML data as a string and load it into a new data group node in order to parse-out the value you’re looking for without ever touching the Data DOM.

Another example would be an extension of my URL Request article. In this case, you could pass some XML data into your form via the URL request if your PDF form was available on a website. Warning: This is a not a secure method to pass data to your form.

Clicking the following link will load some movie data into the “Movies loadXML() Example” section of my sample form:

CreateLoadDataNodes.pdf?data=%3Cmovies%3E%3Cmovie%3E%3Ctitle%3ENacho%20Libre%3C%2Ftitle%3E%3Ccost%3E21.00%3C%2Fcost%3E%3CactorName%3EJack%20Black%3C%2FactorName%3E%3CcatName%3EComedy%3C%2FcatName%3E%3C%2Fmovie%3E%3Cmovie%3E%3Ctitle%3EMI%3AI%3C%2Ftitle%3E%3Ccost%3E20.00%3C%2Fcost%3E%3CactorName%3ETom%20Cruise%3C%2FactorName%3E%3CcatName%3EAction%3C%2FcatName%3E%3C%2Fmovie%3E%3C%2Fmovies%3E

Finally, my sample form also has a handy “URI Encoding Tool” section which you can use to encode various strings to include in a website address (as above).

Samples

Download Sample [pdf]

Minimum Sample Requirements: I created this sample using Designer 8.2 and Acrobat 9 however it should work fine back to Acrobat 7.0.5.

There is also a great example on creating data nodes on page 438 of the LiveCycle Designer ES Scripting Reference for Designer 8.2 — an excellent XFA form scripting reference.


Posted by Stefan Cameron on June 1st, 2009
Filed under CM,Data Binding,Instance Manager,Scripting,Tables,Tutorials,XFA
Both comments and pings are currently closed.

8 Responses to “Creating and Loading Data Nodes”

  1. John Brinkman on June 2nd, 2009

    Stefan:

    Nice article. Lately when I’m creating a simple hierarchy in the data dom I’ve taken to using the assignNode() method. For example, your sample could have been written as:

    xfa.datasets.data.form1.assignNode(“custom.message”, “Hello World!”, 0);

    assignNode() will create the necessary hierarchy. Intermediate nodes are created as dataGroup objects and leaf nodes as dataValue objects.

    John

  2. Stefan Cameron on June 2nd, 2009

    John Brinkman,

    Thanks! That’s a great time-saving tip about assignNode().

  3. Justin on June 16th, 2009

    Hi Stefan,
    excellent blog, I think that everything I’ve done has been based on solutions that I’ve found here. Thanks for sharing!

    I’ve designed a simple time sheet for our work, the worker basically fills out their name, week ending, then goes to a table with date picker, the hour started and finished. The next field automatically calculate their hours and overtime if it applies. There are also text fields to describe the job location and description.

    I then have a button to add a row to make another entry, because they may be half day at one job and half day at another. They can continue to add rows until they have filled out the whole week’s work.

    I have the form connected to an access database with each field in the row bound; an update button to update the new record that was inserted into the database when the form was opened using this scripted in form1 form:ready:

    xfa.sourceSet.DataConnection.addNew()

    When the user enters one entry and updates, this works perfectly.
    When the user clicks to add a new row, the new row contains the data of the first row and when you change one, it changes the other.
    When I set the data bindings to incorporate the [*] wildcard, then the new row is empty.
    Before:
    $record.DataConnection.Start
    After:
    $record.DataConnection.Start[*]

    The problem that I’m running across now is that the update button will only update the info in row[1] and not add the info in row[2].
    I set the following command on the click event for when the table has a new row added:
    xfa.sourceSet.DataConnection.addNew()

    but this didn’t work.
    I’m kinda at a loss now what to do, all I need is for the form to be able to add multiple records to the database with one button without having to open it and add each time separtately.
    Does LiveCycle support multiple fields with the same name in a dataconnection?
    Eg. Hours[1]
    Hours[2]

    Thanks in advance!
    Justin

  4. Stefan Cameron on June 22nd, 2009

    Justin,

    I’m glad to hear you find useful things on my blog!

    I believe the problems you’re experiencing are related to the fact that with bindings to database data connections, there can only be one record that is active at any given time. When you do the “addNew()” call, the data in the fields currently bound to nodes in the data connection is set into the current record and a new record is created.

    While you can display multiple database records at once in a form, you can’t edit multiple records at the same time.

    To do this, I would recommend you read my post on inserting, updating and deleting records in a database. Using this technique, you could have the user add and fill multiple rows in the table and then have a single submit button that they click when they’re done. The submit button’s script would then insert new records into the database, one for each row in the table.

  5. duane on September 15th, 2009

    Hi Stefan,

    I’ve been using xfa.form.createNode and extras.nodes.append to create and append new nodes on form objects (I don’t want to persist the information into the xml data).

    However, this seems to break the Reader Extension usage rights on the form. If I open the form, perform an action which causes these nodes to be created and appended, then save and re-open the form; I get the “This document enabled extended features in Adobe Reader. The document has been changed since it was created and use of extended features is no longer available.” message.

    Any advise as to how I can persist dynamically created object properties without invalidating Reader Extension usage rights?

    thanks
    – duane

  6. Stefan Cameron on September 22nd, 2009

    duane,

    Looks like you got your answer here… I’m glad John was able to help you out!

  7. Akshat on April 12th, 2010

    Hi,

    We have textfield1 on page_01 which is getting repeated according to data coming in xml, we have another textfield2 on the page_02 which is having same binding (used in the page_01 on textfield1) and also repeating one, but the data is not coming in it.
    Its looks like a complicated issue and this functionality needed to be worked as per our requirement, so need your valuable inputs to resolve it.

    Inresting thing is that if we delete the textfield1 on page_01 then textfield2 on the page_02 is working properlly.

  8. Stefan Cameron on April 14th, 2010

    @Akshat,

    textfield2 on page_02 isn’t being filled because the merging algorithm finds textfield1 on page_01 to bind the data first. Therefore, there’s no data left to bind to textfield2.

    If you want textfield2 on page_02 to duplicate the instances of textfield1 on page_01, you would have to remove the binding on textfield2 and use script to duplicate what the state of textfield1.

    For example, you could script the Initialize event (which happens after data has been merged into the form) of the root subform (top-most subform in the Hierarchy palette) in order to inspect the instance of textfield1 and create mirror instances of textfield2.