Morten Sæther

← Blog

Episode 15: Web Templates Introduction

13 April 2026

Creating Reusable, Dynamic Content Templates with Liquid Templating Engine

Introduction

Web templates in Power Pages leverage the powerful Liquid templating engine to create dynamic, data-driven content that adapts to user context and business data. This episode introduces you to the fundamentals of web template creation, from basic variable usage to complex conditional logic and loops. You’ll learn how to build reusable templates that maintain consistency across your portal while providing the flexibility to handle diverse content scenarios and dynamic data presentation.

Key Concepts

Learning Objectives

By the end of this episode, you will be able to:

Why Web Templates Matter

Web templates are the foundation of scalable portal development. They enable you to create consistent, maintainable designs that adapt to changing data and business requirements. Proper template architecture reduces development time, ensures design consistency, and provides the flexibility needed for complex business scenarios while maintaining excellent performance.

Step-by-Step Guide

1. Understanding the Liquid Template Engine

Basic Liquid Syntax and Concepts:

<!-- Variables and Output -->
{{ page.title }}
{{ user.fullname }}
{{ entity.name | escape }}

<!-- Comments (not rendered) -->
{% comment %}
This is a comment that won't appear in the output
{% endcomment %}

<!-- Liquid Tags (logic and control) -->
{% if user.authenticated %}
    Welcome back, {{ user.fullname }}!
{% endif %}

<!-- Filters for data transformation -->
{{ product.price | currency }}
{{ article.content | truncate: 100 }}
{{ customer.email | downcase }}

Template Types in Power Pages:

  1. Page Templates: Define the overall structure of pages
  2. Entity Forms: Control how forms are rendered and behave
  3. Entity Lists: Customize how data lists are displayed
  4. Web Forms: Create complex multi-step forms
  5. Content Snippets: Reusable content blocks

2. Creating Your First Web Template

Basic Page Template Structure:

{% comment %}
Customer Portal Dashboard Template
Purpose: Main landing page for authenticated customers
{% endcomment %}

<!-- Inherit from base layout -->
{% extends "layout.html" %}

<!-- Define page-specific content -->
{% block title %}Dashboard - {{ user.fullname }}{% endblock %}

