Including other templates

You will often want to include other templates inside a template, so that you don’t have to repeat common templates.

There are two ways of doing it. If you know which template you want to include, then the template:X() template function include it by name. If you don’t, you can use deferredRender() on a template and then include the output with the render() template function.

Including a template

Let’s say you have a very simple widget, which renders a heading and some text. In a file called template/widget/example.hsvt in your plugin, you create this template:

<div class="example-widget">
    <h2> heading </h2>
    std:text:paragraph(text)
</div>

This expects a view with a heading and text properties text. When the template is rendered, it uses the current view. You can use within() to control which bit of the view is used by your included template, as shown in the “Including it twice” section below.

But in the very simple case, where you just want to use the current view, you can include this template in another template like this:

<h1> "Widgets" </h1>
<div>
    template:widget:example()
</div>

You can now render it with this view:

{
    "heading": "Widget One",
    "text": "First use"
}

Including it twice

Let’s say you want to use it twice in another template. You now need to render it with a different view, because otherwise it’ll render the same content twice.

We can use the within() template function to move the view to a nested view, and then include the other template.

<h1> "Widgets" </h1>
<div>
    within(one) { template:widget:example() }
    within(two) { template:widget:example() }
</div>

and render it with a view like:

{
    "one": {
        "heading": "Widget One",
        "text": "First use"
    },
    "two": {
        "heading": "Second widget",
        "text": "2nd use"
    }
}

But there’s a trick with yield() you can use to simplify this even further.

Using Platform templates

You can use platform templates in exactly the same way.

To use the std:ui:choose template, you’d prefix the name with template: and use it as you would any other template. For example,

<div class="choices">
    template:std:ui:choose()
</div>

and a view like

{
    "options": [
        {"action":"/do/path", "label":"Choice one"},
        // ...
    ]
}

Tricks with yield()

Template functions can take blocks, and the template:X() functions are no exception. The included template can then use yield() to include those blocks.

IMPORTANT: In most cases, you won’t need to use these tricks. If you’re writing a template that only you’re going to use, it’s going to be easier to just pass in information implicitly in the view.

Let’s write that widget again.

<div class="example-widget">
    <h2> yield() </h2>
    yield:text()
</div>

Now we don’t have to mess around with the view, and can include literal text in the template which includes this widget. For example:

<h1> "Widgets" </h1>
<div>
    template:widget:example() { "Heading One" }
        text { textFromView }

    template:widget:example() { secondHeadingFromView }
        text { std:text:paragraph("Yielded text") }
</div>

Deferred rendering

If you’re writing a plugin which composes the UI from other plugins, you should use deferredRender() in the JavaScript code, and the render() function in your template. This ensures you’re never unsafely including HTML without proper escaping.

For example, a plugin might use a service to collect content from other plugins.

// Service to collect content
P.implementService("example:included", function(content) {
    content.push(P.template("example/one").deferredRender({
        title: "Example",
        // rest of view goes here
    }));
});

// Request handler to fetch all the content from the
// various implementations of the service, and display it.
P.respond("GET", "/do/display/all", [
], function(E) {
    var content = [];
    O.service("example:included", content);
    E.render({
        content: content
    });
});

Your template to display all the content then looks like:

<div>
    each(content) {
        render(.)
    }
</div>

Here, . is a special value which is the root of the current view, that is, the current element in the content array.

Immediate deferreds

If you need to provide a deferred render to an API, but you are generating it within a different security context, you may find it convenient to call immediate() on the deferred to render it with the right user active. For example:

P.implementService("example:service", function(apiObject) {
  var deferred = O.impersonating(O.SYSTEM, function() {
      return P.template("tmpl").
          deferredRender(view).
          immediate();
  });
  apiObject.addDeferred(deferred);
});