Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
358 lines (258 sloc) 14.9 KB
<!--
title: Blocks
order: 6
-->
{{#markdown}}
Blocks lets you define reusable statements that can be invoked with a new context allowing the creation custom iterators and helpers -
making it easy to encapsulate reusable functionality and reduce boilerplate for common functionality.
The syntax for blocks follows the familiar [handlebars block helpers](https://handlebarsjs.com/block_helpers.html) in both syntax and functionality.
Templates also includes most of handlebars.js block helpers which are useful in a HTML template language whilst minimizing any porting efforts if
needing to reuse existing JavaScript handlebars templates.
We'll walk through creating a few of the built-in Template blocks to demonstrate how to create them from scratch.
### noop
We'll start with creating the `noop` block (short for "no operation") which functions like a block comment by removing its inner contents
from the rendered page:
{{/markdown}}
{{ 'gfm/blocks/01.md' | githubMarkdown }}
{{#markdown}}
The `noop` block is also the smallest implementation possible which needs to inherit `TemplateBlock` class, overrides the `Name` getter with
the name of the block and implements the `WriteAsync()` method which for the noop block just returns an empty `Task` there by not writing anything
to the Output Stream, resulting in its inner contents being ignored:
{{/markdown}}
{{ 'gfm/blocks/02.md' | githubMarkdown }}
{{#markdown}}
All Block's are executed with 3 parameters:
- `TemplateScopeContext` - The current Execution and Rendering context
- `PageBlockFragment` - The parsed Block contents
- `CancellationToken` - Allows the async render operation to be cancelled
{{/markdown}}
<h3 id="registering-blocks">Registering Blocks</h3>
{{#markdown}}
The same flexible registration options for [Registering Filters](/docs/filters#registering-filters) is also available for registering blocks
where if it wasn't already built-in, `TemplateNoopBlock` could be registered by adding it to the `TemplateBlocks` collection:
{{/markdown}}
{{ 'gfm/blocks/03.md' | githubMarkdown }}
<h5 id="scan-types">Autowired using TemplateContext IOC</h5>
{{#markdown}}
Autowired instances of blocks and filters can also be created using TemplateContext's configured IOC where they're also injected with any
registered IOC dependencies by registering them in the `ScanTypes` collection:
{{/markdown}}
{{ 'gfm/blocks/04.md' | githubMarkdown }}
{{#markdown}}
When the `TemplateContext` is initialized it will go through each Type and create an autowired instance of each Type and register them in the
`TemplateBlocks` collection. An alternative to registering individual Types is to register an entire Assembly, e.g:
{{/markdown}}
{{ 'gfm/blocks/05.md' | githubMarkdown }}
{{#markdown}}
Where it automatically registers any Blocks or Filters contained in the Assembly where the `MyBlock` Type is defined.
{{/markdown}}
{{#markdown}}
### bold
A step up from `noop` is the **bold** Template Block which markup its contents within the `<b/>` tag:
{{/markdown}}
<pre>
{{#raw}}{{#bold}}This text will be bold{{/bold}}{{/raw}}
</pre>
{{#markdown}}
Which calls the `base.WriteBodyAsync()` method to evaluate and write the Block's contents to the `OutputStream` using the current
`TemplateScopeContext`:
{{/markdown}}
{{ 'gfm/blocks/06.md' | githubMarkdown }}
{{#markdown}}
### with
The `with` Block shows an example of utilizing arguments. To maximize flexibility arguments passed into your block are captured in a free-form
string (specifically a `ReadOnlyMemory<char>`) which allows creating Blocks varying from simple arguments to complex LINQ-like expressions - a
feature some built-in Blocks take advantage of.
The `with` block works similarly to [handlebars with helper](https://handlebarsjs.com/block_helpers.html#with-helper) or JavaScript's
[with statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with) where it extracts the properties (or Keys)
of an object and adds them to the current scope which avoids needing a prefix each property reference,
e.g. being able to use `{{Name}}` instead of `{{person.Name}}`:
<pre>
{{#with person}}
Hi {{Name}}, your Age is {{Age}}.
{{/with}}
</pre>
Also the `with` Block's contents are only evaluated if the argument expression is `null`.
The implementation below shows the optimal way to implement `with` by calling `GetJsExpressionAndEvaluate()` to resolve a cached
AST token that's then evaluated to return the result of the Argument expression.
If the argument evaluates to an object it calls the `ToObjectDictionary()` extension method to convert it into a `Dictionary<string,object>`
then creates a new scope with each property added as arguments and then evaluates the block's Body contents with the new scope:
{{/markdown}}
{{ 'gfm/blocks/07.md' | githubMarkdown }}
{{#markdown}}
To better highlight how this works, a non-cached version of `GetJsExpressionAndEvaluate()` involves parsing the Argument string into
an AST Token then evaluating it with the current scope:
{{/markdown}}
{{ 'gfm/blocks/08.md' | githubMarkdown }}
{{#markdown}}
The `ParseJsExpression()` extension method is able to parse virtually any [JavaScript Expression](/docs/expression-viewer) into an AST tree
which can then be evaluated by calling its `token.Evaluate(scope)` method.
##### Final implementation
The actual [TemplateWithBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Templates/Blocks/TemplateWithBlock.cs)
used in Templates includes extended functionality which uses `GetJsExpressionAndEvaluateAsync()` to be able to evaluate both **sync** and **async**
results.
##### else if/else statements
It also evaluates any `block.ElseBlocks` statements which is **functionality available to all blocks** which are able to evaluate any alternative
**else/else if** statements when the main template isn't rendered, e.g. in this case when the `with` block is called with a `null` argument:
{{/markdown}}
{{ 'gfm/blocks/09.md' | githubMarkdown }}
{{#markdown}}
### if
Since all blocks are able to execute any number of `{{else}}` statements by calling `base.WriteElseAsync()`, the implementation for
the `{{if}}` block ends up being even simpler which just needs to evaluate the argument to `bool`.
If **true** it writes the body with `WriteBodyAsync()` otherwise it evaluates any `else` statements with `WriteElseAsync()`:
{{/markdown}}
{{ 'gfm/blocks/10.md' | githubMarkdown }}
{{#markdown}}
### each
From what we've seen up till now, the [handlebars.js each block](https://handlebarsjs.com/block_helpers.html#iterators) is also
straightforward to implement which just iterates over a collection argument that evaluates its body with a new scope containing the
elements **properties**, a conventional `it` binding for the element and an `index` argument that can be used to determine the
index of each element:
{{/markdown}}
{{ 'gfm/blocks/11.md' | githubMarkdown }}
{{#markdown}}
Despite its terse implementation, the above Template Block can be used to iterate over any expression that evaluates to a collection,
inc. objects, POCOs, strings as well as Value Type collections like ints.
##### Built-in each
However the built-in [TemplateEachBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Templates/Blocks/TemplateEachBlock.cs)
has a larger implementation to support its richer feature-set where it also includes support for async results, custom element bindings and LINQ-like syntax for
maximum expressiveness whilst utilizing expression caching to ensure any complex argument expressions are only parsed once.
{{/markdown}}
{{ 'gfm/blocks/12.md' | githubMarkdown }}
{{#markdown}}
By using `ParseJsExpression()` to parse expressions after each "LINQ modifier", `each` supports evaluating complex JavaScript expressions in each
of its LINQ querying features, e.g:
{{/markdown}}
<div class="linq-preview {{ output }}">
<div class="template">
<textarea autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" class="form-control" rows="6"
>{{#raw}}{{#each c in customers
where c.City == 'London' and c.Country == 'UK' orderby c.CompanyName descending
skip 3 - (2 - 1)
take 1 + 2 }}
{{index + 1}}. {{c.CustomerId}} from <b>{{c.CompanyName}}</b> - {{c.City}}, {{c.Country}}.
{{/each}}{{/raw}}</textarea>
</div>
<div class="result">
<div class="output"></div>
</div>
</div>
{{#markdown}}
##### Custom bindings
When using a custom binding like `{{#each c in customers}}` above, the element is only accessible with the custom `c` binding which is more efficient
when only needing to reference a subset of the element's properties as it avoids adding each of the elements properties in the items execution scope.
Check out [LINQ Examples](/linq/restriction-operators) for more live previews showcasing advanced usages of the `{{#each}}` block.
### raw
The `{{#raw}}` block is similar to [handlebars.js's raw-helper](https://handlebarsjs.com/block_helpers.html#raw-blocks) which captures
the template's raw text content instead of having its content evaluated, making it ideal for emitting content that could contain
template expressions like client-side JavaScript or template expressions that shouldn't be evaluated on the server such as
Vue, Angular or Ember templates:
<pre>
{{#raw}}
&lt;div id="app"&gt;
{{ message }}
&lt;/div&gt;
{{/raw}}
</pre>
When called with no arguments it will render its unprocessed raw text contents. When called with a single argument, e.g. `{{#raw varname}}` it will
instead save the raw text contents to the specified global `PageResult` variable and lastly when called with the `appendTo` modifier it will append
its contents to the existing variable, or initialize it if it doesn't exist.
This is now the preferred approach used in all [.NET Core and .NET Framework Web Templates](http://docs.servicestack.net/templates-websites) for
pages and partials to append any custom JavaScript script blocks they need on the page, e.g:
<pre>
{{#raw appendTo scripts}}
&lt;script&gt;
//...
&lt;/script&gt;
{{/raw}}
</pre>
Where any captured custom scripts are rendered at the
[bottom of _layout.html](https://github.com/NetCoreTemplates/templates/blob/8a082a299d59a0b53b9b6e0a07a6fbabf7bf6e2c/MyApp/wwwroot/_layout.html#L49) with:
<pre>
&lt;script src="/assets/js/default.js"&gt;&lt;/script&gt;
{{ scripts | raw }}
&lt;/body&gt;
&lt;/html&gt;
</pre>
The implementation to support each of these usages is contained within
[TemplateRawBlock.cs](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Templates/Blocks/TemplateRawBlock.cs)
below which inspects the `block.Argument` to determine whether it should capture the contents into the specified variable or write its raw
string contents directly to the OutputStream:
{{/markdown}}
{{ 'gfm/blocks/13.md' | githubMarkdown }}
{{#markdown}}
### capture
The `{{#capture}}` block is similar to the raw block except instead of using its raw text contents, it instead evaluates its contents and captures
the output. It also supports evaluating the contents with scoped arguments where by each property in the object dictionary is added in the
scoped arguments that the template is executed with:
{{/markdown}}
{{ 'gfm/blocks/14.md' | githubMarkdown }}
{{#markdown}}
With this we can dynamically generate some markdown, capture its contents and convert the resulting markdown to html using the `markdown` Filter transformer:
{{/markdown}}
{{ 'gfm/blocks/15.md' | githubMarkdown }}
{{#markdown}}
### markdown
The `{{#markdown}}` block makes it even easier to embed markdown content directly in web pages which works as you'd expect where content in a
`markdown` block is converted into HTML, e.g:
{{/markdown}}
<pre>{{#raw}}
{{#markdown}}
## TODO List
- Item 1
- Item 2
- Item 3
{{/markdown}}
{{/raw}}
</pre>
{{#markdown}}
Which is now the easiest and preferred way to embed Markdown content in content-rich hybrid web pages like
[Razor Rockstars content pages](https://github.com/NetCoreTemplates/rockwind-webapp/blob/master/rockstars/dead/cobain/index.html),
or even this [blocks.html WebPage itself](https://github.com/NetCoreApps/TemplatePages/blob/master/src/wwwroot/docs/blocks.html) which
makes extensive use of markdown.
As `markdown` block only supports 2 usages its implementation is much simpler than the `capture` block above:
{{/markdown}}
{{ 'gfm/blocks/16.md' | githubMarkdown }}
{{#markdown}}
### partial
The `{{#partial}}` block lets you create In Memory partials which is useful when working with partial filters like `selectPartial` as
it lets you declare multiple partials within the same page, instead of requiring multiple individual files. See docs on
[Inline partials](/docs/partials#inline-partials) for a Live comparison of using in memory partials.
{{/markdown}}
{{#markdown}}
### html
The purpose of the html blocks is to pack a suite of generically useful functionality commonly used when generating html. All html blocks
inherit the same functionality with blocks registered for the most popular HTML elements, currently:
`ul`, `ol`, `li`, `div`, `p`, `form`, `input`, `select`, `option`, `textarea`, `button`, `table`, `tr`, `td`, `thead`, `tbody`, `tfoot`,
`dl`, `dt`, `dd`, `span`, `a`, `img`, `em`, `b`, `i`, `strong`.
Ultimately they reduce boilerplate, e.g. you can generate a menu list with a single block:
<pre>
{{#ul {each:items, id:'menu', class:'nav'} }}
&lt;li&gt;{{it}}&lt;/li&gt;
{{/ul}}
</pre>
A more advanced example showcasing many of its different features is contained in the example below:
{{/markdown}}
{{ 'gfm/blocks/18.md' | githubMarkdown }}
{{#markdown}}
This example utilizes many of the features in html blocks, namely:
- `if` - only render the template if truthy
- `each` - render the template for each item in the collection
- `where` - filter the collection
- `it` - change the name of each element `it` binding
- `class` - special property implementing [Vue's special class bindings](https://vuejs.org/v2/guide/class-and-style.html) where an **object literal**
can be used to emit a list of class names for all **truthy** properties, an **array** can be used to display a list of class names or you can instead use
a **string** of class names.
All other properties like `id` and `selected` are treated like HTML attributes where if the property is a boolean like `selected` it's only displayed
if its true otherwise all other html attribute's names and values are emitted as normal.
For a better illustration we can implement the same functionality above without using any html blocks:
{{/markdown}}
{{ 'gfm/blocks/19.md' | githubMarkdown }}
<h3 id="removing-blocks">Removing Blocks</h3>
{{#markdown}}
Like everything else in Templates, all built-in Blocks can be removed. To make it easy to remove groups of related blocks you can just remove the
plugin that registered them using the `RemovePlugins()` API, e.g:
{{/markdown}}
{{ 'gfm/blocks/20.md' | githubMarkdown }}
{{ "doc-links" | partial({ order }) }}
You can’t perform that action at this time.