{% block content %}
<div class="dashboard-container">
    <!-- Welcome Section -->
    <div class="dashboard-header">
        <div class="welcome-message">
            <h1>Welcome back, {{ user.fullname | default: "Guest" }}!</h1>
            <p class="last-login">Last login: {{ user.last_successful_logon | date: "%B %d, %Y at %I:%M %p" }}</p>
        </div>
        
        <div class="user-avatar">
            {% if user.profile_image %}
                <img src="{{ user.profile_image.url }}" alt="{{ user.fullname }}" class="avatar-image">
            {% else %}
                <div class="avatar-placeholder">
                    {{ user.fullname | slice: 0 | upcase }}
                </div>
            {% endif %}
        </div>
    </div>
    
    <!-- Dashboard Stats -->
    <div class="dashboard-stats">
        {% assign open_cases = entities['incident'] | where: 'customerid', user.contact.id | where: 'statecode', 0 %}
        {% assign total_orders = entities['salesorder'] | where: 'customerid', user.contact.id %}
        {% assign pending_invoices = entities['invoice'] | where: 'customerid', user.contact.id | where: 'statecode', 1 %}
        
        <div class="stat-card">
            <div class="stat-number">{{ open_cases.size }}</div>
            <div class="stat-label">Open Support Cases</div>
            <div class="stat-action">
                <a href="/support/cases" class="btn btn-outline-primary btn-sm">View All</a>
            </div>
        </div>
        
        <div class="stat-card">
            <div class="stat-number">{{ total_orders.size }}</div>
            <div class="stat-label">Total Orders</div>
            <div class="stat-action">
                <a href="/orders" class="btn btn-outline-primary btn-sm">View History</a>
            </div>
        </div>
        
        <div class="stat-card">
            <div class="stat-number">{{ pending_invoices.size }}</div>
            <div class="stat-label">Pending Invoices</div>
            <div class="stat-action">
                <a href="/billing/invoices" class="btn btn-outline-primary btn-sm">Pay Now</a>
            </div>
        </div>
    </div>
    
    <!-- Recent Activity -->
    <div class="dashboard-section">
        <h2>Recent Activity</h2>
        
        {% assign recent_cases = open_cases | sort: 'createdon' | reverse | limit: 5 %}
        {% if recent_cases.size > 0 %}
            <div class="activity-list">
                {% for case in recent_cases %}
                    <div class="activity-item">
                        <div class="activity-icon case-icon">🎫</div>
                        <div class="activity-content">
                            <div class="activity-title">
                                <a href="/support/case/{{ case.id }}">{{ case.title }}</a>
                            </div>
                            <div class="activity-meta">
                                Case #{{ case.ticketnumber }} • 
                                Created {{ case.createdon | date: "%b %d, %Y" }} • 
                                Priority: {{ case.prioritycode.label }}
                            </div>
                        </div>
                        <div class="activity-status">
                            <span class="status-badge status-{{ case.statecode.value }}">
                                {{ case.statecode.label }}
                            </span>
                        </div>
                    </div>
                {% endfor %}
            </div>
        {% else %}
            <div class="empty-state">
                <div class="empty-icon">✅</div>
                <h4>No open support cases</h4>
                <p>You're all caught up! If you need help, feel free to create a new support case.</p>
                <a href="/support/new-case" class="btn btn-primary">Create Support Case</a>
            </div>
        {% endif %}
    </div>
    
    <!-- Quick Actions -->
    <div class="dashboard-section">
        <h2>Quick Actions</h2>
        <div class="quick-actions-grid">
            <a href="/support/new-case" class="quick-action-card">
                <div class="action-icon">🎫</div>
                <div class="action-title">Create Support Case</div>
                <div class="action-description">Get help with technical issues</div>
            </a>
            
            <a href="/orders/new" class="quick-action-card">
                <div class="action-icon">🛒</div>
                <div class="action-title">Place New Order</div>
                <div class="action-description">Browse and order products</div>
            </a>
            
            <a href="/profile/edit" class="quick-action-card">
                <div class="action-icon">👤</div>
                <div class="action-title">Update Profile</div>
                <div class="action-description">Manage your account information</div>
            </a>
            
            <a href="/billing/payment-methods" class="quick-action-card">
                <div class="action-icon">💳</div>
                <div class="action-title">Payment Methods</div>
                <div class="action-description">Manage billing and payments</div>
            </a>
        </div>
    </div>
</div>
{% endblock %}

{% block scripts %}
<script>
    // Dashboard-specific JavaScript
    document.addEventListener('DOMContentLoaded', function() {
        // Auto-refresh dashboard data every 5 minutes
        setInterval(function() {
            location.reload();
        }, 300000);
        
        // Add click tracking for quick actions
        document.querySelectorAll('.quick-action-card').forEach(function(card) {
            card.addEventListener('click', function() {
                // Track analytics
                gtag('event', 'dashboard_quick_action', {
                    'action_type': this.querySelector('.action-title').textContent
                });
            });
        });
    });
</script>
{% endblock %}

3. Working with Template Variables and Data

Accessing Different Data Types:

<!-- User Context Variables -->
{{ user.id }}                    <!-- Current user ID -->
{{ user.fullname }}              <!-- User's full name -->
{{ user.email }}                 <!-- User's email address -->
{{ user.authenticated }}          <!-- Boolean: is user logged in -->
{{ user.roles }}                 <!-- Array of user roles -->

<!-- Page Context Variables -->
{{ page.title }}                 <!-- Current page title -->
{{ page.id }}                    <!-- Current page ID -->
{{ page.url }}                   <!-- Current page URL -->
{{ page.parent }}                <!-- Parent page object -->

<!-- Entity Data Access -->
{% assign contact = entities.contact[user.contact.id] %}
{{ contact.fullname }}
{{ contact.emailaddress1 }}
{{ contact.address1_city }}

