Updated docs for Marko v3

This commit is contained in:
Patrick Steele-Idem 2016-03-11 09:36:01 -08:00
parent b2728fda40
commit f59c561dc6
8 changed files with 713 additions and 306 deletions

View File

@ -1,5 +1,3 @@
:information_source: ___The master branch currently contains the code for the next major version of Marko (Marko v3). The docs on markojs.com have not yet been updated.___
![Marko Logo](https://raw.githubusercontent.com/marko-js/branding/master/marko-logo-small.png)
[![Build Status](https://travis-ci.org/marko-js/marko.svg?branch=master)](https://travis-ci.org/marko-js/marko) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/marko-js/marko)

180
docs/async-taglib.md Normal file
View File

@ -0,0 +1,180 @@
Marko Async Taglib
=====================
The `marko-async` taglib provides support for the more efficient and simpler "Pull Model "approach to providing templates with view model data.
* __Push Model:__ Request all needed data upfront and wait for all of the data to be received before building the view model and then rendering the template.
* __Pull Model:__ Pass asynchronous data provider functions to template immediately start rendering the template. Let the template _pull_ the data needed during rendering.
The Pull Model approach to template rendering requires the use of a templating engine that supports asynchronous template rendering (e.g. [marko](https://github.com/marko-js/marko) and [dust](https://github.com/linkedin/dustjs)). This is because before rendering the template begins not all of data may have been fully retrieved. Parts of a template that depend on data that is not yet available are rendered asynchronously with the Pull Model approach.
# Push Model versus Pull Model
The problem with the traditional Push Model approach is that template rendering is delayed until _all_ data has been fully received. This reduces the time to first byte, and it also may result in the server sitting idle while waiting for data to be loaded from remote services. In addition, if certain data is no longer needed by a template then only the template needs to be modified and not the controller.
With the new Pull Model approach, template rendering begins immediately. In addition, fragments of the template that depend on data from data providers are rendered asynchronously and wait only on the associated data provider to complete. The template rendering will only be delayed for data that the template actually needs.
# Example
```javascript
var template = require('./template.marko');
module.exports = function(req, res) {
var userId = req.query.userId;
template.render({
userProfileDataProvider: function(callback) {
userProfileService.getUserProfile(userId, callback);
}
}, res);
}
```
```html
<async-fragment data-provider=data.userProfileDataProvider
var="userProfile">
<ul>
<li>
First name: ${userProfile.firstName}
</li>
<li>
Last name: ${userProfile.lastName}
</li>
<li>
Email address: ${userProfile.email}
</li>
</ul>
</async-fragment>
```
# Out-of-order Flushing
The marko-async taglib also supports out-of-order flushing. Enabling out-of-order flushing requires two steps:
1. Add the `client-reorder` attribute to the `<async-fragment>` tag:<br>
```html
<async-fragment data-provider=data.userProfileDataProvider
var="userProfile"
client-reorder=true>
<ul>
<li>
First name: ${userProfile.firstName}
</li>
<li>
Last name: ${userProfile.lastName}
</li>
<li>
Email address: ${userProfile.email}
</li>
</ul>
</async-fragment>
```
2. Add the `<async-fragments>` to the end of the page.
If the `client-reorder` is `true` then a placeholder element will be rendered to the output instead of the final HTML for the async fragment. The async fragment will be instead rendered at the end of the page and client-side JavaScript code will be used to move the async fragment into the proper place in the DOM. The `<async-fragments>` will be where the out-of-order fragments are rendered before they are moved into place. If there are any out-of-order fragments then inline JavaScript code will be injected into the page at this location to move the DOM nodes into the proper place in the DOM.
# Taglib API
## `<async-fragment>`
Supported Attributes:
* __`arg`__ (expression): The argument object to provide to the data provider function.
* __`arg-<arg_name>`__ (string): An argument to add to the `arg` object provided to the data provider function.
* __`client-reorder`__ (boolean): If `true`, then the async fragments will be flushed in the order they complete and JavaScript running on the client will be used to move the async fragments into the proper HTML order in the DOM. Defaults to `false`.
* __`data-provider`__ (expression, required): The source of data for the async fragment. Must be a reference to one of the following:
- `Function(callback)`
- `Function(args, callback)`
- `Promise`
- Data
* __`error-message`__ (string): Message to output if the fragment errors out. Specifying this will prevent the rendering from aborting.
* __`name`__ (string): Name to assign to this async fragment. Used for debugging purposes as well as by the `show-after` attribute (see below).
* __`placeholder`__ (string): Placeholder text to show while waiting for an out-of-order fragment to complete. Only applicable if `client-reorder` is set to `true`.
* __`show-after`__ (string): When `client-reorder` is set to `true` then displaying this fragment will be delayed until the referenced async fragment is shown.
* __`timeout`__ (integer): Override the default timeout of 10 seconds with this param. Units are in
milliseconds so `timeout="40000"` would give a 40 second timeout.
* __`timeout-message`__ (string): Message to output if the fragment times out. Specifying this
will prevent the rendering from aborting.
* __`var`__: Variable name to use when consuming the data provided by the data provider
## `<async-fragment-placeholder>`
This tag can be used to control what text is shown while an out-of-order async fragment is waiting to be loaded. Only applicable if `client-reorder` is set to `true`.
Example:
```html
<async-fragment data-provider=data.userDataProvider var="user" client-reorder>
<async-fragment-placeholder>
Loading user data...
</async-fragment-placeholder>
<ul>
<li>First name: ${user.firstName}</li>
<li>Last name: ${user.lastName}</li>
</ul>
</async-fragment>
```
## `<async-fragment-error>`
This tag can be used to control what text is shown when an async fragment errors out.
Example:
```html
<async-fragment data-provider=data.userDataProvider var="user">
<async-fragment-error>
An error occurred!
</async-fragment-error>
<ul>
<li>First name: ${user.firstName}</li>
<li>Last name: ${user.lastName}</li>
</ul>
</async-fragment>
```
## `<async-fragment-timeout>`
This tag can be used to control what text is shown when an async fragment times out.
Example:
```html
<async-fragment data-provider=data.userDataProvider var="user">
<async-fragment-timeout>
A timeout occurred!
</async-fragment-timeout>
<ul>
<li>First name: ${user.firstName}</li>
<li>Last name: ${user.lastName}</li>
</ul>
</async-fragment>
```
## `<async-fragments>`
Container for all out-of-order async fragments. If any `<async-fragment>` have `client-reorder` set to true then this tag needs to be included in the page template (typically, right before the closing `</body>` tag).
Example:
```html
<!DOCTYPE html>
<html>
...
<body>
...
<async-fragment ... client-reorder/>
...
<async-fragments/>
</body>
</html>
```

View File

@ -24,9 +24,9 @@ exports.render = function(input, out) {
```
For users of Marko Widgets: Invoking `input.renderBody` is equivalent to using the `w-body` attribute for tags (in conjunction with the `getInitialBody()` lifecycle method; see [getInitialBody()](https://github.com/marko-js/marko-widgets#getinitialbodyinput-out)).
A tag renderer should be mapped to a custom tag by creating a `marko-taglib.json` as shown in the next few sections.
A tag renderer should be mapped to a custom tag by creating a `marko.json` as shown in the next few sections.
# marko-taglib.json
# marko.json
## Sample Taglib
@ -43,7 +43,7 @@ A tag renderer should be mapped to a custom tag by creating a `marko-taglib.json
}
```
Marko also supports a short-hand for declaring tags and attributes. The following `marko-taglib.json` is equivalent to the `marko-taglib.json` above:
Marko also supports a short-hand for declaring tags and attributes. The following `marko.json` is equivalent to the `marko.json` above:
```json
{
@ -58,7 +58,7 @@ The short-hand will be used for the remaining of this documentation.
# Defining Tags
Tags can be defined by adding `"<tag_name>": <tag_def>` properties to your `marko-taglib.json`:
Tags can be defined by adding `"<tag_name>": <tag_def>` properties to your `marko.json`:
```json
{
@ -103,7 +103,7 @@ Marko supports a directory scanner to make it easier to maintain a taglib by int
* Every tag directory must contain a `renderer.js` that is used as the tag renderer or, alternatively, a `template.marko`
* Each tag directory may contain a `marko-tag.json` file or the tag definition can be embedded into `renderer.js`
With this approach, `marko-taglib.json` will be much simpler:
With this approach, `marko.json` will be much simpler:
```json
{
@ -120,7 +120,7 @@ Given the following directory structure:
* __my-bar/__
* renderer.js
* marko-tag.json
* marko-taglib.json
* marko.json
The following three tags will be exported:
@ -162,15 +162,15 @@ It is often necessary for tags to have a parent/child or ancestor/descendent rel
```xml
<ui-tabs orientation="horizontal">
<ui-tabs.tab title="Home">
<ui-tabs:tab title="Home">
Content for Home
</ui-tabs.tab>
<ui-tabs.tab title="Profile">
</ui-tabs:tab>
<ui-tabs:tab title="Profile">
Content for Profile
</ui-tabs.tab>
<ui-tabs.tab title="Messages">
</ui-tabs:tab>
<ui-tabs:tab title="Messages">
Content for Messages
</ui-tabs.tab>
</ui-tabs:tab>
</ui-tabs>
```
@ -187,7 +187,7 @@ ___ui-tabs/marko-tag.json___
}
```
This allows a `tabs` to be provided using nested `<ui-tabs.tab>` tags or the tabs can be provided as a `tabs` attribute (e.g. `<ui-tabs tabs="[tab1, tab2, tab3]"`). The nested `<ui-tabs.tab>` tags will be made available to the renderer as part of the `tabs` property for the parent `<ui-tabs>`. Because of the `[]` suffix on `<tab>[]` the tabs property will be of type `Array` and not a single object. That is, the `[]` suffix is used to declare that a nested tag can be repeated. The sample renderer that accesses the nested tabs is shown below:
This allows a `tabs` to be provided using nested `<ui-tabs:tab>` tags or the tabs can be provided as a `tabs` attribute (e.g. `<ui-tabs tabs="[tab1, tab2, tab3]"`). The nested `<ui-tabs:tab>` tags will be made available to the renderer as part of the `tabs` property for the parent `<ui-tabs>`. Because of the `[]` suffix on `<tab>[]` the tabs property will be of type `Array` and not a single object. That is, the `[]` suffix is used to declare that a nested tag can be repeated. The sample renderer that accesses the nested tabs is shown below:
___ui-tabs/renderer.js___
@ -228,15 +228,15 @@ ___ui-tabs/template.marko___
```xml
<div class="tabs">
<ul class="nav nav-tabs">
<li class="tab" for="tab in data.tabs">
<li class="tab" for(tab in data.tabs)>
<a href="#${tab.title}">
${tab.title}
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane" for="tab in data.tabs">
<invoke function="tab.renderBody(out)"/>
<div class="tab-pane" for(tab in data.tabs)>
<invoke tab.renderBody(out)/>
</div>
</div>
</div>
@ -246,17 +246,17 @@ Below is an example of using nested tags that are not repeated:
```xml
<ui-overlay>
<ui-overlay.header class="my-header">
<ui-overlay:header class="my-header">
Header content
</ui-overlay.header>
</ui-overlay:header>
<ui-overlay.body class="my-body">
<ui-overlay:body class="my-body">
Body content
</ui-overlay.body>
</ui-overlay:body>
<ui-overlay.footer class="my-footer">
<ui-overlay:footer class="my-footer">
Footer content
</ui-overlay.footer>
</ui-overlay:footer>
</ui-overlay>
```
@ -309,33 +309,45 @@ Finally, the sample template to render the `<ui-overlay>` tag is shown below:
```xml
<div class="overlay">
<!-- Header -->
<div class="overlay-header ${data.header['class']}" if="data.header">
<invoke function="data.header.renderBody(out)"/>
<div class="overlay-header ${data.header['class']}" if(data.header)>
<invoke data.header.renderBody(out)/>
</div>
<!-- Body -->
<div class="overlay-body ${data.body['class']}" if="data.body">
<invoke function="data.body.renderBody(out)"/>
<div class="overlay-body ${data.body['class']}" if(data.body)>
<invoke data.body.renderBody(out)/>
</div>
<!-- Footer -->
<div class="overlay-footer ${data.footer['class']}" if="data.footer">
<invoke function="data.footer.renderBody(out)"/>
<div class="overlay-footer ${data.footer['class']}" if(data.footer)>
<invoke data.footer.renderBody(out)/>
</div>
</div>
```
# Taglib Discovery
Given a template file, the `marko` module will automatically discover all taglibs by searching relative to the template file. The taglib discoverer will search up and also look into `node_modules` to discover applicable taglibs.
Given a template file, the `marko` module will automatically discover all taglibs by searching relative to the template file. The taglib discoverer will automatically import all taglibs associated with packages found as dependencies in the containing package's root `package.json` file.
As an example, given a template at path `/my-project/src/pages/login/template.marko`, the search path will be the following:
As an example, given a template at path `/my-project/src/pages/login/template.marko` and given a `/my-project/package.json` similar to the following:
1. `/my-project/src/pages/login/marko-taglib.json`
2. `/my-project/src/pages/login/node_modules/*/marko-taglib.json`
3. `/my-project/src/pages/marko-taglib.json`
4. `/my-project/src/pages/node_modules/*/marko-taglib.json`
5. `/my-project/src/marko-taglib.json`
6. `/my-project/src/node_modules/*/marko-taglib.json`
7. `/my-project/marko-taglib.json`
8. `/my-project/node_modules/*/marko-taglib.json`
```json
{
"name": "my-package",
"dependencies": {
"foo": "1.0.0"
},
"devDependencies": {
"bar": "1.0.0"
}
}
```
The search path will be the following:
1. `/my-project/src/pages/login/marko.json`
2. `/my-project/src/pages/marko.json`
3. `/my-project/src/marko.json`
4. `/my-project/marko.json`
5. `/my-project/node_modules/foo/marko.json`
6. `/my-project/node_modules/bar/marko.json`

View File

@ -138,7 +138,7 @@ out.end();
Despite rendering the first chunk asynchronously, the above program will stream out the output in the correct order to `index.html`:
```xml
```html
BEGIN Hello World! END
```

View File

@ -229,34 +229,6 @@ Default options:
```javascript
{
/**
* Set of tag names that should automatically have whitespace preserved.
* Alternatively, if value is `true` then whitespace will be preserved
* for all tags.
*/
preserveWhitespace: {
'pre': true,
'textarea': true,
'script': true
},
/**
* Set of tag names that should be allowed to be rendered as a self-closing
* XML tag. A self-closing tag will only be rendered if the tag has no nested
* content. HTML doesn't allow self-closing tags so you should likely
* never use this.
*/
allowSelfClosing: {},
/**
* Set of tag names that should be rendered with a start tag only.
*/
startTagOnly: {
'img': true,
'br': true,
'input': true,
'meta': true,
'link': true,
'hr': true
},
/**
* If true, then the compiler will check the disk to see if a previously compiled
* template is the same age or newer than the source template. If so, the previously
@ -272,6 +244,11 @@ Default options:
* compiled templates will not be written to disk (i.e., no `.marko.js` file will
* be generated)
*/
writeToDisk: true
}
writeToDisk: true,
/**
* If true, then the compiled template on disk will assumed to be up-to-date if it exists.
*/
assumeUpToDate: NODE_ENV == null ? false : (NODE_ENV !== 'development' && NODE_ENV !== 'dev')
};
```

View File

@ -8,35 +8,36 @@ Language Guide
Almost all of the Marko templating directives can be used as either an attribute or as an element. For example:
_Applying directives using attributes:_
```xml
<!-- Colors available -->
<ul if="notEmpty(colors)">
<li for="color in colors">
$color
<ul if(notEmpty(colors))>
<li for(color in colors)>
${color}
</li>
</ul>
<!-- No colors available -->
<div if="empty(colors)">
<!-- No colors available-->
<div if(empty(colors))>
No colors!
</div>
```
_Applying directives using elements:_
```xml
<!-- Colors available -->
<if test="notEmpty(colors)">
<if(notEmpty(colors))>
<ul>
<for each="color in colors">
<for(color in colors)>
<li>
$color
${color}
</li>
</for>
</ul>
</if>
<!-- No colors available -->
<if test="empty(colors)">
<if(empty(colors))>
<div>
No colors!
</div>
@ -47,11 +48,10 @@ The disadvantage of using elements to control structural logic is that they chan
# Text Replacement
Dynamic text is supported using either `$<variable-reference>` or `${<javascript-expression>}`.
Dynamic text is supported using `${<javascript-expression>}`.
Examples:
```xml
Hello $data.name!
Hello ${data.name}!
Hello ${data.name.toUpperCase()}!
```
@ -71,31 +71,66 @@ Test: ${hello}
-->
```
# Expressions
# Attributes
Wherever expressions are allowed, they are treated as JavaScript expressions and copied out to the compiled template verbatim. However, you can choose to use alternate versions of the following JavaScript operators:
JavaScript Operator | Marko Equivalent
------------------- | -----------------
`&&` | `and`
<code>&#124;&#124;</code> | `or`
`===` | `eq`
`!==` | `ne`
`<` | `lt`
`>` | `gt`
`<=` | `le`
`>=` | `ge`
For example, both of the following are valid and equivalent:
All attribute values are parsed as _JavaScript expressions_. In addition, placeholders (`${<javascript-expression>}`) are allowed in single and double quoted strings.
```xml
<div if="searchResults.length > 100">
Show More
</div>
<div class=data.myClassName>
<input type="checkbox" checked=data.isChecked/>
<my-component string="Hello"/>
<my-component number=1/>
<my-component template-string="Hello ${name}"/>
<my-component boolean=true/>
<my-component array=[1, 2, 3]/>
<my-component object={hello: 'world'}/>
<my-component variable=name/>
<my-component function-call=data.foo()/>
<my-component complex-expression=1+2/>
<my-component super-complex-expression=(data.foo() + data.bar(['a', 'b', 'c']))/>
```
In addition, Marko has special support for the `class` and `style` attributes as shown below:
## Style attribute
The value of the style attribute can now resolve to an object expression (in addition to a string value) as shown below:
```xml
<div if="searchResults.length gt 100">
<div style={color: 'red', 'font-weight': 'bold'}>
```
Output:
```html
<div style="color:red;font-weight:bold">
```
## Class attribute
The value of the class attribute can now be an object expression or an array expression as shown below:
```xml
<!-- array: -->
<div class=['a', null, 'c']>
<!-- object: -->
<div class={a: true, b: false, c: true}>
```
In both cases, the output will be the same:
```html
<div class="a c">
```
# Expressions
Wherever expressions are allowed, they are treated as JavaScript expressions and copied out to the compiled template verbatim. For example:
```xml
<div if(searchResults.length > 100)>
Show More
</div>
```
@ -105,13 +140,13 @@ For example, both of the following are valid and equivalent:
Marko supports includes/partials. Other Marko files can be included using the `<include>` tag and a relative path. For example:
```xml
<include template="./greeting.marko" name="Frank" count="30"/>
<include("./greeting.marko") name="Frank" count=30/>
```
Alternatively, you can pass the template data using the `template-data` attribute whose value should be a JavaScript expression that resolves to the template data as shown below:
```xml
<include template="./greeting.marko" template-data="{ name: 'Frank', count: 30 }"/>
<include("./greeting.marko", {name: "Frank", count: 30})/>
```
The value of the `template` attribute can also be a dynamic JavaScript expression that resolves to a loaded template as shown below:
@ -133,16 +168,18 @@ template.render({
And then in your template:
```xml
<include template="${data.myIncludeTarget}" name="Frank" count="30"/>
<include template="${data.anotherIncludeTarget}" name="Frank" count="30"/>
<include(data.myIncludeTarget) name="Frank" count=30/>
<include(data.anotherIncludeTarget) name="Frank" count=30/>
```
You can also choose to load the include target within the calling template as shown below:
```xml
<require module="./my-include-target.marko" var="myIncludeTarget" />
<script marko-init>
var myIncludeTarget = require('./my-include-target.marko');
</script>
...
<include template="${data.myIncludeTarget}" name="Frank" count="30"/>
<include(data.myIncludeTarget) name="Frank" count=30/>
```
# Variables
@ -150,22 +187,22 @@ You can also choose to load the include target within the calling template as sh
Input data passed to a template is made available using a special `data` variable. It's possible to declare your own variables as shown in the following sample code:
```xml
<var name="name" value="data.name.toUpperCase()" />
<var name=data.name.toUpperCase()/>
```
To assign a new value to an existing variable the `<assign>` tag can be used as shown in the following sample code:
```xml
<assign var="name" value="data.name.toLowerCase()" />
<assign name=data.name.toLowerCase()/>
```
The `<with>` directive can be used to create scoped variables as shown in the following sample code:
```xml
<with vars="nameUpper=data.name.toUpperCase(); nameLower=data.name.toLowerCase()">
Hello $nameUpper!
Hello $nameLower!
</with>
<var nameUpper=data.name.toUpperCase() nameLower=data.name.toLowerCase()>
Hello ${nameUpper}!
Hello ${nameLower}!
</var>
```
# Conditionals
@ -181,39 +218,38 @@ Any element or fragment of HTML can be made conditional using the following dire
*Applied as attributes:*
```xml
<!--Simple if-->
<div if="someCondition">
<!-- Simple if -->
<div if(someCondition)>
Hello World
</div>
<!-- Simple unless -->
<div unless(someCondition)>
Hello World
</div>
<!--Simple unless-->
<div unless="someCondition">
Hello World
</div>
<!--Complex if-->
<div if="test === 'a'">
A
</div>
<div else-if="test === 'b'">
B
</div>
<div else-if="test === 'c'">
C
</div>
<div else>
Something else
</div>
<!--Complex unless-->
<div unless="test === 'a'">
<!-- Complex if -->
<div if(test === "a")>
A
</div>
<div else-if="test === 'b'">
B
<div else-if(test === "b")>
B
</div>
<div else-if(test === "c")>
C
</div>
<div else>
Something else
Something else
</div>
<!-- Complex unless -->
<div unless(test === "a")>
A
</div>
<div else-if(test === "b")>
B
</div>
<div else>
Something else
</div>
```
@ -221,26 +257,25 @@ Any element or fragment of HTML can be made conditional using the following dire
```xml
<!-- Colors available -->
<!--Simple if-->
<if test="someCondition">
<!-- Simple if -->
<if(someCondition)>
<div>
Hello World
</div>
</if>
<!--Complex if-->
<if test="test === 'a'">
<!-- Complex if -->
<if(test === "a")>
<div>
A
</div>
</if>
<else-if test="test === 'b'">
<else-if(test === "b")>
<div>
B
</div>
</else-if>
<else-if test="test === 'c'">
<else-if(test === "c")>
<div>
C
</div>
@ -257,45 +292,45 @@ Any element or fragment of HTML can be made conditional using the following dire
The `unless` directive is also supported as an alternative to `if` in cases where the condition should be negated.
```xml
<!--Simple unless-->
<div unless="someCondition">
<!-- Simple unless -->
<div unless(someCondition)>
Hello World
</div>
<!--Complex unless-->
<div unless="test === 'a'">
<!-- Complex unless -->
<div unless(test === "a")>
A
</div>
<div else-if="test === 'b'">
B
<div else-if(test === "b")>
B
</div>
<div else>
Something else
Something else
</div>
```
*Applied as elements:*
```xml
<!--Simple unless-->
<unless test="someCondition">
<!-- Simple unless -->
<unless(someCondition)>
<div>
Hello World
</div>
</unless>
<!--Complex unless-->
<unless test="test === 'a'">
<!-- Complex unless -->
<unless(test === "a")>
<div>
A
</div>
</unless>
<else-if test="test === 'b'">
<else-if(test === "b")>
<div>
B
</div>
</else-if>
<else-if test="test === 'c'">
<else-if(test === "c")>
<div>
C
</div>
@ -309,37 +344,36 @@ The `unless` directive is also supported as an alternative to `if` in cases wher
## Shorthand Conditionals
Shorthand conditionals allow for conditional values inside attributes or wherever expressions are allowed. Shorthand conditionals are of the following form:
`{?<expression>;<true-template>[;<false-template>]}`
Regular JavaScript can be used to achieve shorthand conditional values inside attributes or wherever expressions are allowed:
For example:
```xml
<div class="{?active;tab-active}">Hello</div>
<div class=(active && 'tab-active')>Hello</div>
```
With a value of `true` for `active`, the output would be the following:
```xml
```html
<div class="tab-active">Hello</div>
```
With a value of `false` for `active`, the output would be the following:
```xml
```html
<div>Hello</div>
```
_NOTE: If the expression inside an attribute evaluates to `null` or an empty string then the attribute is not included in the output._
_NOTE: If an attribute value expression evaluates to `null`, `false` or an empty string then the attribute is not included in the output._
As shown in the previous example, the "else" block for shorthand conditionals is optional. The usage of an else block is shown below:
A ternary condition can also be used:
```xml
<div class="{?active;tab-active;tab-inactive}">Hello</div>
<div class=(active ? 'tab-active' : 'tab-inactive')>Hello</div>
```
With a value of `false` for `active`, the output would be the following:
```xml
```html
<div class="tab-inactive">Hello</div>
```
@ -361,18 +395,16 @@ For example, given the following data:
And the following template:
```xml
<img src="foo.png" alt="${data.title}">
<img src="foo.png" alt=data.title/>
<div class="{?data.active;tab-active}"></div>
<div class=(data.active && "tab-active")/>
<input type="checkbox"
checked="${data.checked}"
disabled="${data.disabled}">
<input type="checkbox" checked=data.checked disabled=data.disabled/>
```
The output HTML will be the following:
```xml
```html
<img src="foo.png">
<div></div>
@ -390,7 +422,7 @@ _Applied as an attribute:_
```xml
<ul>
<li for="item in items">${item}</li>
<li for(item in items)>${item}</li>
</ul>
```
@ -398,7 +430,7 @@ _Applied as an element:_
```xml
<ul>
<for each="item in items">
<for(item in items)>
<li>${item}</li>
</for>
</ul>
@ -413,7 +445,7 @@ Given the following value for items:
The output would be the following:
```xml
```html
<ul>
<li>red</li>
<li>green</li>
@ -427,11 +459,11 @@ The `for` directive also supports a loop status variable in case you need to kno
```xml
<ul>
<li for="color in colors; status-var=loop">
$color
<li for(color in colors | status-var=loop)>
${color}
${loop.getIndex()+1}) of ${loop.getLength()}
<if test="loop.isFirst()"> - FIRST</if>
<if test="loop.isLast()"> - LAST</if>
<if(loop.isFirst())> - FIRST</if>
<if(loop.isLast())> - LAST</if>
</li>
</ul>
```
@ -439,10 +471,11 @@ The `for` directive also supports a loop status variable in case you need to kno
### Loop Separator
```xml
<for each="color in colors" separator=", ">$color</for>
<for(color in colors | separator=", ")>${color}</for>
<div>
<span for="color in colors; separator=', '" style="color: $color">$color</span>
<span for(color in colors | separator=", ") style="color: ${color}">
${color}
</span>
</div>
```
@ -454,23 +487,23 @@ The `from`, `to` and `step` values must be numerical expressions. If not specifi
```xml
<ul>
<li for="i from 0 to 10">
$i
<li for(i from 0 to 10)>
${i}
</li>
</ul>
```
```xml
<ul>
<li for="i from 0 to 10 step 2">
$i
<li for(i from 0 to 10 step 2)>
${i}
</li>
</ul>
```
```xml
<ul>
<li for="i from 0 to myArray.length-1">
<li for(i from 0 to myArray.length-1)>
${myArray[i]}
</li>
</ul>
@ -481,13 +514,20 @@ The `from`, `to` and `step` values must be numerical expressions. If not specifi
```xml
<ul>
<li for="(name,value) in settings">
<b>$name</b>:
$value
<li for(name,value in settings)>
<b>${name}</b>: ${value}
</li>
</ul>
```
### Native JavaScript for-loop
```xml
<for(var i=1; i<=3; i++)>
${i}
</for>
```
### Custom Iterator
A custom iterator function can be passed as part of the view model to the template to control looping over data.
@ -510,25 +550,29 @@ The custom iterator can then be used in a template as shown below:
_Applied as part of a `for` attribute:_
```xml
<div for="item in ['a', 'b', 'c']; iterator=data.reverseIterator">
$item
<div for(item in ['a', 'b', 'c'] | iterator=data.reverseIterator)>
${item}
</div>
<!--
```
Output:
<div>c</div><div>b</div><div>a</div>
-->
```html
<div>c</div>
<div>b</div>
<div>a</div>
```
_Applied as part of a `<for>` element:_
```xml
<for each="item in ['a', 'b', 'c']" iterator="data.reverseIterator">
$item
<for(item in ['a', 'b', 'c'] | iterator=data.reverseIterator)>
${item}
</for>
<!--
Output:
```
```html
cba
-->
```
Custom iterators also support providing a custom status object for each loop iteration:
@ -548,108 +592,79 @@ Custom iterators also support providing a custom status object for each loop ite
_Applied as part of a `for` attribute:_
```xml
<div for="item in ['a', 'b', 'c']; iterator=data.reverseIterator; status-var=status">
${status.index}$item
<div for(item in ['a', 'b', 'c'] | iterator=data.reverseIterator status-var=status)>
${status.index}${item}
</div>
<!--
```
Output:
<div>2c</div><div>1b</div><div>0a</div>
-->
```html
<div>2c</div>
<div>1b</div>
<div>0a</div>
```
_Applied as part of a `<for>` element:_
```xml
<for each="item in ['a', 'b', 'c']" iterator="data.reverseIterator" status-var="status">
${status.index}$item
<for(item in ['a', 'b', 'c'] | iterator=data.reverseIterator status-var=status)>
${status.index}${item}
</for>
<!--
```
Output:
```html
2c1b0a
-->
```
# Macros
Parameterized macros allow for reusable fragments within an HTML template. A macro can be defined using the `<def>` directive.
## def
## macro
The `<def>` directive can be used to define a reusable function within a template.
The `<macro>` directive can be used to define a reusable function within a template.
```xml
<def function="greeting(name, count)">
Hello $name! You have $count new messages.
</def>
<macro greeting(name, count)>
Hello ${name}! You have ${count} new messages.
</macro>
```
The above macro can then be invoked as part of any expression. Alternatively, the [`<invoke>`](#invoke) directive can be used invoke a macro function using named attributes. The following sample template shows how to use macro functions inside expressions:
```xml
<def function="greeting(name, count)">
Hello $name! You have $count new messages.
</def>
<p>
${greeting("John", 10)}
</p>
<p>
${greeting("Frank", 20)}
</p>
```
## invoke
The `<invoke>` directive can be used to invoke a function defined using the `<def>` directive or a function that is part of the input data to a template. The `<invoke>` directive allows arguments to be passed using element attributes, but that format is only supported for functions that were previously defined using the `<def>` directive.
```xml
<def function="greeting(name, count)">
<macro greeting(name, count)>
Hello ${name}! You have ${count} new messages.
</def>
<invoke function="greeting" name="John" count="${10}"/>
<invoke function="greeting('Frank', 20)"/>
```
The output for the above template would be the following:
```xml
</macro>
<p>
Hello John! You have 10 new messages.
<greeting("John", 10)/>
</p>
<p>
Hello Frank! You have 20 new messages.
<greeting("Frank", 20)/>
</p>
```
_NOTE:_ By default, the arguments will be of type "string" when using `<invoke>.` However, argument attributes support JavaScript expressions which allow for other types of arguments. Example:
```xml
count="10" <!-- string argument -->
count="${10}" <!-- number argument -->
```
# Structure Manipulation
## attrs
## Dynamic attributes
The `attrs` attribute allows attributes to be dynamically added to an element at runtime. The value of the attrs attribute should be an expression that resolves to an object with properties that correspond to the dynamic attributes. For example:
```xml
<div attrs="myAttrs">
<var myAttrs={style: "background-color: #FF0000;", "class": "my-div"} />
<div ${myAttrs}>
Hello World!
</div>
```
Given the following value for the `myAttrs` variable:
Output:
```javascript
{style: "background-color: #FF0000;", "class": "my-div"}
```
The output would then be the following:
```xml
```html
<div style="background-color: #FF0000;" class="my-div">
Hello World!
</div>
@ -660,7 +675,7 @@ The output would then be the following:
If you find that you have a wrapper element that is conditional, but whose body should always be rendered then you can use the `body-only-if` attribute to handle this use case. For example, to only render a wrapping `<a>` tag if there is a valid URL then you could do the following:
```xml
<a href="${data.linkUrl}" body-only-if="!data.linkUrl">
<a href=data.linkUrl body-only-if(!data.linkUrl)>
Some body content
</a>
```
@ -730,7 +745,7 @@ Example template:
</a>
<textarea>
Hello
World</textarea
World</textarea>
</div>
```
@ -744,22 +759,22 @@ World</textarea</div>
The following options are available to control whitespace removal:
__Option 1)__ Disable whitespace removal using the `compiler-options` tag:
__Option 1)__ Disable whitespace removal using the `marko-compiler-options` tag:
```xml
<compiler-options whitespace="preserve" />
<marko-compiler-options preserve-whitespace/>
<div>
<img src="foo.jpg">
<img src="foo.jpg">
<img src="foo.jpg"/>
<img src="foo.jpg"/>
</div>
```
__Option 2)__ Disable whitespace removal using the `c-whitespace` attribute:
__Option 2)__ Disable whitespace removal using the `marko-preserve-whitespace` attribute:
```xml
<div c-whitespace="preserve">
<img src="foo.jpg">
<img src="foo.jpg">
<div marko-preserve-whitespace>
<img src="foo.jpg"/>
<img src="foo.jpg"/>
</div>
```
@ -769,20 +784,18 @@ __Option 3)__ Disable _all_ whitespace removal by changing a compiler option
require('marko/compiler').defaultOptions.preserveWhitespace = true;
```
__Option 4)__ Control whitespace removal for specific tags
```javascript
require('marko/compiler').defaultOptions.preserveWhitespace = {
'pre': true,
'textarea': true,
'script': true
};
```
__Option 5)__ Configured a custom tag to preserve whitespace
__Option 4)__ Control whitespace removal for specific tags (in `marko.json`/`marko-tag.json`)
Adding the `"preserve-whitespace": true` property to a tag definition will result in the Marko compiler preserving whitespace wherever that tag is encountered in a template.
```javascript
{
"<my-custom-tag>": {
"preserve-whitespace": true
}
}
```
# Helpers
Since Marko template files compile into CommonJS modules, any Node.js module can be "imported" into a template for use as a helper module. For example, given the following helper module:
@ -803,7 +816,9 @@ The above module can then be imported into a template as shown in the following
_src/template.marko_:
```xml
<require module="./util" var="util" />
<script marko-init>
var util = require("./util");
</script>
<div>${util.reverse('reverse test')}</div>
```
@ -838,6 +853,16 @@ __empty()/notEmpty()__
To deal with "empty" data, Marko provides the empty() and notEmpty() helpers. Both helpers can be used to check for empty objects (objects, that are set to null), arrays of length zero or empty strings; empty() returns true for these cases exclusively. Therefore, not all "falsy" JavaScript values are reported as "empty" - e.g.: a boolean value that is set to "false" is not empty, hence notEmpty() would return "true". As their name already suggests, both helpers are contrary to each other.
# Miscellaneous
## invoke
The `<invoke>` directive can be used to invoke a standard JavaScript function during rendering:
```xml
<invoke console.log('Hello World')/>
```
# Global Properties
The `$global` property is used to add data that is available to all templates encountered during rendering by having the data hang off the wrapped writer.
@ -880,7 +905,7 @@ Below illustrates how to use a simple custom tag:
The output of the above template might be the following:
```xml
```html
<div>
Hello World!
</div>
@ -904,9 +929,9 @@ template.render({
```
```xml
<async-fragment data-provider="data.userProfileDataProvider"
<async-fragment data-provider=data.userProfileDataProvider
var="userProfile"
arg-userId="${data.userId}">
arg-userId=data.userId>
<ul>
<li>
@ -923,7 +948,7 @@ template.render({
</async-fragment>
```
For more details, please see [https://github.com/marko-js/marko-async](https://github.com/marko-js/marko-async).
For more details, please see [Marko Async Taglib](http://markojs.com/docs/marko/async-taglib/).
# Layout Taglib
@ -957,11 +982,11 @@ _default-layout.marko:_
_Usage of `default-layout.marko`:_
```xml
<layout-use template="./default-layout.marko" show-header="$true">
<layout-use("./default-layout.marko") show-header=true>
<layout-put into="title">My Page</layout-put>
<layout-put into="body">BODY CONTENT</layout-put>
</layout-use>
```
For more details, please see [https://github.com/marko-js/marko-layout](https://github.com/marko-js/marko-layout).
For more details, please see [Marko Layout Taglib](http://markojs.com/docs/marko/layout-taglib/).

146
docs/layout-taglib.md Normal file
View File

@ -0,0 +1,146 @@
Marko Layout Taglib
============
This module provides the builtin `layout` taglib for Marko. The `layout` taglib provides support for separating out an HTML layout from content. A layout a just a normal Marko template with placeholders that allow for additional content to be provided by another template.
# Example
Example usage of of the `layout` taglib is shown in the sample code below:
_default-layout.marko:_
```xml
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>
<layout-placeholder name="title"/>
</title>
</head>
<body>
<h1 if(data.showHeader !== false)>
<layout-placeholder name="title"/>
</h1>
<p>
<layout-placeholder name="body"/>
</p>
<div>
<layout-placeholder name="footer">
Default Footer
</layout-placeholder>
</div>
</body>
</html>
```
_Usage of `default-layout.marko`:_
```xml
<layout-use("./default-layout.marko") show-header=true>
<layout-put into="title">My Page</layout-put>
<layout-put into="body">BODY CONTENT</layout-put>
</layout-use>
```
# Defining a layout
A layout is just a standard Marko template with special `<layout-placeholder>` tags.
## `<layout-placeholder>`
Supported attributes:
- **name** - The name to assign to the placeholder (required)
Each `<layout-placeholder>` tag should be assigned a name using the `name` attribute.
Each placeholder can have default content that is shown if no content for the placeholder is provided by the caller. The default content (if any) should be provided as nested content as shown below:
```xml
<layout-placeholder name="footer">
This is the default content for the "footer" placeholder. If
no "footer" content is provided by the caller then this content will
be rendered.
</layout-placeholder>
```
The user of a layout template can provide content for a placeholder using the `<layout-put>` tag (described later).
# Using a Layout
## `<layout-use>`
The `<layout-use(template[, templateData])>` tag is used to render a layout template with content being provided by the caller.
Supported attributes:
- **template** - The path to the layout template or a JavaScript expression that resolves to a loaded template instance.
- Any remaining attributes can be used to provide additional data to the layout template (described later)
Example:
```xml
<layout-use("./default-layout.marko") show-header=true>
...
</layout-use
```
## `<layout-put>`
The `<layout-put>` tag is used to provide layout content.
Supported attributes:
- **into** (required) - The name to of the placeholder that the content should replace
- **value** (optional) - The content that should be used. If not provided the layout content for the corresponding placeholder should be provided as nested content.
If nested content is provided then it will be used the content for the corresponding placeholder.
Example usage:
```xml
<layout-put into="title">My Page</layout-put>
```
Alternatively, the content can be provided using the `value` attribute as shown below:
```xml
<layout-put into="title" value="My Page"/>
```
# Layout Data
Additional data can be provided to a layout template by the caller. Data is separate from content and be used to control how the layout renders. Layout data will be accessible as properties in the standard `data` variable. Any additional attributes other than the "template" attribute that are provided to the `<layout-use>` tag are used to pass additional data to a layout template.
The example below shows a `showHeader` option can be passed to a layout template to control how the header content renders:
_default-layout.marko:_
```xml
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>
<layout-placeholder name="title"/>
</title>
</head>
<body>
<h1 if(data.showHeader !== false)>
<layout-placeholder name="title"/>
</h1>
...
</body>
</html>
```
_Usage of `default-layout.marko`:_
```xml
<layout-use("./default-layout.marko") show-header=true>
...
</layout-use>
```
_NOTE: All data attributes will be de-hyphenated and converted to camel case (e.g., `show-header` will be accessible via the `showHeader` property on the `data` object)._

View File

@ -12,9 +12,80 @@ For building rich UI components with client-side behavior please check out the [
<a href="http://markojs.com/try-online/" target="_blank">Try Marko Online!</a>
![Marko Syntax](syntax.png)
# Syntax
Improved syntax highlighting available for [Atom](https://atom.io/) by installing the [language-marko](https://atom.io/packages/language-marko) package and for [Sublime Text](http://www.sublimetext.com/) by installing the [marko-sublime](https://github.com/merwan7/sublime-marko) package.
Marko supports _both_ a familiar HTML syntax, as well as a more concise indentation-based syntax. Both syntaxes are equally supported. Regardless of which syntax you choose, the compiled code will be exactly the same.
Syntax highlighting is available in the following editors and IDEs:
- Atom: [language-marko](https://atom.io/packages/language-marko)
- Sublime Text: [marko-sublime](https://github.com/merwan7/sublime-marko)
- WebStorm: [marko.tmbundle](https://github.com/marko-js/marko-tmbundle) (See: [Importing TextMate Bundles](https://www.jetbrains.com/phpstorm/help/importing-textmate-bundles.html))
- TextMate: [marko.tmbundle](https://github.com/marko-js/marko-tmbundle)
## HTML syntax
```xml
<!DOCTYPE html>
<html lang="en">
<head>
<title>Marko Templating Engine</title>
</head>
<body>
<h1>
Hello ${data.name}!
</h1>
<ul if(notEmpty(data.colors))>
<li for(color in data.colors)>
${color}
</li>
</ul>
<div else>
No colors!
</div>
</body>
</html>
```
## Concise syntax
The following concise template is equivalent to the previous template:
```xml
<!DOCTYPE html>
html lang="en"
head
title - Marko Templating Engine
body
h1 - Hello ${data.name}!
ul if(notEmpty(data.colors))
li for(color in data.colors)
${color}
div else
- No colors!
```
## Mixed syntax
You can even mix and match the concise syntax with the HTML syntax within the same document.
The following template is equivalent to the previous templates:
```xml
<!DOCTYPE html>
html lang="en"
head
title - Marko Templating Engine
body
<h1>
Hello ${data.name}!
</h1>
ul if(notEmpty(data.colors))
li for(color in data.colors)
${color}
div else
- No colors!
```
# Sample Code
@ -23,11 +94,10 @@ A basic template with text replacement, looping and conditionals is shown below:
_hello-world.marko:_
```xml
Hello ${data.name}!
<ul if="notEmpty(data.colors)">
<li style="color: $color" for="color in data.colors">
$color
<h2>Hello ${data.name}!</h2>
<ul if(notEmpty(data.colors))>
<li style="color: ${color}" for(color in data.colors)>
${color}
</li>
</ul>
<div else>
@ -53,8 +123,8 @@ template.render({
The output of running the above program will be the following (formatted for readability):
```xml
Hello World!
```html
<h2>Hello World!</h2>
<ul>
<li style="color: red">red</li>
<li style="color: green">green</li>
@ -74,7 +144,7 @@ For comparison, given the following data consisting of an empty array of colors:
The output would be the following:
```xml
Hello World!
<h2>Hello World!</h2>
<div>
No colors!
</div>
@ -104,7 +174,7 @@ Let's compare Marko with Handlebars (a text-based templating language):
__Handlebars:__
```xml
Hello {{name}}!
<h2>Hello {{name}}!</h2>
{{#if colors}}
<ul>
@ -124,10 +194,9 @@ Hello {{name}}!
__Marko:__
```xml
Hello ${data.name}!
<ul if="notEmpty(data.colors)">
<li class="color" for="color in data.colors">
<h2>Hello ${data.name}!</h2>
<ul if(notEmpty(data.colors))>
<li class="color" for(color in data.colors)>
${color}
</li>
</ul>