Trio Icon
Trio v5.0.0
Documentation is evolving and is a WIP

Tag-Based Callbacks

Trio scans your composites for HTML tags that are decorated with the data-trio-callback attribute and asynchronously calls the JavaScript Node modules they name, passing them a single object whose properties can be used along with cheerio's selector API to augment the composites with dynamic content.

  • file location: root/source/callbacks
  • file type/content: .js/JavaScript Node Module

Trio treats your project's markup as plain text, meaning there is no DOM involved, and parses it using the jQuery-like selector API provided by the awesome cheerio OS library. jQuery's selector API documentation can be found at jquery.com.

Declaring Tag-Based Callbacks In Your Markup

To declare tag-based callbacks in your markup you decorate the tags you want to target in template, fragment and include assets with data-trio-callback attributes, and assign them the names of the callbacks located in the root/source/callbacks folder:

<ul data-trio-callback="blogtaglist"></ul>
Example: Declaring A Tag-Based Callback

The callbacks can then reference the decorated tags to augment them with dynamic content using the $tag property. See Implementing Tag Based Callbacks below for more information.

Implementing Tag-Based Callbacks

Each module must export a single function which, when called by Trio, is passed a single argument

{ $tag, $page, asset, site, cheerio }

which can be destructured

({ $tag, $page, asset, site, cheerio })

to access its properties. See Unpacking fields from objects passed as function parameter for more information.

Callbacks can be implemented to run synchronously

module.exports = ({ $tag, site }) => {
    site.tagsCatalog.forEach(item => {
        $tag.append(`
            <li class="tag__list-item">
                <a data-trio-link class="tag__list-item-link"
                href="/blog/tag/${item.tag.toLowerCase()}">${item.tag}</a>
            </li>
        `);
    });
};
Example: Synchronous JavaScript Callback

or asynchronously, using async/await.

module.exports = async ({ $tag, site }) => {
    const catalog = await getCatalogFromCloud(...);
    catalog.forEach(item => {
        $tag.append(`
            <li class="catalog-item__list-item">
                <a data-trio-link class="catalog-item__list-item-link"
                href="/catalog/item/${item.name}">${item.price.toFixed(2)}</a>
            </li>
        `)
    });
};
Example: Asynchronous JavaScript Callback

Argument Properties

$page

Please note that this property was originally named $ but was renamed to $page in v1.0.0-rc.5.

$page is a cheerio function. It is equivalent to jQuery's $ and jQuery() functions and can be used to return a collection of matched tags in the composite when you are targeting tags other than $tag.

module.exports = ({ $page, site }) => {
    site.tagsCatalog.forEach(item => {
        $page("ul.tag__list").append(`
            <li class="tag__list-item">
                <a data-trio-link class="tag__list-item-link"
                href="/blog/tag/${item.tag.toLowerCase()}">${item.tag}</a>
            </li>
        `);
    });
    $page("div.page-modified-date").append(new Date().toDateString());
};
Example: Targeting A Composite's Content Using $page

$tag

$tag is a cheerio wrapper for the tag which was decorated with the data-trio-callback attribute. It can be used to target the tag with dynamic content.

module.exports = ({ $tag }) => {
    $tag.append(new Date().toDateString());
};
Example: Targeting A Tag's Content Using $tag

site

site exposes the organized collection of metadata that Trio creates from your project's assets. Its catalogs - frags, articlesCatalog, categoriesCatalog, tagsCatalog, dataCatalog - as well as its other properties can be used to augment your composites with dynamic content.

module.exports = ({ $tag, site }) => {
    site.tagsCatalog.forEach(item => {
        $tag.append(`
            <li class="tag__list-item">
                <a data-trio-link class="tag__list-item-link"
                href="/blog/tag/${item.tag.toLowerCase()}">${item.tag}</a>
            </li>
        `);
    });
};
Example: Targeting A Tag's Content With Data From site.tagsCatalog

asset

asset exposes the metadata specific to the fragment, including its front matter. Its catalogs - relatedArticlesByCategory, relatedArticlesByTag, relatedArticlesByTagFlattened - as well as its other properties can be used to augment your composites with dynamic content.

