Skip to content

Commit

Permalink
DOC Document changes to template layer (#591)
Browse files Browse the repository at this point in the history
  • Loading branch information
GuySartorelli authored Oct 30, 2024
1 parent 3b15e89 commit 9f14ed6
Show file tree
Hide file tree
Showing 27 changed files with 380 additions and 165 deletions.
2 changes: 1 addition & 1 deletion en/00_Getting_Started/04_Directory_Structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ We use `app/` as the default folder.
| `app/_config` | YAML configuration specific to your application |
| `app/src` | PHP code specific to your application (subdirectories are optional) |
| `app/tests` | PHP unit/functional/end-to-end tests |
| `app/templates` | HTML [templates](/developer_guides/templates) with `*.ss-extension` for the `$default` theme |
| `app/templates` | [templates](/developer_guides/templates) for the `$default` theme |
| `app/client/src` | Conventional directory for source resources (images/CSS/JavaScript) for your CMS customisations |
| `app/client/dist` | Conventional directory for transpiled resources (images/CSS/JavaScript) for your CMS customisations |
| `app/client/lang` | Conventional directory for [JavaScript translation tables](/developer_guides/i18n/#translation-tables-in-javascript) |
Expand Down
61 changes: 26 additions & 35 deletions en/02_Developer_Guides/01_Templates/01_Syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ icon: code

# Template syntax

A template can contain any markup language (e.g HTML, CSV, JSON, etc) and before being rendered to the user, they're
processed through [SSViewer](api:SilverStripe\View\SSViewer). This process replaces placeholders such as `$Var` with real content from your
A template can contain any markup language (e.g HTML, CSV, JSON, etc). The rendering process replaces placeholders such as `$Var` with real content from your
model (see [Model and Databases](../model)) and allows you to use logic like `<% if $Var %>` in your templates.

An example of a Silverstripe CMS template is below:
Expand Down Expand Up @@ -69,32 +68,33 @@ This will result in trying to fetch a `Title` property from the data being displ
Variables can be chained together, and include arguments.

```ss
$Foo(param)
$Foo("param")
$Foo.Bar
```

These variables will call a method / field on the object and insert the returned value as a string into the template.

Arguments don't have to be literals - you can pass template variables as arguments as well.

> [!WARNING]
> If you wish to pass arguments to getter functions, you must use the full method name. e.g. `$Thing` will try to access `Thing` as a property, which will ultimately result in `getThing()` being called with no arguments To pass arguments you must use `$getThing('param')`.
>
> Also, arguments must be literals, and cannot be other template variables (`$getThing($variable)` will pass the literal string `'$variable'` to the `getThing()` method).
> Arguments cannot be literal arrays (e.g `$myMethod(["val1", "val2"])`). The behaviour for this is currently undefined.
- `$Foo("param")` will call `$obj->Foo("param")`
- `$Foo.Bar` will call `$obj->Foo()->Bar`
- `$Foo($Bar)` will call `$obj->Bar` and then pass the result into `$obj->Foo($result)`

> [!NOTE]
> Arguments passed into methods can be any non-array literal type (not just strings), e.g:
> Arguments passed into methods can be almost any type (not just strings), e.g:
>
> - `$Foo(1)` will pass `1` as an int
> - `$Foo(0.5)` will pass `0.5` as a float
> - `$Foo(true)` will pass `true` as a boolean
> - `$Foo(null)` will pass `null` as a null primitive
> - `$Foo("param")`, `$Foo('param')`, and `$Foo(param)` will all pass `'param'` as a string. It is recommended that you always use quotes when passing a string for clarity
> - `$Foo("param")`, `$Foo('param')`, and `$Foo(param)` will all pass `'param'` as a string. It is recommended that you always use quotes when passing a string for clarity and for future-proofing.
If a variable returns a string, that string will be inserted into the template. If the variable returns an object, then
the system will attempt to render the object through its `forTemplate()` method. If the `forTemplate()` method has not
been defined, the system will return an error.
been defined, the system will attempt to cast the object to a string.

> [!NOTE]
> For more details around how variables are inserted and formatted into a template see
Expand All @@ -111,8 +111,7 @@ use SilverStripe\ORM\DataObject;
class MyObject extends DataObject
{
// ...

public function UsersIpAddress()
public function usersIpAddress()
{
return $this->getRequest()->getIP();
}
Expand All @@ -125,10 +124,11 @@ class MyObject extends DataObject
```

> [!NOTE]
> Method names that begin with `get` will automatically be resolved when their prefix is excluded. For example, the above method call `$UsersIpAddress` would also invoke a method named `getUsersIpAddress()`.
> Method names that begin with `get` will automatically be resolved when their prefix is excluded. For example, if the method `usersIpAddress()` didn't exist, the above reference to `$UsersIpAddress` would invoke a method named `getUsersIpAddress()`.
> Note that a method with the exact name you're referencing will always be used if it exists.
The variables that can be used in a template vary based on the object currently in scope (see [scope](#scope) below). Scope defines what
object the methods get called on. For the standard `Page.ss` template the scope is the current [`ContentController`](api:SilverStripe\CMS\Controllers\ContentController)
object the methods get called on. For the standard `Page.ss` template the scope is usually the current [`ContentController`](api:SilverStripe\CMS\Controllers\ContentController)
object. This object provides access to all the public methods on that controller, as well as the public methods, relations, and database fields for its corresponding [`SiteTree`](api:SilverStripe\CMS\Model\SiteTree) record.

```ss
Expand All @@ -141,6 +141,12 @@ $SilverStripeNavigator
$Content
```

### Case sensitivity

Just like in PHP, method names are case insensitive, but property names are case sensitive.

Using `$MyValue` in a template will successfully call a `myValue()` method, or a `getMyValue()` method even though the case doesn't match what you used in the template. But it *will not* match a `myValue` property or database field because the case doesn't match.

## Conditional logic

The simplest conditional block is to check for the presence of a value. This effectively works the same as [`isset()`](https://www.php.net/manual/en/function.isset.php) in PHP - i.e. if there is no variable available with that name, or the variable's value is `0`, `false`, or `null`, the condition will be false.
Expand Down Expand Up @@ -206,7 +212,7 @@ For more nuanced conditions you can use the `!=` operator.

### Boolean logic

Multiple checks can be done using `||`/`or`, or `&&`/ `and`.
Multiple checks can be done using `||`/`or`, or `&&`/`and`.

> [!NOTE]
> `or` is functionally equivalent to `||` in template conditions, and `and` is functionally equivalent to `&&`.
Expand Down Expand Up @@ -564,39 +570,24 @@ refer directly to properties and methods of the [`Member`](api:SilverStripe\Secu

### `fortemplate()` and `$Me` {#fortemplate}

If you reference some `ModelData` object directly in a template, the `forTemplate()` method on that object will be called.
This can be used to provide a default template for an object.

```php
// app/src/PageType/HomePage.php
namespace App\PageType;

use Page;

class HomePage extends Page
{
public function forTemplate(): string
{
// We can also render a template here using $this->renderWith()
return 'Page: ' . $this->Title;
}
}
```
If you reference some object directly in a template, the `forTemplate()` method on that object will be called. This can be used to provide a default template for an object.
If the `forTemplate()` method isn't implemented, the system will attempt to cast your object to a string.
The default implementation of this method on `ModelData` renders the model using templates named after the class or its superclasses.

```ss
<%-- calls forTemplate() on the first page in the list and prints Page: Home --%>
<%-- calls forTemplate() on the first page in the list --%>
$Pages->First
```

You can also use the `$Me` variable, which outputs the current object in scope by calling `forTemplate()` on the object.
This is especially helpful when you want to directly render items in a list you're looping over.

> [!WARNING]
> If the object does not have a `forTemplate()` method implemented, this will throw an error.
> If the object does not have an appropriate template, implement the `forTemplate()` method, or implement [`__toString()`](https://www.php.net/manual/en/language.oop5.magic.php#object.tostring), this will throw an error.
```ss
<% loop $Pages %>
<%-- calls forTemplate() on the current object in scope and prints Page: Home --%>
<%-- calls forTemplate() on the current object in scope --%>
$Me
<% end_loop %>
```
Expand Down
10 changes: 5 additions & 5 deletions en/02_Developer_Guides/01_Templates/02_Common_Variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ have a read of the [Formatting, Modifying and Casting Variables](casting) docume
</head>
```

The `<% base_tag %>` placeholder is replaced with the HTML base element. Relative links within a document (such as
The `<% base_tag %>` placeholder is replaced with the HTML `<base>` element. Relative links within a document (such as
`<img src="someimage.jpg" alt="">`) will become relative to the URI specified in the base tag. This ensures the
browser knows where to locate your site’s images and CSS files.

It renders in the template as `<base href="https://www.example.com" /><!--[if lte IE 6]></base><![endif]-->`
It renders in the template as `<base href="https://www.example.com/">`

> [!CAUTION]
> A `<% base_tag %>` is nearly always required or assumed by Silverstripe CMS to exist.
Expand Down Expand Up @@ -117,20 +117,20 @@ on a per-page basis.
By default `$MetaTags` renders (assuming 5.1.0 is the current version of `silverstripe/framework`):

```ss
```html
<title>Title of the Page</title>
<meta name="generator" content="Silverstripe CMS 5.1">
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
```

`$MetaTags(false)` will render

```ss
```html
<meta name="generator" content="Silverstripe CMS 5.1">
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
```

If using `$MetaTags(false)` we can provide a more custom `title`.
If using `$MetaTags(false)` we can provide a custom `<title> tag`.

```ss
$MetaTags(false)
Expand Down
4 changes: 2 additions & 2 deletions en/02_Developer_Guides/01_Templates/03_Requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Also see [Direct resource urls](#direct-resource-urls) below if you need to incl
## PHP requirements API

It is common practice to include most Requirements either in the `init()` method of your [controller](../controllers/), or
as close to rendering as possible (e.g. in [FormField](api:SilverStripe\Forms\FormField)).
as close to rendering as possible (e.g. in [`FormField::Field()`](api:SilverStripe\Forms\FormField::Field())).

```php
namespace App\Control;
Expand Down Expand Up @@ -451,7 +451,7 @@ If you want to get a resource for a *specific* theme or from somewhere that is n
```

> [!TIP]
> Notice the `vendor/module:some/path/to/file.jpg` syntax (used to get a resource from a specific module) is only valid for the `$resourceURL()` helper method. It won't work for `themedResourceURL()`.
> Notice the `vendor/module:some/path/to/file.jpg` syntax (used to get a resource from a specific module) is only valid for the `$resourceURL()` helper method. It won't work for `$themedResourceURL()`.
### Resource URLs or filepaths from a PHP context

Expand Down
92 changes: 79 additions & 13 deletions en/02_Developer_Guides/01_Templates/04_Rendering_Templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ icon: code

# Rendering data to a template

> [!NOTE]
> The template syntax, file extensions, and specifics about which templates are chosen from a set as described on this page are specific to the default [`SSTemplateEngine`](api:SilverStripe\View\SSTemplateEngine) - but many of the concepts here (especially the PHP code) should work with any template engine you choose to use.
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 `ModelData` object.
All of the `<% if %>`, `<% loop %>` and other variables are methods or parameters that are called on the current object in
scope (see [scope](syntax#scope) in the syntax section).
Expand All @@ -19,6 +22,9 @@ The following will render the given data into a template. Given the template:

Our application code can render into that view using the [`renderWith()`](api:SilverStripe\Model\ModelData) method provided by `ModelData`. Call this method on any instance of `ModelData` or its subclasses, passing in a template name or an array of templates to render.

> [!IMPORTANT]
> Don't include the `.ss` file extension when referencing templates.
```php
namespace App\Model;

Expand All @@ -44,6 +50,11 @@ class MyModel extends DataObject

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.

These examples assume you have moved the `templates/Coach_Message.ss` template file to `templates/Layout/Coach_Message.ss`

> [!WARNING]
> While a lot of the concepts on this page apply for any template engine, the `$Layout` functionality is specific to the default [`SSTemplateEngine`](api:SilverStripe\View\SSTemplateEngine).
```php
namespace App\Model;

Expand Down Expand Up @@ -71,7 +82,6 @@ class MyModel extends DataObject
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.

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

Expand Down Expand Up @@ -157,16 +167,15 @@ class MyPageController extends PageController
## Rendering arbitrary data in templates
Any data you want to render into the template that does not extend `ModelData` should be wrapped in an object that
does, such as `ArrayData` or `ArrayList`.
While `ModelData` has some methods on it you may find useful for reprensenting complex data, you should be able to use just about anything as a model in a template.
To actually render the data, you can use the `customise()` method to add your arbitrary data on top of an existing model:
```php
namespace App\PageType;
use PageController;
use SilverStripe\Control\Director;
use SilverStripe\Model\ArrayData;
use SilverStripe\Model\List\ArrayList;
class MyPageController extends PageController
{
Expand All @@ -178,14 +187,14 @@ class MyPageController extends PageController
return $this->customise([
'Name' => 'John',
'Role' => 'Head Coach',
'Experience' => ArrayList::create([
ArrayData::create([
'Experience' => [
[
'Title' => 'First Job',
])
ArrayData::create([
],
[
'Title' => 'Second Job',
]),
]),
],
],
])->renderWith('AjaxTemplate');
} else {
return $this->httpError(400);
Expand All @@ -194,5 +203,62 @@ class MyPageController extends PageController
}
```
> [!WARNING]
> 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 `ModelData` instance as mentioned above.
Or wrap the data in a `ModelData` subclass such as `ArrayList`:

```php
namespace App\PageType;

use PageController;
use SilverStripe\Model\ArrayData;

class MyPageController extends PageController
{
// ...

public function getMyRenderedData()
{
return ArrayData::create([
'Name' => 'John',
'Role' => 'Head Coach',
'Experience' => [
[
'Title' => 'First Job',
],
[
'Title' => 'Second Job',
],
],
])->renderWith('MyTemplate');
}
}
```

Or you can hand the data to `SSViewer` directly:

```php
namespace App\PageType;

use PageController;
use SilverStripe\View\SSViewer;

class MyPageController extends PageController
{
// ...

public function getMyRenderedData()
{
return SSViewer::create('MyTemplate')->process([
'Name' => 'John',
'Role' => 'Head Coach',
'Experience' => [
[
'Title' => 'First Job',
],
[
'Title' => 'Second Job',
],
],
]);
}
}
```
15 changes: 0 additions & 15 deletions en/02_Developer_Guides/01_Templates/05_Template_Inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,6 @@ to determine resolution priority. This search is based on the following three co
the module author, and does not normally need to be customised. This includes the `$project` and
`$other_modules` placeholder values.

### ThemeResourceLoader

The resolution of themes is performed by a [ThemeResourceLoader](api:SilverStripe\View\ThemeResourceLoader)
instance, which resolves a template (or list of templates) and a set of themes to a system template path.

For each path the loader will search in this order:

- Loop through each theme which is configured.
- If a theme is a set (declared with the `$` prefix, e.g. `$default`) it will perform a nested search within
that set.
- When searching the `$default` set, all modules will be searched in the order declared via the `module_priority`
config, interpolating keys `$project` and `$other_modules` as necessary. By default, your project's templates should
be checked before any vendor modules.
- When the first template is found, it will be immediately returned, and will not continue to search.

### Declaring themes

All themes can be enabled and sorted via the `SilverStripe\View\SSViewer.themes` config value. For reference
Expand Down
Loading

0 comments on commit 9f14ed6

Please sign in to comment.