<!-- Query Entities with Filters -->
{% assign active_orders = entities['salesorder'] 
   | where: 'customerid', user.contact.id 
   | where: 'statecode', 0 
   | sort: 'createdon' 
   | reverse %}

<!-- Accessing Related Entity Data -->
{% for order in active_orders %}
    <div class="order-item">
        <h4>Order {{ order.name }}</h4>
        <p>Customer: {{ order.customerid.fullname }}</p>
        <p>Total: {{ order.totalamount | currency }}</p>
        
        <!-- Access related order products -->
        {% assign order_products = entities['salesorderdetail'] | where: 'salesorderid', order.id %}
        <ul class="order-products">
            {% for product in order_products %}
                <li>{{ product.productid.name }} - Qty: {{ product.quantity }}</li>
            {% endfor %}
        </ul>
    </div>
{% endfor %}

4. Advanced Control Structures and Logic

Complex Conditional Logic:

<!-- Multi-condition Logic -->
{% if user.authenticated and user.roles contains 'Premium Customer' %}
    <div class="premium-dashboard">
        <!-- Premium customer content -->
        {% include 'premium-features' %}
    </div>
{% elsif user.authenticated %}
    <div class="standard-dashboard">
        <!-- Standard customer content -->
        {% include 'standard-features' %}
    </div>
{% else %}
    <div class="public-content">
        <!-- Non-authenticated content -->
        <h2>Welcome to Our Customer Portal</h2>
        <p>Please <a href="/signin">sign in</a> to access your account.</p>
    </div>
{% endif %}

<!-- Switch-like Logic with Case -->
{% case user.subscription_level %}
    {% when 'enterprise' %}
        {% assign support_priority = 'High' %}
        {% assign response_time = '2 hours' %}
    {% when 'professional' %}
        {% assign support_priority = 'Medium' %}
        {% assign response_time = '4 hours' %}
    {% when 'basic' %}
        {% assign support_priority = 'Standard' %}
        {% assign response_time = '24 hours' %}
    {% else %}
        {% assign support_priority = 'Low' %}
        {% assign response_time = '48 hours' %}
{% endcase %}

<div class="support-info">
    <p>Your support level: {{ support_priority }}</p>
    <p>Expected response time: {{ response_time }}</p>
</div>

Advanced Looping and Data Manipulation:

<!-- Complex Data Grouping -->
{% assign orders_by_year = entities['salesorder'] 
   | where: 'customerid', user.contact.id 
   | group_by: 'createdon' %}

<div class="orders-by-year">
    {% for year_group in orders_by_year %}
        {% assign year = year_group.name | date: "%Y" %}
        <div class="year-section">
            <h3>{{ year }} Orders</h3>
            
            {% assign year_total = 0 %}
            {% for order in year_group.items %}
                {% assign year_total = year_total | plus: order.totalamount %}
            {% endfor %}
            
            <p class="year-summary">
                {{ year_group.items.size }} orders • 
                Total: {{ year_total | currency }}
            </p>
            
            <div class="orders-grid">
                {% for order in year_group.items limit: 5 %}
                    <div class="order-card">
                        <div class="order-header">
                            <span class="order-number">{{ order.name }}</span>
                            <span class="order-date">{{ order.createdon | date: "%b %d" }}</span>
                        </div>
                        <div class="order-amount">{{ order.totalamount | currency }}</div>
                        <div class="order-status">
                            <span class="status-badge status-{{ order.statecode.value }}">
                                {{ order.statecode.label }}
                            </span>
                        </div>
                    </div>
                {% endfor %}
                
                {% if year_group.items.size > 5 %}
                    <div class="see-more">
                        <a href="/orders?year={{ year }}">
                            See all {{ year_group.items.size }} orders from {{ year }}
                        </a>
                    </div>
                {% endif %}
            </div>
        </div>
    {% endfor %}
</div>

<!-- Pagination Logic -->
{% assign page_size = 10 %}
{% assign current_page = request.params.page | default: 1 | plus: 0 %}
{% assign offset = current_page | minus: 1 | times: page_size %}

{% assign all_articles = entities['knowledgearticle'] 
   | where: 'statecode', 3 
   | sort: 'createdon' 
   | reverse %}

