/gia

Minimalistic JavaScript library for managing component lifecycle with server rendered websites.

Primary LanguageJavaScriptMIT LicenseMIT

Gia

Minimalistic JavaScript framework for server rendered websites. 2.68 Kb minified gzipped with all it’s parts loaded with a script tag. Gia is modular in it’s nature. Following is the table of module sizes.

4.88 Kb Component (with code splitting support)
1.80 Kb BaseComponent
1.62 Kb eventbus
1.39 Kb loadComponents
1.26 Kb removeComponents
0.88 Kb destroyInstance
0.79 Kb createInstance
0.69 Kb config
0.56 Kb getComponentFromElement

Following sizes are for modules included separately (bundled separately). Modules often include common code, so when included and bundled together, final sizes are smaller and don't just add up.

Installation

Usage

Component

Helpers

Installation

Include Gia with scripts tag

<script src="./dist/gia.min.js"></script>
<!-- exposes all modules under global gia object -->

<!-- also possible to include only parts -->
<script src="./dist/BaseComponent.min.js"></script>
<script src="./dist/loadComponents.min.js"></script>

or with npm and import

npm install gia --save
// import needed modules from npm
import Component from 'gia/Component';
import loadComponents from 'gia/loadComponents';

Usage

First, a component needs to be created.

class SampleComponent extends Component {
    mount() {
        console.log("Hello world!");
    }
}

Define the element where the component needs to be attached:

<div g-component="SampleComponent">
    ...
</div>

And let the magic begin.

const components = {
    "SampleComponent": SampleComponent
}

loadComponents(components);

This simple setup will give you component with a simple lifecycle, scoped to the DOM element, plus some other super powers!

Component

Component is the building stone of Gia architecture. Gia only works with your HTML through components. To create new component, extend Gia default Component, or modified Gia component. If you’re really trying to go for minimal size and code splitting is not going to be needed, it is also possible to use BaseComponent, which does not include polyfills needed for code splitting.

import Component from 'gia/Component';

class SampleComponent extends Component {
  mount() {
    console.log("Hello world!");
  }
}

Variables

There are several variables available in component by default.

element

Variable holding the root element of the component.

this.element; // DOM element

ref

Variable holding object with all the elements marked with g-ref attribute within the root element, where the contents of the attribute is used as ref name. By setting the ref variable, component gets a signal to look for the elements available within the root element of component. So this..

<div g-component="SampleComponent">
    <div g-ref="singleRef">
    <div g-ref="multipleRefs">
    <div g-ref="multipleRefs">
</div>
constructor(element) {
    super(element);
    this.ref = {
        singleRef: null, // looks for single element
        multipleRefs: [], // looks for multiple element
    }
}

…will end up in following value of the ref variable.

console.log(this.ref);
// { "multipleRefs": [DOM element, DOM element], "singleRef": DOM element }

In case an empty object is set, component will look for any elements available and assume for all to be multiple (store in array).

constructor(element) {
    super(element);
    this.ref = {};
    console.log(this.ref);
    // { "multipleRefs": [DOM element, DOM element], singleRef: [ DOM element ] }
}

In case some components are overlapping, but you would still like to use a same names for the ref elements, it is also possible to define the component for which the element is intended for inside of g-ref attribute.

<div g-component="SampleComponent">
    <div g-component="AnotherComponent">
        <div g-ref="SampleComponent:refElement">
    </div>
</div>

The refElement will only be selected and stored by SampleComponent, and no others.

options

Variable holding options of the component. Default options can be set in constructor of the component. Options get automatically rewritten from the g-options attribute.

<div g-component="SampleComponent" g-options='{"someOption": "customValue"}'>
constructor(element) {
    super(element);
    this.options = {
        someOptions: "defaultValue"
    };
    console.log(this.options); // {someOption: "customValue"}
}

state

Variable holding state of the component. It is not necessary to use state at all, as components are not used to actually render HTML. However, in combination with setState and stateChange methods, state can be useful for certain components, like some sort of filter where simple state can be helpful. Another use case is component which often updates DOM, as by using setState and stateChange methods, modifications of DOM are made only when the state actually changed. State should only be changed by setState function and takes a form of object.

console.log(this.state); // {}

Methods

Component has a set of methods that can be used through it's lifecycle.

require

Method used for code splitting to require any libraries needed for the component to work. This method is asynchronous and after it is resolved, the execution of mount method follows.

async require() {
    this.throttle = await import('lodash/throttle');
}

mount

Method called after all assets are loaded - if any defined (after require method is resolved). This is where you would add all your listeners and such...

