Forms Tutorial 3
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.