Adaptors
In some cases you want to write your UI in Ractive but have a custom back-end manage the data. Adaptors allow you to teach Ractive how to talk to those custom data sources without having to change the way you write Ractive or having to write a lot of connector code up front.
Writing
Adaptors translate custom non-POJO (non-Plain Old JS Object) data sources into POJOs and vice-versa. Generally, an adaptor does the following:
- Checks if data needs to be adapted, and if so, "wraps" an adaptor to it.
- Provides a POJO version of your data source for Ractive to use.
- Captures data changes on your data source and mirror them to the data in Ractive.
- Captures data changes on the data in Ractive and mirror them to the data source.
const myAdaptor = {
filter ( value, keypath, ractive ) {
// return `true` if a particular object is of the type we want to adapt.
},
wrap ( ractive, value, keypath, prefixer ) {
// Setup
return {
get () {
// Returns POJO version of your data backend.
},
set (property, value) {
// Data setter for POJO property keypaths.
},
reset (value) {
// Data setter for POJO keypath.
},
teardown () {
// Code executed on teardown.
}
}
}
}
An adaptor definition is an object with two methods:
filter
: A function which, if it returnstrue
, tells Ractive to use the adaptor on the value.value
: The value to check.keypath
: The keypath ofvalue
relative to the instance root.ractive
: The instance wherevalue
resides.
wrap
: A function that sets up an adaptor.ractive
: The instance wherevalue
resides.value
: The value to adapt.keypath
: The keypath ofvalue
relative to the instance root.prefixer
: A function that accepts an object and returns a shallow clone with its keys prepended withkeypath
.
The wrap
function must return an object with four methods:
get
: A function that returns the POJO representation of the adapted data.set
: A function that's called when the keypath being updated is a property of the adapted data.property
: The property name to be updated.value
: The new value.
reset
: A function that's called when the keypath being updated is the adapted data itself. Ifreset
returnsfalse
, Ractive will replace the data on the keypath with the POJO version, and tear down the adaptor.value
: The new value
teardown
: A function that's called when an adaptor is being removed.
Adaptors only adapt one level
An adaptor only adapts an object's immediate properties. Updating nested data via Ractive or via the data source will not update the other.
No built-in infinite loop detection
There is no built-in mechanism for avoiding infinite loops. If your adaptor calls ractive.set()
on adapted data, which in turn will call the adaptor's set()
method, which may directly or indirectly trigger another ractive.set()
on the same adapted data, a stack overflow error might occur.
This isn't a problem with primitive values since Ractive doesn't bother calling set()
if a value hasn't changed. But with objects and arrays, there's no easy and performant way to tell if the contents have changed. So set()
gets called in case something changed rather than because something changed.
Different for every back-end
The adaptor structure only provides you with the means to talk to and listen from a custom back-end. It does not impose any rules on how to write an adaptor for a certain back-end. For instance, an adaptor for a constructor-based object may be written differently from an adaptor meant to interact with a socket server.
Registering
Like other plugins, there's 3 ways you can register adaptors:
Globally, via the Ractive.adaptors
static property.
Ractive.adaptors.myAdaptor = myAdaptor
Per component, via the component's adaptors
initialization property.
const MyComponent = Ractive.extend({
adaptors: { myAdaptor }
})
Per instance, via the instance's adaptors
initialization property.
const ractive = Ractive({
adaptors: { myAdaptor }
})
Using
In order to use an adaptor, you must tell the component or an instance to use it using the adapt
initialization option.
const ractive = Ractive({
adapt: [ 'myAdaptor' ]
})
Examples
In the following example, we have a Box
constructor that uses accessors to get and set its width
and height
properties. Since an instance of Box
will have no publicly visible properties, Ractive cannot bind to them directly.
function Box(width, height) {
var _width = width
var _height = height
this.getWidth = () => { return _width; }
this.setWidth = (width) => { _width = width; }
this.getHeight = () => { return _height; }
this.setHeight = (height) => { _height = height }
}
In order for Ractive to properly use a Box
instance, we build an adaptor for Box
.
Ractive.adaptors.boxAdaptor = {
filter ( object ) {
// Checks if the piece of data is an instance of Box.
return object instanceof Box
},
wrap ( ractive, box, keypath, prefixer ) {
// We keep a reference to the original functions before monkey-patching.
const setWidth = box.setWidth
const setHeight = box.setHeight
// Use ractive.set on the the adapted data whenever the setters are used.
box.setWidth = (width) => {
ractive.set(prefixer({
width: width
}))
}
box.setHeight = (height) => {
ractive.set(prefixer({
height: height
}))
}
return {
// Return a POJO representation of an instance of Box.
get () {
return {
width: box.getWidth(),
height: box.getHeight()
}
},
// Update the adapted object's properties
set (property, value) {
if(property === 'width') setWidth.call(box, value)
if(property === 'height') setHeight.call(box, value)
},
// Update the adapted object.
reset (data) {
// We don't adapt non-objects. And if the new data is an instance of Box
// there's a high chance that its a new instance. In either case, we
// need to tear down this adapter and have Ractive set it up again if
// necessary.
if(typeof data !== 'object' || data instanceof Box) return false
// Otherwise, we just parse through the data and update the existing box
// instance.
if(data.width !== undefined) setWidth.call(box, data.width)
if(data.height !== undefined) setHeight.call(box, data.height)
},
// Delete the monkey-patched methods.
teardown () {
delete box.setWidth
delete box.setHeight
}
}
}
}
Then we use boxAdaptor
on an instance. The data can now be treated like regular Ractive data. Updates done directly on box
will reflect on Ractive. Any changes via Ractive will reflect on box
.
const ractive = Ractive({
el: 'body',
// Tell the instance we'll be using boxAdaptor
adapt: [ 'boxAdaptor' ],
// We write Ractive like normal.
template: `
<div>Box is {{ box.width }}x{{ box.height }}</div>,
<div><input type="text" value="{{ box.width }}"></div>
<div><input type="text" value="{{ box.height }}"></div>
`
})
const box = new Box(3, 4)
// Set the Box instance as if it were a POJO.
ractive.set('box', box)
// Both box instance and box object will have 7 width and 11 height and will
// be rendered in the UI accordingly.
box.setWidth(7)
ractive.set('box.height', 11)
Components
In many situations, you want to encapsulate behaviour and markup into a single reusable component, which can be dropped into Ractive applications. Components are simply custom-configured "subclasses" of Ractive (analogous, but technically incorrect).
Writing
There are several ways to write Ractive components. Standard initialization options apply for configuration unless where changes are explicitly mentioned.
The most common way to define a component is by using Ractive.extend()
.
// A subclass of Ractive
const MyComponent = Ractive.extend({
template: `
<div class="my-component">
<span class="my-component__message">{{ message }}</span>
</div>
`,
css: `
.my-component__message { color: red }
`,
data: { message: 'Hello World' }
})
Another way to define a component is by using component files and loaders.
<div class="my-component">
<span class="my-component__message">{{ message }}</span>
</div>
<style>
.my-component__message { color: red }
</style>
<script>
component.exports = {
data: { message: 'Hello World' }
}
</script>
Registering
Like other plugins, there's 3 ways you can register components:
Globally, via the Ractive.components
static property:
// Available to all instances of Ractive.
Ractive.components.MyComponent = Ractive.extend({ ... })
Per component, via the component's components
initialization property.
// Only available for instances of AnotherComponent.
const AnotherComponent = Ractive.extend({
components: { MyComponent }
})
Per instance, via the instance's components
initialization property.
// Only available to this specific instance.
const ractive = Ractive({
components: { MyComponent }
})
Using
Components are simply subclasses of Ractive, which means they are also instatiable.
const ractive = MyComponent({ ... })
But where components really shine is when they're used on templates. They are written like custom elements. Each custom element notation represents one instance of the component.
const AnotherComponent = Ractive.extend({
template: `
<div>
<MyComponent /> <!-- One instance of MyComponent -->
<MyComponent /> <!-- Another instance of MyComponent -->
<MyComponent /> <!-- Yet another instance of MyComponent -->
</div>
`
})
The component's tag name depends on the name used upon registration. The same component can be registered more than once using different names.
const MyComponent = Ractive.extend({...})
Ractive.components.MyComponent = MyComponent
Ractive.components.MyComponentOtherName = MyComponent
const AnotherComponent = Ractive.extend({
template: `
<div>
<MyComponent /> <!-- Using MyComponent -->
<MyComponentOtherName /> <!-- Using MyComponent's other name -->
</div>
`
})
Examples
Decorators
A decorator is a simple way to add behaviour to a node when it is rendered, or to augment it in some way. Decorators are a good way to integrate DOM manipulation libraries with Ractive, such as jQuery UI or Bootstrap.
Writing
const myDecorator = (node[, ...args]) => {
// Setup code
return {
teardown () {
// Cleanup code
},
update ([...args]) {
// Update code
},
invalidate () {
// Code called on invalidate
}
}
}
Decorators are simply functions that are called upon to setup the decorator once Ractive detects its use. It takes a node
argument and returns an object with a teardown
and update
property.
node
is the element to which the decorator is applied to.
[args]
are optional arguments provided by the decorator directive.
teardown
is a function that gets called when the decorator is torn down.
update
is an optional function that gets called when the arguments update.
invalidate
, starting with 0.9.3, is an optional function that gets called when any bit of template downstream from the node is being updated.
Any updates to the arguments will call the decorator's teardown
and run the decorator function again, essentially setting up the decorator again. If an update
function is provided on the return object, that will be called instead of the teardown
and setup function.
Registering
Like other plugins, there's 3 ways you can register decorators:
Globally, via the Ractive.decorators
static property.
Ractive.decorators.myDecorator = myDecorator
Per component, via the component's decorators
initialization property.
const MyComponent = Ractive.extend({
decorators: { myDecorator }
})
Per instance, via the instance's decorators
initialization property.
const ractive = Ractive({
decorators: { myDecorator }
})
Using
One or more decorators can be added to an element via the as-*
directive. Arguments are passed to the decorator function by supplying comma-separated expressions as values. These expressions will become the second argument and onwards of the transition function.
<!-- without arguments -->
<div as-myDecorator>...</div>
<!-- with arguments -->
<div as-myDecorator="arg1, .some.other.arg2, 10 * @index" as-somethingElseToo>...</div>
Examples
The following example builds a decorator that updates the time.
Ractive.decorators.timer = (node, time) => {
node.innerHTML = 'Hello World!'
return {
teardown () {
node.innerHTML = ''
},
update (time) {
node.innerHTML = time
}
}
}
const ractive = Ractive({
el: 'body',
template: `
<span as-timer="time"></span>
`,
data: {
time: 0
}
})
setInterval(function () {
ractive.set('time', Date.now())
}, 1000)
Easings
Easing functions describe the animation's progression from start to finish. Easings are used by ractive.animate
and some transitions. Out of the box, Ractive comes with linear
, easeIn
, easeOut
and easeInOut
.
Writing
const myEasing = ( t ) => {
// Calculation
return v
}
Easing functions receive t
, a value from 0
to 1
, which represents the linear progression of the animation from start to finish. The easing function must then return v
, a value also from 0
to 1
, which represents the progress of the animation for that easing.
Registering
Like other plugins, there's 3 ways you can register an easing function:
Globally via the Ractive.easing
static property.
Ractive.easing.myEasing = myEasing
Per component via the component's easing
initialization property.
const MyComponent = Ractive.extend({
easing: { myEasing }
})
Per instance, via the instance's easing
initialization property.
const ractive = Ractive({
easing: { myEasing }
})
Using
Easing functions are utilized by ractive.animate()
and transitions to define animations.
ractive.animate('counter', 60, {
easing: 'myEasing'
})
Examples
Here's an elastic
easing function taken from danro's excellent easing.js library.
Ractive.easing.elastic = ( t ) => {
return -1 * Math.pow(4,-8*t) * Math.sin((t*6-1)*(2*Math.PI)/2) + 1
}
Events
Events allow custom-named events on DOM elements. Common use cases for custom DOM events include normalizing cross-browser events, normalizing cross-device events, implementing custom events, and so much more.
Writing
const myEvent = (node, fire) => {
// Setup code
return {
teardown () {
// Cleanup code
}
}
}
Events are simply functions that are called to set up the event on a DOM element. The event function accepts two arguments: node
and fire
, and returns an object containing a teardown
property.
node
is the element to which the event is being applied.
fire
is the function that must be called when the event has taken place. fire
takes a single argument, the event object received by handlers. The minimum requirement for the event object is a node
property that references DOM node the event is attached to, and an original
property which references the native DOM event
object supplied by the native handler, if available.
The event object will be augmented with context
, keypath
and index
properties, whose values depend on the data context the node is in. context
references the data context that surrounds the node, keypath
is a string that leads to the current data context and index
is a number that references the index number of the data, should the data context be in an array.
teardown
is a function that gets called once the element is torn down. This allows the event to clean up after itself.
Registering
Like other plugins, there's 3 ways you can register events:
Globally, via the Ractive.events
static property.
Ractive.events.myEvent = myEvent
Per component, via the component's events
initialization property.
const MyComponent = Ractive.extend({
events: { myEvent }
})
Per instance, via the instance's events
initialization property.
const ractive = Ractive({
events: { myEvent }
})
Using
Events use the same on-*
attribute syntax as component and DOM events. When Ractive encounters an on-*
attribute on a DOM element, it looks for a registered event and applies it on the element. If no matching event name was found, Ractive will think the event name is a native DOM event and will attach one accordingly.
// This will apply the "myEvent" custom event if one is registered.
// Otherwise, Ractive will think it's a DOM event. You have been warned!
<span on-myEvent="foo()">Click me!</span>
Examples
Here's an example of a "long press" event which fires when the mouse is clicked and held for 1 second.
// Definition
Ractive.events.longpress = (node, fire) => {
let timer = null
function clearTimer () {
if(timer) clearTimeout(timer)
timer = null
}
function mouseDownHandler(event) {
clearTimer()
timer = setTimeout(() => {
fire({
node: node,
original: event
})
}, 1000)
}
function mouseUpHandler () {
clearTimer()
}
node.addEventListener('mousedown', mouseDownHandler)
node.addEventListener('mouseup', mouseUpHandler)
return {
teardown () {
node.removeEventListener('mousedown', mouseDownHandler)
node.removeEventListener('mouseup', mouseUpHandler)
}
}
}
// Usage:
Ractive({
el: 'body',
template: `
<button type="button" on-longpress="@this.greetz()">Click Me!</button>
`,
greetz () {
console.log('Hello World!')
}
})
Interpolators
Interpolator functions describe a value's progression during an animation from start to finish. Interpolator functions also determine the value on the model as well as what gets rendered during the animation.
Writing
const myInterpolator = (from, to) => {
return t => {
return valueAtTimeT
}
}
Interpolator plugins accept from
and to
. from
is the value on the animated keypath before calling ractive.animate()
and to
is the value being set by ractive.animate()
. The plugin must return a function that receives t
and returns a value that represents the animated value at t
. t
is a value from 0
to 1
representing the animation's progress with easing already applied.
Registering
Like other plugins, there's 3 ways you can register an interpolator function:
Globally via the Ractive.interpolators
static property.
Ractive.interpolators.myInterpolator = myInterpolator
Per component via the component's interpolators
initialization property.
const MyComponent = Ractive.extend({
interpolators: { myInterpolator }
})
Per instance, via the instance's interpolators
initialization property.
const ractive = Ractive({
interpolators: { myInterpolator }
})
Using
Interpolator functions are utilized by ractive.animate()
by supplying the interpolator name to the interpolator
option.
ractive.animate('counter', 60, {
interpolator: 'myInterpolator'
})
Examples
Here's an abridged version of Ractive's built-in number
interpolator which interpolates numeric values (numbers and number-like strings) during animation.
Ractive.interpolators.number = (from, to) => {
const start = Number(from)
const end = Number(to)
if (Number.isNaN(start) || Number.isNaN(end)) return null
const delta = end - start
const noChangeInterpolator = () => start
const changeInterpolator = t => start + (t * delta)
return delta ? changeInterpolator : noChangeInterpolator
}
Partials
A partial is a template snippet which can be reused in templates or in other partials. They help to keep templates uncluttered, non-repetetive and easy to read.
Writing
const myPartial = '<!-- template -->'
Partials are simply Ractive templates.
Registering
Unlike other plugins, partials have more than 3 registration options.
Globally via the Ractive.partials
static property.
Ractive.partials.myPartial = MyPartial
Globally, via a non-executing script tag on the current page.
<script type="ractive/template" id="myPartial">
...
</script>
Per component, via the component's partials
initialization property.
const MyComponent = Ractive.extend({
partials: { myPartial }
})
Per instance, via the instance's partials
initialization property.
const ractive = Ractive({
partials: { myPartial }
})
In-template, using the {{#partial}}
mustache.
{{#partial myPartial}}
...
{{/}}
Using
Partials can be used using {{>partialName}}
. Partials work where any template would work and takes on its surrounding context.
{{#partial myPartial}}
<div class="message">{{message}}</div>
{{/partial}}
<div class="app">
{{>myPartial}}
</div>
Valid names
Partials may be named with the same rules as any other identifier in Ractive or JavaScript, but since there isn't much danger of trying to do math in a partial name, they enjoy relaxed naming requirements that allow otherwise reserved globals and keywords to be used for partial names.
Partial names may also contain -
and /
characters as long as they are surrounded by other valid characters e.g. templates/some-partial-template
.
Context
Partials be given an explicit context. Simply supply a reference to the context following the partial name on usage.
Ractive({
data: {
foo:{
bar: 'Hello, World!'
}
},
template: `
{{#partial myPartial}}{{bar}}{{/partial}}
{{>myPartial foo}}
`
})
Aliases
References can also be aliased in partials. Simply define aliases as comma-separated reference as alias
pairs following the partial name on usage.
Ractive({
data: {
status: 'Me, Hungry!',
foo:{
bar: 'Hello, World!'
}
},
template: `
{{#partial myPartial}}{{msg}} {{state}}{{/partial}}
{{>myPartial foo.bar as msg, status as state}}
`
})
Recursive partials
Partials can be used recursively. A common use case for this is when reusing partials on a tree-like structure, like a directory listing.
<div class='fileSystem'>
{{#root}}
{{>folder}}
{{/root}}
</div>
{{#partial folder}}
<ul class='folder'>
{{#files}}
{{>file}}
{{/files}}
</ul>
{{/partial}}
{{#partial file}}
<li class='file'>
<img class='icon-{{type}}'>
<span>{{filename}}</span>
<!-- if this is actually a folder, embed the folder partial -->
{{# type === 'folder' }}
{{>folder}}
{{/ type === 'folder' }}
</li>
{{/partial}}
rv = Ractive({
el: 'container',
template: '#myTemplate',
data: {
root: {
files: [
{ type: 'jpg', filename: 'hello.jpg' },
{ type: 'mp3', filename: 'NeverGonna.mp3' },
{ type: 'folder', filename: 'subfolder', files: [
{ type: 'txt', filename: 'README.txt' },
{ type: 'folder', filename: 'rabbithole', files: [
{ type: 'txt', filename: 'Inception.txt' }
]}
]}
]
}
}
})
In the example above, subfolders use the {{>folder}}
partial, which uses the {{>file}}
partial for each file, and if any of those files are folders, the {{>folder}}
partial will be invoked again, and so on until there are no more files.
Beware of cyclical data structures! Ractive makes no attempt to detect cyclicality, and will happily continue rendering partials until the Big Crunch (or your browser exceeds its maximum call stack size. Whichever is sooner).
Injecting partials
One good use of partials is to vary the shape of a template according to some condition, the same way you might use dependency injection elsewhere in your code.
For example, you might offer a different view to mobile users:
<div class='main'>
<div class='content'>
{{>content}}
</div>
<div class='sidebar'>
{{>sidebar}}
</div>
</div>
isMobile = /mobile/i.test( navigator.userAgent ); // please don't do this in real life!
ractive = Ractive({
el: myContainer,
template: myTemplate,
partials: {
content: isMobile ? mobileContentPartial : desktopContentPartial,
sidebar: isMobile ? mobileSidebarPartial : desktopSidebarPartial
}
})
Or you might make it possible to extend a subclass without overriding its template:
<div class='modal-background'>
<div class='modal'>
{{>modalContent}}
</div>
</div>
// Create a Modal subclass
Modal = Ractive.extend({
template: modalTemplate,
init () {
var self = this, resizeHandler
resizeHandler = () => {
self.center()
}
// when the window resizes, keep the modal horizontally and vertically centred
window.addEventListener( 'resize', resizeHandler )
// clean up after ourselves later
this.on( 'teardown', function () {
window.removeEventListener( 'resize', resizeHandler )
})
// manually call this.center() the first time
this.center()
},
center () {
// centering logic goes here
}
})
helloModal = Modal({
el: document.body,
partials: {
modalContent: '<p>Hello!</p><a class="modal-button" proxy-tap="close">Close</a>'
}
})
helloModal.on( 'close', function () {
this.teardown()
})
Partial expressions
Expressions can also be used to reference a partial instead of fixed partial names. Ractive will evaluate the expression and use its return value to select a partial. This is useful when you want the data to indicate what type of partial it should render with.
In the following example, organisms
contains a list of organisms whose type
can either be person
or animal
. Partials for each type is defined and the list uses the type
of each item to determine which partial to use.
{{#partial person}}Hi! I am {{.name}} and I have {{.fingerCount}} fingers!{{/}}
{{#partial animal}}Hi! I am a {{.name}} and I have {{.legCount}} legs!{{/}}
<ul>
{{# organisms }}
<li>{{> type }}</li>
{{/}}
</ul>
In the case where there's a collision between the expression and a partial of the same name, Ractive will not evaluate the expression and instead, will immediately resolve to that partial.
In the following example, a partial named type
is defined. Instead of evaluating type
's value, it will resolve to the partial named type
.
{{#partial person}}Hi! I am {{.name}} and I have {{.fingerCount}} fingers!{{/}}
{{#partial animal}}Hi! I am a {{.name}} and I have {{.legCount}} legs!{{/}}
{{#partial type}}Hi! I am {{.name}}{{/}}
<ul>
{{# organisms }}
<li>{{> type }}</li>
{{/}}
</ul>
Partials can also be registered on-the-fly as well as have functions determine the partial to use.
In the following example, makePartial
registers a partial on the fly using template
and id
, and returns the partial name for use in the template.
Ractive({
el: 'main',
template: `
<span>Add a partial:</span>
<textarea value="{{tpl}}" /></div>
<button on-click="@this.add()">Add</button><br/>
{{#list}}
{{> @this.makePartial(.id, .template) }}
{{/}}
`,
data: {
list: [],
tpl: '',
},
add () {
this.push('list', {
id: Math.random(),
template: this.get('tpl')
})
this.set('tpl', '')
},
makePartial (id, template) {
const name = 'partial-' + id
this.partials[name] = this.partials[name] || template
return name
}
})
Updating Partials
Partials may be reset after they are rendered using ractive.resetPartial()
. A reset partial will update everywhere it is referenced, so if it is used multiple times or inherited by a component, those instances will be updated as well. If a component has a partial with the same name as a parent partial, partial resets will not affect it since it is a different partial.
It should be noted that partials evaluate lazily, so it is possible to cause a single partial to update by wrapping it in a conditional section and causing the section to be hidden and re-shown.
{{^toggle}}{{>rickroll}}{{/}}
ractive.partials.rickroll = 'I wouldn\'t do that to you, chum.'
ractive.set('toggle', true)
ractive.set('toggle', false)
Examples
Here's an example of a gallery using a partial for its thumbnail information:
<!-- The partial called "thumbnail" -->
<figure class='thumbnail'>
<img src='assets/thumbnails/{{id}}.jpg'>
<figcaption>{{description}}</figcaption>
</figure>
<!-- The template that uses the partial -->
<div class='gallery'>
{{#items}}
{{>thumbnail}}
{{/items}}
</div>
Transitions
Transitions allow you to control how enter the DOM and how they leave the DOM. This allows you to define intro and outro behavior, particularly animations, when elements go in and out of the page.
Writing
const myTransition = ( t[, ...args]] ) => {
// Manipulate the DOM.
// Call t.complete() when completed.
}
Transitions are simply functions that get called to animate a specified element.
t
is a transition helper object that aids in the implementation of the transition.
[args]
is the arguments passed in via the transition directive.
Transitions are expected to be asynchronous. In order to signal Ractive that the transition has completed, the transition must call the t.complete()
.
Note that transitions control the element's arrival and departure to the page. That means upon completion, any styling done to the element will be removed automatically.
Registering
Like other plugins, there's 3 ways you can register transitions:
Globally, via the Ractive.transitions
static property.
Ractive.transitions.myTransition = myTransition
Per component, via the component's transitions
initialization property.
const MyComponent = Ractive.extend({
transitions: { myTransition }
})
Per instance, via the instance's transitions
initialization property.
const ractive = Ractive({
transitions: { myTransition }
})
Using
Transitions are added to an element via the *-in
, *-out
or *-in-out
directives. The transition may be applied on element render (-in
), on element removal (-out
), or both (-in-out
).
<div myTransition-in>Intro-only</div>
<div myTransition-out>Outro-only</div>
<div myTransition-in-out>Intro and outro</div>
Arguments are passed to the transition function by supplying comma-separated expressions as values to *-in
, *-out
or *-in-out
. These expressions will become the second argument and onwards of the transition function.
<div myTransition-in-out="'Hello, World!', 42">Hello, World!</div>
Examples
The following example demonstrates a "flash" transition which colors the element green upon attachment, and red prior to removal.
Ractive.transitions.flash = (t, params) => {
// Process params and declaring defaults.
const options = t.processParams(params, {
color: t.isIntro ? '#0f0' : '#f00',
duration: 400
})
// The "transition"
t.setStyle('color', options.color)
// Signal Ractive that the transition is complete.
setTimeout(t.complete, options.duration)
}
Ractive({
el: 'body',
template: `
<button on-click='@this.insert()'>Insert item</button>
<button on-click='@this.remove()'>Remove item</button>
<ul>
{{#items}}
<li flash-in-out>{{this}}</li>
{{/items}}
</ul>
`,
data: {
items: []
},
insert () {
this.push('items', Date.now())
},
remove () {
this.splice('items', 0, 1)
}
})