mount() {
    this.scrollHandler = this.handleScroll.bind(this);

    this.element.addEventListener('click', this.handleClick.bind(this));
    window.addEventListener('scroll', this.scrollHandler);
}

unmount

Method called before the component is destroyed (on removeComponents call). This is where you would remove any global listeners. Note that any listeners attached on or within the component root element are removed when the element is removed from DOM, as the component instance is stored within the element, so there is no need to remove those listeners, in case the element is removed from DOM.

unmount() {
    this.element.removeEventListener('click', this.handleClick); // mostly not needed
    window.removeEventListener('scroll', this.scrollHandler);
}

setState

Method called to update state of the component. Only the changes of state are required to be passed in a form of object. Component will merge the changes with current state on it's own.

this.setState({
    a: "a"
});

Note that it is recommended to only use simple state with one layer for the state to work correctly. State is here to allow some simple manipulation powered with state, not to store a complex state of whole application.

this.setState({
    a: "a",
    b: ["a"],
    c: {a: "a"}
}); // all fine

this.setState({
    a: {
        b: {c: "c"}
    }
}); // not recommended

stateChange

This function gets called by setState method and any changes to state are passed as an argument. Ideally, any manipulation with DOM would be done within this function, as only the actual changes to state are passed on to here in a form of object.

stateChange(stateChanges) {
   if('a' in stateChanges) {
        // "a" property of state was updated
   }
   console.log(stateChanges, this.state);
}
this.setState({ a: "a", b: "b" }); // { a: "a", b: "b" }    { a: "a", b: "b" }
this.setState({ a: "a", b: "c" }); // { b: "c" }    { a: "a", b: "c" }
this.setState({ b: "d" }); // { b: "d" }    { a: "a", b: "d" }

Helpers

loadComponents

Initialises components within defined scope. In case an instance of the component already exists on the element, function skips initialisation. That means loadComponents can be always called without a context, but context should be defined for best performance.

import loadComponents from 'gia/loadComponents';

const components = {
    "SampleComponent": SampleComponent,
}

loadComponents(components [, context]); // context is optional and defaults to document.documentElement

removeComponents

Calls destroy method on every component within the context and removes component instance from the element.

import removeComponents from 'gia/removeComponents';

removeComponents([context]); // context is optional and defaults to document.documentElement

createInstances

Creates and returns instance of component. This function is used by loadComponents, but can be helpful in case of manually creating component instances. Classic use case would be using a set of components with one parent component controlling the others.

import createInstance from 'gia/createInstance';

let instance = createInstance(element, componentName, component[, options]);
// options are optional and are passed into component contructor as second argument

instance._load(); // this is necessary

Note that calling instance._load() is required to start the lifecycle of the component.

destroyInstances

Calls unmount method of component and removes instance of component form the element. This function is used by removeComponents.

import destroyInstance from 'gia/destroyInstance';

destroyInstance(element);

getComponentFromElement

Returns instance of component from element.

import getComponentFromElement from 'gia/getComponentFromElement';

let element = document.getElementById('element');
let componentInstance = getComponentFromElement(element);

config

Config is used as a store for options used in Gia and also as an interface to change default options. Currently, only the log option is available.

import config from 'gia/config';

config.set('log', false); // disables unnecessary console.log calls

eventbus

Eventbus can be used to communicate between components in a clear way. While it is possible to get an instance of another component and modify it directly, eventbus provides a simple interface that makes the interaction clearly visible from within the component.

import eventbus from 'gia/eventbus';
import Component from 'gia/Component';

class Component1 extends Component {
    mount() {
        eventbus.on('writeConsole', this.handleEventBusCall);
    }

    handleEventBusCall() {
        console.log("Component2 triggered this through evenbus.");
    }
}

class Component2 extends Component {
    mount() {
        eventbus.emit("writeConsole");
    }
}

const components = {
    "Component1": Component1,
    "Component2": Component2
}

loadComponents(components); // will console.log "Component2 triggered this through evenbus."

on

Registers handler of event.

eventbus.on('eventName', handler);

once

Registers handler of event, but handler is only called once and then removed.

eventbus.once('eventName', handler);

emit

Calls any handlers previously registered with the same event name. Optional event object can be used as a argument, which gets passed into a handlers as an argument.

eventbus.emit('eventName'[, eventObject]);

off

Unregisters handler of event. In case no handler is defined, eventbus removes all handlers for that event. In case not even event name is defined, eventbus removes all handlers for all events.

eventbus.off('eventName', handler);