Skip to main content

Themes and templates (v2)

Themes

The layout, look and feel of a Tuskfish site is determined by the template sets or themes that are in use. Themes (and layouts) can be assigned on a per-page basis, or varied dynamically according to programmatic conditions, so you can easily vary the layout or appearance for different sections of your site. Tuskfish gives you complete flexibilit.

Themes, unsurprisingly, live in the 'themes' directory. Each theme occupies a subdirectory bearing its name, for example 'default' and 'admin' are the default themes for the front end and the administrative area, respectively. Tuskfish also ships with an 'rss' theme to format your feeds and a 'signin' theme that provides login forms for your site.

To add a new theme to your site, upload it into the /themes directory and it will immediately be available for use. The default theme contains:

  • layout.html: This file sets the main layout of the page, for example the basic column / grid structure and elements such as navigation menu and footer, if present, and a placeholder for the main content area of the page. It also includes links to all the CSS and Javascript libraries in use. Other templates slot into layout.html as sub-templates, generally fitting in the main content area of the page:
<!-- Main page -->
<?php if ($page ?? null): ?>
<?php echo $page; ?> // Sub-template content, eg. listView.html, displayed here.
<?php endif; ?>
  • listView.html: A template displaying a feed of the latest content across all content types (except for tags and static pages), in teaser format. This is is used as the main content area of the home page.
  • For each content type, there is a specific template eg. 'article.html', 'image.html' and 'video.html', which display the full description of an object in single view mode.
  • A few other HTML files for components such as forms, tables etc. as required.
  • style.css: This contains custom CSS classes specific to this theme or that override the standard Bootstrap classes.

Templates are hierarchical. In the above example, layout.html is the outermost template dictating overall page structure. The $page placeholder will display the sub-template relevant to the current view, eg. listView.html on the home/index page, or article.html if a single news article were being viewed. Templates must be rendered in reverse order (innermost first) so that they can be assigned to the next/outer level template.

For the default set up of a Tuskfish theme you need (at a minimum) layout.html, everything else is optional. 

Assigning themes and layouts to pages

Themes and layouts are assigned at the individual page level using a single line of code, usually in the constructor of the viewModel for a given route. In this example the 'default' template set is assigned, using its directory name. If no layout file is specified, then 'layout.html' will be used by default:

// From \Tfish\Content\ViewModel\Listing:
public function __construct($model, \Tfish\Entity\Preference $preference)
{
        $this->model = $model;
        $this->preference = $preference;
        $this->theme = 'default'; // Change the theme name here.
        $this->pageTitle = TFISH_LATEST_POSTS;
}

You could assign a different theme and/or layout to this page by specifying some other theme and/or layout you have installed on the site. For example, let's assign a 'custom' theme that you designed yourself, with an alternative 'customLayout' file:

...
// Specify an alternative theme and layout.
$this->theme = 'custom';
$this->layout = 'customLayout'; // customLayout.html must be in the custom theme directory.
...

You can override the theme elswhere in the viewmodel methods to suit different contexts, simply by changing the value of the theme property. But it is also sometimes convenient to vary the theme from the controller of a custom route by calling setTheme() on the viewmodel:

