Say Goodbye to 80% of Your Frontend

Transform the DOM over-the-wire with a simple state machine and remove 80% of all that glue code that's making everything a sticky mess. No javascript, no libraries, no transpilers required.

Download

~1.47kb!

Setup

Define your DOM transformations in a state machine

const fsm = new Domx({
  initialState: "red",
  listeners: [["#stoplight", "click", "changeState"]],
  states: {
    red: {
      entry: [
        ["addClass", "#stoplight__red", "active"],
        ["removeClass", "#stoplight__green", "active"],
        ["dispatch", "changeState", 2000],
      ],
      changeState: [["state", "yellow"]],
    },
    yellow: {
      entry: [
        ["addClass", "#stoplight__yellow", "active"],
        ["removeClass", "#stoplight__red", "active"],
        ["dispatch", "changeState", 2000],
      ],
      changeState: [["state", "green"]],
    },
    green: {
      entry: [
        ["addClass", "#stoplight__green", "active"],
        ["removeClass", "#stoplight__yellow", "active"],
        ["dispatch", "changeState", 2000],
      ],
      changeState: [["state", "red"]],
    },
  },
});

// Alternatively you can use the custom element
// <dom-x>...</dom-x> <== locally defined fsm
// <dom-x src="/my-fsm.json">...</dom-x> <== remotely defined fsm
        

Markup

Render the markup the state machine will operate on.

<div id="stoplight">
  <div id="stoplight__red"></div>
  <div id="stoplight__yellow"></div>
  <div id="stoplight__green"></div>
</div>
          

Result

Click the stoplight

How Is That Better!?

In Short:

Behavior is local (no more following wires), the state is modeled (the only thing that's actually hard), less client/server coupling (it's all backend) and it's toolable (you can write your own transformers). And in the end, it's just a lot less code and complexity.

Transformers

All The Things You Can Do


// Name: Append
// Desc: Append to element
// Type: [transformer: "append", selector: string, html: string];
["append", "#list", "<li>new item</li>"],

// Name: Attr
// Desc: Set an attribute
// Type: [transformer: "attr", selector: string, attr: string, value: string];
["attr", "#dialog", "open", "true"],

// Name: Add Event Listener
// Desc: Attach an event handler to trigger an action
// Type: [transformer: "addEventListener", selector: string, event: string, stateEvent:string];
["addEventListener", "#btn", "click", "changeState"],

// Name: Dispatch
// Desc: Dispatch an action
// Type: [transformer: "dispatch", action: string, delay?: number];
["dispatch", "reset", 2000],

// Name: Get
// Desc: Send a get request with the selected data to and endpoint and process the result (a transformation object)
// Type: [transformer: "get", url: string, ...data: 
//   | [key: string, selector: string, prop: "value"]
//   | [key: string, selector: string, prop: "attribute", propKey: string]
//   | [key: string, selector: string, prop: "dataset", propKey: string]]
// []];
["get", "/some/endpoint", [
  ["first-name", "#first-name-field", "value"],
]],

// Name: History
// Desc: Update the browser history
// Type: [transformer: "history", method: string, ...args: (string | number)[]];
["history", "pushState", {}, "Record List", "/records/list"],

// Name: Location
// Desc: Update the browser location
// Type: [transformer: "location", url:string];
["location", "/records/list"],

// Name: Post
// Desc: Post selected data to endpoint and process the result (a transformation object)
// Type: [transformer: "post", url: string, ...data: 
//   | [key: string, selector: string, prop: "value"]
//   | [key: string, selector: string, prop: "attribute", propKey: string]
//   | [key: string, selector: string, prop: "dataset", propKey: string]]
// []];
["post", "/some/endpoint", [
  ["first-name", "#first-name-field", "value"],
]],

// Name: Remove attribute
// Desc: Remove an attribute
// Type: [transformer: "removeAttribute", selector: string, attr: string];
["removeAttribute", "#dialog", "open"],

// Name: Remove class
// Desc: Remove a class
// Type: [transformer: "removeClass", selector: string, className: string];
["removeClass", "#dialog", "open"],

// Name: RemoveEventListener
// Desc: Remove an event listener
// Type: [transformer: "removeEventListener", selector: string, event: string, stateEvent: string];
["removeEventListener", "#btn", "click", "changeState"],

// Name: Replace
// Desc: Replace an element
// Type: [transformer: "replace", selector: string, html: string];
["replace", "#list", "<li>updated item</li>"],

// Name: SetAttribute
// Desc: Set an attribute
// Type: [transformer: "setAttribute", selector: string, attr: string, value: string];
["setAttribute", "#dialog", "open", "true"],

// Name: State
// Desc: Change current state
// Type: [transformer: "state", state: string];
["state", "processing"],

// Name: Submit
// Desc: Submit a form and process the result (a transformation object)
// Type: [transformer: "submit", formSelector: string];
["submit", "#form-id"],

// Name: Text content
// Desc: Set the text content of an element
// Type: [transformer: "textContent", selector: string, text: string];
["textContent", "#dialog", "Processing..."],

// Name: Wait
// Desc: Add a wait between transformations
// Type: [transformer: "wait", ms: number];
["wait", 2000],

// Name: Window
// Desc: Call a method on the window object
// Type: [transformer: "win", method: string, ...args: any[]];
["window", "alert", "Hello World!"],

Write Your Own Transformer

Take a look at the "customTransformer" transformer

      // first define the function
function addCustomTransformer(domxInstance: Domx, arg1: string, arg2: string) {
  // ...do your worst...
}

// then register it to your domx instance
myfsm.addTransformer("customTransformer", addCustomTransformer);

// in your state machine you can now use it
["customTransformer", "argument one", "argument two"]

    

What Else?

Couple domx with my other library, cap ui

cap ui is a set of cut and paste ui components. The state/behavior of the components are expressed via BEM syntax which means adding/removing classes pretty much controls everything. This makes domx and cap ui a match made in heaven.

©Copyright 2024 All rights reserved. Made in the USA 🇺🇸 by Kevin Lint as a product of Lint Trap Media.