Tutorial 3: Advanced Templating and Data Manipulation
Objective
Enhance our Equipment Request form with advanced Tera templating features, conditional logic, calculations, and better data presentation.
Advanced Tera Template Features - part 1
1. Conditional Logic
Tera supports various conditional statements. Let's enhance our template with better conditional logic:
<!-- replace "request_info" section in header.html -->
<div class="info_row">
<span>Request Number: {{ data.requisition.requisitionNumber }}</span>
<span>Date: {{ data.requisition.createdDatetime | date(format="%d/%m/%Y") }}</span>
</div>
<div class="request_info">
{% if data.requisition.status == "DRAFT" %}
<div class="status_banner draft">DRAFT - Not Yet Submitted</div>
{% elif data.requisition.status == "SENT" %}
<div class="status_banner sent">SUBMITTED - Awaiting Approval</div>
{% elif data.requisition.status == "FINALISED" %}
<div class="status_banner approved">APPROVED</div>
{% else %}
<div class="status_banner">Status: {{ data.requisition.status }}</div>
{% endif %}
</div>
</div>
<!-- Conditional supplier information -->
{% if data.requisition.otherParty %}
<div class="supplier_info">
<h3>Supplier Information</h3>
<p><strong>{{ data.requisition.otherParty.name }}</strong></p>
<p>Code: {{ data.requisition.otherParty.code }}</p>
{% if data.requisition.otherParty.address1 %}
<p>{{ data.requisition.otherParty.address1 }}</p>
{% if data.requisition.otherParty.address2 %}
<p>{{ data.requisition.otherParty.address2 }}</p>
{% endif %}
{% endif %}
</div>
{% endif %}2. Loops and Filters
Enhance the items table with more sophisticated looping and filters:
<!-- update "items_table" section in template.html -->
<table class="items_table">
<thead>
<tr class="table_header">
<th>Line #</th>
<th>Item Code</th>
<th>Item Name</th>
<th>Requested Qty</th>
<th>Priority</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for line in data.requisition.lines.nodes %}
<tr class="item_row {% if loop.index is odd %}odd{% else %}even{% endif %}">
<td class="line_number">{{ loop.index }}</td>
<td>{{ line.item.code }}</td>
<td>{{ line.item.name }}</td>
<td class="quantity">{{ line.requestedQuantity | default(value=0) }}</td>
<td class="priority">
{% if line.requestedQuantity > 100 %} HIGH {% elif line.requestedQuantity > 50 %} MEDIUM {%
else %} LOW {% endif %}
</td>
<td class="status">
{% if line.requestedQuantity %} REQUESTED {% else %} PENDING {% endif %}
</td>
</tr>
{% endfor %} {% if data.requisition.lines.nodes | length == 0 %}
<tr>
<td colspan="6" class="no_items">No items requested</td>
</tr>
{% endif %}
</tbody>
</table>3. Macros for Reusable Code
Define macros for commonly used formatting:
<!-- At the top of template.html -->
{% macro formatDate(datetime) %} {% if datetime %} {{ datetime | date(format="%d/%m/%Y") }} {% else
%} N/A {% endif %} {% endmacro %} {% macro formatCurrency(amount) %} {% if amount %} ${{ amount |
round(precision=2) }} {% else %} $0.00 {% endif %} {% endmacro %} {% macro priorityBadge(quantity)
%} {% if quantity > 100 %}
<span class="priority high">HIGH</span>
{% elif quantity > 50 %}
<span class="priority medium">MEDIUM</span>
{% else %}
<span class="priority low">LOW</span>
{% endif %} {% endmacro %}
<!-- Using the macros -->
<div class="dates">
<p>Created: {{ self::formatDate(datetime=data.requisition.createdDatetime) }}</p>
<p>Updated: {{ self::formatDate(datetime=data.requisition.finalisedDatetime) }}</p>
</div>
<!-- In the table replace the "priority" line with -->
<td class="priority">{{ self::priorityBadge(quantity=line.requestedQuantity) }}</td>
Note that this requires that you add the new finalisedDatetime field to the requisition query in query.graphql
Enhanced CSS for New Features
Add these styles to style.css and then regenerate the form:
/* Status banners */
.status_banner {
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
font-weight: bold;
text-align: center;
}
.status_banner.draft {
background-color: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
}
.status_banner.sent {
background-color: #d1ecf1;
border: 1px solid #bee5eb;
color: #0c5460;
}
.status_banner.approved {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
/* Priority badges */
.priority {
padding: 3px 8px;
border-radius: 3px;
font-size: 8pt;
font-weight: bold;
}
.priority.high {
background-color: #dc3545;
color: white;
}
.priority.medium {
background-color: #ffc107;
color: black;
}
.priority.low {
background-color: #28a745;
color: white;
}
/* Table enhancements */
.item_row.odd {
background-color: #f8f9fa;
}
.item_row.even {
background-color: white;
}
.line_number {
text-align: center;
width: 50px;
font-weight: bold;
}
.cost,
.total {
text-align: right;
font-family: monospace;
}
.no_items {
text-align: center;
font-style: italic;
color: #666;
padding: 20px;
}
/* Summary section */
.summary {
margin-top: 30px;
padding: 20px;
border: 2px solid #333;
background-color: #f8f9fa;
}
.summary_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-top: 10px;
}
.summary_item {
display: flex;
justify-content: space-between;
padding: 8px;
background-color: white;
border: 1px solid #ddd;
border-radius: 3px;
}
.summary_item label {
font-weight: bold;
}
/* Supplier info box */
.supplier_info {
float: right;
width: 300px;
padding: 15px;
border: 1px solid #ddd;
background-color: #f9f9f9;
margin-left: 20px;
margin-bottom: 20px;
}
.supplier_info h3 {
margin-top: 0;
color: #333;
}
/* Print-specific adjustments */
@media print {
.status_banner {
border: 2px solid #333 !important;
background-color: white !important;
color: black !important;
}
.priority {
border: 1px solid #333 !important;
background-color: white !important;
color: black !important;
}
.summary_grid {
display: block;
}
.summary_item {
margin-bottom: 5px;
}
}Advanced Tera Template Features - part 2
4. Variables and Calculations
Use set and set_global for calculations in template.html:
<!-- Initialise totals -->
{% set_global total_items = 0 %} {% set_global high_priority_items = 0 %}
<!-- Add to <tr class="table_header"> section -->
<th>Est. Cost</th>
<th>Line Total</th>
<!-- Add at the start of the {% for line in data.requisition.lines.nodes %} loop -->
{% set_global total_items = total_items + line.requestedQuantity %} {% if line.requestedQuantity >
100 %} {% set_global high_priority_items = high_priority_items + 1 %} {% endif %} {% set
estimated_cost = 25.00 %}
<!-- This would come from item data -->
{% set line_total = line.requestedQuantity * estimated_cost %}
<!-- Add to <tr class="item_row"> section -->
<td class="cost">{{ self::formatCurrency(amount=estimated_cost) }}</td>
<td class="total">{{ self::formatCurrency(amount=line_total) }}</td>
<!-- Add summary section -->
<div class="summary">
<h3>Request Summary</h3>
<div class="summary_grid">
<div class="summary_item">
<label>Total Items:</label>
<span>{{ total_items }}</span>
</div>
<div class="summary_item">
<label>High Priority Items:</label>
<span>{{ high_priority_items }}</span>
</div>
<div class="summary_item">
<label>Total Lines:</label>
<span>{{ data.requisition.lines.nodes | length }}</span>
</div>
</div>
</div>Working with Complex Data Structures
When dealing with nested data, use dot notation and filters effectively:
<!-- Accessing nested data safely -->
{% if data.requisition.lines.nodes %} {% for line in data.requisition.lines.nodes %}
<!-- Safe access with defaults -->
<tr>
<td>{{ line.item.code | default(value="NO CODE") }}</td>
<td>{{ line.item.name | default(value="Unknown Item") }}</td>
<!-- Number formatting -->
<td>{{ line.requestedQuantity | round(precision=0) }}</td>
<!-- String manipulation -->
<td>{{ line.item.name | upper | truncate(length=30) }}</td>
<!-- Date formatting with timezone -->
<td>{{ line.createdDatetime | date(format="%d/%m/%Y %H:%M", timezone="UTC") }}</td>
</tr>
{% endfor %} {% else %}
<tr>
<td colspan="5">No data available</td>
</tr>
{% endif %}Error Handling in Templates
Add robust error handling:
<!-- Safe data access with error handling -->
<div class="request_info">
{% try %}
<h2>Request #{{ data.requisition.requisitionNumber }}</h2>
<p>Status: {{ data.requisition.status | title }}</p>
{% catch %}
<h2>Equipment Request</h2>
<p class="error">Request information unavailable</p>
{% endtry %}
</div>
<!-- Conditional blocks with fallbacks -->
{% if data.requisition %}
<!-- Main content -->
{% else %}
<div class="error_message">
<h2>Error: No Request Data Available</h2>
<p>Please ensure you have selected a valid requisition.</p>
</div>
{% endif %}Testing Advanced Features
-
Create test data with various scenarios:
- Empty requisitions
- Different statuses
- Large quantities
- Missing optional fields
-
Test print layout:
- Use browser's print preview
- Check page breaks
- Verify color handling in print mode
-
Validate calculations:
- Ensure totals are correct
- Test edge cases (zero quantities, etc.)
What We've Learned
- Advanced Tera conditionals and loops
- Macro definitions and usage
- Variable calculations and global state
- Safe data access patterns
- Print-specific styling
- Error handling in templates
In Tutorial 4, we'll dive deep into GraphQL queries and data fetching strategies.