Control Structures
Status: Partially implemented (v1.0)
Control structures enable dynamic template behavior based on data.
For Loop (Implemented ✅)
Iterates over array data, expanding rows downward for each element.
Syntax
<For each="variableName in dataPath">
<!-- content repeated for each element -->
</For>
Attributes
each (required)
- Type: String
- Format:
<variable> in <path> - Description: Defines the loop variable and data source
Components:
<variable>: Local variable name for the current iteration<path>: Dot-notation path to array in data context
How It Works
- Parser evaluates
<path>to get an array from data context - For each element in array:
- Creates local scope with
<variable>bound to current element - Renders content within
<For>tags - Advances cursor downward by number of rows generated
- Creates local scope with
- After loop completes, cursor is positioned after all generated content
Basic Example
Template:
<Grid>
| Name | Email |
</Grid>
<For each="user in users">
<Grid>
| {{ user.name }} | {{ user.email }} |
</Grid>
</For>
Data:
{
"users": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"},
{"name": "Charlie", "email": "charlie@example.com"}
]
}
Output:
A1: Name | B1: Email
A2: Alice | B2: alice@example.com
A3: Bob | B3: bob@example.com
A4: Charlie | B4: charlie@example.com
Loop Variables
Built-in variables available within <For> loops.
loop.index
- Type: Integer
- Description: Zero-based iteration index
- Range: 0 to (array.length - 1)
loop.number
- Type: Integer
- Description: One-based iteration number
- Range: 1 to array.length
loop.startRow
- Type: Integer
- Description: Starting row number for current iteration (absolute)
- Use case: Building cell references in formulas
loop.endRow
- Type: Integer
- Description: Ending row number for current iteration (absolute)
- Use case: Creating dynamic ranges
Example with Loop Variables
<Grid>
| # | Item | Quantity | Price | Total |
</Grid>
<For each="item in items">
<Grid>
| {{ loop.number }} | {{ item.name }} | {{ item.qty }} | {{ item.price }} | =C{{ loop.number + 1 }}*D{{ loop.number + 1 }} |
</Grid>
</For>
<Grid>
| | | | Total: | =SUM(E2:E{{ items.length + 1 }}) |
</Grid>
Nested Loops
Loops can be nested to handle hierarchical data structures.
Basic Nested Loop
<Grid>
| Category | Product | Price |
</Grid>
<For each="category in categories">
<Grid>
| {{ category.name }} | | |
</Grid>
<For each="product in category.products">
<Grid>
| | {{ product.name }} | {{ product.price }} |
</Grid>
</For>
</For>
Data:
{
"categories": [
{
"name": "Electronics",
"products": [
{"name": "Laptop", "price": 1200},
{"name": "Mouse", "price": 25}
]
},
{
"name": "Books",
"products": [
{"name": "Novel", "price": 15},
{"name": "Textbook", "price": 80}
]
}
]
}
Nested Loop Variables
In nested loops, each level has its own loop variable:
<For each="category in categories">
<Grid>
| Category {{ loop.number }}: {{ category.name }} |
</Grid>
<For each="item in category.items">
<Grid>
| Item {{ loop.number }} in category {{ loop.parent.number }}: {{ item.name }} |
</Grid>
</For>
</For>
Note: loop.parent access is planned for future versions.
Advanced For Loop Patterns
With Formulas
<Grid>
| Product | Q1 | Q2 | Q3 | Q4 | Total |
</Grid>
<For each="product in products">
<Grid>
| {{ product.name }} | {{ product.q1 }} | {{ product.q2 }} | {{ product.q3 }} | {{ product.q4 }} | =SUM(B{{ loop.number + 1 }}:E{{ loop.number + 1 }}) |
</Grid>
</For>
<Grid>
| Total | =SUM(B2:B{{ products.length + 1 }}) | =SUM(C2:C{{ products.length + 1 }}) | =SUM(D2:D{{ products.length + 1 }}) | =SUM(E2:E{{ products.length + 1 }}) | =SUM(F2:F{{ products.length + 1 }}) |
</Grid>
With Conditional Content
<For each="order in orders">
<Grid>
| Order #{{ order.id }} | {{ order.date }} | ${{ order.total }} | {{ order.status }} |
</Grid>
<!-- Future: Will be replaced with <If> when implemented -->
<!-- For now, use empty expressions or conditional data preparation -->
</For>
With Multi-Row Content
<For each="invoice in invoices">
<!-- Invoice header -->
<Grid>
| Invoice #{{ invoice.number }} | | Date: {{ invoice.date }} |
</Grid>
<Merge range="A{{ loop.startRow }}:B{{ loop.startRow }}" />
<!-- Invoice items -->
<For each="item in invoice.items">
<Grid>
| {{ item.name }} | {{ item.qty }} | ${{ item.price }} |
</Grid>
</For>
<!-- Invoice total -->
<Grid>
| | Total: | ${{ invoice.total }} |
</Grid>
<!-- Spacer row -->
<Grid>
| | | |
</Grid>
</For>
If / Else (Conditional Rendering)
Conditionally render content based on boolean expressions.
Status: Planned for v1.1 (not yet implemented in goxcel v1.0)
Syntax
<If cond="expression">
<!-- rendered if expression is truthy -->
</If>
With Else:
<If cond="expression">
<!-- rendered if truthy -->
<Else>
<!-- rendered if falsy -->
</Else>
</If>
Attributes
cond (required)
- Type: String (expression)
- Description: Expression evaluated to boolean
Truthy values:
- Non-zero numbers:
1,-5,3.14 - Non-empty strings:
"hello","false" - Boolean true:
true - Non-empty arrays:
[1, 2] - Non-null objects:
{"key": "value"}
Falsy values:
- Zero:
0,0.0 - Empty string:
"" - Boolean false:
false - Null:
null - Undefined:
undefined - Empty array:
[]
Examples
Simple conditional:
<If cond="showHeader">
<Grid>
| Company Name | Report Date |
</Grid>
</If>
<Grid>
| Data | Data |
</Grid>
With Else:
<If cond="isPremium">
<Grid>
| Premium Customer | Discount: 20% |
</Grid>
<Else>
<Grid>
| Standard Customer | Discount: 5% |
</Grid>
</Else>
</If>
Comparison operators:
<If cond="total > 1000">
<Grid>
| Discount Applied | 10% |
</Grid>
</If>
<If cond="status == 'paid'">
<Grid>
| Payment Status | PAID |
</Grid>
<Else>
<Grid>
| Payment Status | PENDING |
</Grid>
</Else>
</If>
With nested paths:
<If cond="user.subscription.isPremium">
<Grid>
| Welcome, Premium Member! |
</Grid>
</If>
Combining with For Loops
<For each="item in items">
<If cond="item.inStock">
<Grid>
| {{ item.name }} | In Stock | ${{ item.price }} |
</Grid>
<Else>
<Grid>
| {{ item.name }} | Out of Stock | - |
</Grid>
</Else>
</If>
</For>
Nested Conditionals
<If cond="hasData">
<If cond="dataType == 'sales'">
<Grid>
| Sales Report |
</Grid>
<Else>
<Grid>
| Other Report |
</Grid>
</Else>
</If>
<Else>
<Grid>
| No Data Available |
</Grid>
</Else>
</If>
Switch / Case (Future)
Status: Under consideration for v2.0+
Multiple conditional branches based on a value:
<Switch value="status">
<Case match="pending">
<Grid>| Status: Pending |</Grid>
</Case>
<Case match="approved">
<Grid>| Status: Approved |</Grid>
</Case>
<Case match="rejected">
<Grid>| Status: Rejected |</Grid>
</Case>
<Default>
<Grid>| Status: Unknown |</Grid>
</Default>
</Switch>
While Loop (Future)
Status: Under consideration (low priority)
Conditional looping:
<While cond="index < maxRows">
<Grid>
| Row {{ index }} |
</Grid>
<!-- Note: Need mechanism to update 'index' -->
</While>
Challenges:
- Requires mutable state
- Risk of infinite loops
- Complex to implement safely
Alternative: Pre-process data to create finite arrays, then use <For>
Best Practices
1. Keep Loops Simple
Good:
<For each="item in items">
<Grid>
| {{ item.name }} | {{ item.value }} |
</Grid>
</For>
Avoid:
<For each="item in items">
<For each="sub in item.subs">
<For each="detail in sub.details">
<!-- Too deeply nested -->
</For>
</For>
</For>
2. Use Descriptive Variable Names
Good:
<For each="employee in employees">
<For each="product in products">
<For each="transaction in transactions">
Avoid:
<For each="i in items">
<For each="x in list">
<For each="e in data">
3. Pre-calculate Complex Logic
Instead of complex conditionals in template, prepare data:
Better approach:
{
"items": [
{"name": "A", "displayPrice": "$10.00", "showDiscount": true},
{"name": "B", "displayPrice": "$20.00", "showDiscount": false}
]
}
<For each="item in items">
<Grid>
| {{ item.name }} | {{ item.displayPrice }} |
</Grid>
</For>
4. Document Complex Loops
<!--
Generate invoice sections
Each invoice contains:
- Header row with invoice number
- Item rows (nested loop)
- Total row
- Blank separator
-->
<For each="invoice in invoices">
<!-- header -->
<Grid>| Invoice #{{ invoice.number }} |</Grid>
<!-- items -->
<For each="item in invoice.items">
<Grid>| {{ item.name }} | ${{ item.price }} |</Grid>
</For>
<!-- total -->
<Grid>| Total | ${{ invoice.total }} |</Grid>
<Grid>| | |</Grid>
</For>
5. Handle Empty Arrays
Prepare data to always have valid arrays:
{
"items": [] // Empty array instead of null/undefined
}
Or use conditionals (when available):
<If cond="items.length > 0">
<For each="item in items">
<Grid>| {{ item.name }} |</Grid>
</For>
<Else>
<Grid>| No items found |</Grid>
</Else>
</If>
Error Handling
Invalid Data Path
If dataPath doesn't exist or isn't an array:
- Behavior: Loop is skipped (zero iterations)
- Warning: Implementation should log warning
Null/Undefined Values
If data path resolves to null or undefined:
- Behavior: Treated as empty array (zero iterations)
- No error: Graceful degradation
Non-Array Values
If data path resolves to non-array value:
- Behavior: Implementation-dependent
- goxcel v1.0: Treats as single-element array
- Future: May throw error or skip
Performance Considerations
Large Datasets
For loops generate rows during rendering. Very large arrays can:
- Increase memory usage
- Slow rendering
- Create huge Excel files
Recommendations:
- Limit arrays to reasonable sizes (< 10,000 rows)
- Use pagination for large datasets
- Consider streaming mode (future feature)
Nested Loops
Each nesting level multiplies row count:
- 100 categories × 50 products = 5,000 rows
- 10 invoices × 20 items = 200 rows
Watch for:
- Cartesian products (unintended)
- Deep nesting (>3 levels)
Related Topics
- Expressions - Variable interpolation syntax
- Data Context - How data is structured and accessed
- Rendering Semantics - How loops affect rendering
Implementation Status
| Feature | Status | Version |
|---|---|---|
<For> loops | ✅ Implemented | v1.0 |
Loop variables (loop.index, loop.number) | ✅ Implemented | v1.0 |
| Nested loops | ✅ Implemented | v1.0 |
| Array iteration | ✅ Implemented | v1.0 |
| Map/object iteration | ✅ Implemented | v1.0 |
loop.startRow, loop.endRow | ⏳ Planned | v1.1 |
<If> / <Else> | ⏳ Planned | v1.1 |
<Switch> / <Case> | 💭 Consideration | v2.0+ |
Legend: ✅ Implemented | ⏳ Planned | 💭 Under consideration
| <While> | 💭 Under consideration | TBD |