Skip to main content
Deno 2 is finally here šŸŽ‰ļø
Learn more

! 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!

image

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

npm Build bundlephobia

The proactive web framework for the unprofessional

out4

Facts - not highlights, just facts:

  • Alternative to React + Redux or Vue + Vuex, with support for routing
  • No virtual dom
  • Support for Deno (without jspm or pika)
  • 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

Deno

Domdom has full support for Deno!
See https://github.com/eirikb/domdom-deno and https://deno.land/x/domdom .

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);