{% assign total_articles = all_articles.size %}
{% assign total_pages = total_articles | divided_by: page_size | ceil %}
{% assign articles = all_articles | offset: offset | limit: page_size %}

<div class="articles-list">
    {% for article in articles %}
        <article class="article-card">
            <h3><a href="/kb/{{ article.articlenumber }}">{{ article.title }}</a></h3>
            <p>{{ article.description | truncate: 150 }}</p>
            <div class="article-meta">
                Published {{ article.createdon | date: "%B %d, %Y" }} • 
                {{ article.content | size | divided_by: 250 }} min read
            </div>
        </article>
    {% endfor %}
</div>

<!-- Pagination Controls -->
{% if total_pages > 1 %}
    <nav class="pagination">
        {% if current_page > 1 %}
            <a href="?page={{ current_page | minus: 1 }}" class="page-link">← Previous</a>
        {% endif %}
        
        {% for page in (1..total_pages) %}
            {% if page == current_page %}
                <span class="page-link current">{{ page }}</span>
            {% elsif page == 1 or page == total_pages or page >= current_page | minus: 2 and page <= current_page | plus: 2 %}
                <a href="?page={{ page }}" class="page-link">{{ page }}</a>
            {% elsif page == current_page | minus: 3 or page == current_page | plus: 3 %}
                <span class="page-ellipsis">…</span>
            {% endif %}
        {% endfor %}
        
        {% if current_page < total_pages %}
            <a href="?page={{ current_page | plus: 1 }}" class="page-link">Next →</a>
        {% endif %}
    </nav>
{% endif %}

5. Template Inheritance and Reusable Components

Base Layout Template:

<!-- layout.html - Base template for all pages -->
<!DOCTYPE html>
<html lang="{{ page.language | default: 'en' }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{{ page.title }}{% endblock %} - {{ website.name }}</title>
    
    <!-- Meta tags -->
    <meta name="description" content="{% block description %}{{ page.description | default: website.description }}{% endblock %}">
    <meta name="keywords" content="{% block keywords %}{{ page.keywords | default: website.keywords }}{% endblock %}">
    
    <!-- Favicon -->
    <link rel="icon" href="{{ 'favicon.ico' | theme_asset_url }}">
    
    <!-- CSS -->
    <link rel="stylesheet" href="{{ 'bootstrap.min.css' | theme_asset_url }}">
    <link rel="stylesheet" href="{{ 'main.css' | theme_asset_url }}">
    {% block stylesheets %}{% endblock %}
    
    <!-- Analytics -->
    {% if website.google_analytics_id %}
        <!-- Google Analytics -->
        <script async src="https://www.googletagmanager.com/gtag/js?id={{ website.google_analytics_id }}"></script>
        <script>
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '{{ website.google_analytics_id }}');
        </script>
    {% endif %}
</head>
<body class="{% block body_class %}{% endblock %}">
    <!-- Skip to main content (accessibility) -->
    <a href="#main-content" class="skip-link">Skip to main content</a>
    
    <!-- Header -->
    <header class="site-header">
        {% include 'header' %}
    </header>
    
    <!-- Main Navigation -->
    <nav class="main-navigation">
        {% include 'navigation' %}
    </nav>
    
    <!-- Breadcrumbs -->
    {% if page.breadcrumbs %}
        <nav class="breadcrumbs" aria-label="Breadcrumb">
            {% include 'breadcrumbs' %}
        </nav>
    {% endif %}
    
    <!-- Main Content -->
    <main id="main-content" class="main-content">
        {% block content %}
            <div class="container">
                <h1>{{ page.title }}</h1>
                {{ page.content }}
            </div>
        {% endblock %}
    </main>
    
    <!-- Sidebar -->
    {% block sidebar %}
        {% if page.show_sidebar %}
            <aside class="sidebar">
                {% include 'sidebar' %}
            </aside>
        {% endif %}
    {% endblock %}
    
    <!-- Footer -->
    <footer class="site-footer">
        {% include 'footer' %}
    </footer>
    
    <!-- JavaScript -->
    <script src="{{ 'jquery.min.js' | theme_asset_url }}"></script>
    <script src="{{ 'bootstrap.min.js' | theme_asset_url }}"></script>
    <script src="{{ 'main.js' | theme_asset_url }}"></script>
    {% block scripts %}{% endblock %}
    
    <!-- Live Chat Widget -->
    {% if user.authenticated and settings['enable_live_chat'] %}
        {% include 'live-chat-widget' %}
    {% endif %}
