Skip to content

matthewlamdotjs/Chunx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 

Repository files navigation

** Disclaimer: for educational purposes, not performance optimized **

Chunx

Simple "no-frills" Front-End Web Component Framework featuring:

  • Reusable template components
  • Automatic re-render on component variable changes
  • Single dependency with no additional build time

I wanted to experiment with creating my own front-end framework, so I did it in "chunx" :)

Table of Contents

  1. Docs
    1. Getting Started
    2. Chunx Constructor
    3. Chunx Props
    4. Chunx Controller
  2. Examples
    1. Creating root component and attaching to DOM
    2. Declaring template variables by passing props
    3. Declaring template variables with controller
    4. Getting template variables
    5. Declaring functions that can be referenced in template
    6. Tracking variables and adding callbacks
    7. Computed variables
    8. Adding subcomponents (children)
    9. Adding subcomponents as computed variables (tracked children)
    10. Adding repeated subcomponents using props from data array
    11. Adding repeated subcomponents as computed variable
  3. Tips

Docs

Getting Started

Place chunx.js somewhere in your project directory

Chunx Constructor

This function is used to define all of your re-usable components, or "chunx" and returns a function that when called returns an instance of the chunx.

/**
 * Chunx Template Constructor
 * @param {DOMstring} htmlString 
 * @param {object} props 
 * @param {function} controller 
 * @returns chunx template
 */
chunx(htmlString, props = {}, controller = () => {})

Chunx Props

Props are tracked variables that are declared within the constructor. Each variable name should be a key in the props object with an initial value.

const component = chunx(
    /* html */
    `<div>{{someVariable}}</div>`,

    /* props */
    {
        someVariable: 1
    }
);

Note: variables can also be declared within the controller using the injected set() function.

Chunx Controller

The Chunx component controller is a function that contains all of the component's logic. The logic is implemented using chunx component methods that are passed to the function in an object. This is done so you can destructure the object and cherry pick only the methods you need.

const component = chunx(
    /* html */
    `<div>{{someVariable}}</div>`,

    /* props */
    {
        someVariable: 1
    },

    /* controller */
    ({get, set, setFn, track, computedVar, template, repeat}) => {
        // add component logic here
        set('someVariable', 2);
    }
);

The available methods passed to the controller are:

get, set, setFn, track, computedVar, template, repeat

/**
 * Getter for tracked variables
 * @param {string} variableName 
 * @returns variable
 */
get(variableName)
/**
 * Add or update a trackable variable to element
 * @param {string} name 
 * @param {any} value 
 */
set(name, value)
/**
 * Add function to element that can be called by name from HTML template
 * @param {string} fnName 
 * @param {function} fnValue 
 */
setFn(fnName, fnValue)
/**
 * Track a variable and provide a callback function onchange
 * @param {string} variableName 
 * @param {function} callbackFn 
 */
track(variableName, callbackFn)
/**
 * Declare a computed variable that re-evaluates when tracked variables are updated
 * @param {string} name 
 * @param {array<string>} tracked 
 * @param {function} computeFn 
 */
computedVar(name, tracked = [], computeFn = () => null)
/**
 * Initializes an instance of a chunx template and attaches to parent
 * @param {chunx template} component 
 * @param {object} data 
 * @returns htmlString
 */
template(component, data = {})
/**
 * Repeat a chunx template applying properties to each from dataArray and attach to parent
 * @param {chunx template} component 
 * @param {array<object>} dataArray 
 * @returns htmlString
 */
repeat(component, dataArray = [])

You can find examples of the controller functions in the section below.

Examples

Creating root component and attaching to DOM

./index.js

import { chunx, attachRootElement } from 'chunx.js';

const rootComponent = chunx(
    /* html */`
    <div>
        <h1>Hello World</h1>
    </div>
`);

// attach root element to dom
attachRootElement('root', rootComponent);

./index.html

<!DOCTYPE html>
<html>
    <head>
        <script type="module" src='index.js'></script>
    </head>
    <body>
        <div id='root'></div>
    </body>
</html>

Declaring template variables by passing props

./index.js

import { chunx, attachRootElement } from 'chunx.js';

const rootComponent = chunx(
    /* html */`
    <div>
        <h1>{{myGreeting}}</h1>
    </div>`,

    /* props */
    {
        myGreeting: 'Hello World'
    }
);

// attach root element to dom
attachRootElement('root', rootComponent);

Resulting computed HTML:

<div>
    <h1>Hello World</h1>
</div>

