Version 5 supported

Rendering data to a template

Templates do nothing on their own. Rather, they are used to generate markup - most typically they are used to generate HTML markup, using variables from some ViewableData object. All of the <% if %>, <% loop %> and other variables are methods or parameters that are called on the current object in scope (see scope in the syntax section).

The following will render the given data into a template. Given the template:

<%-- app/templates/Coach_Message.ss --%>
<strong>$Name</strong> is the $Role on our team.

Our application code can render into that view using the renderWith() method provided by ViewableData. Call this method on any instance of ViewableData or its subclasses, passing in a template name or an array of templates to render.

namespace App\Model;

use SilverStripe\ORM\DataObject;
use SilverStripe\View\ArrayData;

class MyModel extends DataObject
{
    // ...

    public function getRenderedMessage()
    {
        $arrayData = ArrayData::create([
            'Name' => 'John',
            'Role' => 'Head Coach',
        ]);

        // renders "<strong>John</strong> is the Head Coach on our team."
        return $arrayData->renderWith('Coach_Message');
    }
}

If you want to render an arbitrary template into the $Layout section of a page, you need to render your layout template and pass that as the Layout parameter to the Page template.

namespace App\Model;

use SilverStripe\ORM\DataObject;

class MyModel extends DataObject
{
    // ...

    public function getRenderedMessageAsPage()
    {
        $data = [
            'Title' => 'Message from the Head Coach',
        ];

        return $this->customise([
            'Layout' => $this
                        ->customise($data)
                        ->renderWith(['Layout/Coach_Message']),
        ])->renderWith(['Page']);
    }
}

In this case it may be better to use an implicit Layout type template, and rely on template inheritance to figure out which templates to use.

// This assumes you have moved the Coach_Message template to `templates/Layout/Coach_Message.ss`
$this->customise($data)->renderWith(['Coach_Message', 'Page']);

This will look for a global templates/Coach_Message.ss template, and if it doesn't find one, will use templates/Page.ss as the main template. Then, when it encounters $Layout in that template, it will find and use the templates/Layout/Coach_Message.ss file to substitute that variable.

You will often have templates named after specific classes, as discussed in template types and locations. In that case, you can simply use MyClass::class syntax here. e.g:

use App\Model\Coach;

$this->customise($data)->renderWith([Coach::class . '_Message', Page::class]);

This will search for the following templates:

  • templates/App/Model/Coach_Message.ss
  • templates/Page.ss
  • templates/App/Model/Layout/Coach_Message.ss
  • templates/Layout/Page.ss

See template types and locations for more information.

Most classes in Silverstripe CMS you want in your template extend ViewableData and allow you to call renderWith. This includes Controller, FormField and DataObject instances.

$controller->renderWith([MyController::class, MyBaseController::class]);

use SilverStripe\Security\Security;
Security::getCurrentUser()->renderWith('Member_Profile');

Advanced use cases

renderWith() can be used to override the default template process. For instance, to provide an ajax version of a template.

namespace App\PageType;

use PageController;
use SilverStripe\Control\Director;

class MyPageController extends PageController
{
    private static $allowed_actions = [
        'iwantmyajax',
    ];

    public function iwantmyajax()
    {
        if (Director::is_ajax()) {
            return $this->renderWith('AjaxTemplate');
        } else {
            return $this->httpError(404);
        }
    }
}

Controller already has a shortcut for the above scenario. Instead of explicitly calling renderWith() above, you can declare a template with the following naming convension: [modelOrControllerClass]_[action].ss e.g. Page_iwantmyajax.ss, HomePage_iwantmyajax.ss, or PageController_iwantmyajax.ss.

With a template that follows that naming convention in place, the PHP for the iwantmyajax() becomes:

public function iwantmyajax()
{
    if (!Director::is_ajax()) {
        return $this->httpError(400);
    }
    // will feed $this into $this->prepareResponse(), which will render $this using templates defined
    // in $this->getViewer()
    return $this;
}

This ultimately uses SSViewer::get_templates_by_class() to find the templates for the class or its parent classes with the action as a suffix.

If you don't have any logic to add in the action, you can forego implementing a method altogether - all you need is to add the action name in the $allowed_actions configuration array and make sure you have an appropriately named template.

Rendering arbitrary data in templates

Any data you want to render into the template that does not extend ViewableData should be wrapped in an object that does, such as ArrayData or ArrayList.

namespace App\PageType;

use PageController;
use SilverStripe\Control\Director;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;

class MyPageController extends PageController
{
    // ...

    public function iwantmyajax()
    {
        if (Director::is_ajax()) {
            return $this->customise([
                'Name' => 'John',
                'Role' => 'Head Coach',
                'Experience' => ArrayList::create([
                    ArrayData::create([
                        'Title' => 'First Job',
                    ])
                    ArrayData::create([
                        'Title' => 'Second Job',
                    ]),
                ]),
            ])->renderWith('AjaxTemplate');
        } else {
            return $this->httpError(400);
        }
    }
}

A common mistake is trying to loop over an array directly in a template - this won't work. You'll need to wrap the array in some ViewableData instance as mentioned above.

Related lessons