Renderer objects provide all of callbag-jsx
's core functionalities.
1linkimport { makeRenderer } from 'callbag-jsx';
2link
3linkconst renderer = makeRenderer();
.create()
method creates elements or components. JSX is transpiled to renderer.create()
,
so these two are the same:
1linkconst x = <div class='X'/>;
2linkconst y = <Component prop={42}>Hellow There!</Component>;
1linkconst x = renderer.create('div', {'class': 'X'});
2linkconst y = renderer.create(Component, {'prop': 42}, 'Hellow There!');
.render()
method renders given elements on/before/after target elements:
1linkrenderer.render(x).on(document.body);
2linkrenderer.render(<div>Hellow</div>).before(x);
3linkrenderer.render(<span>!</span>).after(x);
👉 When an element is rendered on the main document (appearing on screen) using .render()
, its life-cycle
hooks will also be activated. This causes all callbags embedded in the element to also
be tracked and used to update the element's content / attributes:
1linkconst x = <div>{interval(1000)} have been passed.</div>;
2link
3link/*~warn~*/document~~~~~~~~.~body~~~~.~appendChild~~~~~~~~~~~(~x~)~;~/*~warn~*/
4link// 👉 `x` will be rendered on body, but the interval will not be tracked,
5link// so its content won't be updated.
6link
7linkrenderer.render(x).on(document.body);
8link// 👉 `x` will be rendered on body, and the interval will also be tracked,
9link// updating `x`'s content every second.
⚠️ IMPORTANT ⚠️
Always use
renderer.render()
instead ofdocument.body.appendChild()
, or equivalent methods of DOM APIs.
.remove()
function will remove given element from its parents:
1linkrenderer.remove(x);
When an element is removed from the main document using .remove()
, its life-cycle hooks
will also be cleared. This ensures that subscriptions and other allocated resources for the
element will also be cleared.
⚠️ IMPORTANT ⚠️
Always use
renderer.remove()
for removing elements, as otherwise you will have orphan subscriptions and other resources not cleaned up and sticking in the memory.
Renderers also provide the following methods useful for manipulating DOM:
1linkrenderer.append(target, host); // --> appends target to host
2linkrenderer.setProp(node, prop, target); // --> sets given property to target on given node
3linkrenderer.setContent(node, target); // --> sets content (e.g. inner HTML) of given node to given target
4linkrenderer.hook(node, { bind(), clear() });// --> attach life-cycle hooks to the given node
5linkrenderer.leaf(); // --> creates a leaf node (e.g. text node)
6linkrenderer.fragment; // --> creates a fragment (e.g. `DocumentFragment`)
👉 It is highly recommendable to use these methods whenever you want to do DOM manipulations, as they utilize
all rendering plugins and additional features otherwise not available.
For example, .setProp()
can set properties to callbags, etc.
The makeRenderer()
function returns a DOM Renderer
with some callbag-related plugins plugged:
1linkimport { CommonDOMRenderer, LiveDOMRenderer } from 'render-jsx/dom'; // @see [Render JSX](https://loreanvictor.github.io/render-jsx/docs/usage/dom/overview)
2linkimport {
3link CallbagAppendPlugin, CallbagPropPlugin, CallbagContentPlugin, CallbackTrackPlugin,
4link CallbagInputValuePlugin, CallbagInputStatePlugin, CallbagEventHandlerPlugin,
5link CallbagClassPlugin, CallbagStylePlugin,
6link} from 'callbag-jsx/plugins';
7link
8link
9linkexport function makeRenderer(dom?: DOMWindow) {
10link return new CommonDOMRenderer(dom).plug(
11link () => new CallbagAppendPlugin<Node>(), // --> allows appending callbags to other nodes
12link () => new CallbagPropPlugin<Node>(), // --> allows setting node properties to callbags
13link () => new CallbagContentPlugin<Node>(), // --> allows setting `_content` attribute to callbags
14link () => new CallbagTrackPlugin<Node>(), // --> allows components to track callbags on their lifecycle hooks
15link () => new CallbagInputValuePlugin(), // --> allows input values being bound to callbags
16link () => new CallbagInputStatePlugin(), // --> allows input state sync with a callbag state
17link () => new CallbagEventHandlerPlugin(), // --> allows handling events by piping them to callbags
18link () => new CallbagClassPlugin(), // --> allows setting dynamic classes using callbags
19link () => new CallbagStylePlugin(), // --> allows setting dynamic styles using callbags
20link ) as LiveDOMRenderer;
21link}
👉 You can plug these plugins into other JSX renderers (e.g. a native renderer) for enabling callbag support.
👉 You can plug other plugins into your renderers for additional functionality.
In this example we create a custom plugin that allows us to render Date
objects on the DOM:
1linkimport { makeRenderer } from 'callbag-jsx';
2linkimport { DatePlugin } from './date.plugin'; // @see tab:Plugin Code
3link
4linkconst renderer = makeRenderer().plug(() => new DatePlugin<Node>());
5link
6linkrenderer.render(
7link <>
8link <h1>Hellow!</h1>
9link <p>Today is {new Date()}</p>
10link </>
11link).on(document.body);
1linkimport { AppendPlugin, Plugin } from 'render-jsx/plugin';
2linkimport { RendererLike } from 'render-jsx';
3link
4linkexport class DatePlugin<N>
5link extends Plugin<N, RendererLike<N>>
6link implements AppendPlugin<N, RendererLike<N>> {
7link
8link priority() { return Plugin.PriorityFallback; }
9link
10link append(target: any, host: any) {
11link if (target instanceof Date) {
12link this.renderer().append(target.toDateString(), host);
13link return true;
14link }
15link
16link return false;
17link }
18link}
In this example we create a custom plugin that provides given config object to all components:
1linkexport function Greetings({ to }, renderer) {
2link return <h1>{this.config.greeting} {to}!</h1>;
3link}
1linkimport { makeRenderer } from 'callbag-jsx';
2linkimport { ConfigPlugin } from './config.plugin'; // @see tab:Plugin Code
3linkimport { Greetings } from './greetings';
4link
5linkconst renderer = makeRenderer()
6link .plug(() => new ConfigPlugin<Node>({
7link greeting: 'Hellow',
8link }));
9link
10linkrenderer.render(
11link <>
12link <Greetings to='World'/>
13link <p>Today is {new Date()}</p>
14link </>
15link).on(document.body);
1linkimport { ComponentProcessor } from 'render-jsx/component';
2linkimport { RendererLike } from 'render-jsx';
3link
4linkexport class ConfigPlugin<N>
5link extends ComponentProcessor<N, RendererLike<N>> {
6link
7link constructor(readonly config: any) { super(); }
8link
9link priority() { return ComponentProcessor.PriorityFallback; }
10link
11link process(provide) {
12link provide({
13link config: this.config
14link });
15link }
16link}
In this example we create a plugin that enables use of class-based components:
1linkimport { Component } from './class-comp.plugin';
2link
3linkexport class Greetings extends Component<Node> {
4link render(renderer) {
5link return <h1>Hellow {this.props.to}</h1>
6link }
7link}
1linkimport { makeRenderer } from 'callbag-jsx';
2linkimport { ClassComponentPlugin } from './class-comp.plugin'; // @see tab:Plugin Code
3linkimport { Greetings } from './greetings';
4link
5linkconst renderer = makeRenderer()
6link .plug(() => new ClassComponentPlugin<Node>());
7link
8linkrenderer.render(
9link <>
10link <Greetings to='World'/>
11link <p>Today is {new Date()}</p>
12link </>
13link).on(document.body);
1linkimport { RendererLike } from 'render-jsx';
2linkimport { ComponentPlugin } from 'render-jsx/component/plugins';
3link
4link
5linkexport abstract class Component<Node, Renderer=RendererLike<Node>> { // --> a base class for our components
6link static __COMP_CLASS_BASE__ = true; // --> this allows us to check if given tag is a class extending this base class
7link
8link constructor(
9link protected props, // --> collect given props
10link protected children, // --> collect the children
11link protected renderer, // --> collect the renderer
12link protected provision // --> collect additional provisions
13link ) {}
14link
15link abstract render(renderer: Renderer); // --> this will be invoked for rendering stuff
16link}
17link
18link
19linkexport class ClassComponentPlugin<Node> // --> so our plugin is generic towards node type
20link extends ComponentPlugin<Node, RendererLike<Node>> { // --> and can work with any renderer
21link
22link priority() { return ComponentPlugin.PriorityMax; }
23link
24link match(component) { // --> determines if given component data match this plugin
25link return typeof component.tag === 'function' // --> check if the tag is a function (constructor)
26link && component.tag.__COMP_CLASS_BASE__; // --> check if it is a class extending our base component class
27link }
28link
29link createComponent(component, provision) {
30link return new component.tag( // --> invoke the constructor
31link component.props, // --> give it the props
32link component.children, // --> give it the children
33link this.renderer(), // --> give it the plugged in renderer
34link provision // --> give it the additional provisions
35link ).render(this.renderer()); // --> call its render
36link }
37link}