You can create your own custom plugins by extending the Plugin
class
and implementing one of the core plugin interfaces.
info NOTE
Using TypeScript (or an IDE with smart type inference and suggestions) is highly recommended, so that you automatically get the functions you need to fill in.
Plugins implementing the AppendPlugin
interface can add/alter functionality
for when something is appended to another node.
1linkimport { AppendPlugin, Plugin } from 'render-jsx/plugin';
2linkimport { LiveRendererLike } from 'render-jsx';
3link
4linkimport pipe from 'callbag-pipe';
5linkimport subscribe from 'callbag-subscribe';
6link
7link
8linkexport class CallbagAppendPlugin<Node> // --> is generic towards node type
9link extends Plugin<Node, LiveRendererLike<Node>> // --> but requires a `LiveRendererLike` (life-cycle concept)
10link implements AppendPlugin<Node> { // --> and is an append plugin
11link
12link priority() { return Plugin.PriorityFallback; } // --> allows other plugins to supercede this plugin
13link
14link append(target: any, host: Node) { // --> `target` is to be appended to `host`
15link if (typeof target === 'function') { // --> this plugin only operates when `target` is a function (a callbag)
16link const renderer = this.renderer(); // --> get the renderer the plugin is plugged in to
17link const leaf = renderer.leaf(); // --> create a leaf node
18link
19link renderer.hook(leaf, { // --> hooks to life-cycle of the leaf
20link bind: () => pipe(
21link target,
22link subscribe(v => renderer.setContent(leaf, v?.toString()))
23link )
24link });
25link
26link renderer.append(leaf, host); // --> asks renderer to append the leaf node
27link return true; // --> indicates that this plugin handled the append operation
28link }
29link
30link return false; // --> indicates that this plugin does not handle the append operation
31link }
32link}
1linkimport { CommonDOMRenderer } from 'render-jsx/dom';
2linkimport interval from 'callbag-interval';
3link
4linkimport { CallbagAppendPlugin } from './callbag-append.plugin';
5link
6linkconst renderer = new CommonDOMRenderer().plug(() => new CallbagAppendPlugin<Node>());
7link
8linkrenderer
9link .render(<div>You've been here for {interval(1000)} seconds.</div>)
10link .on(document.body);
info IMPORTANT
Append plugins MUST return a
boolean
from their.append()
method. Returningtrue
means the plugin took over the appending process and there is no need to try other plugins,false
means the plugin could not handle the append and other plugins should be tried.
info NOTE
The
.priority()
method should return something between 0 and 1, and is used by renderers to prioritize this plugin over others. 0 means fallback, i.e. if no other plugin took over the operation then this plugin should be tested, and 1 means this is the first plugin that should be tested..priority()
method should be defined by all plugin types.
Plugins implementing PropPlugin
interface can affect how properties of nodes are set:
1linkimport { PropPlugin, Plugin } from 'render-jsx/plugin';
2linkimport { LiveRendererLike } from 'render-jsx';
3link
4linkimport pipe from 'callbag-pipe';
5linkimport subscribe from 'callbag-subscribe';
6link
7link
8linkexport class CallbagPropPlugin<Node> // --> is generic towards node type
9link extends Plugin<Node, LiveRendererLike<Node>> // --> but requires a `LiveRendererLike` (life-cycle concept)
10link implements PropPlugin<Node> { // --> and is a `PropPlugin`
11link
12link priority() { return Plugin.PriorityFallback; } // --> allows other plugins to supercede this plugin
13link
14link setProp(node: Node, prop: string, target: any) { // --> we want to set property `prop` on `node` to `target`
15link if (typeof target === 'function') { // --> check if it is a function (i.e. a callbag)
16link const renderer = this.renderer(); // --> get the renderer we are plugged into
17link
18link renderer.hook(node, { // --> attach to life-cycle of the node
19link bind: () => pipe(
20link target,
21link subscribe(v => renderer.setProp(node, prop, v?.toString()))
22link )
23link });
24link
25link return true; // --> indicates that we handled the setProp operation
26link }
27link
28link return false; // --> indicates that we couldn't handle the setProp operation
29link }
30link}
1linkimport { CommonDOMRenderer } from 'render-jsx/dom';
2linkimport interval from 'callbag-interval';
3linkimport pipe from 'callbag-pipe';
4linkimport map from 'callbag-map';
5link
6linkimport { CallbagPropPlugin } from './callbag-prop.plugin';
7link
8linkconst renderer = new CommonDOMRenderer().plug(() => new CallbagPropPlugin<Node>());
9link
10linkconst style = pipe(
11link interval(1000),
12link map(v => v % 2 === 0 ? 'color: red' : 'color: blue')
13link);
14link
15linkrenderer.render(
16link <div style={style}>
17link Hellow There!
18link </div>
19link).on(document.body);
info IMPORTANT
Prop plugins MUST return a
boolean
from their.setProp()
method. Returningtrue
means the plugin took over setting the property and there is no need to try other plugins,false
means the plugin could not set given property on given node and other plugins should be tried.
Plugins implementing SetStylePlugin
interface can affect how particular style values are set
via style objects:
1linkimport { Plugin } from 'render-jsx/plugin';
2linkimport { SetStylePlugin } from 'render-jsx/dom/plugins';
3linkimport { LiveRendererLike } from 'render-jsx';
4link
5linkimport pipe from 'callbag-pipe';
6linkimport subscribe from 'callbag-subscribe';
7link
8link
9linkexport class CallbagStylePlugin
10link extends Plugin<Node, LiveRendererLike<Node>>
11link implements SetStylePlugin<LiveRendererLike<Node>> {
12link
13link priority() { return Plugin.PriorityFallback; }
14link
15link setStyle(
16link node: HTMLElement,
17link style: string,
18link target: any,
19link set: (value: string|object) => void
20link ): boolean {
21link if (typeof target === 'function') {
22link const renderer = this.renderer();
23link
24link renderer.hook(node, {
25link bind: () => pipe(
26link target,
27link subscribe(v => set(v?.toString()))
28link )
29link });
30link
31link return true;
32link }
33link
34link return false;
35link }
36link}
1linkimport { CommonDOMRenderer } from 'render-jsx/dom';
2linkimport interval from 'callbag-interval';
3linkimport pipe from 'callbag-pipe';
4linkimport map from 'callbag-map';
5link
6linkimport { CallbagStylePlugin } from './callbag-style.plugin';
7link
8linkconst renderer = new CommonDOMRenderer()
9link.plug(() => new CallbagStylePlugin<Node>());
10link
11linkconst color = pipe(
12link interval(1000),
13link map(v => v % 2 === 0 ? 'red' : 'blue')
14link);
15link
16linkrenderer.render(
17link <div style={{color: color}}>
18link Hellow There!
19link </div>
20link).on(document.body);
Similarly, plugins implementing AddClassPlugin
or ToggleClassPlugin
can affect how classes are set.
Both of these plugins are also exported from render-jsx/dom/plugins
.
AddClassPlugin
are utilizied when a custom value is provided in a class array.ToggleClassPlugin
are used when custom values are provided in class toggle maps.info IMPORTANT
You MUST have
StylePlugin
plugged into your renderer for anySetStylePlugin
to have any effect. Similarly, you MUST haveClassPlugin
plugged into your renderer for anyAddClassPlugin
orToggleClassPlugin
to have any effect.
info IMPORTANT
Similar to prop plugins, style and class plugins MUST return a
boolean
from their.setStyle()
,.addClass()
or their.addClassToggle()
methods, weretrue
indicates that the plugin is handling setting the particular class/style andfalse
means other plugins should be attempted.
Plugins implementing ContentPlugin
can add functionality for setting the
content of a node. The meaning of content of a node is context-dependent,
for example in DOM it is the same thing as inner HTML for elements and text content
of text nodes.
1link import { ContentPlugin, Plugin } from 'render-jsx/plugin';
2linkimport { LiveRendererLike } from 'render-jsx';
3link
4linkimport pipe from 'callbag-pipe';
5linkimport subscribe from 'callbag-subscribe';
6link
7link
8linkexport class CallbagContentPlugin<Node> // --> is generic towards node type
9link extends Plugin<Node, LiveRendererLike<Node>> // --> but needs a `LiveRendererLike` (life-cylce concept)
10link implements ContentPlugin<Node> { // --> and is a `ContentPlugin`
11link
12link priority() { return Plugin.PriorityFallback; } // --> allows for other plugins to supercede this plugin
13link
14link setContent(node: Node, target: any) { // --> set `node`'s content to `target`
15link if (typeof target === 'function') { // --> if `target` is a function (i.e. a callbag)
16link const renderer = this.renderer();
17link
18link renderer.hook(node, { // --> bind to `node`'s lifecycle
19link bind: () => pipe(
20link target,
21link subscribe(v => renderer.setContent(node, v?.toString()))
22link )
23link });
24link
25link return true; // --> indicates this plugin set the content
26link }
27link
28link return false; // --> indicates this plugin could not set the content
29link }
30link}
1linkimport { CommonDOMRenderer } from 'render-jsx/dom';
2linkimport interval from 'callbag-interval';
3linkimport pipe from 'callbag-pipe';
4linkimport map from 'callbag-map';
5link
6linkimport { CallbagContentPlugin } from './callbag-content.plugin';
7link
8linkconst renderer = new CommonDOMRenderer()
9link .plug(() => new CallbagContentPlugin<Node>());
10link
11linkconst content = pipe(
12link interval(1000),
13link map(v => `You have been here for ${v} seconds.`)
14link);
15link
16linkrenderer.render(
17link <div _content={content}/>
18link).on(document.body);
info IMPORTANT
Content plugins MUST return a
boolean
from their.setContent()
method. Returningtrue
means the plugin set the content, andfalse
means it couldn't and the renderer should try other plugins.
Plugins implementing CreatePlugin
can modify how nodes are created. Returning undefined
means the plugin could not create the node and the renderer should try other plugins.
1linkinterface CreatePlugin<Node, Renderer> extends Plugin<Node, Renderer> {
2link create(tag: any, props?: {[prop: string]: any}, ...children: any[]): Node | undefined;
3link}
Plugins implementing LeafPlugin
can determine how leaf nodes are made. Only the highest
priority LeafPlugin
will be used by the renderer, its fallback mechanism will also be discarded.
1linkinterface LeafPlugin<Node, Renderer> extends Plugin<Node, Renderer> {
2link leaf(): Node;
3link}
Plugins implementing FragmentPlugin
can determine how fragments are created.
The result of this plugin will be passed to renderer's .create()
method as the tag.
Only the highest priority FragmentPlugin
will be used by the renderer, its fallback mechanism
will also be discarded.
1linkinterface FragmentPlugin<Node, Renderer> extends Plugin<Node, Renderer> {
2link fragment(): Node;
3link}
Plugins implementingPostCreatePlugin
can run some post-processing on nodes after they
are created.
1linkinterface PostCreatePlugin<Node, Renderer> extends Plugin<Node, Renderer> {
2link postCreate(node: Node): void;
3link}
Plugins implementing PostRenderPlugin
can run some post-processing on nodes
after they are rendered (might happen multiple times for a single node).
1linkexport interface PostRenderPlugin<Node, Renderer> extends Plugin<Node, Renderer> {
2link postRender(node: Node): void;
3link}