// From \Tfish\Content\Controller\Listing
public function display(): array
{
    $this->viewModel->setTheme('custom'); // Override theme from controller.
    $this->viewModel->setLayout('customLayout'); // Override the layout file from controller.
    ...

Switching themes and layouts conditionally

By adding some conditions you can make a page switch themes and/or layouts dynamically. In the example below a visuals-heavy landing page theme is set as default, but if parameters are available requesting a specific page view (ie. indicating that content has been requested) then a more text-friendly theme is used.

// From \Tfish\Content\Controller\Listing
// Use Agency theme for home page, Marketing theme for everything else.
if ($cleanId || $cleanStart || $cleanTag) {
    $this->viewModel->setTheme('marketing');
} else {
    $this->viewModel->setTheme('agency');
}

Another way to do this would be to use one theme with two alternative layout files, which would be a better choice if you want the look and feel to stay the same, but want to vary the arrangement of the page.

// From \Tfish\Content\Controller\Listing
...
// Use custom layout for the home page (display landing page if no content parameters detected).
if (!$id && !$start && !$tag) {
    $this->viewModel->setlayout('layoutHome.html');
}

// If no layout file is specified (ie. for everything else) Tuskfish will look for layout.html by default.
...

Cloning an existing theme

You can easily create a new theme from an existing one:

  • Make a copy of the theme with a different directory name. 
  • Edit the <head> section of the theme and update the links to style.css and any specific scripts and fonts, to match the new directory path.

The new theme is fully independent and you can modify it without any fear of damaging the old one.

Modifying a third-party Bootstrap theme to work with Tuskfish

It's not hard to convert a third-party theme to work with Tuskfish. Basically, you need to insert placeholders where you want Tuskfish to dynamically insert content:

  • Modify the <head> section to draw on Tuskfish site preferences / metadata and to point to the local Bootstrap and jQuery libraries. Just take a look at the default theme as an example; you can copy the meta tags directly and they will work just fine.
  • Insert placeholders into the template, as relevant, eg. $page for the main page area.

Possibly, depending on the nature of the template set, you may need to apply some of its CSS styles to content-specific templates, to make them consistent with the overall look and feel. The default templates that ship with Tuskfish only contain vanilla HTML, so you (might) need to apply classes to style them.

Be aware that while designers often bundle the Javascript libraries they have used with their themes, these are usually out of date by the time you download them and may contain security vulnerabilities. It is best to delete bundled standard libraries such as jQuery and Bootstrap, and substitute links to the versions bundled with Tuskfish, so they will get updated with Tuskfish patches.

Templates

The template system is managed by the \Tfish\Entity\Template class. An instance of Template is automatically initialised as a property of the view. Templates are rendered by \Tfish\FrontController, just before it exits.

You don't need to interact with the template object, rather, you will interact with the viewmodel (available in all templates as $viewModel). The viewmodel can be used to store data that you want to insert into HTML templates, by setting its properties, or  to request data from the model, by calling its methods.

Assigning data to templates

Simply assign your data to the viewmodel as a property. External parameters are usually set via the controller for a given route:

// From \Tfish\Content\Controller:
...
// If the ID parameter is set, show a specific object, otherwise display a list.
$id = (int) ($_GET['id'] ?? 0);

if (!empty($id)) {
    $this->viewModel->setId($id); // Setting ID on viewmodel.
    $cacheParams['id'] = $id;
    $this->viewModel->displayObject();
} else {
    $this->viewModel->displayList();
}

Within the template access the viewmodel as a PHP object. By convention, content objects are normally retrieved from the viewmodel at the start of a template, so that they can be accessed directly thereafter:

...
<!-- From article.html - display a single article object -->
<?php $content = $viewModel->content(); ?>
<?php $contentTags = $viewModel->contentTags(); ?>
<?php $parent = $viewModel->parent(); ?>

<!-- Container -->
<div class="container">

<!-- Title and meta information -->
<div>
  <h2 class="title"><?php echo xss($content->title()); ?></h2>
...

Rendering templates

Templates are rendered automatically by the \Tfish\FrontController via a call to the view. You don't need to handle anything. But if you try to render a template that does not exist in your theme an error will be thrown.

// From \Tfish\FrontController
...
/**
 * Renders the layout (main template) of a theme.
 *
 * @param \Tfish\Entity\Metadata $metadata Instance of the Tuskfish metadata class.
 * @param string $viewModel Instance of a viewModel class.
 */
private function renderLayout(Entity\Metadata $metadata, $viewModel)
{
    $page = $this->view->render();
    $metadata->update($viewModel->metadata());
    $session = $this->session;

    $theme = $this->trimString($viewModel->theme() ?? 'default');
    $layout = $this->trimString($viewModel->layout() ?? 'layout');

    if ($this->hasTraversalorNullByte($theme) || $this->hasTraversalorNullByte($layout)) {
        \trigger_error(TFISH_ERROR_TRAVERSAL_OR_NULL_BYTE, E_USER_ERROR);
        exit; // Hard stop due to high probability of abuse.
    }

    include_once TFISH_THEMES_PATH . $theme . "/" . $layout . ".html";
}

Creating new content object templates

Some content types have multiple templates that you can choose from to suit the piece. For example there are three article templates that let you position the leading image left, centre or right, collections can be displayed as a compact list or as teasers with more detail, and videos have a choice of templates to suit different aspect ratio content.

Creating additional, custom templates is a three step process:

  • Create your new template, give it a unique "name.html" and save a copy in each theme directory.
  • Edit the file trust_path/libraries/tuskfish/class/Tfish/Content/Traits/ContentTypes.php, and add it as an option for the relevant content type in listTemplates().
/**
     * Returns a list of template names used by specific content types.
     *
     * Used to validate template selections set in jQuery function listed below. The options listed
     * here MUST be kept synchronised with those in:
     *
     * vendor/tuskfish/contentForm.js => loadTemplateOptions()
     *
     * @return  array Array of type-template key values.
     */
    public function listTemplates(): array
    {
        return [
            'TfArticle' => ['article', 'article-left', 'article-right'],
            'TfAudio' => ['audio'],
            'TfBlock' => ['block'],
            'TfCollection' => ['collection', 'collection-compact'],
            'TfDownload' => ['download'],
            'TfImage' => ['image'],
            'TfTag' => ['tag'],
            'TfTrack' => ['track'],
            'TfStatic' => ['static'],
            'TfVideo' => ['video1x1', 'video4x3', 'video16x9', 'video21x9'],
        ];
    }
  • Edit the file vendor/tuskfish/contentForm.js and add your template as an additional option for the relevant content type is loadTemplateOptions(). The key must match the template file name, without the .html extension.
/**
     * Returns a list of template names used by specific content types.
     *
     * Used to validate template selections set in jQuery function listed below. The options listed
     * here MUST be kept synchronised with those in:
     *
     * vendor/tuskfish/contentForm.js => loadTemplateOptions()
     *
     * @return  array Array of type-template key values.
     */
    public function listTemplates(): array
    {
        return [
            'TfArticle' => ['article', 'article-left', 'article-right'],
            'TfAudio' => ['audio'],
            'TfBlock' => ['block'],
            'TfCollection' => ['collection', 'collection-compact'],
            'TfDownload' => ['download'],
            'TfImage' => ['image'],
            'TfTag' => ['tag'],
            'TfTrack' => ['track'],
            'TfStatic' => ['static'],
            'TfVideo' => ['video1x1', 'video4x3', 'video16x9', 'video21x9'],
        ];
    }

Bootstrap

Tuskfish uses the excellent Bootstrap framework for front end presentation. If you already know Bootstrap you can skip this section. Tuskfish uses Bootstrap 5.

Bootstrap borrows the 'layout grid' metaphor from print publishing as the basis for designing sites. It is first-rate for developing responsive, mobile-friendly designs that work across different sized displays. Bootstrap provides a library of useful CSS styles, and components including controls, menus and vector icons. It is very well documented with free starter themes and code examples. 

Simply applying Bootstrap styles to some vanilla HTML will give you highly polished output. Since vanilla HTML is what I can do and highly polished output is what I want, that's why we are using it :) At its heart Tuskfish is just a back-end data entry and retrieval system; when it comes to actual presentation of that data the show is handed over to the HTML templates, which just contain vanilla mark up. Bootstrap is used for styling and presentation, but you can swap it out for something else if you want.

Wiring one up to work with Tuskfish only takes a few minutes; essentially Tuskfish generates placeholders containing your content and you just need to insert them into a template were appropriate and possibly apply some CSS classes to your HTML. Rename the index file to layout.html, copy the sub-templates from the default theme into the new one and you are ready to go.

There are loads of beautiful, mobile-friendly Bootstrap themes available for free and you can also buy themes from many commercial creators. There are many sites offering quality work for "nearly free" if you are prepared to tolerate a few strings such as providing a backlink to the designer's site (AKA free advertising, for them) and not being able to redistribute the themes to anyone else. To me a work with such conditions is not actually free, but designers seem to have a different idea about what this word means.

As a developer I need themes that I can redistribute with my code, if I can't redistribute a theme then it is useless to me. Also, it is one thing to ask that your name be credited via a comment in the code (no problem), but it is another to require people to publicly display your name on the face of their website as a licensing condition. Many business and corporate users simply can't do that, and again, themes that come with such conditions should not be described as "free". Of particular annoyance are people that expect public attribution for every little form icon and button - all it does is ensure that nobody ever uses their work.

While I'm in rant mode the fact that the GNU General Public License does not cover artwork that ships with GPL code is a constant source of annoyance to me. Why is it that the graphic designer gets paid, but the programmer doesn't?

For royalty-free photos that you can use without having to plaster ugly attributions all over your website, sign up to Death To The Stock Photo.

Copyright, all rights reserved.