module.exports = ({ $tag, asset }) => {
    const data = asset.matter.data;
    $tag.find("h1.article__title").append(data.title);
    $tag.find("span.article__category").append(`"${data.category}"`);
    $tag.find("span.article__date").append(asset.articleDate);
    $tag.find("img.article__img").attr("src", `/media/${data.image}`);
};
Example: Targeting A Tag's Content With Data From asset

cheerio

cheerio exposes a constructor function that can be used to load and manipulate dynamic tag structures, such as:

const $ = cheerio.load('<h2 class="title">Hello world</h2>');

$('h2.title').text('Hello there!');
$('h2').addClass('welcome');

$.html();
//=> <html><head></head><body><h2 class="title welcome">Hello there!</h2></body></html>
};
Example: Using The Cheerio Constructor Function To Load And Manipulate Dynamic Tag Structures
Since both $page and $tag also provide methods for creating and manipulating dynamic tag structures the cheerio property is therefore basically redundant. It is currently being maintained for legacy sake but you are advised that the cheerio property is a strong candidate for deprecation in a future release of Trio.

Internal Module Dependencies And Caching

Tag-Based-callbacks can, of course, have their own internal module dependencies. When they do, though, you should import them using import-fresh (or some similar package) to guarantee that you are always importing uncached copies of them.

const importFresh = require('import-fresh');
const capitalize = importFresh("../lib/capitalize");

module.exports = ({ $tag, site }) => {
    site.articlesCount && site.articlesCatalog.forEach(item => {
        const data = item.matter.data;
        $tag.append(`
                <li>
                    <a data-trio-link href="${item.url}">
                        ${data.title} - Posted to ${capitalize(data.category[0])} - ${item.articleDate}
                    </a>
                </li>
            `);
    });
};
Example: Tag-Based Callback Importing Uncached Module Dependency

In the above example, if you had used require instead of import-fresh, and you were running any of Trio's build commands with the watch option, then any changes you make to the module ./lib/capitalize would be ignored because Node will import the module from its cache.

Trio uses import-fresh internally to import uncached tag-based callbacks.

Declaring Your Tag-Based Callback Module's Internal Dependencies To Trigger Incremental Builds

When building incrementally, Trio will detect that you have made modifications to your tag-based callbacks, and it will trigger a build to regenerate only those composites whose assets relate by way of their chains of dependencies to the modified callback. However, Trio will not automatically trigger a build when:

  • You have made modifications to modules that your tag-based callbacks might require (internal module dependencies), such as to a library that you created in root/source/callbacks/lib.
  • You have made modifications to JSON files in the root/source/data folder that your tag-based callbacks might require

To remedy these 2 situations, you can optionally add front matter to your tag-based callbacks with the properties discussed below that will inform Trio to trigger a build whenever these internal dependencies are modified.

Declaring Your Tag-Based Callback Module's Internal Module Dependencies

You can declare a single module dependency using the moduleDependencies property

/*
moduleDependencies: ../lib/getDate
*/
Example: Declaring A Single Module Dependency

and you can declare multiple module dependencies.

/*
moduleDependencies:
- ../lib/getDate
- ../lib/getTime
*/
Example: Declaring Multiple Module Dependencies

Declaring Your Tag-Based Callback Module's Internal JSON Data File Dependencies

You can declare a single JSON file dependency using the dataDependencies property.

/*
dataDependencies: contactInfo
*/
Example: Declaring A Single JSON File Dependencies

and you can declare multiple JSON file dependencies.

/*
dataDependencies:
- contactInfo
- portfolio
*/
Example: Declaring Multiple JSON File Dependencies

Declaring External Dependencies On Metadata To Trigger Incremental Builds

When building incrementally, Trio will detect that you have made modifications to your fragment, include, template and tag-based callback assets, and it will trigger a build to regenerate only those composites whose assets relate by way of their chains of dependencies to those modified assets. However, Trio will not regenerate composites whose own assets haven't changed but which consume the metadata generated from unrelated modified assets, such as for pages that contain lists of other pages (e.g. blog index pages, tag pages, category pages, catalog pages, portfolio pages, etc.).

To remedy this situation, you can add the alwaysBuild property to your fragment's front matter which will cause Trio to always mark this fragment as stale.

Using collections, which were introduced in v2.0.0, can eliminate having to use alwaysBuild.

<!--
template: blogpage
title: Trio Blog | Official blog for Trio static site generator.
alwaysBuild: true
-->
Example: Using The Front Matter alwaysBuild Property

See Also