Skip to content
  1. Extras
  2. MiniShop3
  3. Frontend interface
  4. Product page

Product page

The product page is a detailed view of a single product with gallery, price, options, and add-to-cart form.

Page structure

ComponentFilePurpose
Page templateelements/templates/product.tplProduct page layout
Gallerytpl.msGalleryImage slider with lightbox
Product optionstpl.msProductOptionsProduct specifications output

Page template

Path: core/components/minishop3/elements/templates/product.tpl

The template extends the base template (base.tpl) and contains the following sections:

fenom
{extends 'file:templates/base.tpl'}
{block 'pagecontent'}
    <div class="container py-4">
        {* Breadcrumbs *}
        {* Main info (gallery + card) *}
        {* Tabs (description, specs, delivery) *}
        {* Related products *}
    </div>
{/block}

Page sections

Navigation chain from home to the current product:

fenom
<nav aria-label="breadcrumb" class="mb-4">
    <ol class="breadcrumb">
        <li class="breadcrumb-item"><a href="/">Home</a></li>
        {if $_modx->resource.parent > 0}
            <li class="breadcrumb-item">
                <a href="/{$_modx->resource.parent | resource : 'uri'}">
                    {$_modx->resource.parent | resource : 'pagetitle'}
                </a>
            </li>
        {/if}
        <li class="breadcrumb-item active">{$_modx->resource.pagetitle}</li>
    </ol>
</nav>

The gallery uses Splide for the slider and GLightbox for full-size viewing.

fenom
{'!msGallery'|snippet: [
    'tpl' => 'tpl.msGallery'
]}
  • Main slider — large images with fade effect
  • Thumbnails — image navigation (hidden when there is only one photo)
  • Lightbox — full-size view on click
  • Lazy loading — deferred image loading
  • Placeholder — shown when there are no images

Placeholders in tpl.msGallery

PlaceholderTypeDescription
{$files}arrayArray of product images
{$file['url']}stringFull image URL
{$file['small']}stringThumbnail URL (small)
{$file['medium']}stringMedium size URL
{$file['name']}stringFile name
{$file['description']}stringImage description

See also: msGallery


Product info

Right column with product data includes:

Vendor and title

fenom
{if $vendor_name?}
    <div class="text-muted text-uppercase mb-2">
        {$vendor_name}
    </div>
{/if}

<h1 class="mb-3">{$_modx->resource.pagetitle}</h1>

SKU and stock status

fenom
<div class="d-flex align-items-center gap-3 mb-3">
    {if $article?}
        <span class="text-muted">SKU: <strong>{$article}</strong></span>
    {/if}

    {if $stock? && $stock > 0}
        <span class="badge bg-success">In stock</span>
    {else}
        <span class="badge bg-secondary">On order</span>
    {/if}
</div>

Product badges

BadgeConditionStyle
NEW{$new?}badge bg-primary
BEST SELLER{$popular?}badge bg-warning text-dark
RECOMMENDED{$favorite?}badge bg-danger

Price block

Price is in a separate block with background:

fenom
<div class="product-price mb-4 p-4 bg-light rounded">
    {if $old_price? && $old_price > 0}
        <div class="old-price text-muted text-decoration-line-through mb-2">
            {$old_price}
        </div>

        {if $discount?}
            <div class="badge bg-danger mb-2">
                Discount {$discount}%
            </div>
        {/if}
    {/if}

    <div class="current-price display-4 fw-bold text-primary">
        {$price ?: 0}
    </div>
</div>

Discount calculation

Discount percentage is calculated automatically by the msProducts snippet when old_price is present. Formula: (old_price - price) / old_price * 100


Product options

If the product has color or size options, they are output as buttons:

fenom
{if $color?}
    <div class="option-group mb-3">
        <label class="form-label fw-semibold">Color:</label>
        <div class="d-flex flex-wrap gap-2">
            {foreach $color as $colorOption}
                <button type="button" class="btn btn-outline-secondary btn-sm option-btn">
                    {$colorOption}
                </button>
            {/foreach}
        </div>
    </div>
{/if}

JavaScript activates the first option by default and handles clicks for switching.


Add to cart form

The page contains two forms with state switching:

"Add" state

Shown when the product is not in the cart:

fenom
<form method="post" class="ms3_form" data-cart-state="add">
    <input type="hidden" name="id" value="{$_modx->resource.id}">
    <input type="hidden" name="options" value="[]">
    <input type="hidden" name="ms3_action" value="cart/add">

    <div class="row g-3 align-items-end">
        <div class="col-auto">
            <label class="form-label">{'ms3_cart_count' | lexicon}:</label>
            <input type="number" name="count" value="1" min="1" class="form-control">
        </div>
        <div class="col">
            <button type="submit" class="btn btn-primary btn-lg w-100">
                {'ms3_cart_add' | lexicon}
            </button>
        </div>
    </div>
</form>

"In cart" state

Shown when the product is already added:

