Frontend Templates¶
Overview¶
Frontend templates handle all user-facing pages in the Gold Standard Module. These templates follow responsive design principles and integrate with XOOPS themes while maintaining module-specific styling.
Template Flow¶
flowchart TB
subgraph "Request Flow"
A[User Request] --> B[Controller]
B --> C[Service Layer]
C --> D[View Model]
D --> E[Template Engine]
E --> F[Rendered HTML]
end
subgraph "Template Composition"
G[Layout Template] --> H[Page Template]
H --> I[Partial 1]
H --> J[Partial 2]
H --> K[Partial N]
end Main Templates¶
Index Template (goldstandard_index.tpl)¶
The homepage template displays featured and recent articles:
{* goldstandard_index.tpl *}
<div class="goldstandard-index">
{* Hero Section with Featured Article *}
{if $featured_article}
<section class="hero-section">
<article class="featured-article">
{if $featured_article.featured_image}
<div class="featured-image">
<img src="{$featured_article.featured_image}"
alt="{$featured_article.title|escape}"
class="img-fluid">
</div>
{/if}
<div class="featured-content">
<span class="category-badge">
{$featured_article.categories[0].name|escape}
</span>
<h1>
<a href="{$featured_article.url}">
{$featured_article.title|escape}
</a>
</h1>
<p class="excerpt">{$featured_article.excerpt}</p>
<div class="meta">
<span class="author">
<img src="{$featured_article.author_avatar}"
alt="" class="avatar-sm">
{$featured_article.author_name|escape}
</span>
<span class="date">
{$featured_article.published_at|date_format:'%B %d, %Y'}
</span>
</div>
</div>
</article>
</section>
{/if}
{* Category Navigation *}
<nav class="category-nav" aria-label="Categories">
<ul class="category-list">
<li class="{if !$current_category}active{/if}">
<a href="{$module_url}">All</a>
</li>
{foreach $categories as $category}
<li class="{if $current_category == $category.id}active{/if}">
<a href="{$category.url}">{$category.name|escape}</a>
</li>
{/foreach}
</ul>
</nav>
{* Article Grid *}
<section class="articles-section">
<h2 class="section-title">{$section_title|default:'Latest Articles'}</h2>
<div class="article-grid">
{foreach $articles as $article}
{include file='db:goldstandard_partials_article_card.tpl'
article=$article}
{foreachelse}
<div class="no-articles">
<p>{$lang.no_articles}</p>
</div>
{/foreach}
</div>
{* Pagination *}
{if $total_pages > 1}
{include file='db:goldstandard_partials_pagination.tpl'
current_page=$current_page
total_pages=$total_pages
base_url=$module_url}
{/if}
</section>
</div>
Article Template (goldstandard_article.tpl)¶
Single article display with full content:
{* goldstandard_article.tpl *}
<article class="goldstandard-article" itemscope itemtype="https://schema.org/Article">
{* Breadcrumb Navigation *}
<nav class="breadcrumb-nav" aria-label="Breadcrumb">
{include file='db:goldstandard_partials_breadcrumb.tpl'
items=$breadcrumb}
</nav>
{* Article Header *}
<header class="article-header">
{if $article.categories}
<div class="categories">
{foreach $article.categories as $category}
<a href="{$category.url}" class="category-link">
{$category.name|escape}
</a>
{/foreach}
</div>
{/if}
<h1 itemprop="headline">{$article.title|escape}</h1>
<div class="article-meta">
<div class="author" itemprop="author" itemscope itemtype="https://schema.org/Person">
<img src="{$article.author_avatar}"
alt="{$article.author_name|escape}"
class="author-avatar">
<span itemprop="name">{$article.author_name|escape}</span>
</div>
<time datetime="{$article.published_at|date_format:'%Y-%m-%d'}"
itemprop="datePublished">
{$article.published_at|date_format:'%B %d, %Y'}
</time>
<span class="reading-time">
{$article.reading_time} min read
</span>
{if $article.updated_at != $article.published_at}
<span class="updated">
Updated: {$article.updated_at|date_format:'%B %d, %Y'}
</span>
{/if}
</div>
</header>
{* Featured Image *}
{if $article.featured_image}
<figure class="featured-image">
<img src="{$article.featured_image}"
alt="{$article.featured_image_alt|escape|default:$article.title}"
itemprop="image"
class="img-fluid">
{if $article.featured_image_caption}
<figcaption>{$article.featured_image_caption|escape}</figcaption>
{/if}
</figure>
{/if}
{* Article Content *}
<div class="article-content prose" itemprop="articleBody">
{$article.content}
</div>
{* Tags *}
{if $article.tags}
<footer class="article-footer">
<div class="tags">
<span class="tags-label">Tags:</span>
{foreach $article.tags as $tag}
<a href="{$tag.url}" class="tag" rel="tag">
#{$tag.name|escape}
</a>
{/foreach}
</div>
{* Share Buttons *}
{include file='db:goldstandard_partials_share.tpl'
title=$article.title
url=$article.url}
</footer>
{/if}
{* Author Bio *}
{if $show_author_bio}
<aside class="author-bio">
<img src="{$article.author_avatar}" alt="" class="author-avatar-lg">
<div class="author-info">
<h3>{$article.author_name|escape}</h3>
<p>{$article.author_bio}</p>
<a href="{$article.author_url}">View all posts</a>
</div>
</aside>
{/if}
{* Related Articles *}
{if $related_articles}
<section class="related-articles">
<h2>Related Articles</h2>
<div class="article-grid article-grid-sm">
{foreach $related_articles as $related}
{include file='db:goldstandard_partials_article_card.tpl'
article=$related
compact=true}
{/foreach}
</div>
</section>
{/if}
{* Comments Section *}
{if $comments_enabled}
<section class="comments-section" id="comments">
<h2>Comments ({$article.comment_count})</h2>
{include file='db:goldstandard_partials_comments.tpl'
comments=$comments
article_id=$article.id}
</section>
{/if}
</article>
Category Template (goldstandard_category.tpl)¶
Category archive page:
{* goldstandard_category.tpl *}
<div class="goldstandard-category">
{* Breadcrumb *}
<nav class="breadcrumb-nav" aria-label="Breadcrumb">
{include file='db:goldstandard_partials_breadcrumb.tpl'
items=$breadcrumb}
</nav>
{* Category Header *}
<header class="category-header">
{if $category.image}
<div class="category-image">
<img src="{$category.image}" alt="{$category.name|escape}">
</div>
{/if}
<h1>{$category.name|escape}</h1>
{if $category.description}
<p class="category-description">{$category.description}</p>
{/if}
<span class="article-count">
{$category.article_count} {if $category.article_count == 1}article{else}articles{/if}
</span>
</header>
{* Subcategories *}
{if $category.children}
<nav class="subcategories" aria-label="Subcategories">
<h2>Subcategories</h2>
<ul class="subcategory-list">
{foreach $category.children as $child}
<li>
<a href="{$child.url}">
{$child.name|escape}
<span class="count">({$child.article_count})</span>
</a>
</li>
{/foreach}
</ul>
</nav>
{/if}
{* Sort Options *}
<div class="sort-options">
<label for="sort">Sort by:</label>
<select id="sort" onchange="location.href=this.value">
<option value="{$category.url}?sort=newest"
{if $sort == 'newest'}selected{/if}>Newest</option>
<option value="{$category.url}?sort=popular"
{if $sort == 'popular'}selected{/if}>Most Popular</option>
<option value="{$category.url}?sort=title"
{if $sort == 'title'}selected{/if}>Title A-Z</option>
</select>
</div>
{* Article List *}
<div class="article-list">
{foreach $articles as $article}
{include file='db:goldstandard_partials_article_card.tpl'
article=$article
show_category=false}
{foreachelse}
<div class="no-articles">
<p>No articles in this category yet.</p>
</div>
{/foreach}
</div>
{* Pagination *}
{if $total_pages > 1}
{include file='db:goldstandard_partials_pagination.tpl'
current_page=$current_page
total_pages=$total_pages
base_url=$category.url}
{/if}
</div>
Search Template (goldstandard_search.tpl)¶
Search results page:
{* goldstandard_search.tpl *}
<div class="goldstandard-search">
<header class="search-header">
<h1>Search Results</h1>
{* Search Form *}
<form action="{$module_url}/search.php" method="get" class="search-form">
<div class="search-input-group">
<input type="search"
name="q"
value="{$query|escape}"
placeholder="Search articles..."
aria-label="Search">
<button type="submit">
<span class="sr-only">Search</span>
<svg><!-- search icon --></svg>
</button>
</div>
{* Filters *}
<div class="search-filters">
<select name="category" aria-label="Category filter">
<option value="">All Categories</option>
{foreach $categories as $cat}
<option value="{$cat.id}"
{if $filter_category == $cat.id}selected{/if}>
{$cat.name|escape}
</option>
{/foreach}
</select>
<select name="date" aria-label="Date filter">
<option value="">Any Time</option>
<option value="day" {if $filter_date == 'day'}selected{/if}>
Past 24 Hours
</option>
<option value="week" {if $filter_date == 'week'}selected{/if}>
Past Week
</option>
<option value="month" {if $filter_date == 'month'}selected{/if}>
Past Month
</option>
<option value="year" {if $filter_date == 'year'}selected{/if}>
Past Year
</option>
</select>
</div>
</form>
</header>
{* Results Summary *}
{if $query}
<div class="results-summary">
<p>
{if $total_results > 0}
Found <strong>{$total_results}</strong> results for
"<strong>{$query|escape}</strong>"
{else}
No results found for "<strong>{$query|escape}</strong>"
{/if}
</p>
</div>
{/if}
{* Search Results *}
{if $results}
<div class="search-results">
{foreach $results as $result}
<article class="search-result">
<h2>
<a href="{$result.url}">{$result.title|escape}</a>
</h2>
<p class="result-excerpt">
{$result.excerpt_highlighted}
</p>
<div class="result-meta">
<span class="category">{$result.category_name|escape}</span>
<span class="date">{$result.published_at|date_format:'%B %d, %Y'}</span>
</div>
</article>
{/foreach}
</div>
{* Pagination *}
{if $total_pages > 1}
{include file='db:goldstandard_partials_pagination.tpl'
current_page=$current_page
total_pages=$total_pages
base_url="{$module_url}/search.php?q={$query|escape:'url'}"}
{/if}
{else if $query}
{* No Results *}
<div class="no-results">
<h2>No articles found</h2>
<p>Try different keywords or browse our categories.</p>
<div class="suggestions">
<h3>Popular Categories</h3>
<ul>
{foreach $popular_categories as $cat}
<li><a href="{$cat.url}">{$cat.name|escape}</a></li>
{/foreach}
</ul>
</div>
</div>
{/if}
</div>
Partial Templates¶
Article Card (partials/article_card.tpl)¶
Reusable article card component:
{* goldstandard_partials_article_card.tpl *}
<article class="article-card{if $compact} article-card--compact{/if}">
{if $article.featured_image && !$compact}
<a href="{$article.url}" class="article-card__image">
<img src="{$article.featured_image}"
alt="{$article.title|escape}"
loading="lazy">
</a>
{/if}
<div class="article-card__content">
{if $show_category|default:true && $article.categories}
<a href="{$article.categories[0].url}" class="article-card__category">
{$article.categories[0].name|escape}
</a>
{/if}
<h3 class="article-card__title">
<a href="{$article.url}">{$article.title|escape}</a>
</h3>
{if !$compact}
<p class="article-card__excerpt">
{$article.excerpt|truncate:120:'...'}
</p>
{/if}
<div class="article-card__meta">
<span class="article-card__author">
{$article.author_name|escape}
</span>
<span class="article-card__date">
{$article.published_at|date_format:'%b %d'}
</span>
{if $article.reading_time}
<span class="article-card__reading-time">
{$article.reading_time} min
</span>
{/if}
</div>
</div>
</article>
Pagination (partials/pagination.tpl)¶
{* goldstandard_partials_pagination.tpl *}
<nav class="pagination" aria-label="Pagination">
<ul class="pagination__list">
{* Previous *}
<li class="pagination__item pagination__item--prev">
{if $current_page > 1}
<a href="{$base_url}{if strpos($base_url, '?') !== false}&{else}?{/if}page={$current_page - 1}"
aria-label="Previous page">
« Previous
</a>
{else}
<span aria-disabled="true">« Previous</span>
{/if}
</li>
{* Page Numbers *}
{assign var="start" value=max(1, $current_page - 2)}
{assign var="end" value=min($total_pages, $current_page + 2)}
{if $start > 1}
<li class="pagination__item">
<a href="{$base_url}?page=1">1</a>
</li>
{if $start > 2}
<li class="pagination__item pagination__ellipsis">...</li>
{/if}
{/if}
{for $page=$start to $end}
<li class="pagination__item{if $page == $current_page} pagination__item--active{/if}">
{if $page == $current_page}
<span aria-current="page">{$page}</span>
{else}
<a href="{$base_url}{if strpos($base_url, '?') !== false}&{else}?{/if}page={$page}">{$page}</a>
{/if}
</li>
{/for}
{if $end < $total_pages}
{if $end < $total_pages - 1}
<li class="pagination__item pagination__ellipsis">...</li>
{/if}
<li class="pagination__item">
<a href="{$base_url}?page={$total_pages}">{$total_pages}</a>
</li>
{/if}
{* Next *}
<li class="pagination__item pagination__item--next">
{if $current_page < $total_pages}
<a href="{$base_url}{if strpos($base_url, '?') !== false}&{else}?{/if}page={$current_page + 1}"
aria-label="Next page">
Next »
</a>
{else}
<span aria-disabled="true">Next »</span>
{/if}
</li>
</ul>
</nav>
Breadcrumb (partials/breadcrumb.tpl)¶
{* goldstandard_partials_breadcrumb.tpl *}
<ol class="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
{foreach $items as $index => $item}
<li class="breadcrumb__item{if $item@last} breadcrumb__item--active{/if}"
itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
{if !$item@last}
<a href="{$item.url}" itemprop="item">
<span itemprop="name">{$item.title|escape}</span>
</a>
{else}
<span itemprop="name">{$item.title|escape}</span>
{/if}
<meta itemprop="position" content="{$index + 1}">
</li>
{/foreach}
</ol>
CSS Classes Reference¶
| Class | Description |
|---|---|
.goldstandard-* | Module-scoped wrapper classes |
.article-card | Article preview card |
.article-card--compact | Compact card variant |
.article-grid | CSS Grid container for cards |
.pagination | Pagination wrapper |
.breadcrumb | Breadcrumb navigation |
.prose | Typography for article content |