Skip to content

Comfey is a tiny data binding library inspired by React hook useState. Read more in Comfey Wiki

Notifications You must be signed in to change notification settings

dejavu1987/comfey

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Comfey StandWithUkraine RussianWarship Rate this package

SWUbanner

Comfey is a tiny data binding library inspired by React hook useState. 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
  • 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 a DOMElement as root of the component. By default document will be used.

import Comfey from 'comfey';
const myComponent = new Comfey(document.getElementById('my-component'));

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
}

Multi Apps

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

  • Multiple app 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

// scoped code blocks can use same getters / setters, etc names if desired.
// name uniquely if needs to be in the same scope
(() => {
  const app = new Comfey(document.getElementById('app1'), COMFEY_DEBUG);
  const [, setActive] = app.useState('stateActive', false);

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

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

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

More Multi App DEMOs

Templating

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

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 from 'comfey';
const app = new Comfey(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