</body>
</html>

Reusable Component Templates:

<!-- _status-badge.html - Reusable status badge component -->
{% assign status_config = settings['status_badges'][status_type] | default: settings['status_badges']['default'] %}

<span class="status-badge status-{{ status_value | downcase | replace: ' ', '-' }}" 
      style="background-color: {{ status_config.background }}; color: {{ status_config.text }};">
    {% if status_config.icon %}
        <span class="status-icon">{{ status_config.icon }}</span>
    {% endif %}
    {{ status_label | default: status_value }}
</span>

<!-- Usage in other templates -->
{% include 'status-badge', status_type: 'case', status_value: case.statecode.value, status_label: case.statecode.label %}
<!-- _data-table.html - Reusable data table component -->
<div class="data-table-container">
    <table class="data-table {{ table_class }}">
        <thead>
            <tr>
                {% for column in columns %}
                    <th class="{% if column.sortable %}sortable{% endif %} {% if column.class %}{{ column.class }}{% endif %}">
                        {{ column.title }}
                        {% if column.sortable %}
                            <span class="sort-indicator"></span>
                        {% endif %}
                    </th>
                {% endfor %}
            </tr>
        </thead>
        <tbody>
            {% for row in data %}
                <tr class="data-row" data-id="{{ row.id }}">
                    {% for column in columns %}
                        <td class="{{ column.class }}">
                            {% if column.formatter %}
                                {% include column.formatter, value: row[column.key], row: row %}
                            {% else %}
                                {{ row[column.key] }}
                            {% endif %}
                        </td>
                    {% endfor %}
                </tr>
            {% endfor %}
        </tbody>
    </table>
    
    {% if data.size == 0 %}
        <div class="empty-state">
            <div class="empty-icon">{{ empty_icon | default: '📊' }}</div>
            <h4>{{ empty_title | default: 'No data available' }}</h4>
            <p>{{ empty_message | default: 'There are no records to display.' }}</p>
        </div>
    {% endif %}
</div>

<!-- Usage example -->
{% assign order_columns = site.data.table_configs['orders'] %}
{% assign order_data = entities['salesorder'] | where: 'customerid', user.contact.id %}

{% include 'data-table', 
   columns: order_columns, 
   data: order_data,
   table_class: 'orders-table',
   empty_title: 'No orders found',
   empty_message: 'You haven\'t placed any orders yet.' %}

6. Performance Optimization Techniques

Efficient Data Loading:

<!-- Lazy Loading with Pagination -->
{% assign page_size = 20 %}
{% assign current_page = request.params.page | default: 1 | plus: 0 %}

<!-- Only load data for current page -->
{% assign products = entities['product'] 
   | where: 'statecode', 0 
   | offset: current_page | minus: 1 | times: page_size 
   | limit: page_size %}

<!-- Cache expensive calculations -->
{% assign user_stats = cache['user_stats_' | append: user.id] %}
{% unless user_stats %}
    {% assign user_orders = entities['salesorder'] | where: 'customerid', user.contact.id %}
    {% assign user_cases = entities['incident'] | where: 'customerid', user.contact.id %}
    
    {% assign user_stats = hash %}
    {% assign user_stats['total_orders'] = user_orders.size %}
    {% assign user_stats['total_cases'] = user_cases.size %}
    {% assign user_stats['last_order'] = user_orders | sort: 'createdon' | reverse | first %}
    
    {% cache 'user_stats_' | append: user.id, user_stats, 300 %} <!-- Cache for 5 minutes -->
{% endunless %}

