Almost — because only two functions from a library are used:
-
Create
DOM Element -
Update
DOM Element
This library simplifies and streamlines the usage of native DOM functions, such as createElement
and replaceChild
. The Fusor library is all about making these functions easier and more concise to use.
Below are many examples of common problems. Try to recreate them using the tools you are currently using. You might be surprised to find that developing with Fusor could be the most concise, flexible, lightweight, and performant way to build frontend applications.
Contents
- Install
- Create & Update DOM
- Parameters & Children Syntax
- Stateful Component
- Controlled Input Component
- Precise DOM Update
- Escape Update Recursion
- Component Lifecycle
- Automatic/Reactive Updates
- Routing
- Switching Components
- Create & Update DOM Dynamically
- Caching & Memoization
- Exception Handling
Install
npm install @fusorjs/dom
Or:
- JSX boilerplate for: JavaScript or TypeScript
- CDN: https://esm.sh/@fusorjs/dom or https://cdn.skypack.dev/@fusorjs/dom
Create & Update DOM
import {getElement, update} from '@fusorjs/dom';
import {section, div} from '@fusorjs/dom/html';
let count = 0;
const block = section(
{class: () => (count % 2 ? 'odd' : 'even')},
div('Seconds ', () => count, ' elapsed'),
div('Minutes ', () => Math.floor(count / 60), ' elapsed'),
);
document.body.append(getElement(block));
setInterval(() => {
count++;
update(block);
}, 1000);
Only tiny portions of the block
DOM tree are updated if they differ from the current values.
JSX Support
import {getElement, update} from '@fusorjs/dom';
let count = 0;
const block = (
<section class={() => (count % 2 ? 'odd' : 'even')}>
<div>Seconds {() => count} elapseddiv>
<div>Minutes {() => Math.floor(count / 60)} elapseddiv>
section>
);
document.body.append(getElement(block));
setInterval(() => {
count++;
update(block);
}, 1000);
Parameters & Children Syntax
import {getElement, update} from '@fusorjs/dom';
import {section} from '@fusorjs/dom/html';
let count = 0;
const block = section(
{
id: 'set attribute or property automatically',
title_a: 'set attribute',
style_p: 'set property',
focus_e: () => 'set bubbling event handler',
blur_e_capture_once: () => 'set capturing event handler once',
// update dynamic values in this DOM node:
click_e_update: () => count++, // same as
click_e: () => {count++; update(block);}, // same as
click_e: (event, self) => {count++; update(self);},
class: count % 2 ? 'odd' : 'even', // static
class: () => (count % 2 ? 'odd' : 'even'), // dynamic
},
'Static child ', count, ' never changes.',
'Dynamic child ', () => count, ' is wrapped in a function.',
);
document.body.append(getElement(block));
Stateful Component
import {getElement} from '@fusorjs/dom';
import {button, div} from '@fusorjs/dom/html';
const ClickCounter = (count = 0) =>
button({click_e_update: () => count++}, 'Clicked ', () => count, ' times');
const App = () => div(ClickCounter(), ClickCounter(22), ClickCounter(333));
document.body.append(getElement(App()));
JSX Version
import {getElement} from '@fusorjs/dom';
const ClickCounter = ({count = 0}) => (
<button click_e_update={() => count++}>Clicked {() => count} timesbutton>
);
const App = () => (
<div>
<ClickCounter />
<ClickCounter count={22} />
<ClickCounter count={333} />
div>
);
document.body.append(getElement(<App />));
Components in both versions are interoperable.
Same Component Defined Differently
import {button} from '@fusorjs/dom/html';
const ClickCounter = (count = 0) => {
const self = button(
{click_e: () => {count++; update(self);}},
'Clicked ', () => count, ' times',
);
return self;
};
const ClickCounter = (count = 0) =>
button(
{click_e: (event, self) => {count++; update(self);}},
'Clicked ', () => count, ' times',
);
const ClickCounter = (count = 0) =>
button(
{click_e_update: () => count++},
'Clicked ', () => count, ' times',
);
Controlled Input Component
"Controlled Input" in React terms.
import {getElement} from '@fusorjs/dom';
import {input, div} from '@fusorjs/dom/html';
const UppercaseInput = (value = '') =>
input({
value: () => value.toUpperCase(),
input_e_update: (event) => (value = event.target.value),
});
document.body.append(
getElement(
div(UppercaseInput(), UppercaseInput('two'), UppercaseInput('three')),
),
);
Precise DOM Update
import {getElement, update} from '@fusorjs/dom';
import {section, div} from '@fusorjs/dom/html';
let count = 0;
const seconds = div('Seconds ', () => count, ' elapsed');
const block = section(
seconds,
div('Minutes ', () => Math.floor(count / 60), ' elapsed'),
);
document.body.append(getElement(block));
setInterval(() => {
count++;
update(seconds); // not minutes
}, 1000);
This will update only the seconds, not the minutes.
Escape Update Recursion
import {getElement, update} from '@fusorjs/dom';
import {section, div} from '@fusorjs/dom/html';
let count = 0;
const seconds = div('Seconds ', () => count, ' elapsed');
const block = section(
() => seconds, // wrapped in a function to escape
div('Minutes ', () => Math.floor(count / 60), ' elapsed'),
);
document.body.append(getElement(block));
setInterval(() => {
count++;
update(block);
}, 1000);
This will update only the minutes, not the seconds.
Only components (seconds
, block
) are updated recursively. () => seconds
is a function
, not a component.
Every function from @fusorjs/dom/html
returns a component, provided it contains dynamic values. The same applies to JSX definitions.
Component Lifecycle
- Create component
- Connect to DOM
- Update DOM
- Disconnect from DOM
import {getElement, update} from '@fusorjs/dom';
import {div} from '@fusorjs/dom/html';
const IntervalCounter = (count = 0) => {
console.log('1. Create component');
return div(
{
mount: (self) => {
console.log('2. Connect to DOM');
const timerId = setInterval(() => {
count++;
update(self);
console.log('3. Update DOM');
}, 1000);
const unmount = () => {
clearInterval(timerId);
console.log('4. Disconnect from DOM');
};
return unmount;
},
},
'Since mounted ', () => count, ` seconds elapsed`,
);
};
const instance = IntervalCounter(); // 1. Create component
const element = getElement(instance);
document.body.append(element); // 2. Connect to DOM
setTimeout(() => element.remove(), 15000); // 4. Disconnect from DOM
Automatic/Reactive Updates
Automatic/reactive updates in big frameworks are nothing more than an implementation of the Observable pattern. This includes State in React, Signals in Solid, Redux, MobX, and many others. In Fusor, you can use any of those libraries.
Here, we discuss the generic solution:
Router Library
import {update} from '@fusorjs/dom';
import {Observable} from 'Any/Observable/Signal/Redux/Mobx...';
// Modern routing handling
const observable = new Observable();
const read = () => location.hash.substring(1); // omit "#"
let route = read();
window.addEventListener(
'popstate',
() => {
const next = read();
if (route === next) return;
route = next;
observable.notify();
},
false,
);
export const getRoute = () => route;
// Fusor integration
export const mountRoute = (self) => observable.subscribe(() => update(self));
Reactive Component
Switching components when current route is selected.
import {span, a} from '@fusorjs/dom/html';
import {getRoute, mountRoute} from './router';
export const RouteLink = (title, route) =>
span({mount: mountRoute}, () =>
getRoute() === route
? title // when selected
: a({href: `#${route}`}, title),
);
Create & Update DOM Dynamically
import {getElement} from '@fusorjs/dom';
import {ul, li} from '@fusorjs/dom/html';
import {RouteLink} from './RouteLink';
const block = ul(
[...Array(10)].map((v, i) =>
li(RouteLink(`${i + 1}. Section`, `url-to-${i + 1}-section`)),
),
);
document.body.append(getElement(block));
Caching & Memoization
The heavy component is created only once.
import {div, br} from '@fusorjs/dom/html';
let isVisible = true; // can change
const block = div(
(
(cache = HeavyComponent()) =>
() =>
isVisible && cache
)(),
br(),
() => RecreatedEveryUpdate(),
);
Exception Handling
import {section, p} from '@fusorjs/dom/html';
const Value = (value) => {
if (value === undefined) throw new Error(`provide a value`);
return p(value);
};
const block = section(
p('Before'),
(() => {
try {
return [
Value(1),
Value(), // will throw
Value(3),
];
} catch (error) {
if (error instanceof Error) return p('Exception: ', error.message);
return p('Exception: unknown');
}
})(),
p('After'),
);
Conclusion
Now you know everything you need to start developing modern front-end applications with Fusor.
As far as I know, developing with Fusor is the most concise, flexible, lightweight, and performant way to build frontend applications.