Skip to content

lideming/webfx

Repository files navigation

Webfx

Web UI framework and utilities.

It was originally created for MusicCloud.

npm

Demos

Project Structure

  • buildDOM.ts - View and DOM builder
  • view.ts - The core of View
  • packages/
  • views/ - Some built-in Views and helpers
    • Basics
    • ListView
    • Dialog
    • Menu
    • Overlay
    • Toast
    • LoadingIndicator
    • Section
  • style.css - CSS for the built-in views
  • dist/ - Bundles
    • webfx.js - UMD bundle for browsers (+ .min.js version)
    • webfxcore.min.js - UMD bundle without the viewlib and style
    • webfx.esm.js - ESM bundle

Installation

Import from modules

Install the module locally using npm:

npm install @yuuza/webfx

Import from ES modules:

import { View, ButtonView } from "@yuuza/webfx";

Load into the browser directly

Add webfx to your web page:

<script src="https://cdn.jsdelivr.net/npm/@yuuza/webfx@1.9.12/dist/webfx.min.js"></script>

Or if you don't need the viewlib:

<script src="https://cdn.jsdelivr.net/npm/@yuuza/webfx@1.9.12/dist/webfxcore.min.js"></script>

Then the module can be accessed from global variable webfx:

const { View, ButtonView, mountView } = webfx;

Usage

A basic view component

// Define a very basic view class
class Hello extends View {
  createDom() {
    // Returns a DOM expression object for rendering.
    return {
      tag: "p.text.bold#hello",
      text: "hello webfx",
    };
  }
}

// Create an instance of the view and mount it onto the <body>.
mountView(document.body, new Hello());

Renders:

<p class="text bold" id="hello">hello webfx</p>

Note: You can omit the createDom() can be omited if you want an empty <div>.

DOM Expression

A DOM Expression is an object of type BuildDomNode, or a View object, a string, a number, or a function that returns a string or number.

type BuildDomNode = {
  tag?: BuildDomTag; // A string that indicates a DOM tag name, class names, and id, similar to a CSS selector.
  child?: BuildDomExpr[] | BuildDomExpr; // One or more DOM expressions as the children.
  text?: FuncOrVal<string>; // A shortcut for `textContent`. It can be a function, see below.
  hidden?: FuncOrVal<boolean>; // The `hidden` property, but can be a function, see below.
  init?: Action<HTMLElement>; // A callback that is called when the DOM is created.
  update?: Action<HTMLElement>; // A callback that is called when the view is updated.
  // ...internal properties omitted...
};

The text and hidden callbacks will be called in updateDom().

Properties and Child Elements

Webfx does not have states; they are just properties.

You can use the child key in the DOM expression for child elements.

// Inject Webfx CSS.
webfx.injectWebfxCss();

class Counter extends View {
  constructor() {
    super();
    this.count = 0;
  }

  createDom() {
    return {
      tag: "div.counter",
      child: [
        "Count: ",
        () => this.count,
        { tag: "br" },
        new ButtonView({
          text: "Click me",
          onActive: () => {
            this.updateWith({ count: this.count + 1 });
          },
        }),
      ],
    };
  }
}

// Mount the view onto the body element.
mountView(document.body, new Counter());

Renders:

<div class="counter">
  Count: 9
  <br />
  <div class="btn" tabindex="0">Click me</div>
</div>

Hook Methods

Webfx provides two hook methods:

  • postCreateDom(): called when the DOM is just created.
  • updateDom(): called after postCreateDom(). Can also be called manually by updateDom().

updateWith() is a shortcut for changing properties and calling updateDom().

Note: When overriding these methods, call the super method.

webfx.injectWebfxCss();

class Counter extends View {
  constructor() {
    super();
    this.count = 0;
  }

  createDom() {
    return {
      tag: "div.counter",
      child: [
        "Count: ",
        {
          tag: "span",
          text: () => this.count,
          init: (dom) => {
            console.info("the <span> DOM is created", dom);
          },
          update: (dom) => {
            dom.style.fontSize = `${14 + this.count}px`;
          },
        },
        { tag: "br" },

        new ButtonView({
          text: "Click me",
          onActive: () => {
            this.updateWith({ count: this.count + 1 });
          },
        }),
      ],
    };
  }

  postCreateDom() {
    super.postCreateDom();
    console.info("the counter DOM is created", this.dom);
  }

  updateDom() {
    super.updateDom();
    console.info("the counter DOM is updated", this.dom);
  }
}

// Mount the view onto the body element.
mountView(document.body, new Counter());

ListView

(TBD)

Using JSX/TSX

Before using JSX/TSX, make sure to set jsx() as the JSX factory in the transpiler.

You can do this in one of two ways:

  • Set "jsxFactory": "jsx" in tsconfig.json.
  • Use /** @jsx jsx */ in your source code.
/** @jsx jsx */
import { View, ButtonView, jsx } from "@yuuza/webfx";

webfx.injectWebfxCss();

class Counter extends View {
  constructor() {
    super();
    this.count = 0;
  }

  createDom() {
    return (
      <div class="counter">
        Count: {() => this.count}
        <br />
        <ButtonView
          onActive={() => {
            this.updateWith({ count: this.count + 1 });
          }}
        >
          Click me
        </ButtonView>
      </div>
    );
  }
}

// Mount the view onto the body element.
mountView(document.body, new Counter());

Todos

  • Implement functional DOM tree updating.
  • Add React-like function components with hooks.

(TBD)