Skip to content

Commit a94c0b2

Browse files
Merge pull request #473 from telerik/new-kb-clone-repeating-section-content-control-radwordsprocessing-e67d04396d9a4684b5fb602ba05f963a
Added new kb article clone-repeating-section-content-control-radwordsprocessing
2 parents 8ca2994 + 5dfc0ad commit a94c0b2

File tree

4 files changed

+320
-0
lines changed

4 files changed

+320
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
---
2+
title: How to Clone and Populate Repeating Section Content Controls in RadWordsProcessing
3+
description: Learn how to programmatically clone and populate repeating section content controls in RadWordsProcessing documents.
4+
type: how-to
5+
page_title: How to Clone and Populate Repeating Section Content Controls in RadWordsProcessing
6+
slug: clone-repeating-section-content-control-radwordsprocessing
7+
tags: wordsprocessing, document, processing, content, controls, clone, repeating, section
8+
res_type: kb
9+
ticketid: 1668130
10+
---
11+
12+
## Environment
13+
14+
| Version | Product | Author |
15+
| --- | --- | ---- |
16+
| 2024.3.806| RadWordsProcessing |[Desislava Yordanova](https://www.telerik.com/blogs/author/desislava-yordanova)|
17+
18+
## Description
19+
20+
When working with MS Word documents that contain [Content Controls]({%slug wordsprocessing-model-content-controls%}), it's straightforward to map model properties to the appropriate content control. However, cloning a [RepeatingSection][Content Controls]({%slug wordsprocessing-model-content-controls%}#repeatingsection) with content controls inside presents a challenge as there seems to be no direct method to clone a repeating section content control along with its contents.
21+
22+
This KB article shows a sample approach how to duplicate the content controls inside a [RepeatingSection][Content Controls]({%slug wordsprocessing-model-content-controls%}#repeatingsection) in a Word document and populate them with data considering the mapped object.
23+
24+
|Original Document|Result Document|
25+
|----|----|
26+
|![Original Document](images/originalRepeatingSection.png)|![Result Document](images/resultRepeatingSection.png)|
27+
28+
## Solution
29+
30+
Let's have an Employee object defined below. We need to use the [RepeatingSection][Content Controls]({%slug wordsprocessing-model-content-controls%}#repeatingsection) to list all of the telephones associated with the respective Employee.
31+
32+
```csharp
33+
public class Employee
34+
{
35+
public int EmployeeId { get; set; }
36+
public string FirstName { get; set; }
37+
public string LastName { get; set; }
38+
public DateTime Dob { get; set; }
39+
public int WorkLocationId { get; set; }
40+
public WorkLocation WorkLocation { get; set; }
41+
public List<Telephone> Phones { get; set; } = new List<Telephone>();
42+
}
43+
44+
public class Telephone
45+
{
46+
public int Id { get; set; }
47+
public string Carrier { get; set; }
48+
public string PhoneNumber { get; set; }
49+
public int PhoneTypeId { get; set; }
50+
public TelephoneType PhoneType { get; set; }
51+
}
52+
53+
public class TelephoneType
54+
{
55+
public int Id { get; set; }
56+
public string Name { get; set; }
57+
58+
public override string ToString()
59+
{
60+
return this.Name;
61+
}
62+
}
63+
```
64+
To simulate the MS Word behavior of duplicating a repeating section with all the content controls within the section, you can follow the custom approach detailed below. This solution involves generating table rows based on the number of instances in a collection, such as a list of telephone numbers associated with an employee. Note that this solution is custom and may require adjustments to fit specific requirements.
65+
66+
1. **Enumerate Content Controls and Identify Repeating Sections**: Iterate through all content controls in the document and identify those that are type `SdtType.RepeatingSection`.
67+
68+
2. **Clone Repeating Sections Programmatically**: For each identified repeating section, dynamically create and populate new table rows based on the data in the collection associated with the repeating section.
69+
70+
3. **Remove Original Repeating Section Content Controls**: After cloning and populating the new sections, remove the original repeating section content controls from the document to avoid duplication.
71+
72+
The following code snippet demonstrates how to populate a template document with dynamic data by cloning repeating section content controls:
73+
74+
```csharp
75+
static void Main(string[] args)
76+
{
77+
DocxFormatProvider provider = new DocxFormatProvider();
78+
var (employees, _, _) = MockDataGenerator.GenerateMockData();
79+
List<Employee> allEmployees = employees;
80+
int EmployeeId = 2;
81+
Employee employee = allEmployees.FirstOrDefault(e => e.EmployeeId == EmployeeId);
82+
RadFlowDocument document;
83+
using (Stream stream = File.OpenRead("SampleDocContentControls.docx"))
84+
{
85+
document = provider.Import(stream);
86+
}
87+
88+
PopulateTemplate(document, employee);
89+
string outputFilePath = "Sample.docx";
90+
using (Stream output = File.OpenWrite(outputFilePath))
91+
{
92+
provider.Export(document, output);
93+
}
94+
95+
Process.Start(new ProcessStartInfo() { FileName = outputFilePath, UseShellExecute = true });
96+
}
97+
private static void SetContentControlValue(SdtRangeStart contentControl, string value)
98+
{
99+
var paragraph = contentControl.Paragraph;
100+
if (paragraph != null)
101+
{
102+
paragraph.Inlines.Clear();
103+
paragraph.Inlines.AddRun(value);
104+
}
105+
}
106+
private static object GetValueFromEmployee(object instance, string tag)
107+
{
108+
string propertyPath = tag.Contains(".") ? tag.Substring(tag.LastIndexOf(".")+1) : tag;
109+
110+
return GetValueFromObject(instance, propertyPath);
111+
}
112+
113+
private static object GetValueFromObject(object obj, string propertyPath)
114+
{
115+
var properties = propertyPath.Split('.');
116+
object value = obj;
117+
118+
foreach (var prop in properties)
119+
{
120+
if (value == null)
121+
{
122+
return string.Empty;
123+
}
124+
125+
// Handle collection properties
126+
if (value is IEnumerable<object> collection)
127+
{
128+
if (int.TryParse(prop, out int index))
129+
{
130+
value = collection.ElementAtOrDefault(index);
131+
if (value == null)
132+
{
133+
return string.Empty;
134+
}
135+
}
136+
else
137+
{
138+
return string.Join(", ", collection.Select(item => GetValueFromObject(item,
139+
string.Join(".", properties.Skip(Array.IndexOf(properties, prop))))));
140+
}
141+
continue;
142+
}
143+
144+
var propInfo = value.GetType().GetProperty(prop);
145+
if (propInfo == null)
146+
{
147+
return string.Empty;
148+
}
149+
value = propInfo.GetValue(value);
150+
}
151+
return value;
152+
}
153+
154+
private static void PopulateTemplate(RadFlowDocument document, Employee employee)
155+
{
156+
var contentControls = document.EnumerateChildrenOfType<SdtRangeStart>().ToList();
157+
158+
foreach (var contentControl in contentControls)
159+
{
160+
if (string.IsNullOrEmpty(contentControl.SdtProperties.Tag))
161+
continue;
162+
163+
if (contentControl.SdtProperties.Type == SdtType.RepeatingSection)
164+
{
165+
var value = GetValueFromEmployee(employee, contentControl.SdtProperties.Tag);
166+
List<Telephone> telephones = value as List<Telephone>;
167+
if (telephones==null)
168+
{
169+
continue;
170+
}
171+
TableRow startRow = contentControl.Paragraph.Parent.Parent as TableRow;
172+
TableRow endRow = contentControl.End.Paragraph.Parent.Parent as TableRow;
173+
Table repeatingTable = startRow.Table;
174+
int startIndex = repeatingTable.Rows.IndexOf(startRow);
175+
int endIndex = repeatingTable.Rows.IndexOf(endRow);
176+
foreach (Telephone tel in telephones)
177+
{
178+
for (int k = startIndex; k < endIndex + 1; k++)
179+
{
180+
TableRow newRow = repeatingTable.Rows.AddTableRow();
181+
TableCell cell = newRow.Cells.AddTableCell();
182+
cell.PreferredWidth = repeatingTable.Rows[k].Cells[0].PreferredWidth;
183+
Paragraph p = repeatingTable.Rows[k].Cells[0].Blocks[0] as Paragraph;
184+
string telPropName = (p.Inlines.Last() as Run).Text;
185+
cell.Blocks.AddParagraph().Inlines.AddRun(telPropName);
186+
cell = newRow.Cells.AddTableCell();
187+
cell.PreferredWidth = repeatingTable.Rows[k].Cells[1].PreferredWidth;
188+
var telValue = GetValueFromEmployee(tel, telPropName.Replace(" ", string.Empty));
189+
cell.Blocks.AddParagraph().Inlines.AddRun(telValue?.ToString() ?? string.Empty);
190+
}
191+
}
192+
for (int z = startIndex; z < endIndex + 1; z++)
193+
{
194+
repeatingTable.Rows.RemoveAt(0);
195+
}
196+
RadFlowDocumentEditor editor = new RadFlowDocumentEditor(document);
197+
editor.RemoveStructuredDocumentTag(contentControl,false);
198+
}
199+
else
200+
{
201+
202+
var value = GetValueFromEmployee(employee, contentControl.SdtProperties.Tag);
203+
SetContentControlValue(contentControl, value?.ToString() ?? string.Empty);
204+
}
205+
}
206+
}
207+
```
208+
209+
The MockDataGenerator is responsible for returning sample data:
210+
211+
```csharp
212+
public static class MockDataGenerator
213+
{
214+
public static (List<Employee> Employees, List<WorkLocation> WorkLocations,
215+
List<TelephoneType> PhoneTypes) GenerateMockData()
216+
{
217+
// Create WorkLocations
218+
var workLocations = new List<WorkLocation>
219+
{
220+
new WorkLocation { Id = 1, Name = "Los Angeles", Description = "West Coast HQ" },
221+
new WorkLocation { Id = 2, Name = "New York", Description = "East Coast Office" },
222+
new WorkLocation { Id = 3, Name = "Boston", Description = "Research Center" }
223+
};
224+
225+
// Create PhoneTypes
226+
var phoneTypes = new List<TelephoneType>
227+
{
228+
new TelephoneType { Id = 1, Name = "Mobile" },
229+
new TelephoneType { Id = 2, Name = "Office" },
230+
new TelephoneType { Id = 3, Name = "Home" }
231+
};
232+
233+
// Create Employees with Phones
234+
var employees = new List<Employee>
235+
{
236+
new Employee
237+
{
238+
EmployeeId = 1,
239+
FirstName = "John",
240+
LastName = "Doe",
241+
Dob = new DateTime(1985, 5, 15),
242+
WorkLocationId = 1,
243+
WorkLocation = workLocations[0],
244+
Phones = new List<Telephone>
245+
{
246+
new Telephone { Id = 1, Carrier = "Verizon", PhoneNumber = "310-555-0101",
247+
PhoneTypeId = 1, PhoneType = phoneTypes[0] },
248+
new Telephone { Id = 2, Carrier = "AT&T", PhoneNumber = "310-555-0102",
249+
PhoneTypeId = 2, PhoneType = phoneTypes[1] }
250+
}
251+
},
252+
new Employee
253+
{
254+
EmployeeId = 2,
255+
FirstName = "Jane",
256+
LastName = "Smith",
257+
Dob = new DateTime(1990, 8, 22),
258+
WorkLocationId = 2,
259+
WorkLocation = workLocations[1],
260+
Phones = new List<Telephone>
261+
{
262+
new Telephone { Id = 3, Carrier = "T-Mobile", PhoneNumber = "212-555-0103",
263+
PhoneTypeId = 1, PhoneType = phoneTypes[0] },
264+
new Telephone { Id = 4, Carrier = "Sprint", PhoneNumber = "212-555-0104",
265+
PhoneTypeId = 3, PhoneType = phoneTypes[2] },
266+
new Telephone { Id = 5, Carrier = "AT&T", PhoneNumber = "212-555-0105",
267+
PhoneTypeId = 2, PhoneType = phoneTypes[1] }
268+
}
269+
},
270+
new Employee
271+
{
272+
EmployeeId = 3,
273+
FirstName = "Robert",
274+
LastName = "Johnson",
275+
Dob = new DateTime(1988, 3, 10),
276+
WorkLocationId = 3,
277+
WorkLocation = workLocations[2],
278+
Phones = new List<Telephone>
279+
{
280+
new Telephone { Id = 6, Carrier = "Verizon", PhoneNumber = "617-555-0106",
281+
PhoneTypeId = 1, PhoneType = phoneTypes[0] },
282+
new Telephone { Id = 7, Carrier = "AT&T", PhoneNumber = "617-555-0107",
283+
PhoneTypeId = 2, PhoneType = phoneTypes[1] }
284+
}
285+
},
286+
new Employee
287+
{
288+
EmployeeId = 4,
289+
FirstName = "Maria",
290+
LastName = "Garcia",
291+
Dob = new DateTime(1992, 11, 28),
292+
WorkLocationId = 1,
293+
WorkLocation = workLocations[0],
294+
Phones = new List<Telephone>
295+
{
296+
new Telephone { Id = 8, Carrier = "T-Mobile", PhoneNumber = "310-555-0108",
297+
PhoneTypeId = 1, PhoneType = phoneTypes[0] },
298+
new Telephone { Id = 9, Carrier = "Verizon", PhoneNumber = "310-555-0109",
299+
PhoneTypeId = 2, PhoneType = phoneTypes[1] },
300+
new Telephone { Id = 10, Carrier = "AT&T", PhoneNumber = "310-555-0110",
301+
PhoneTypeId = 3, PhoneType = phoneTypes[2] }
302+
}
303+
}
304+
};
305+
306+
return (employees, workLocations, phoneTypes);
307+
}
308+
}
309+
```
310+
311+
Usually, the content controls are mostly used by the editor controls like MS Word that allow the end user fill the required data. In case you are planning to edit the document programmatically, the [MailMerge]({%slug radwordsprocessing-editing-mail-merge%}) functionality should be also considered as an appropriate solution.
312+
313+
314+
## See Also
315+
316+
- [Content Controls]({%slug wordsprocessing-model-content-controls%})
317+
- [Populate a Table with Data using Nested Mail Merge Functionality]({%slug populate-table-data-mail-merge%})
318+
- [Generating a Word Document with Data Using MailMerge in RadWordsProcessing]({%slug generate-doc-template-and-populate-with-collection-data-mail-merge%})
319+
- [Mail Merge Functionality in RadWordsProcessing]({%slug radwordsprocessing-editing-mail-merge%})
Loading
14.5 KB
Loading

libraries/radwordsprocessing/model/content-controls/content-controls.md

+1
Original file line numberDiff line numberDiff line change
@@ -207,5 +207,6 @@ The __Text__ content control allows you to enter plain text. The text content co
207207

208208
* [Working with Content Controls]({%slug wordsprocessing-model-working-with-content-controls%})
209209
* [Generating Dynamic DOCX Documents with Tables and CheckBoxes using RadWordsProcessing]({%slug dynamic-docx-document-generation-radwordsprocessing%})
210+
* [How to Clone and Populate Repeating Section Content Controls in RadWordsProcessing]({%slug clone-repeating-section-content-control-radwordsprocessing%})
210211

211212

0 commit comments

Comments
 (0)