01. Obtaining vertex.js

Vertex is a 1kloc SPA framework containing everything you need from React, Ractive-Load and jQuery while still being jQuery-compatible.

vertex.js is a single, self-contained file with no build step and no dependencies. Download it from the Gist below and drop it wherever your project serves static assets.

Or fetch it from the command line:

# save to your project's static directory
curl -o static/vertex.js \
  https://gist.githubusercontent.com/LukeB42/ef5b142325fc2bcd4915ba9b452f6230/raw/867cf609e8ec6bcb6700eda7f81c7c60e9ee01c9/vertex.js

It ships as a UMD module, so it works equally as a plain <script> tag, a CommonJS require(), or an AMD define().

02. Loading in the <head>

Add a single <script> tag. Place it before any code that references Vertex or V$.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>My App</title>

  <script src="/static/vertex.js"></script>

  <!-- If you also use jQuery, load it BEFORE vertex.js.
       vertex.js detects $ and leaves it untouched. -->
  <!-- <script src="/static/jquery.min.js"></script> -->
</head>
<body>
  <div id="root"></div>
</body>
</html>

After loading, the following globals are available:

Global Description
Vertex Full namespace — all features live here
V$ Shorthand DOM wrapper — always available
$ Also set to the DOM wrapper only when jQuery is absent

03. Setting a templates directory