Declaring template variables with controller

./index.js

import { chunx, attachRootElement } from 'chunx.js';

const rootComponent = chunx(
    /* html */`
    <div>
        <h1>{{myGreeting}}</h1>
    </div>`,

    /* props */ {},
    
    /* controller */
    ({set}) => {
        set('myGreeting', 'Hello World');
    });
);

// attach root element to dom
attachRootElement('root', rootComponent);

Getting template variables

./index.js

import { chunx, attachRootElement } from 'chunx.js';

const rootComponent = chunx(
    /* html */`
    <div>
        <h1>{{myGreeting}}</h1>
    </div>`,

    /* props */ {},

    /* controller */
    ({get, set}) => {
        set('myGreeting', 'Hello World');

        console.log(get('myGreeting')); // prints "Hello World"
    });
);

// attach root element to dom
attachRootElement('root', rootComponent);

Declaring functions that can be referenced in template

./index.js

import { chunx, attachRootElement } from 'chunx.js';

const rootComponent = chunx(
    /* html */`
    <div>
        <h1>{{myGreeting}}</h1>
        
        <button onclick={{changeGreeting()}}>
            Change Greeting
        </button>
    </div>`,

    /* props */ {},

    /* controller */
    ({set, setFn}) => {
        set('myGreeting', 'Hello World');

        // Declare a function that can be referenced in html attributes:
        //
        // Declaring this function with setFn(fnName, fnValue) allows
        // the function to be referenced in template as {{changeGreeting()}}
        // within html attributes that accept javascript functions as values
        // such as `onclick`.
        setFn('changeGreeting', () => {
            set('myGreeting', 'Goodbye World');
        });
    });
);

// attach root element to dom
attachRootElement('root', rootComponent);

Note: referencing functions declared with setFn() will not actually call the function and display its returned value.

This means if you have a function setFn('myName', () => { return 'matt' } and reference it like this <h1>{{myName()}}<h1/> it will NOT render <h1>matt<h1/> but instead print a global reference to the function. If you want the output of the function to display on screen wherever it is referenced in the template see computed variables.

Tracking variables and adding callbacks

./index.js

import { chunx, attachRootElement } from 'chunx.js';

const rootComponent = chunx(
    /* html */`
    <div>
        <h1>{{myGreeting}}</h1>
        
        <button onclick={{changeGreeting()}}>
            Change Greeting
        </button>
    </div>`,

    /* props */ {},

    /* controller */
    ({set, setFn, track}) => {
        set('myGreeting', 'Hello World');

        setFn('changeGreeting', () => {
            set('myGreeting', 'Goodbye World');
        });

        // Track changes to the variable myGreeting:
        //
        // `track(variableName, callback)` listens for calls to set() and
        // executes callback when variable <variableName> changed using set().
        track('myGreeting', () => {
            alert('My title changed!');
        });
    });
);

// attach root element to dom
attachRootElement('root', rootComponent);

Computed variables

./index.js

import { chunx, attachRootElement } from 'chunx.js';

const rootComponent = chunx(
    /* html */`
    <div>
        <h1>{{myGreeting}}</h1>
        
        <button onclick={{changeGreeting()}}>
            Change Greeting
        </button>

        <p>{{result}}</p>
    </div>`,

    /* props */ {},

    /* controller */
    ({set, get, setFn, computedVar}) => {
        set('myGreeting', 'Hello World');

        setFn('changeGreeting', () => {
            set('myGreeting', 'Goodbye World');
        });

        // Computed variables are used in template as normal variables
        // but they are re-calculated when tracked variables change.
        // example: 'result' will be re-calculated whenever 'myGreeting' changes
        computedVar('result',       // variable name
                    ['myGreeting'], // array of variables to track
                    /* function used to compute value */
                    () => get('myGreeting') === 'Hello World' ? 'Nice!' : 'Awww...';
    });
);

// attach root element to dom
attachRootElement('root', rootComponent);

Adding subcomponents (children)

./index.js

import { chunx, attachRootElement } from 'chunx.js';
import mySubcomponent from './components/mySubcomponent.js'

const rootComponent = chunx(
    /* html */`
    <div>
        <h1>{{myGreeting}}</h1>
        {{paragraph}}
    </div>`,

    /* props */ {},

    /* controller */
    ({set, template}) => {
        // Init subcomponent as component variable:
        //
        // `template(component, props)` creates an instance of the component with props,
        // links it to the current component as its parent, and returns an element reference
        // that can be stored in a variable used in the template to render the subcomponent.
        set('paragraph', template(mySubcomponent, /* props (optional) */ { myText: 'What\'s up?' }));
    });
);