<!-- Use the cached data -->
<div class="user-summary">
    <p>Total Orders: {{ user_stats.total_orders }}</p>
    <p>Total Cases: {{ user_stats.total_cases }}</p>
    {% if user_stats.last_order %}
        <p>Last Order: {{ user_stats.last_order.createdon | date: "%B %d, %Y" }}</p>
    {% endif %}
</div>

Practical Example: Customer Support Portal Template

Let’s create a complete support case template that demonstrates advanced templating:

<!-- support-case-detail.html -->
{% extends "layout.html" %}

{% assign case_id = request.params.id %}
{% assign support_case = entities.incident[case_id] %}

{% unless support_case %}
    {% redirect '/support/cases' %}
{% endunless %}

<!-- Verify user has access to this case -->
{% unless support_case.customerid.id == user.contact.id or user.roles contains 'Support Agent' %}
    {% redirect '/access-denied' %}
{% endunless %}

{% block title %}Support Case {{ support_case.ticketnumber }}{% endblock %}

{% block content %}
<div class="support-case-detail">
    <!-- Case Header -->
    <div class="case-header">
        <div class="case-title-section">
            <h1>{{ support_case.title }}</h1>
            <div class="case-meta">
                <span class="case-number">Case #{{ support_case.ticketnumber }}</span>
                <span class="case-date">Created {{ support_case.createdon | date: "%B %d, %Y at %I:%M %p" }}</span>
            </div>
        </div>
        
        <div class="case-status-section">
            {% include 'status-badge', 
               status_type: 'case', 
               status_value: support_case.statecode.value, 
               status_label: support_case.statecode.label %}
            
            {% include 'status-badge', 
               status_type: 'priority', 
               status_value: support_case.prioritycode.value, 
               status_label: support_case.prioritycode.label %}
        </div>
    </div>
    
    <!-- Case Details Grid -->
    <div class="case-details-grid">
        <div class="case-info-card">
            <h3>Case Information</h3>
            <dl class="case-details">
                <dt>Subject:</dt>
                <dd>{{ support_case.title }}</dd>
                
                <dt>Description:</dt>
                <dd>{{ support_case.description | newline_to_br }}</dd>
                
                <dt>Category:</dt>
                <dd>{{ support_case.caseorigincode.label }}</dd>
                
                <dt>Product:</dt>
                <dd>{{ support_case.productid.name | default: 'Not specified' }}</dd>
                
                {% if support_case.cr5f3_estimatedresolutiondate %}
                    <dt>Estimated Resolution:</dt>
                    <dd>{{ support_case.cr5f3_estimatedresolutiondate | date: "%B %d, %Y" }}</dd>
                {% endif %}
            </dl>
        </div>
        
        <div class="case-contact-card">
            <h3>Contact Information</h3>
            <div class="contact-info">
                <div class="contact-avatar">
                    {% if support_case.customerid.profile_image %}
                        <img src="{{ support_case.customerid.profile_image.url }}" alt="{{ support_case.customerid.fullname }}">
                    {% else %}
                        <div class="avatar-placeholder">
                            {{ support_case.customerid.fullname | slice: 0 | upcase }}
                        </div>
                    {% endif %}
                </div>
                <div class="contact-details">
                    <p class="contact-name">{{ support_case.customerid.fullname }}</p>
                    <p class="contact-email">{{ support_case.customerid.emailaddress1 }}</p>
                    <p class="contact-phone">{{ support_case.customerid.telephone1 }}</p>
                </div>
            </div>
        </div>
    </div>
    
    <!-- Case Timeline -->
    <div class="case-timeline-section">
        <h3>Case Timeline</h3>
        
        {% assign case_activities = entities['activitypointer'] 
           | where: 'regardingobjectid', support_case.id 
           | sort: 'createdon' 
           | reverse %}
        
        <div class="timeline">
            {% for activity in case_activities %}
                <div class="timeline-item activity-{{ activity.activitytypecode }}">
                    <div class="timeline-marker">
                        {% case activity.activitytypecode %}
                            {% when 'email' %}📧
                            {% when 'phonecall' %}📞
                            {% when 'task' %}✅
                            {% when 'appointment' %}📅
                            {% else %}💬
                        {% endcase %}
                    </div>
                    
                    <div class="timeline-content">
                        <div class="activity-header">
                            <h4>{{ activity.subject }}</h4>
                            <span class="activity-date">
                                {{ activity.createdon | date: "%b %d, %Y at %I:%M %p" }}
                            </span>
                        </div>
                        
                        {% if activity.description %}
                            <div class="activity-description">
                                {{ activity.description | newline_to_br }}
                            </div>
                        {% endif %}
                        
                        <div class="activity-meta">
                            By {{ activity.createdby.fullname }}
                        </div>
                    </div>
                </div>
            {% endfor %}
            
            <!-- Case Creation Event -->
            <div class="timeline-item timeline-start">
                <div class="timeline-marker">🎫</div>
                <div class="timeline-content">
                    <div class="activity-header">
                        <h4>Case Created</h4>
                        <span class="activity-date">
                            {{ support_case.createdon | date: "%b %d, %Y at %I:%M %p" }}
                        </span>
                    </div>
                    <div class="activity-description">
                        Support case was created by {{ support_case.createdby.fullname }}
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <!-- Action Buttons -->
    <div class="case-actions">
        {% if support_case.statecode.value == 0 %}
            <button type="button" class="btn btn-primary" onclick="addCaseComment()">
                Add Comment
            </button>
            
            {% if user.id == support_case.customerid.id %}
                <button type="button" class="btn btn-outline-secondary" onclick="escalateCase()">
                    Request Escalation
                </button>
            {% endif %}
        {% endif %}
        
        <a href="/support/cases" class="btn btn-outline-secondary">
            Back to All Cases
        </a>
        
        <button type="button" class="btn btn-outline-info" onclick="printCase()">
            Print Case
        </button>
    </div>
