Wednesday, May 11, 2005

InfoPath: Add a row to a repeating table with JScript

One of the most typical questions on the InfoPath newsgroup is how to programmatically add a new row to a repeating table. The XML DOM is very fine-grained when it comes to adding new elements and attributes, demanding a lot of boring code especially when adding stuff like a new row to a table. Unfortunately, MSXML does not allow for adding an XML fragment from a string, which would have made things very simple. InfoPath script uses the MSXML component.

The trick is an old one with MSXML and bad horror movies: cloning !

This code shows how to create a new row, reset the values, and add it to the XML document:

//get parent row
var parent = XDocument.DOM.selectSingleNode("/dfs:myFields/dfs:dataFields//s1:InvoiceCommission");

//get first row
var rowOne = parent.selectSingleNode("./s1:InvoiceDetailsRow");

// Create xsi:nil attribute with the proper namespace.
var xmlNil = parent.ownerDocument.createNode(2, "xsi:nil", "http://www.w3.org/2001/XMLSchema-instance");
xmlNil.text = "true";

//clone the first row
var rowClone = rowOne.cloneNode(true);

//reset values
var description = rowClone.selectSingleNode("s1:Description");
description.text = "CLONED";
rowClone.selectSingleNode("s1:IsVatCharged").text = 0;
rowClone.selectSingleNode("s1:VatAmount").text = 0;

//nillable: The order is important. Attribute must be removed when setting actual value.
var amount = rowClone.selectSingleNode("s1:NetAmount");
amount.text = "";
amount.setAttributeNode(xmlNil);

//append row to XML document
parent.insertBefore(rowClone, rowOne);

Your XML must ofcourse contain a "seed" row in the table, otherwise there will be nothing to clone. Do not use parent.appendChild() as this can make your XML document invalid against your XSD schema. This will happen when the XSD defines a <xs:sequence> and the row element you add is not at the very end in the schema definition.

Note that MSXML does not have an .insertAfter() method. The .NET assembly System.Xml does, but this will require you to use managed code in your form, which makes deployment more complicated than a script based solution.

12 comments:

Anonymous said...

Do you know of an easy way to deal with nodes that have a default value? The default always overrides any value that is assigned programatically. I would like to get around this.

Kjell-Sverre Jerijærvi said...

Do you mean a XSD default value on the node (element) or an InfoPath field default values ? I never use XSD default values anymore, and I use very few IP default values, especially not to do calculations, as I got the same problem as you. Thus, for setting calculated default values, I now use rules.

Anonymous said...

Thanks for your reply. I am using InfoPath Defaults. I thought of using rules but was having problems generating desired behaviour because I was applying the rule to the row element. I just applied the rule to the repeating table's data element and now all is well. Thanks

T-Bone said...

Can you tell me what's wrong with the code below. The first time it runs as i'd expect, but every time after that it inserts 2 additional rows. Any ideas?

//get parent row
var parent = XDocument.DOM.selectSingleNode("/my:myFields/my:History");

//get first row
var rowOne = parent.selectSingleNode("./my:History_Group");

//clone first row
var rowClone = rowOne.cloneNode(true);

//set values
rowClone.selectSingleNode("./my:column1").text = "col1";
rowClone.selectSingleNode("./my:column2").text = "col2";
rowClone.selectSingleNode("./my:column3").text = "col3";

//append row to XML document
parent.insertBefore(rowClone, rowOne);

Kjell-Sverre Jerijærvi said...

You should check that your script is not run twice due to event bubbling or the delete+insert pair operation on updates in InfoPath.
I recommend you to debug the script by adding a 'debugger;' statement to see why and when the script gets called.
See: http://support.microsoft.com/?scid=kb;en-us;827002&spid=2515&sid=229

Alibek Junisbayev said...

Thank you very much for samplecode.
Tried to use it but got a problem: can't add a row with numeric data.
String data goes fine.
Can you advice on that?

set parent = XDocument.DOM.selectSingleNode("//my:MyFields")
set rowOne = parent.selectSingleNode("./my:RepeatingGroup")
set rowClone = rowOne.cloneNode(true)
rowClone.selectSingleNode("./my:Repeatingtext").text = "col1"
rowClone.selectSingleNode("./my:RepeatingNumber").text = cdbl(123123)
rowClone.selectSingleNode("./my:RepeatingDate").text = date
parent.insertBefore rowClone, rowOne

Kjell-Sverre Jerijærvi said...

I assume that your XSD defines "RepeatingNumber" as a decimal data type: try to use the data type aware property .nodeValue. The .text property should not be used for typed elements or sub-trees

Note that it is MSXML that is used by the script, not the .NET XML library.

MSXML SDK for IXMLDOMElement: http://msdn.microsoft.com/library/en-us/xmlsdk/html/461c0798-1bb6-4d0d-8b9f-c84481eb8530.asp?frame=true

Teddy said...

one little question: what if you don't have a seed row ? how could you create one ? i know the post is old but i am facing this exactly issue ... i have no seed row so what do i do?

Kjell-Sverre Jerijærvi said...

then you'll have to create a row node from scratch and add child elements accoriding to the XSD schema manually in your code

Teddy said...

can you point me out to an example please or can you post an example here?

Kjell-Sverre Jerijærvi said...

something like this, but rather use insertBefore instead of appendChild:

IXMLDOMNode docData =
thisXDocument.DOM.selectSingleNode("//my:DocumentsData");

IXMLDOMElement proc = thisXDocument.DOM.createElement("Process");

docData.appendChild(proc);

Kjell-Sverre Jerijærvi said...

Also see these four .NET FW based options:

http://enterprise-solutions.swits.net/blog/?p=51