UNPKG

7.68 kBMarkdownView Raw
1# Create your own renderer
2
3abstract-state-router is cool because you can use it with any templating/dom library you like. To learn how to create a new rendering layer, read on!
4
5## Where to start
6
7The general idea of a rendering object can be observed in [the mock used by the tests](https://github.com/TehShrike/abstract-state-router/blob/master/test/helpers/renderer-mock.js#L21).
8
9When writing your own renderer implementation, I would recommend forking the [state-router-example](https://github.com/TehShrike/state-router-example) and creating a new directory in the implementations folder for your new templating. Add your new dependencies to the package.json, and add a new build script.
10
11Implementing that basic todo app is a good functional test for your renderer, and ensures that all the basic functionality is at hand.
12
13## What is a renderer really
14
15It's a function!
16
17You'll pass it to the state router like this:
18
19```js
20var StateRouter = require('abstract-state-router')
21
22var stateRouter = StateRouter(yourRendererFunction, 'body')
23```
24
25Your renderer function will be passed a single argument which is the state router object itself.
26
27This function should return an object with four properties (all functions), which are described below.
28
29All of the functions are asynchronous, and take an error-first callback function as the final argument - but if you're more of a promises type, you can return a promise instead, no problem.
30
31### render
32
33Is passed an object with four properties:
34
35- **template**: comes from the original `stateRouter.addState` call when the state was created. The template to be rendered in the DOM. ASR doesn't care at all what this is, so it can be a string, some parsed template object, or anything else - whatever your templating library supports.
36- **element**: also comes from the original `stateRouter.addState` call. The element where the template should be rendered. Again, ASR doesn't care what this is - if you want to make people pass in actual element objects, or just selector strings, your call.
37- **content** generated by the resolve function provided by the user. If possible, you should apply this object to your DOM interface so that the data will be reflected in the template in the DOM immediately.
38- **parameters**: the state parameters
39
40```js
41function render(context, cb) {
42 var myHtml = myTemplateParser(context.template, context.content) // Compile template and content
43 $(context.element).html(myHtml) // Apply to the DOM
44
45 // domApi is a jquery object in these examples
46 // You should expose the interface provided by your dom manipulation library of choice
47 var domApi = $(context.element)
48 cb(null, domApi)
49}
50```
51
52Your render function should return in the promise/callback whatever object your chosen template library uses to represent an instantiated template. This is the object that is passed to the consumer's activate function as the `domApi` property, and is also passed in to...
53
54### getChildElement
55
56Is passed whatever DOM/template manipulation object your render function returned.
57
58This `getChildElement` function must return the element in the DOM where a child state should be inserted. This element object, whatever it is, will be passed to the `render` function above.
59
60Convention in the renderers so far is to find a `<ui-view></ui-view>` element, in a nod to ui-router. This is totally up to you, though - whatever you choose to to use as child elements, make sure to document in your renderer's readme.
61
62```js
63function getChildElement(domApi, cb) {
64 // domApi is a jquery object in these examples
65 var child = domApi.children('ui-view').first()
66 cb(null, child)
67}
68```
69
70### reset
71
72Is passed an object with four properties:
73
74- **domApi**: the object returned by your `render` function
75- **content**: the result of the user's `resolve` function - the same kind of thing passed to `render` above
76- **template**: the template provided to the `stateRouter.addState` call, same as above
77- **parameters**: the state parameters
78
79This function is similar in function to the `render` function above, but with a difference - the template has already been rendered.
80
81This `reset` option exists so that if a state changes, but the template is the same as the old state, the whole thing doesn't need to be destroyed, and then all of the DOM elements re-created.
82
83Your reset implementation should
84
85- wipe out all content/state in `domApi`
86- apply the new `content` to the same `domApi`
87
88This function doesn't have to return anything, it's totally fine to call the callback or return a resolved promise without any value once the resetting is complete. The router will assume that the previous DOM API is still valid.
89
90If you do pass a truthy value to the callback or in the promise, the router will assume that the value is the new DOM API which should replace the previous one.
91
92```js
93function reset(context, cb) {
94 // Note the similarity to `render()`
95 var myHtml = myTemplateParser(context.template, context.content) // Compile template and content
96 // context.domApi is a jquery object in these examples
97 context.domApi.html(myHtml) // Apply to the DOM
98 cb(null)
99}
100```
101
102### destroy
103
104Is passed the `domApi` object returned by your `render` function above.
105
106Here, all you need to do is whatever it is that wipes out the contents in the DOM. If you need to emit any cleanup events or anything, this is the place to do it.
107
108**NOTE**: only clean up things having to do with the templating/DOM. You shouldn't be signalling to your code that it's cleanup time, the code should be watching for the `destroy` event to be emitted on the context object passed to the `activate` function.
109
110```js
111function destroy(domApi, cb) {
112 // domApi is a jquery object in these examples
113 domApi.remove()
114}
115```
116
117## Other functionality you'll want in your renderer
118
119This is a matter of taste, but I think that the renderer should expose some basic functionality within all templates:
120
121- easily linking to other states
122- setting an `active` class on an element if a given state is active
123
124You'll need to implement those in order to get the example todo app working. See the Ractive `app` state template [here](https://github.com/TehShrike/state-router-example/blob/gh-pages/implementations/ractive/app/app.html#L5-L7) using the Ractive decorator exposed by ractive-state-router that sets an `active` class on the element when the given state name is active, and the `makePath` function that builds a url to a state given a state name and some properties.
125
126You can implement these with functionality exposed by the state router:
127
128- the [stateRouter.stateIsActive](https://github.com/TehShrike/abstract-state-router#staterouterstateisactivestatename-stateparameters) function tells you whether or not a state is active
129- the [stateRouter.makePath](https://github.com/TehShrike/abstract-state-router#stateroutermakepathstatename-stateparameters) function gives you a path you can link to
130
131## Other minutia
132
133### What should your new rendering module be named?
134
135I named the first renderer implementation `ractive-state-router`, and I regret that choice. I would recommend that your module follow in the pattern of `riot-state-renderer` and `virtualdom-state-renderer`, and go with `[templating library name]-state-renderer`.
136
137### Don't forget
138
139Open a pull request with your changes to the `state-router-example`, I would really like for it to be an example of using the abstract-state-router with every single supported renderer.
140
141### Any other questions?
142
143[Open an issue](https://github.com/TehShrike/abstract-state-router/issues) or [ping me on Twitter](https://twitter.com/TehShrike) and I'll be happy to help!