</div>
{% endblock %}

{% block scripts %}
<script>
    function addCaseComment() {
        // Open comment modal or redirect to comment form
        window.location.href = '/support/case/{{ support_case.id }}/comment';
    }
    
    function escalateCase() {
        if (confirm('Are you sure you want to request escalation for this case?')) {
            // Submit escalation request
            fetch('/api/cases/{{ support_case.id }}/escalate', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
                }
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    alert('Escalation request submitted successfully.');
                    location.reload();
                } else {
                    alert('Error submitting escalation request.');
                }
            });
        }
    }
    
    function printCase() {
        window.print();
    }
</script>
{% endblock %}

Troubleshooting Common Template Issues

Common Problems and Solutions:

  1. Template Compilation Errors: Check Liquid syntax and ensure all tags are properly closed
  2. Data Access Issues: Verify entity relationships and permissions
  3. Performance Problems: Implement caching and limit data queries
  4. Variable Scope Issues: Understand Liquid variable scoping rules
  5. Security Concerns: Always escape user input and validate permissions

Key Takeaways

Web templates are the foundation of dynamic portal development. Master Liquid templating syntax, implement proper data access patterns, and create reusable components to build scalable, maintainable portal solutions that adapt to your business needs.

In the next episode, you’ll learn about Content Snippets and Localization to create multilingual portals with reusable content blocks.

4. Documentation and Maintenance

Set yourself up for long-term success:

Practical Example

Consider implementing this episode’s techniques for a customer portal where users need enhanced functionality beyond basic templates. The implementation should:

Apply the specific techniques from this episode while following the general principles of good portal development.

Common Implementation Patterns

Pattern When to Use Benefits Considerations
Progressive Enhancement Adding advanced features Ensures basic functionality always works Requires careful planning
Responsive Design Multi-device support Consistent experience across devices May increase complexity
Modular Development Complex customizations Easier maintenance and updates Requires good architecture
Performance Monitoring All implementations Early detection of issues Ongoing effort required

Troubleshooting Tips

Advanced Considerations

As you implement these intermediate techniques, consider:

Summary

Web templates unlock the full power of Power Pages by enabling dynamic, data-driven content. Start with simple templates and gradually add complexity as you learn. Document your template logic for future maintenance. In the next episode, you’ll learn about content snippets and localization for global reach.