fenom
<form method="post" class="ms3_form product-cart-controls-hidden" data-cart-state="change">
    <input type="hidden" name="product_key" value="">
    <input type="hidden" name="ms3_action" value="cart/change">

    <div class="row g-3 align-items-end">
        <div class="col-auto">
            <div class="input-group">
                <button class="btn btn-outline-primary dec-qty" type="button">−</button>
                <input type="number" name="count" value="1" min="0" class="form-control text-center">
                <button class="btn btn-outline-primary inc-qty" type="button">+</button>
            </div>
        </div>
        <div class="col">
            <button type="button" class="btn btn-success btn-lg w-100" disabled>
{'ms3_cart_in_cart' | lexicon}
            </button>
        </div>
    </div>
</form>

Switching happens automatically via the JavaScript ProductCardUI module on the ms3:cart:updated event.


Additional info

Block with icons for weight, country of origin, and delivery:

fenom
<ul class="list-unstyled mb-0">
    {if $weight? && $weight > 0}
        <li class="mb-2">
            <svg width="16" height="16"><use href="#icon-box"/></svg>
            <span class="text-muted">Weight:</span> <strong>{$weight} kg</strong>
        </li>
    {/if}
    {if $made_in?}
        <li class="mb-2">
            <svg width="16" height="16"><use href="#icon-globe"/></svg>
            <span class="text-muted">Country of origin:</span> <strong>{$made_in}</strong>
        </li>
    {/if}
</ul>

Info tabs

Bootstrap tabs for organizing content:

TabContent
DescriptionFull description from {$_modx->resource.description}
SpecificationsProduct properties table
DeliveryDelivery methods info
fenom
<ul class="nav nav-tabs mb-4" role="tablist">
    <li class="nav-item">
        <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#description">
            Description
        </button>
    </li>
    <li class="nav-item">
        <button class="nav-link" data-bs-toggle="tab" data-bs-target="#specs">
            Specifications
        </button>
    </li>
    <li class="nav-item">
        <button class="nav-link" data-bs-toggle="tab" data-bs-target="#delivery">
            Delivery
        </button>
    </li>
</ul>

<div class="tab-content">
    <div class="tab-pane fade show active" id="description">
        {$_modx->resource.description}
    </div>
    <!-- ... other tabs ... -->
</div>

Specifications table

Filled automatically from product fields:

FieldPlaceholder
SKU{$article}
Vendor{$vendor_name}
Country of origin{$made_in}
Weight{$weight}
Available colors{$color} (array)
Available sizes{$size} (array)

Block with products from the same category:

fenom
<div class="related-products mt-5">
    <h3 class="mb-4">Related products</h3>
    <div class="row">
        {'!msProducts' | snippet: [
            'tpl' => 'tpl.msProducts.row',
            'parents' => $_modx->resource.parent,
            'resources' => '-' ~ $_modx->resource.id,
            'limit' => 4,
            'formatPrices' => 1,
            'withCurrency' => 0
        ]}
    </div>
</div>
ParameterValuePurpose
parentsParent category IDProducts from the same category
resources-ID of current productExclude current product
limit4Show 4 products

Product placeholders

All fields from msProduct and msProductData tables are available on the product page:

Main fields

PlaceholderTypeDescription
{$_modx->resource.id}intProduct resource ID
{$_modx->resource.pagetitle}stringProduct title
{$_modx->resource.introtext}stringShort description
{$_modx->resource.description}stringFull description
{$_modx->resource.parent}intParent category ID
{$_modx->resource.uri}stringProduct URL

msProductData fields

PlaceholderTypeDescription
{$article}stringSKU
{$price}floatPrice
{$old_price}floatOld price
{$weight}floatWeight
{$stock}intStock quantity
{$vendor_id}intVendor ID
{$vendor_name}stringVendor name
{$made_in}stringCountry of origin
{$new}bool"New" flag
{$popular}bool"Popular" flag
{$favorite}bool"Recommended" flag

Product options

PlaceholderTypeDescription
{$color}arrayAvailable colors
{$size}arrayAvailable sizes
{$discount}intDiscount percentage (calculated)

Customization

Creating your own template

  1. Copy product.tpl to your theme folder
  2. Make the desired changes
  3. Assign the template to products in the manager

Create your own chunk and specify it in the call:

fenom
{'!msGallery'|snippet: [
    'tpl' => 'myCustomGallery'
]}

Adding your own tabs

Extend the tabs block in the template:

fenom
<li class="nav-item">
    <button class="nav-link" data-bs-toggle="tab" data-bs-target="#reviews">
        Reviews
    </button>
</li>

<div class="tab-pane fade" id="reviews">
    {'!msProductReviews' | snippet: ['product' => $_modx->resource.id]}
</div>

CSS classes

ClassElement
.product-infoProduct info container
.product-pricePrice block
.product-optionsOptions container
.option-groupOption group (color, size)
.option-btnOption selection button
.product-metaAdditional info
.product-tabsTabs container
.related-productsRelated products block
.ms3-galleryGallery container
.ms3-gallery-mainMain slider
.ms3-gallery-thumbsThumbnails slider

Dependencies

The template uses the following libraries:

LibraryVersionPurpose
Bootstrap 55.3.3CSS framework
Splide4.1.4Gallery slider
GLightbox3.3.0Image lightbox

Libraries are loaded via CDN. For production, using local copies is recommended.