! Attention: This whole repo is automatically generated !
Building, linting and unit tests can be found in original repo:
https://github.com/eirikb/domdom
This repo exists only for pure Deno support.
It makes domdom available without use of pika.dev or jspm!
Usage with Deno
index.tsx
import domdom from 'https://deno.land/x/domdom';
const { React, data, append } = domdom();
append(document.body, ({ on }) => <div>Hello {on('test')}</div>);
data.set('test', 'World!');
tsconfig.json
{
"compilerOptions": {
"lib": ["dom", "esnext"],
"noImplicitAny": false
}
}
Original readme below here
The proactive web framework for the unprofessional
Facts - not highlights, just facts:
- Alternative to React + Redux or Vue + Vuex, with support for routing
- No virtual dom
- Nothing reactive - totally unreactive - fundamentally different from React
- One global observable state
- Support for re-usable components (with partition of global state)
- No local state
- JSX return pure elements
- Doesnāt support arrays
- Itās not as bad as you might think - Not great, not terrible
Getting started
npm i @eirikb/domdom
index.html:
<body>
<script src="app.jsx"></script>
</body>
app.jsx:
import domdom from "@eirikb/domdom";
const view = ({ on }) => <div>Hello, {on("name")}</div>;
const dd = domdom(document.body, view);
dd.set("name", "world!");
Run:
npx parcel index.html
APIsh
Initialize domdom
import domdom from "@eirikb/domdom";
const dd = domdom(parentElement, view);
This will put React
(required for JSX, not my naming) on global
/window
.
If youāre not a madlad you can alternatively create a local React
:
import domdom from "@eirikb/domdom";
const { React, data, append } = domdom();
append(document.body, view);
Note, here React
isnāt global and must be defined before each JSX.
It must be passed from where itās created, or exported
.
Each React
returned from domdom
is unique, which is why itās returned from domdom()
.
Elements
All elements created with jsx, in the context of domdom, are elements which can be instantly referenced.
const element = <div>Behold!</div>;
element.style.color = "red";
āComponentsā
By creating a function you create a component.
function MyComponent({ on }) {
return (
<ul>
{on("players.$id.name", name => (
<li>Player {name}</li>
))}
</ul>
);
}
Children / Composition
Content of a component will be passed as children
.
function Button({ children }) {
return <button>{children}</button>;
}
const view = () => (
<div>
<Button>
<b>Hello</b>
</Button>
</div>
);
Events
All attributes starting with āonā are added to addEventListener
on the element.
function MyButton({ trigger }) {
return <button onClick={() => trigger("Clicked!")}>Click me!</button>;
}
on(path, callback)
callback
is optional, if omitted the result will be returned as-is,
either as string or JSON of object.
const view = ({ on }) => (
<ul>
{on("players.$id.name", name => (
<li>Player {name}</li>
))}
{on("info")}
</ul>
);
This will match on players.1.name
and players.2.name
etc.
path
can contain wildcards, either names with $
or any with *
.
Named wildcards can be resolved in the callback:
on("players.$id", (player, { $id }) => console.log(`Id is ${$id}`));
You can have multiple wildcards: players.$id.items.$itemId.size
.
Sub-paths
By using >.
itās possible to listen to relative paths āfrom parentā.
This is how itās possible to make re-usable ādetachedā components.
They have data in global state, but donāt rely on the parent path.
const view = ({ on }) => (
<div>
{on("players.$id", player => (
<div>
Player name: (won't update on change): {player.name} <br />
{on(">.name", name => (
<span>Player name: {name}</span>
))}
</div>
))}
</div>
);
when(path, oddEvenArrayOfCheckAndResult)
Heard of pattern matching? This isnāt it.
Odd/Even of conditional and view. View must be a function.
const view = ({ when }) => (
<div>
{when("route", [
"home",
() => <div>Home!</div>,
"away",
() => <div>Away!</div>,
route => (route || "").startsWith("eh"),
() => <div>Eh?</div>,
false,
() => <div>Route is literally boolean false</div>
])}
</div>
);
or
Neither on
or when
will trigger unless there is a value on the path, in order to show something at all
until some value is set or
must be used.
const view = ({ when }) => (
<div>
{when("routing", ["home", () => <div>Home!</div>]).or(
<div>Loading app in the fastest possible way...</div>
)}
</div>
);
dd-model
This is a convenience hack for putting data into and out of a data path from an input.
Similar to v-model and ng-model.
Suggest not using this if possible, using forms directly like in recipes is much better.
dd.on("= search", event => {
event.preventDefault();
dd.set("result", `Data for ${dd.get("text")} here...`);
});
const view = ({ when, on, trigger }) => (
<form onSubmit={e => trigger("search", e)}>
<input type="search" dd-model="text" />
<input type="checkbox" dd-model="more" />
{when("more", [true, () => "This is more"])}
Current text: {on("text")}
<button type="submit">Search</button>
{on("result", _ => _)}
</form>
);
Attributes
Itās possible to use on
directly on attributes.
It might feel and look a bit quirky, but there it is.
const view = ({ on, set, get }) => (
<div>
<button onClick={() => set("toggle", !get("toggle"))}>Toggle</button>
<button disabled={on("toggle").or(true)}>A</button>
<button disabled={on("toggle", res => !res)}>B</button>
</div>
);
Arrays / lists
domdom doesnāt support arrays.
Meaning itās not possible to put arrays into state, if you try they will be converted into properties based on index.
E.g.,
dd.set("users", ["Mr. A", "Mr. B"]);
// Becomes
const result = {
users: {
0: "Mr. A",
1: "Mr. B"
}
};
You can provide a key as third argument to set
in order to use a property as key instead of index.
E.g.,
dd.set(
"users",
[
{ id: "a", name: "Mr. A" },
{ id: "b", name: "Mr. B" }
],
"id"
);
// Becomes
const result = {
users: {
a: { id: "a", name: "Mr. A" },
b: { id: "b", name: "Mr. B" }
}
};
Arrays in JSX
If you provide a path with a wildcard such as $id
to on
elements will render as a list/array,
but you canāt control things like order. Itās just a way to get ādata out thereā.
E.g.,
const view = ({ on }) => (
<ul>
{on("users.$id", user => (
<li>{user.name}</li>
))}
</ul>
);
const dd = domdom(document.body, view);
dd.set(
"users",
[
{ id: "a", name: "Mr. A" },
{ id: "b", name: "Mr. B" },
{ id: "b", name: "Mr. C" }
],
"id"
);
Will render as:
<ul>
<li>Mr. A</li>
<li>Mr. B</li>
<li>Mr. C</li>
</ul>
Pathifier
If you want custom sorting and/or filtering you can use a Pathifier.
Creating one is a bit quirky, you call on
without a listener, and then chain with map
. Weird.
E.g.,
const view = ({ on }) => (
<ul>
{on("users")
.map(user => <li>{user.name}</li>)
.filter(user => user.name !== "Mr. B")
.sort((a, b) => b.id.localeCompare(a.id))}
</ul>
);
Will render as:
<ul>
<li>Mr. C</li>
<li>Mr. A</li>
</ul>
Thereās also a filterOn
and sortOn
.
Use these if you want to listen for another path for changes, and then filter/sort when these changes.
This will listen both for changes on the given path, and the path provided to the method.
E.g.,
const view = ({ on }) => (
<ul>
{on("users")
.map(user => <li>{user.name}</li>)
.filterOn("test", (filter, user) => user.name !== filter)}
</ul>
);
const dd = domdom(document.body, view);
dd.set("test", "Mr. C");
// Add users as above
Will render as:
<ul>
<li>Mr. A</li>
<li>Mr. B</li>
</ul>
Recipes
How to handle common tasks with domdom
Routing
const view = ({ when }) => (
<div>
{when("route", ["login", () => <Login />, "welcome", () => <Welcome />]).or(
"Loading app..."
)}
</div>
);
function gotoRoute(route) {
window.location.hash = route;
}
window.addEventListener("hashchange", () =>
dd.set("route", window.location.hash.slice(1))
);
Login form
function login(event) {
event.preventDefault();
fetch("/login", {
method: "post",
body: new URLSearchParams(new FormData(event.target))
});
}
const view = () => (
<form onSubmit={login}>
<input name="username" />
<input name="password" type="password" />
<button type="submit">Login</button>
</form>
);
Split view and data
data.js
export default ({ on, set }) => {
on("= search", event => {
event.preventDefault();
const searchText = event.target.search.value;
set("result", `Data for ${searchText} here...`);
});
};
index.jsx
import data from "./data";
import domdom from "@eirikb/domdom";
const dd = domdom();
data(dd);
const view = ({ on, trigger }) => (
<form onSubmit={e => trigger("search", e)}>
<input type="search" name="search" />
<button type="submit">Search</button>
{on("result", _ => _)}
</form>
);
dd.append(document.body, view);
Animation (garbage collection)
At writing moment domdom doesnāt have any unmount callback.
Iām not a big fan of destructors, unmounted, dispose or similar.
This might seem silly, and it might not be obvious how to use say setInterval
,
without this preventing the element from ever being cleaned up by garbage collector.
The idea is to use dd
for such things, as these listeners are automatically cleaned up.
const view = ({ on, get, set }) => {
const img = <img src="https://i.imgur.com/rsD0RUq.jpg" />;
on("tick", time => (img.style.transform = `rotate(${time % 180}deg)`));
return (
<div>
<button onClick={() => set("run", !get("run"))}>Start/Stop</button>
{img}
</div>
);
};
(function loop(time) {
if (dd.get("run")) {
dd.set("tick", time);
}
requestAnimationFrame(loop);
})(0);