comfey

Tiny micro mini data binding library inspired by react hook useState

Usage no npm install needed!

<script type="module">
  import comfey from 'https://cdn.skypack.dev/comfey';
</script>

README

Comfey 2

Comfey is a tiny javascript state management library inspired by React hook useState.Comfey provides a simple data binding library to update your HTML using simple valid HTML data attributes.

Read more in Comfey Wiki

Features

  • 0 dependencies / Lightweight v1.0.0 is 35 lines of code
  • Looks like react hook useState
  • No loops, event driven
  • (Optional) Simple HTML5 data attributes to bind the html elements to the states
  • Finite State Machine as a plugin

Table of content

Install

Using NPM

npm install comfey

Using Yarn

yarn add comfey

Initialize component

Instantiate Comfey - optionally passing in a ViewUpdater.

import Comfey from 'comfey';
const myComponent = new Comfey();

Initialize state

Use .useState() method to initialize a state, useState() accepts 3 parameters. Returns getter and setter functions

/**
 *
 * @param {string} state name of the state
 * @param {any} initialVal initial value
 * @param {function} watcher watcher function that will be called everytime the value of the state changes
 *
 * @returns {Array<[function, function]>} getterFunction and SetterFunction
 */

Example:

const [count, setCount] = app.useState('count', 3, countWatcher);

Watch

Watch gets newValue, oldValue and name of the state and is invoked everytime the state changes.

function countWatcher(newVal, oldVal, stateName) {
  // Do something when value of count state changes
}

Templating

Comfey offers a DOM updater as a separate library. Which will let you use simple HTML data attributes to bind your state data to your DOM.

Create a new Instance of ComfeyDom by

import Comfey, { ComfeyDom } from 'comfey';
const viewUpdater = new ComfeyDom(document.getElementById('app'), DEBUG);

// Now pass in the viewUpdater (ComfeyDom instance) to Comfey's constructor
const app = new Comfey(viewUpdater);

Bind state value to an element

Use data-bind attribute with stateName as its value to bind the innerHTML of the element to the state's value.

Visibility

  • data-bind-visible
  • data-bind-hidden

Bind visible and hidden accepts value to compare.

Example

data-bind-visible="numberStatus::medium"

means the element will be visible if the state numberStatus is set to medium value.

Class

  • data-bind-class

Bind class accepts value to compare, but will not interpolate the bound value as a classname.

Example

data-bind-class="currentPage::active::home"

means the element will get active class if the state currentPage is set to home value.

More bind-class DEMOs

Bind attributes

You can bind an attribute to a state's value w/ data-bind-attr. Data bind attributes can take values delimited by :: which will make each delimited string an argument. The argument pattern looks like

<state>::<dynamic-attr>::<value>

Example:

data-bind-attr="count::style::font-size: $rem"

means, a dynamic attribute will be added to the HTML element when the state count has a value, the attribute added will be style attribute and the value for the style attribute will be

font-size: <StateValue>rem

Multi Apps

You can have any number of applications on a page. Instanciate a new Comfey whenever you need.

  • Multiple apps can use duplicate state names
  • they will be scoped within each app
  • You will have to scope your javascript as well
  • Just avoid declaring getters and setters globally

Example

import Comfey, { ComfeyDom } from 'comfey';
// scoped code blocks can use same getters / setters, etc names if desired.
// name uniquely if needs to be in the same scope

(() => {
  const view = new ComfeyDom(document.getElementById('app1'), COMFEY_DEBUG);
  const app = new Comfey(view, COMFEY_DEBUG);
  const [, setActive] = app.useState('stateActive', false);

  setInterval(() => {
    setActive(Math.random() > 0.5);
  }, 1000);
})();

(() => {
  const view = new ComfeyDom(document.getElementById('app2'), COMFEY_DEBUG);
  const app = new Comfey(view, COMFEY_DEBUG);
  const [, setActive] = app.useState('stateActive', false);

  setInterval(() => {
    setActive(Math.random() > 0.5);
  }, 1000);
})();

More Multi App DEMOs

Examples

Counter Example

CodeSandbox

<div id="my-component">
  <div>
    Count:
    <span data-bind="count">
      <!--   This placeholder will be updated with value of count state -->
    </span>
  </div>
  <div>Show plus: <span data-bind="showPlus">x</span></div>
  <div>Hide minus: <span data-bind="hideMinus">x</span></div>
  <div class="buttons">
    <!--   Increment button will be visible if showPlus state is set to true   -->
    <button id="increment" data-bind-visible="showPlus">+</button>
    <button id="decrement" data-bind-hidden="hideMinus">-</button>

    <!-- Bind attribute, state :: attr :: value, $ for stateValue placeholder -->
    <button id="increment" data-bind-attr="disablePlus::disabled::">+</button>
    <div>
      Count:<span
        data-bind-attr="count::style::font-size: $rem"
        data-bind="count"
        >x</span
      >
    </div>
    <div>
      NumberStatus:
      <span data-bind-visible="numberStatus::medium">Medium</span>
      <span data-bind-visible="numberStatus::max">Max</span>
      <span data-bind-visible="numberStatus::min">Min</span>
    </div>
  </div>
</div>
import Comfey, { ComfeyDom } from 'comfey';

const app = new Comfey(new ComfeyDom(document.getElementById('app')));

// Select buttons
const btnIncrement = document.getElementById('increment');
const btnDecrement = document.getElementById('decrement');

// Initialize states
const [, setShowPlus] = app.useState('showPlus', true);
const [, setHideMinus] = app.useState('hideMinus', false);
const [count, setCount] = app.useState('count', 3, countWatcher);

function countWatcher(newVal) {
  if (newVal > 4) {
    setShowPlus(false);
  } else {
    setShowPlus(true);
    if (newVal < 1) {
      setHideMinus(true);
    } else {
      setHideMinus(false);
    }
  }
}

// Button behaviours
btnIncrement.addEventListener('click', () => {
  setCount(count() + 1);
});
btnDecrement.addEventListener('click', () => {
  setCount(count() - 1);
});

Comfey - Pokemon buddy game

Codesandbox

Multi level navigation

Codesandbox

Change log

v2.0

  • ViewUpdater abstracted, no more DOM manipulation code in main library.
  • Empty instantiation of Comfey will ignore view update (Headless Mode) as opposed to falling back to document
  • ComfeyDom introduced to support DOM manipulation, which can be optionally passed in to Comfey constructor. See examples above or in /demo/ directory.
  • Comfey is an ESM module