Set Vertex.template.load.baseUri once at startup and every subsequent load() call that receives a relative path will automatically prepend it. Absolute URLs (starting with http://, https://, or /) are always used as-is, so fully-qualified paths continue to work unchanged.

// main.js — set the base once, then use short names everywhere
Vertex.template.load.baseUri = "/static/templates/";

// "user-card" resolves to /static/templates/user-card
Vertex.template.load("user-card", {
  el:   "#sidebar",
  data: { name: "Alice", role: "Engineer" }
}).then(instance => {
  instance.on("change", e => console.log("changed:", e));
});

// Absolute paths bypass baseUri entirely
Vertex.template.load("/other/path/special.html", { el: "#special" });
Vertex.template.load("https://cdn.example.com/tmpl.html", { el: "#remote" });

A template file at /static/templates/user-card.html is just a regular HTML fragment wrapped in a <template> tag:

<!-- /static/templates/user-card.html -->
<template>
  <div class="card">
    <h2>{{name}}</h2>
    <p>{{role}}</p>
    {{#if email}}<a href="mailto:{{email}}">{{email}}</a>{{/if}}
  </div>
</template>
Note: If the file has no <template> tag, Vertex.template.load() uses the entire response body as the template string. Both forms work.

04. DOM layer — V$ / VQuery

V$(selector) returns a chainable wrapper around a set of matched elements — identical in spirit to hn.js with a fuller jQuery surface. Every method returns this for chaining.

Selecting & creating elements

// CSS selector
V$(".card").addClass("active");

// HTML creation
const el = V$('<li class="item">Hello</li>');

// Scoped query (2nd arg = context)
V$("li", "#my-list").each(function() {
  console.log(this.textContent);
});

// Document ready
V$(function() {
  console.log("DOM ready");
});

Events — .on() / .off() / .trigger()

// Direct event binding
V$("button").on("click", function(e) {
  V$(this).toggleClass("pressed");
});

// Multiple events at once
V$("input").on("focus blur", function() {
  V$(this).toggleClass("active");
});

// Event delegation (bubbles up from ".row" to "#table")
V$("#table").on("click", ".row", function(e) {
  console.log("row clicked:", this.dataset.id);
});

// Remove handler
const handler = e => doSomething(e);
V$("#btn").on("click", handler);
V$("#btn").off("click", handler);

// Custom event dispatch
V$("#root").trigger("app:ready", { version: "1.0" });

Attributes, styles & values

// .attr(name)           → get
// .attr(name, val)      → set (chainable)
// .css(prop)            → get computed value
// .css(prop, val)       → set style property
// .css({ prop: val })   → set multiple
// .val()                → get input value
// .val(v)               → set input value

V$("img")
  .attr("alt", "A scenic photo")
  .css({ borderRadius: "4px", opacity: "0.9" });

const username = V$("#name-input").val();
V$("#name-input").val("").attr("placeholder", "Enter name…");

Content & traversal

// Content
V$("#output").html("<strong>Done.</strong>");
V$("#label").text("Status: OK");
V$("ul").append("<li>New item</li>");
V$("ul").prepend("<li>First item</li>");

// Traversal
V$(".panel").find("input").val("");   // clear all inputs inside .panel
V$("li.active").parent().addClass("has-active");
V$("li").first().addClass("leader");
V$("li").eq(2).remove();
V$("li").filter(function(el, i) { return i % 2 === 0; }).addClass("even");
V$(".item").not(".disabled").on("click", handleClick);

05. AJAX

Vertex.ajax() wraps the Fetch API with a jQuery-shaped surface: success/error callbacks, dataType, content-type handling, and .done()/.fail() on the returned Promise.

// Full options form
Vertex.ajax({
  url:         "/api/tracks",
  method:      "GET",
  data:        { genre: "bass", limit: 20 },
  dataType:    "json",
  success: tracks => renderTracks(tracks),
  error:   err    => console.error(err)
});

// POST with JSON body
Vertex.ajax({
  url:         "/api/session",
  method:      "POST",
  contentType: "application/json",
  data:        { token: myToken },
  success: session => startSession(session)
});

// Promise style
Vertex.ajax({ url: "/api/ping" })
  .done(res  => console.log("ok", res))
  .fail(err  => console.warn("failed", err));

// Shorthand GET / POST
Vertex.get("/api/user", data => console.log(data));
Vertex.post("/api/save", { title: "Mix A" }, res => console.log(res));

06. Fiber reconciler — createElement & render

Vertex's reconciler follows the same fiber architecture described at pomb.us. The public API is intentionally React-compatible.

createElement

const { createElement: h, render, Fragment } = Vertex;

// Host element
h("div", { className: "card" },
  h("h2", null, "Hello"),
  h("p",  null, "World")
)

// Function component
function Badge({ label, colour }) {
  return h("span", { style: { background: colour } }, label);
}
h(Badge, { label: "Bass", colour: "#c8ff00" })

// Fragment — renders children with no wrapper element
h(Fragment, null,
  h("dt", null, "BPM"),
  h("dd", null, "174")
)

render

// Mount your root component once — Vertex handles all subsequent updates
function App() {
  return h("div", { className: "app" },
    h("h1", null, "Vertex")
  );
}

Vertex.render(
  h(App, null),
  document.getElementById("root")
);

Lazy / async components

// Vertex.lazy() follows the React.lazy Suspense protocol.
// The component is fetched once; Vertex re-renders automatically.
const HeavyChart = Vertex.lazy(() => import("/static/js/chart.js"));

function Dashboard() {
  return h(HeavyChart, { data: chartData });
}

07. Hooks

All hooks follow React's rules: call them only at the top level of a function component, never inside conditionals or loops.

useState

function Counter() {
  const [count, setCount] = Vertex.useState(0);

  return h("div", null,
    h("span", null, String(count)),
    h("button", { onClick: () => setCount(c => c + 1) }, "+"),
    h("button", { onClick: () => setCount(0) }, "reset")
  );
}

useReducer

function reducer(state, action) {
  switch (action.type) {
    case "add":    return { ...state, items: [...state.items, action.item] };
    case "clear":  return { ...state, items: [] };
    default:        return state;
  }
}

function Playlist() {
  const [state, dispatch] = Vertex.useReducer(reducer, { items: [] });
  return h("ul", null,
    ...state.items.map(t => h("li", { key: t.id }, t.title))
  );
}

useEffect

function AudioPlayer({ src }) {
  const audioRef = Vertex.useRef(null);

  // Run once on mount — teardown on unmount
  Vertex.useEffect(function() {
    const ctx = new AudioContext();
    audioRef.current = ctx;
    return () => ctx.close();   // cleanup
  }, []);

  // Re-run when src changes
  Vertex.useEffect(function() {
    if (audioRef.current) loadTrack(audioRef.current, src);
  }, [src]);

  return h("div", { className: "player" }, "Playing: " + src);
}

useMemo & useCallback

function TrackList({ tracks, filter }) {
  // Only re-computed when tracks or filter changes
  const filtered = Vertex.useMemo(
    () => tracks.filter(t => t.genre === filter),
    [tracks, filter]
  );

  // Stable function reference — safe to pass to child components
  const handleClick = Vertex.useCallback(
    t => console.log("selected:", t.title),
    []
  );

  return h("ul", null,
    ...filtered.map(t =>
      h("li", { onClick: () => handleClick(t) }, t.title)
    )
  );
}

useRef

function FocusInput() {
  const inputRef = Vertex.useRef(null);

  Vertex.useEffect(() => {
    if (inputRef.current) inputRef.current.focus();
  }, []);

  return h("input", {
    ref: inputRef,         // Vertex will write the DOM node here
    placeholder: "Type…"
  });
}

createContext & useContext

const ThemeCtx = Vertex.createContext("dark");

function App() {
  return h(ThemeCtx.Provider, { value: "dark" },
    h(Toolbar, null)
  );
}

function Toolbar() {
  const theme = Vertex.useContext(ThemeCtx);
  return h("nav", { className: "toolbar theme-" + theme });
}

08. Template engine — Vertex.template / Mustache

The Vertex.template constructor takes an element target, a mustache template string, and a data object. It renders immediately and re-renders on every .set() or .update().

Basic usage

const r = new Vertex.template({
  el:       "#app",
  template: `
    <h1>{{title}}</h1>
    <ul>
      {{#each tracks}}
        <li>{{@index}}. {{name}} — {{bpm}} BPM</li>
      {{/each}}
    </ul>
  `,
  data: {
    title:  "My Set",
    tracks: [
      { name: "Vortex",    bpm: 174 },
      { name: "Subsonic",  bpm: 140 },
    ]
  }
});

// Update a single key — triggers re-render
r.set("title", "Night Set");

// Merge multiple keys at once
r.update({ title: "Morning Set", tracks: [] });

// Listen for data changes
r.on("change", ({ keypath, value }) => {
  console.log(keypath, "→", value);
});

Mustache syntax reference

SyntaxBehaviour
{{key}} HTML-escaped interpolation
{{{key}}} Raw / unescaped HTML
{{user.name}} Nested dot-path resolution
{{#each items}} … {{/each}} Loop — keys from each item available directly; @index for position
{{#if flag}} … {{/if}} Conditional block
{{#if flag}} … {{else}} … {{/if}} Conditional with fallback

Two-way binding

Add data-bind="keypath" to any <input> inside a template and Vertex will keep the input and the data object in sync automatically:

new Vertex.template({
  el:       "#form",
  template: '<input data-bind="username" placeholder="Username">',
  data:     { username: "" },
  oncomplete() {
    console.log("mounted, username =", this.get("username"));
  }
});

Vertex.template.load() — remote templates

// Set base once at startup
Vertex.template.load.baseUri = "/static/templates/";

// Short name resolves to /static/templates/player.html
Vertex.template.load("player", {
  el:   "#player-container",
  data: { track: "Vortex.wav", playing: false }
}).then(instance => {
  instance.on("change", e => syncBackend(e));
});

09. Hash router — Backbone style

Vertex.Router is a singleton. Routes are matched against the URL fragment (#/…) using named parameters (:name) and splats (*rest).

Singleton API

const { Router } = Vertex;

Router
  .add("",                params => showHome())
  .add("projects",        params => showProjects())
  .add("projects/:id",    params => showProject(params.id))
  .add("files/*path",     params => showFile(params.path))
  .start();               // begins listening; dispatches current fragment

// Navigate programmatically
Router.navigate("projects/42");           // sets #/projects/42
Router.navigate("projects/42", { trigger: true }); // + fire handler

// Remove a route
Router.remove("files/*path");

// Stop / reset
Router.stop();
Router.reset();

Class-based router (Backbone.Router syntax)

const AppRouter = Vertex.RouterClass.extend({
  routes: {
    "":              "home",
    "projects":      "projects",
    "projects/:id":  "project",
    "files/*path":   "file"
  },

  home()            { console.log("home"); },
  projects()        { console.log("projects"); },
  project({ id })   { console.log("project", id); },
  file({ path })    { console.log("file", path); }
});

const router = new AppRouter();
Vertex.Router.start();

10. useHash — router meets reconciler

useHash() is a hook that returns the current location.hash fragment and automatically re-renders the component on every hashchange event. It's the idiomatic way to drive the fiber reconciler from URL state.

const { createElement: h, render, useHash } = Vertex;

const ROUTES = {
  "":         Home,
  "projects": Projects,
  "about":    About
};

function App() {
  const hash = useHash();             // e.g. "#/projects"
  const key  = hash.replace(/^#\//, "");
  const Page = ROUTES[key] || NotFound;

  return h("main", null,
    h("nav", null,
      h("a", { href: "#/"          }, "Home"),
      h("a", { href: "#/projects"  }, "Projects"),
      h("a", { href: "#/about"     }, "About")
    ),
    h(Page, null)
  );
}

render(h(App, null), document.getElementById("root"));

Combine with Vertex.Router when you also need pattern matching on dynamic segments — useHash handles the re-render cycle while the Router extracts params.

11. jQuery compatibility

Vertex is explicitly designed to coexist with jQuery on the same page. The rule is simple: load jQuery first, then vertex.js.

<!-- jQuery loaded first -->
<script src="/static/jquery.min.js"></script>
<script src="/static/vertex.js"></script>

vertex.js checks window.jQuery and window.$ before assigning anything. If they exist it leaves them completely alone. Use V$ or Vertex.$v() for the Vertex DOM wrapper in that scenario:

// jQuery and Vertex DOM layer side by side — no conflict
$("#jq-widget").datepicker();      // ← jQuery
V$("#vx-card").on("click", fn);  // ← Vertex

// Or explicitly via the namespace
Vertex.$v("#vx-card").css("color", "#c8ff00");

jQuery static utilities are mirrored on Vertex.VQuery: VQuery.extend(), VQuery.each(), VQuery.isArray(), VQuery.isFunction(), VQuery.trim(), VQuery.noop(), VQuery.parseJSON(), and VQuery.now().

12. Quick API reference

Vertex namespace

SymbolDescription
Vertex.createElement(type, props, …children)Create a virtual element descriptor
Vertex.render(element, container)Mount or update the component tree
Vertex.FragmentWrapper-free grouping element
Vertex.lazy(factory)Async component loader
Vertex.createContext(default)Create a context object
Vertex.useState(initial)Hook: local state
Vertex.useReducer(reducer, initial)Hook: reducer-based state
Vertex.useEffect(fn, deps)Hook: side effects & cleanup
Vertex.useMemo(fn, deps)Hook: memoised value
Vertex.useCallback(fn, deps)Hook: memoised callback
Vertex.useRef(initial)Hook: mutable ref
Vertex.useContext(ctx)Hook: read context value
Vertex.useHash()Hook: reactive URL hash
Vertex.templateMustache template constructor
Vertex.template.load(url, options)Fetch and mount a remote template file
Vertex.template.load.baseUriBase path prepended to relative URLs (default "")
Vertex.RouterSingleton hash router
Vertex.RouterClassBackbone-style base class
Vertex.$v(selector)VQuery DOM wrapper
Vertex.ajax(options)Fetch wrapper
Vertex.get(url, …)Shorthand GET
Vertex.post(url, …)Shorthand POST

VQuery instance methods

MethodDescription
.on(events, [sel], fn)Bind event (delegation if sel given)
.off(events, [fn])Remove event handler(s)
.trigger(event, [detail])Dispatch CustomEvent
.attr(name, [val])Get / set attribute
.css(prop, [val])Get computed / set inline style
.val([v])Get / set input value
.html([content])Get / set innerHTML
.text([content])Get / set textContent
.addClass / .removeClass / .toggleClass / .hasClassClass manipulation
.append / .prepend / .after / .beforeDOM insertion
.find / .parent / .children / .closest / .siblingsTraversal
.first / .last / .eq(i) / .get(i)Subset selection
.filter / .not / .is / .addFiltering
.remove / .empty / .cloneDOM mutation
.each(fn)Iterate matched elements
.data(key, [val])Get / set data-* attribute
.hide / .show / .toggleVisibility shortcuts
.width / .height / .offsetDimension helpers
.serialize()Serialise form to query string
.prop(name, [val])Get / set DOM property