Data Binding with Repeating Items and Complex Tables in Print Forms
In my live sessions about print form adaptation with the Adobe LiveCycle Designer, data binding is a frequently requested and crucial topic. Especially in scenarios with complex tables and multi-level data structures. That’s why I decided to underline the sessions with some blog posts.
Most topics of the live sessions and blogs focus on the Adobe LiveCycle Designer (ALD) itself. So, they are applicable for several SAP cloud solutions where Adobe Document Services (ADS) are used. For system-related topics, I refer to either SAP Sales/Service Cloud (in former times SAP Cloud for Customer, C4C) or SAP Business ByDesign (ByD). Print form maintenance and output management is handled similarly in these two solutions.
I recommend reading this blog series in the order as stated below. You can find the registration information for my live sessions at the bottom of this blog.
List of print form blogs:
- How to get XML data for local preview of print forms without output history
- Data Binding Basics in Print Forms
- Data Binding with Repeating Items and Simple Tables in Print Forms
- Data Binding with Repeating Items and Complex Tables in Print Forms (this blog)
- Building Tables with Scripts
Introducing a Details Block for the Table
Now that we know how to bind data to repeating elements, let’s have a look at a more sophisticated use case. Most of the SAP delivered print forms come with some sort of details block for the table row. The following screenshot shows a simplified example of such a table:
Before we deep dive into the binding of repeating items within the details block (rowRemarkRow), we first need to discuss the table’s general architecture. Because this already holds some specialties and pitfalls, even if we just want to show the item’s ID in the details block.
When looking at the Hierarchy palette on the left, we notice that there is a new element between the table root (tblMain) and the main row (rowMain): rowSection. It allows us to have more than a single body row. Consequently the section element has two child elements: the main row and the new details block: rowRemarkRow, which is a subform.
If you haven’t worked with this kind of structure yet, you would probably think: “Ok, we have the rowSection element for the repetition and data binding and everything below works as usual…” But unfortunately that’s not the case: The rowSection only has the Repeat Section for Each Data Item flag. But we can’t bind it to the data model.
The following screenshot gives an overview on the binding palette of the three relevant elements:
The screenshot shows the desired state:
- rowSection allows the repetition and has this flag enabled
- rowMain is neither allowed to be repeated, nor bound to any data node
- rowRemarkRow is not allowed to be repeated, but it is bound to the $.DeliveryNote.Item[*] node
Let me explain in detail how that works: By allowing rowSection to be repeated, the two child elements rowMain and rowRemarkRow get repeated as well. That’s the reason why we only need to flag the section element for repetition. The more important and special part is the binding: We have only bound rowRemarkRow to repeating data items. That has two effects:
- rowSection gets repeated per Item[*], because rowRemarkRow is not allowed to be repeated, but its parent rowSection is. That leads to a repetition of the whole section structure with all child elements.
- Due to (1) rowMain gets repeated, but it’s not bound to any data item. And we can’t bind it. Neither rowMain, nor its cells. Instead we have to use little script snippets. Sounds tricky? Don’t worry!
IMPORTANT: Let’s first elaborate why we can’t bind rowMain. The reason is similar to the queue problem described in the simple table blog: If we bound rowMain to the same data node Item[*], rowMain and rowRemarkRow would both fetch the items alternatingly. That means rowMain would show data of the first item (item 10) while rowRemarkRow would already show data for the second item (20). The next instance of rowMain would show data of the third item (30) and the next instance of rowRemarkRow would show data of the fourth item (40) and so on and so forth. That’s why we can only bind one element to the repeating data node.
But how do we fill the cells of rowMain then?
Within rowRemarkRow we can add as many fields and subforms as we want. And not all of them need to be visible. That’s the trick: We can add bound, but hidden text fields for all pieces of information that we want to display in rowMain and place small script snippets to copy the content to there.
Let’s have a look at two examples: The item ID that shall be displayed for sample purposes in rowMain and in rowRemarkRow. And the product name that we only want to show in rowMain.
The text field txtItemID within rowRemarkRow can be bound to $.ItemID as usual. In order to make the main row’s colIndex field show the item ID as well, we place a script inside the initialize event of colIndex. The script is only a single line:
I refer to this as the pull approach, because the script is placed in the target element and we are pulling the data from its source: txtItemID (where it is bound). We could also place the script inside the txtItemID field and push the content to the colIndex field. However, I recommend the pull approach, because that makes it easier for someone else to find the source of the content: If you are looking at the print form for the first time and want to know where the content of the field colIndex is coming from, I’m sure you start by selecting the field, check the binding and if that’s empty, you look at its scripts…
The setup for txtProductName is very similar. We bind it to $.ProductName and write the script for colProduct. Because we don’t want to display the product name inside the details block, we can set the field’s presence to hidden.
Before we jump to the next topic, let me share another thought of binding the main row instead of the details block. In the first place this would work. At least we wouldn’t run into the queue problem. However, we would have to place additional hidden fields somewhere inside the main row (rowMain) to bind them and copy all required content from there to the details block (rowRemarkRow). Although this is technically possible, I don’t recommend this approach, as placing invisible content into the main row can lead to problems when working with the table in the Designer.
This concludes the fundamentals of working with more complex tables that contain a details block and the appropriate data bindings. We will extend the use of the details block in the following sections.
Extending the Details Block with Repeating Subforms
Now that we have the details block working basically, we can concentrate on the next stage: Implementing further repeating items on that level. For this use case we start with a pretty straight forward one and look at an advanced case in the second step. Let’s begin with the easier one: Notes.
Implementing Notes (TextCollection)
SAP Sales/Service Cloud and SAP Business ByDesign offer different ways of storing notes (multi-line text fields) in the front end, depending on the requirements of the business object where they are implemented. Sometimes you can only store a single entry, sometimes you can see the notes history. Sometimes they are language independent, sometimes you can enter the text in different languages, and so on. A common characteristic that they all have in common is the note type, such as Customer Information, Internal Comment, Additional External Comment, etc.
We have to keep these aspects in mind when working with notes in forms. Depending on the business object the print form is based on, we will find different note types in the data model and have to act accordingly when designing the form structure. That means we basically have to control which ones to show and where…
The following screenshot is an excerpt of the data XML containing several note entries:
Notes are typically wrapped in a TextCollection element. In this case we are looking at the notes of an item, hence the element is called ItemTextCollection. The Text element is the container for a single note instance. As we can see in the screenshot, we have three of them:
- Once with TypeCode 10024 which stands for Customer Information and usually contains remarks for the customer. In the case of the delivery note, this could originate from preceding documents like a sales quote or sales order.
The actual note content is contained within the ContentText element. For this note we also have a CreationDateTime.
- Twice with TypeCode 10043 which represents Logistics Execution Information, i.e. hints for the logistics team/the freight forwarder. This note has an additional languageCode attribute for the ContentText element. That means it can be added in several languages and within the print form we need to handle, if everything should be shown or only the current document language, possibly with fallback, etc.
Well, working with notes can quickly become sophisticated when paying attention to all the details, right? Before we continue with the print form design for displaying the notes, let me add two more thoughts that might have come to your mind as well while reading:
- In contrast to many other places where we deal with codes, we do not see an additional “…Name”-element for the TypeCode that reveals what the respective code stands for.
While there are some technical approaches how to get a comprehensive list (e.g. SAP Cloud Applications Studio: Repository Explorer; QueryCodeListIn web service, OData services; Analytics Documentation), the easiest way to find out what each type code stands for is the typical approach for testing: Simply create a sample document and fill all fields with different values and review the XML.
- You may wonder why the consequent lines start at the very left of the XML. That’s important because we don’t use any special strings (like \n or \t) within the XML to represent line breaks or tabs. That means, if you indent the content within the XML it will be reflected in the generated PDF as well.
Ok, now let’s go ahead with the print form design! The structure is shown in the introduction screenshot above (Hierarchy palette). Although I have already mentioned that it might not always make sense to simply show all notes, we will nevertheless start with that case, as it’s the easiest one.
We need at least one surrounding subform that is bound to $.ItemTextCollection.Text[*] and is allowed to repeat. Here, we have to pay attention again that the binding path should not contain anything pointing to Item[*] itself, as this would mean that the structure of the subforms/table or its binding is not correct. Within this subform we can place a text field, bound to $.ContentText. As we are targeting multi-line text, we should maintain the following properties for the text field:
If Allow Page Breaks within Content cannot be enabled for the text field, you have to make sure that all parent elements allow page breaks.
Filtering Notes (TextCollection)
As already mentioned, in many cases it might not be desired to show all notes and we need to filter or hide some of them. Filtering and hiding describes the two approaches that we can use for that task pretty well.
In the first place we can use filter expressions in the data binding to restrict the notes to specific types. We could do that by replacing the binding $.ItemTextCollection.Text[*] of the surrounding subform with something like that:
- $.ItemTextCollection.Text.[TypeCode == “10024”]
- $.ItemTextCollection.Text.[TypeCode == “10024” | TypeCode == “10043”]
The second expression demonstrates that we can also combine multiple filters with logical operators like | for or and & for and, given there are more than the two mentioned type codes and we only want to show two of them.
IMPORTANT: Once more, filter expressions within the data binding come in handy. However, you can only use them for filtering based on elements that always appear for every repeating element! In the example above we might want to filter for the languageCode to show only English texts. Although we can address attributes with the filter expressions, languageCode is not part of every ContentText‘s occurrence. The customer information (TypeCode 10024) for instance, does not have it. If you included it in the filter expression generating the form would fail and the log would tell you something like:
Malformed SOM expression: $.ItemTextCollection.Text.[ContentText.languageCode == "EN"] Error: accessor 'ContentText.languageCode' is unknown. End of processing: Failure
You can test this behavior by using a data XML that only contains the two logistics execution notes that have the languageCode. For this case the expression would work. But as we can’t influence which elements appear by default in the XML, we have to deal with this case and find another solution. The alternative to filtering data within the binding path is simply hiding instances that we don’t need.
For this, we need additional text fields that we bind to the respective elements in the data model. In our example screenshot we already have two fields:
With those fields, we can add a script to the initialize event of the surrounding subform (frmNotes) and write a small script to hide for instance the German version of the logistics execution note:
Both the filter expressions in data binding and the script approach can be combined as you need them and as the data model allows it.
Let’s have a look at the resulting PDF. The following screenshot concludes the example of implementing and filtering notes (TextCollections).
Implementing Serial IDs
The second example for special data binding use cases can also be found in the DeliveryNote when using identified stock in SAP Business ByDesign: It’s the list of serial IDs on item level. I don’t know by heart, if we can find such a structure in SAP Sales/Service Cloud. That’s why I use the ByD’s delivery note for the data binding series.
Let’s begin as usual with a look at the data model:
In contrast to the TextCollections the structure doesn’t look too complicated, right? However, the devil is in the detail: Have you already modeled the form structure in your mind with a repeating subform bound to the repeating item? Well, that’s the specialty here: In contrast to earlier examples, the repeating element directly contains the text.
Let’s examine our options:
First of all: We can’t bind subforms to text elements. That means using a subform which is enabled for repetition and bound to the MaterialSerialID[*] elements is not possible. And if it was, how should we bind the text field then?
Another approach could be a text field without a surrounding subform, bound to the MaterialSerialID[*] elements. However, we can’t enable repetition for text fields. With this approach we would end up seeing the first serial ID of each item and that’s it.
The truth lies in between these two ways. We have to bind the text field to the repeating item $.MaterialSerialID[*] and place it in a subform that is allowed to be repeated. This solution is similar to the structure that we introduced for the details block with the rowSection and rowRemarkRow controls where one element is bound and the parent is allowed to be repeated.
The screenshot above shows the solution. The subform (frmSerialIDWrapper) and the text field (txtSerialID) within the red box are the relevant controls for displaying the serial IDs.
Well, on a closer look and comparison to the XML, you will notice that I have left out one detail: None of the binding paths mentioned so far included the Material node, which is the parent for the repeating MaterialSerialID elements. The sample XML above does not reveal it, but this node can also occur multiple times. That’s one of the reasons why the form structure shown in the screenshot has some more parent subforms above the frmSerialIDWrapper. Let’s have a look at a more detailed example:
The business scenario for the example of the screenshot above would be a delivery note where item 10 contains five items with assigned serial numbers that originate from two different batches (three and two pieces). The Material[*] node reflects the batch level here. The first example didn’t use batches and hence all five pieces were included in a single Material node. In the designer we reflect this with the subform frmMaterial that is bound to $.Material[*]. That’s the final missing piece of the puzzle.
After I have mentioned the binding details of the serial ID scenario bit by bit, let me summarize it again and at the same time give an overview on all relevant fields and subforms that we have used to implement the serial ID and the notes scenario:
The rest of the subforms within the material/serial IDs block were added to create the layout where the caption “SerialIDs:” is shown on the left and the actual serial IDs on the right, together with the batch ID as headline if there is one.
The final result would look something like this as rendered PDF:
Testing Print Forms and Troubleshooting
Over the last blog post(s) we have covered several ways of binding and displaying data for repeating items. This topic error-prone where small mistakes can have a huge impact. Therefore it’s vital to test all implemented or modified features! And an important detail for that is using suitable sample data and paying attention details!
Typical example: You are supposed to modify a print form and receive a sample XML for testing that contains exactly a single item. As long as you only modify the form header this might be ok. But as soon as you start working on the items table you should request an example with at least two, better three or more items. In the best case with different values per item!
If you feel confident enough to work inside the XML, you can also modify the sample data XML right away. This allows you to easily test how the form looks under different circumstances, e.g. with longer texts or with more repeatable items than the original contained without having to visit the system again.
To conclude this topic and also this blog itself, let me show you two examples where the data binding contains mistakes and see how they can affect the final appearance:
The left example shows data of item 20 in the details block rendered right underneath of item 10. That happens if you accidentally bind the main row in addition to the details block to the Item[*] node. The result is the main row and the details block fetching instances from the queue alternatingly. If there were three items in the sample data, you could clearly see item 30 coming next and would be warned. If you only concentrate on some data showing up at all without paying attention to the details, you would probably not notice it.
In the example on the right the main row’s Serial No. field has been bound to the serial ID list in addition to the one in the details block. This results in the first serial number being displayed in the main row and vanished from the queue. The details block then starts with the second one. In this example, we notice this problem immediately at the first view, because our serial numbers are counting upwards and we are showing the serial ID as a visible field in the main row. Imagine the main row’s serial ID field was hidden and the serial numbers look more random. In this case you would only spot this problem by looking very closely at the sample data.
These two examples demonstrate that the biggest problem is not when the form doesn’t show data at all. The most dangerous case is when it’s working partially. That’s why testing with suitable test data and paying attention to details is important. 😉
That concludes the data binding part of this blog series. If you have any questions, feel free to ask them in my live sessions!
Building Tables with Scripts
Another blog post on building tables by scripting (completely and partially combined with data binding) will follow some day when I have time to write it. Until then you can visit my live sessions where this topic appears on the agenda every once in a while. Hints on how to register can be found below.
As already mentioned at the beginning of this post, I’m delivering monthly live sessions with a changing agenda on the topic of print form adaptation. If you are interested and would like to join, here are some details:
The live session is available on SAP Learning Hub. You need a subscription with access to the SAP Customer Experience learning resources in order to register for the live session.
There are two ways to access to the registration page of the live session (as well as the schedule of the next occurrences):
- You can use the following direct link, but please remember you need to be logged in on SAP Learning Hub first to use the link!
Direct link: Live Sessions – SAP Customer Experience: C4C Extensibility
- Alternatively you can go to SAP Learning Hub, and search for “Print Form Adaptation with Adobe LiveCycle Designer”. Look for a webinar with the headline “Live Sessions – SAP Customer Experience: C4C Extensibility” and the code: (LS_CX_C4CEXT_EN / EXPERT_LED) under “Learning Content”
If you want to get informed about the next blogs of this series or updates on the live session, please follow this blog post by clicking on the green “follow” button on the left. You can also follow me Felix Wyskocil for more blog posts about SAP Customer Experience Solutions – Integration and Extensibility topics.