// attach root element to dom
attachRootElement('root', rootComponent);

./components/mySubcomponent.js

import { chunx } from 'chunx.js';

const mySubcomponent = chunx(/* html */`<p>{{myText}}</p>`);

export default mySubcomponent;

Resulting computed HTML:

<div>
    <h1>Hello World</h1>
    <p>What's up?</p>
</div>

Adding subcomponents as computed variables (tracked children)

./index.js

import { chunx, attachRootElement } from 'chunx.js';
import mySubcomponent from './components/mySubcomponent.js'

const rootComponent = chunx(
    /* html */`
    <div>
        <h1>{{myGreeting}}</h1>
        
        <button onclick={{changeGreeting()}}>
            Change Greeting
        </button>

        {{paragraph}}
    </div>`,

    /* props */ {},

    /* controller */
    ({get, set, setFn, template, computedVar}) => {
        set('myGreeting', 'Hello World');

        setFn('changeGreeting', () => {
            set('myGreeting', 'Goodbye World');
        });

        // Initialize subcomponent as a computed variable:
        //
        // Chunx will automatically re-calculate computedVar 'paragraph'
        // using the function provided when variable 'myGreeting' is changed.
        computedVar('paragraph', ['myGreeting'],
                () => template(mySubcomponent, { myText: get('myGreeting') }));
    });
);

// attach root element to dom
attachRootElement('root', rootComponent);

./components/mySubcomponent.js

import { chunx } from 'chunx.js';

const mySubcomponent = chunx(/* html */`<p>My greeting is: {{myText}}.</p>`);

export default mySubcomponent;

Adding repeated subcomponents using props from data array

./index.js

import { chunx, attachRootElement } from 'chunx.js';
import mySubcomponent from './components/mySubcomponent.js'

const rootComponent = chunx(
    /* html */`
    <ul>
        {{listItems}}
    </ul>`,

    /* props */ {},

    /* controller */
    ({get, set, repeat}) => {
        // declare array of objects where objects contain props to be passed
        set('dataArray', [1,2,3,4,5].map(num => {number: num}));

        // Declare variable as repeated subcomponent using dataArray as props:
        //
        // `repeat()` will repeat mySubcomponent get('dataArray').length times
        // with each copy using the data at its respective index in the array
        // as its props.
        set('listItems', repeat(mySubcomponent, get('dataArray')));
    });
);

// attach root element to dom
attachRootElement('root', rootComponent);

./components/mySubcomponent.js

import { chunx } from 'chunx.js';

const mySubcomponent = chunx(/* html */`<li>Item #{{number}}</li>`);

export default mySubcomponent;

Resulting computed HTML:

<ul>
    <li>Item #1</li>
    <li>Item #2</li>
    <li>Item #3</li>
    <li>Item #4</li>
    <li>Item #5</li>
</ul>

Adding repeated subcomponents as computed variable

./index.js

import { chunx, attachRootElement } from 'chunx.js';
import mySubcomponent from './components/mySubcomponent.js'

const rootComponent = chunx(
    /* html */`
    <ul>
        {{listItems}}
    </ul>
    <button onclick="{{addItem()}}">add item</button>`,

    /* props */ {},

    /* controller */
    ({get, set, setFn, computedVar, repeat}) => {
        // Declare array of objects where objects contain props to be passed
        set('dataArray', [1,2,3,4,5].map(num => {number: num}));

        // Declare function to add new item to data array with next number in sequence
        setFn('addItem', () => {
            const array = get('dataArray');
            set('dataArray', [...array, { number: array[array.length-1].number+1 }]);
        });

        // 'listItems' will be re-rendered when dataArray changes (add item button clicked)
        computedVar('listItems', ['dataArray'],
            () => repeat(mySubcomponent, get('dataArray')));
    });
);

// attach root element to dom
attachRootElement('root', rootComponent);

./components/mySubcomponent.js

import { chunx } from 'chunx.js';

const mySubcomponent = chunx(/* html */`<li>Item #{{number}}</li>`);

export default mySubcomponent;

Tips

For html string syntax highlighting in VS Code use plugin tobermory.es6-string-html And make sure html strings are preceded by /* html */ such as

const mySubcomponent = chunx(/* html */`<li>Item #{{number}}</li>`);

About

Simple "no-frills" Front-End Web Component Framework

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published