Back to How-Tos

Cloning your way through nested tables

This how-to with accompanying sample explains how to programatically generate a detail table including nested items, in the most efficient way possible. The method used here is generally refered to as Cloning, since we create copies of the tables and rows, editing them as we go along.

The Data and Requirements

The idea of the nested tables is that you have data that has more than 2 levels. A "regular" transactional documents is:

Record
 - Field1
 - Field2
 - Field3
 - DetailTable[0]
	 - Field1
	 - Field2
 - DetailTable[1]
	 - Field1
	 - Field2
 - DetailTable[2]
	 - Field1
	 - Field2

However, a nested table would be:

Record
 - Field1
 - Field2
 - Field3
 - DetailTable[0]
	 - Field1
	 - Nested[0]
		 - Subfield1
		 - Subfield2
	 - Nested[1]
		 - Subfield1
		 - Subfield2
 - DetailTable[1]
	 - Field1
	 - Nested[0]
		 - Subfield1
		 - Subfield2
 - DetailTable[2]
	 - Field1

This can be achieved inside the DataMapper easily with XML files, but other data types can definitely generate nested tables. The sort of data we're talking about are report, multi-service invoices, etc. In the sample provided in this how-to, an Account Status Report is sent to a reseller which contains data for multiple invoices, each with their own services.

The Basic Idea

So let's say we have this little tidbit of HTML in our page:

<table id="base" class="dynamic">
 <tr>
  <td>Row1</td>
  <td>Row2</td>
 </tr>
</table>

We could use the following script to create 2 copies of the table, with 5 copies of the row in each of these tables. For the purpose of the example, that the script is attached to an element right above the first table (like a paragraph).

// Get base table
const baseTable = query("#base");
// Create clone
const clone = baseTable.clone();
// Change the ID of the clone table (must be unique!)
clone.attr("id", "clone");
// Append the copy 
results.after(clone);

// loop on each table by selecting them with a class
query(".dynamic").each(function(table) {
  // select the first row, the TR, which is the first child of the table.
  const baseRow = table.children()[0];
  // loop 5 times, changing the row contents every time
  for(let i; i<=5; i++) {
    let rowClone = baseRow.clone();
    // Change text (normally with data from the sub-tables in the record!)
    rowClone.find("Row1").text("Row1-"+i);
    rowClone.find("Row2").text("Row2-"+i);
    table.append(rowClone);
  }
  // Remove the first row (as we're not filling it up)
  baseRow.remove();
});

This example has been greatly simplified for the purpose of this how-to and to attempt to simplify the concepts used in the sample template. It's clearly still a complex scripting even this simple - Dynamic Nested Tables are not for the faint of heart. If you've had any experience in web development however, you might feel comfortable here.

Now that we've seen how the script works, it's time to take a look at the actual files. You can download the following and run them (please note that Connect 1.5 or higher is required to view them).

In Connect 1.5.0, a minor issue causes margins in Preview to not correctly displayed. Updating to version 1.6.1 resolves this issue, which does not affect output either way.