Reference
Nue template syntax
Nue uses a simple HTML-based template syntax that you can use for both server-side layouts and reactive, client-side components. For example:
<div class="{ type }">
<img src="{ img }">
<aside>
<h3>{ title }</h3>
<p :if="desc">{ desc }</p>
<slot/>
</aside>
</div>
If React is "just JavaScript", then Nue is "just HTML" because any valid HTML is also valid Nue. You can also extend the standard HTML with template expressions that help you build modern websites and web- applications in simple, declarative way.
Nue is best suited for UX developers dealing with the front of the frontend and with topics like interaction design, accessibility, and user experience.
Nue components
Components are the building blocks of your application. Nue component is a reusable piece of UI. They can be nested within other components forming a tree-like structure and you can build control flows by looping components or rendering them conditionally. Reactive components can also react to user input and re-render themselves to a new state.
Components are HTML fragments with the component name given in the @name
attribute:
<div ı="media-object"ı class="{ class }">
<img src="{ img }">
<aside>
<h3>{ title }</h3>
<p>{ desc }</p>
</aside>
</div>
You can place components inside other components to form more complex applications and tree-like structures. For example:
<section ="media-gallery" class="gallery">
<header>
<h1>{ title }</h1>
<p>{ desc }</p>
</header>
<!-- gallery items -->
<media-object :for="item in items" :bind="item"/>
</section>
Component attributes
You can pass data to your components with attributes. These can be static or dynamic, and the values can be anything: strings, numbers, arrays, and objects. Here we pass the business model object to the application header component in :model
attribute.
<main ="my-app">
<app-header :model="model"/>
</main>
One-way data flow
Attributes form a "one-way data flow": when the parent property updates, it will flow down to the child, but not the other way around. This prevents child components from mutating the parent's state, which makes the data flow harder to understand.
Template expressions and data flow are heavily inspired by Vue, so Vue users should feel right at home when building apps with Nue.
Component instances
Nue components are based on ES6 classes: all the properties and methods are defined the same way as you'd define them in a JavaScript class. You can define getters, setters, public and private members, and static methods. The methods can be asynchronous or synchronous. For example:
<div ="media-object" class="{ type }">
<img src="{ img }">
<aside>
<h3>{ title }</h3>
<p>{ format(desc) }</p>
</aside>
<!-- instance variables and methods -->
<script>
// instance variable
ıtitleı = 'Default title'
// called when the component is created
ıconstructor(data)ı {
}
// custom function called by the template
ıformat(value)ı {
}
</script>
</div>
ES6 classes make your code look amazingly compact and clean. Check out the reasoning behind classes and the HTML-based syntax from our blog entry: rethinking reactivity
Client-side script- tags
Any attribute on the script tag such as type
or src
is passed to the client directly without parsing/rendering:
<!-- passed to the client directly -->
<script async
src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script>
<!-- same here -->
<script type="text/javascript">
const test = { msg: 'brackets have no special meaning here' }
console.info('hello client')
</script>
If you don't like the extra type
attribute you can give a client
- attribute to tell Nue that the script is should not be touched. The client attribute is removed from the final output keeping the HTML clean:
<script client>
// ...
</script>
Is rendered as
<script>
// ...
</script>
The client
attribute work on any tag, not just scripts.
Slots
Slots are a way to extend components with custom functionality. They offer a way for the parent component to inherit functionality from a child. They help you define highly reusable, multi-purpose components.
The <slot/>
element is added to the parent component as follows:
<div class="{ type }">
<img src="{ img }">
<aside>
<h3>{ title }</h3>
<p>{ desc }</p>
| <slot/>
</aside>
</div>
After this, we can pass custom content to the parent:
<media-object :for="item in items" :bind="item">
<h4>{ item.price }</h4>
<button>Add to cart</button>
</media-object>
The slot element on the media object is replaced with the nested content on the loop. The nested content can contain anything, including text, HTML tags, and other custom components, like product rating, commenting, or product metadata.
Component libraries
Nue allows you to define multiple components on a single file. These library files are a great way to group related components. For example, here is a skeleton for a todo-list
application:
<!-- todo list component -->
<ul ="todo-list">
...
</ul>
<!-- the todo-item component -->
<li ="todo-item">
...
</li>
Libraries work similarly on both server- and client side.
Expressions
Text expressions
The simplest form of expressions are placed inside curly brackets:
<span>Text: { text }</span>
The brackets are replaced with the value of the text
property from the corresponding component instance. The value will be updated on the client side whenever the text
property changes.
HTML expressions
The single brackets interpret the data as plain text, not HTML. To output HTML, you will need to use double brackets. Here's how { value: 'Hello, <b>World</b>!' }
is rendered:
<!-- Value is escaped: Hello, <b>World!</b> -->
<p>{ value }</p>
<!-- Value is rendered as HTML: Hello, <b>World!</b> -->
<p>{{ value }}</p>
<!-- Same as the above (Vue compatibility) -->
<p :html="value"/>
Be aware that rendering HTML can lead to XSS vulnerabilities if the content is user-generated.
Complex expressions
Nue supports the full power of JavaScript expressions inside expressions:
<p>{ message.split('').reverse().join('') }</p>
<p>{ ok ? '👍' : '😡' }</p>
An expression is a piece of code that can be evaluated to a value. Therefore, the following will NOT work:
<!-- this is a statement, not an expression -->
{ var a = 1 }
<!-- use a ternary expression, not flow control -->
{ if (ok) { return message } }
Function calls
Expressions can call instance methods
<time :datetime="date.toISOString()">
{ prettyTime(date) }
<script>
prettyTime(date) {
return MY_CUTE_FORMAT.format(date)
}
</script>
</time>
Functions inside expressions are called every time a reactive component updates, so they should not have any side effects, such as changing data or triggering asynchronous operations.
Attributes
You can pass values to your components with attributes. These values can be static or dynamic, and the values can be anything: strings, numbers, arrays, and objects:
<!-- static parameter -->
<media title="Introduction to Nue"/>
<!-- dynamic parameter -->
<media :title="item.title" :class="item.class"/>
<!-- array value -->
<media-list :items="items"/>
All the attribute values are available inside the component:
<div ="media">
<h3>{ title }</h3>
</div>
Standard HTML attributes like id
, class
, style
, and data-*
remain on the element. Nonstandard attributes like :title
are removed after the value is passed to the component.
Interpolation
Nue supports the bracket syntax or string interpolation directly in attribute values:
<!-- attribute value with brackets -->
<input type="{ type }"></input>
<!-- Vue- style binding also works -->
<input :type="type"></input>
<!-- string interpolation with brackets -->
<div class="gallery { class }">
<!-- a more complex example -->
<div style="background: url('{ background }')">
Boolean Attributes
Nue automatically detects boolean attributes. In the following example, the disabled
attribute will be included if is_disabled
has a truthy value, otherwise the attribute will be omitted.
<button :disabled="is_disabled">Press me</button>
Class attribute
Nue supports a special object notation to help render the class
attribute:
<label :class="field { is-active: isActive, has-error: hasError }"></label>
If both isActive
and hasError
are truthful Nue will render:
<label class="field is-active has-error"></label>
You can combine the object notation with other bracket expressions:
<label :class="field { is-active: isActive } { getErrorClass() }"></label>
Class attribute merging
The parent class attribute is automatically merged with the child's class attribute: Suppose we have the following component:
<button ="my-button" class="btn">Click me</button>
And we mount it as follows:
<my-button class="large"/>
Then the final rendered button would merge both classes:
<button class="btn large">click me</button>
Passing data with :bind
Bind directive makes every object property directly accessible on the component. Instead of writing { data.title }
inside the component, you can just write { title }
. This is particularly useful when looping components.
<!-- pass properties for the media object one by one -->
<media-object :for="item in items"
:title="item.title"
:desc="item.desc"
:img="item.img"/>
<!-- or pass all properties at once with :bind -->
<media-object :for="item in items" :bind="item"/>
Rendering attributes with :attr
Attr directive renders a DOM attribute for each property in an object. So following components:
<p :attr="data">
<script>
data = {
title: 'My title',
alt: 'My alt',
}
</script>
</p>
This would be rendered as:
<p title="My title" alt="My alt"></p>
$attrs
property
All parent attributes are available via the $attrs
property. Here, the nested input field will inherit all parent attributes
<label ="field">
<h5>{ title }</h5>
! <input :attr="$attrs">
</label>
When the above component is used as follows:
<field title="Email" type="email" placeholder="me@acme.org" required="true"/>
The rendered HTML would be:
<label>
<h5>Email</h5>
<input title="Email" type="email" placeholder="me@acme.org" required>
</label>
Control flow
:if condition
Use the :if
attribute to conditionally render a block. The block will only be rendered if the given expression returns a truthy value.
<figcaption :if="caption">{ caption }</figcaption>
:else condition
Use :else
to indicate an "else block" for :if
<div>
<h1 :if="cool">I'm cool!</h1>
<h1 :else>I'm ordinary</h1>
<button ="cool = !cool">Toggle</button>
</div>
A :else
element must immediately follow a :if
or a :else-if
element - otherwise it will not be recognized.
:else-if condition
The :else-if
serves as an "else if block" for :if
. It can be chained multiple times:
<b :if="type == 'A'">A</b>
<b :else-if="type == 'B'">B</b>
<b :else-if="type == 'C'">C </b>
<b :else>Not A/B/C</b>
Similar to :else
, a :else-if
element must immediately follow a :if
or a :else-if
block.
:for loop
Nue uses :for
attribute to render over lists and objects. Loops are defined with syntax like item in items
, where items
is the data array and item
is the element being iterated:
<ul>
<li ı:for="item in items"ı>
{ item.lang } = { item.text }
</li>
<script>
items = [
{ lang: 'en', text: 'Hello' },
{ lang: 'es', text: 'Hola' },
{ lang: 'it', text: 'Ciao' },
{ lang: 'fi', text: 'Moi' }
]
</script>
</ul>
Inside the loop, template expressions have access to the item being looped, as well as all parent properties. In addition, :for
supports an optional second alias for the index of the looped item:
<li :for="(item, index) in items">
{ index }: { item.text }
</li>
You can use destructuring for the item variable similar to destructuring function arguments:
<li :for="{ lang, text } in items">
{ lang } = { text }
</li>
Destructuring and the index variable can be used together:
<li :for="({ text }, index) in items">
{ text } { index }
</li>
Loops can be nested and each :for
scope has access to all parent scopes.
<li :for="item in items">
<p :for="child in item.children">
{ item.text } { child.text }
</p>
</li>
You can also use of
as the delimiter instead of in
so that it is closer to JavaScript's syntax for iterators:
<div :for="item of items"></div>
Object loops
You can loop through Object values using the standard Object.entries()
method:
<ul>
<li :for="[lang, text] in Object.entries(items)">
{ lang } = { text }
</li>
<script>
items = {
en: 'Hello',
es: 'Hola',
it: 'Ciao',
fi: 'Moi'
}
</script>
</ul>
You can provide an alias for the index variable as the third argument:
<ul>
<li :for="[lang, text, index] in Object.entries(items)">
{ index } / { lang } = { text }
</li>
</ul>
Conditional loops
When they exist on the same node, :if has a higher priority than :for. That means the :if is executed first.
<li :for="todo in todos" :if="todos">
{{ todo.name }}
</li>
Use the standard hidden
property to conditionally hide elements inside a loop::
<li :for="todo in todos" :hidden="!todo.is_complete">
{{ todo.name }}
</li>
Component loops
Components can also be looped:
<my-component :for="item in items"/>
You can pass the iterated data to the component with attributes:
<my-component
:for="(item, index) in items"
:item="item"
:index="index"
/>
Or you can use :bind attribute to pass all the data at once:
<my-component :for="item in items" :bind="item"/>
The bind attribute makes the item properties accessible directly on the component. So instead of { item.title }
you can write { title }
inside the component.