GUIDE

Introduction

What is Litedom?

Litedom is an elegant Web Component library.

At ~3.5kb gzip, it allows you to create Web Component/Custom Element easily. Litedom can effortlessy be added into exitsing HTML page, without the need to bring in the bloat of big frameworks.

With Litedom, you can create your own custom tag element, to be reused throughout the application.

Components created with Litedom are reactive. Litedom provides an internal state manager, a simple progressive templating language by leveraging Javascript Template Literals, provides a one way data flow, has two-way data biding and events handling, lifecycle, directives, stylemaps. It has no dependecies, no virtual DOM, no JSX, No build tool.

Litedom follows the Web Component V1 specs, which allows you to have Shadow Dom Spec, Custom Element Spec, HTML Template Spec and ES Module Spec. It is compatible with all modern browsers that support ES2015 (ES6), ESM (ES Module), Proxy, etc.

Litedom is set to be easy, simple and straight forward.

Features: Web Components, Custom Element, Template Literals, Reactive, Data Binding, One Way Data Flow, Two-way data binding, Event Handling, Props, Lifecycle, State Management, Computed Properties, Directives and more.

Litedom turns the template into template string literal and doesn't have a virtual DOM, therefor it doesn't keep a DOM tree in memory. Instead it relies on the real DOM, and only mutates it in place whenever there is change. This tends to be memory efficient, and also reduces GC activities

Features

Litedom aims to be simple, easy to use and helps you do much more.

  • Very small
  • Web Components
  • Custom Elements
  • Template literals
  • Directives
  • Data binding
  • One way data flow
  • Two-Way Data Binding on forms
  • Computed properties
  • Event Handling
  • Lifecycle
  • State management
  • No JSX
  • No dependency
  • No virtual DOM
  • No need for CLI
  • No build, the real DOM does it!

Syntax

Create Custom Element

Custom Element create reusable element by specifying a tagName (custom tag).

<script type="module">
  import Litedom from '//unpkg.com/litedom';

  const template = `
    Counting {this.count}
  `;

  Litedom({
    template,
    tagName: `my-counter`,
    data: {
      count: 0
    },
    created() {
      this.data.count = this.prop.start || 0;
      setInterval(_=> {
        this.data.count++;
      }, 1000)
    }
  })
</script>

<!-- HTML -->

<!-- this will start at 5 -->
<my-counter start=5></my-counter>

<!-- this will start at 13 -->
<my-counter start=13></my-counter>

Create Inline Element

Inline element gets created if a tagName was not provided, and the el is refering to the element on the page.

<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    el: `#root`,
    data: {
      count: 0
    },
    created() {
      setInterval(_=> {
        this.data.count++;
      }, 1000)
    }
  })
</script>

<!-- HTML -->
<!-- this will be relifted and shown in place -->
<div id="root">
  Hello I'm inline and counting: {this.count}
</div>

Text/Data Binding

Expression are placed within {...} and are updated whenever the data values are changed, making data reactive.


<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    el: `#root`,
    data: {
      name: 'Litedom',
      license: 'MIT',
      timestamp: Date.now()
    }
  })
</script>

<!-- HTML -->
<div id="root">
  <div>Library: {this.name}</div>
  <div>License: {this.license}</div>
  <div>Timestamp: {this.timestamp}</div>

  <div>Template literal evaluation {1 + 1}</div>

  <!-- real template literal, can do everything -->
  <div>Library Upper: {this.name.toUpperCase()}</div>

  <!-- with HTML data attribute -->
  <div data-license="{this.license}">{this.license.toUpperCase()}</div>
</div>

If/Else Conditional

For conditional use :if and :else


<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    el: `#root`,
    data: {
      count: 0
    },
    created() {
      setInterval(_=> {
        this.data.count++;
      }, 1000)
    }
  })
</script>

<!-- HTML -->
<div id="root">
  Hello I'm inline and counting: {this.count}

  <span :if="this.count % 2 === 0">This Even</span>
  <span :else>This Odd</span>

</div>

For loop

For For-loop use :for

<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    el: `#root`,
    data: {
      items: [
        'bread',
        'butter',
        'sugar',
        'drink',
        'cake'
      ]
    }
  })
</script>

<!-- HTML -->
<div id="root">
  <h2>This is the list</h2>

  <ul>
    <li :for="item in this.items">I want {item}</li>
  </ul>

</div>

Event Listeners: @event-name

To create an event listener, use @$event-name as an attribute in the element.

<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    el: `#root`,
    sayHello(event) {
      console.log('Hello World!')
    }
  })
</script>

<!-- HTML -->
<div id="root">
  <a @click="sayHello" href="#">Say Hello!</a>
</div>

Two-Way Data Binding

Two-way data binding is set on form elements, with @bind pointing to the data to be updated.

<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    el: `#root`,
    data: {
      name: ''
    }
  })
</script>

<!-- HTML -->
<div id="root">
  <div>Name: {this.name}</div>

  <div>Enter name: <input type="text" @bind="name"></div>
</div>

Lifecycle

Lifecycle put some hooks on the component and get executed based on what happens

<script type="module">
  import Litedom from '//unpkg.com/litedom';

  const template = `
    Counting {this.count || 'no count'}
  `;

  Litedom({
    template,
    tagName: `my-counter`,
    created() {
      // runs once, when the element is added
    },
    updated() {
      // run each time the dom is updated from the data
    },
    removed() {
      // when the element is removed from the page
    }
  })
</script>


Installation

Litedom is written in ES2015 and distributed as standard JavaScript modules (ESM). Modules are increasingly supported in JavaScript environments and have shipped in Chrome, Firefox, Edge, Safari, and Opera.

Importing from unpkg.com

The recommended way to import Litedom is via ESM javascript, where we specify the type module in the script tag, and we import it from unpkg.com

Make sure type="module" exists in the script tag (<script type="module">).


<script type="module">
  import Litedom from '//unpkg.com/litedom';

  ...

</script>

The JavaScript import statement only works inside module scripts (<script type="module">), which can be inline scripts (as shown above) or external scripts:

<script type="module" src="$PATH/script.esm.js"></script>

npm

Or by installing it in your project

npm install litedom
import Litedom from 'litedom';

Compatibility

Litedom is a modern library for moden browsers that support ES2015 (ES6), Template Literals, Proxy, and all the fun stuff.

The library is written in ES2015, and will be delivered to you as such. To keep it small Litedom doesn't have any polyfills nor extra code to make new ES20xx features available in non modern browsers, therefor it will not work with browsers that don't support ES6, Template Literals, Proxy, etc.

https://caniuse.com/#feat=es6

https://caniuse.com/#search=proxy


Component

Litedom turns your application into smaller composable fully compliant Web Component (Custom Element + Shadow DOM), which can be used as In-Place elements or Custom Elements with Custom Tags to be reused.

In-Place Element

In-Place Elements is set in place by using the current DOM element section to turn it into reactive. An in-place element is not intended to be reused. It also requires the el to be set, and tagName to be omitted.


<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    el: '#root',
    data: {
      world: 'World'
    }
  })
</script>

<div id="root">
  Hello {this.world}
</div>

Custom Element

Custom Element is set using a Custom Tag, which can be reused in multiple places. And also, as Custom Element, it allows you to place your component in an external JS file.

Unlike In-Place element, Custom Element requires a tagName and a template.


<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    tagName: 'hello-world',
    template: `Hello {this.world} {this.prop.name}!`,
    data: {
      world: 'World'
    }
  })
</script>

<!-- usage -->

<hello-world name='Mardix'></hello-world>

<hello-world name='Sebastien'></hello-world>

<hello-world name='Samien'></hello-world>

Initialize

The recommended way to import Litedom is via ESM javascript, where we specify the type module in the script tag, and we import it from unpkg.com

Make sure type="module" exists in the script tag (<script type="module">).


<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom(options=object|array)
</script>

or

<script type="module" src="$PATH/script.esm.js"></script>

Configurations

Litedom function accepts one argument which can be of:

Object: as a plain object, it contains the config to create and initialize the element.


Litedom({
  tagName: 'component-x',
  template: '...'
})

Array: as an array, it accepts an array of configs, to create and initialize multiple elements at once.

  const componentA = {
    template: '...',
    tagName: 'component-a'
  };
  const componentB = {
    template: '',
    tagName: 'component-b'
  };

  Litedom([componentA, componentB]);

Config Properties

el:

[string|HTMLElement]

To be used mainly when creating In-Place Elements.

This is where the view instance will be created and rendered. It will use thee innerHTML of the element as template.

This can be html selector , ie #someId, [some-data-attribute]. Or a query selector document.querySelector('#myId').

tagName:

[string]

Name for the new custom element. Note that custom element names must contain a hyphen. my-counter will be used as <my-counter></my-counter>
By having a tagName it will automatically turn the component into a Custom Element.

data:

[object]

Is the application state. All data in here are reactive. Whenever a property is added, updated or removed it will trigger the update of the DOM (if necessary).
Values are expected to be the type string, number, plain object, array, boolean, null, undefined or function.
In the case of a function, it will become a computed data.

created

[function]
This is a lifecycle hook method. It runs once the component is added on the page.

updated

[function]
This is a lifecycle hook method. It runs each time the data or the store update the component's state.

removed

[function]
This is a lifecycle hook method. It runs once the component is removed from the page.

template

[string]
A string/text for the body of the element. It contains all the markup to be displayed. When creating Custom Element.

shadowDOM:

[boolean:false]
By default elements are created as normal Custom Element. To set the web component as ShadowDOM, set shadowDOM to true.

$store:

[state management interface]
Unlike data store is where to hook a shared store manager, ie: reStated, Redux. The store instance must have the methods getState() and subscribe(callback:function).

Methods

Along the lifecycle methods created, updated and mounted, you have the ability to define your own methods.

The defined methods are set with the rest of the options.

WARNING:
When creating methods don't use arrow functions such as created: () => this.sayHello(),. Since arrow function doesn't have a this, this will be treated as any other variable and will often result in error such as Uncaught TypeError: Cannot read property of undefined or Uncaught TypeError: this.myMethod is not a function


<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    el: '#root',
    data: {},
    created() {
      this.sayHello('Litedom');
    },
    sayHello(name) {
      console.log(`Hello ${name}`)
    },
  })
</script>

<div id="root"></div>

Properties

Inside of the lifecycle and defined methods, you have access to the following properties:

this.el

Is the instance root element. It allows you to safely query, manipulate the instance's DOM elements.

  Litedom({
    // will run each time there is a re-render
    updated() {
      const allLis = this.el.querySelectorAll('li');
      console.log(allList.length);
    }
  })

this.data

Gives you access to the reactive data. You can get, set and delete properties.
Whenever a data is updated it will trigger re-render (if necessary). You don't have to pre define a property in data to make it reactive.

  Litedom({
    data: {
      name: ''
    },
    methodA() {
      this.data.name = 'Mardix'; // setter
      console.log(this.data.location) // getter
      this.data.myArray = [];
      this.data.myArray.push(1);
      console.log(this.data.myArray.length);
    }
  })

this.prop

Props are the attributes that were set during initialization

  <script>
    Litedom({
      tagName: `my-counter`,
      template: `Counting: {this.count}`
      data: {
        count: 0
      },
      created() {
        this.data.count = this.prop.start || 0;
        setTimeout(_=> { this.data.count++; }, 1000)
      }
    })  

  </script>

  <my-counter start=5></my-counter>

...this.$defined-methods

The other methods you have defined

  Litedom({
    methodA() {
      this.methodB();
    },
    methodB() {
      this.methodC();
    }
    methodC() {
      console.log(`I'm method C :)`)
    }
  })

Lifecycle

For every instance that gets created, Litedom provides two lifecycle methods that get added during the initialization.

All lifecycle methods have:

Properties

*All methods have access to the following instance's properties:

this.el: Is the instance root element. It allows you to safely query, manipulate the instance's DOM elements. ie: this.el.querySelector('ul')

this.data: Gives you access to the reactive data. You can get, set and delete properties. Whenever a data is updated it will trigger re-render (if necessary), ie: console.log(this.data.name)

this.prop: Give you access to the properties that were set as attributes in the custom element.

...this.defined-methods all of the defined methods, ie: this.my-defined-method()

created

created runs once when the Custom Element is added to the page. At the time of running, the DOM is ready, you can query elements.

It is also the place to initialize some async call, ajax etc.

  Litedom({
    created() {
      //... code here
    }
  })

Example with async

  Litedom({
    el: '#root',
    data: {
      loading: false,
      loaded: false,
      results: []
    },
    async created() {
      // Could be used on the page to show spinner
      this.loading = true;
      this.loaded = false;

      const data = await fetch('some-url');
      const result = await data.json();
      this.data.results = results;

      // Tell the page everything is good to go
      this.loading = false;
      this.loaded = true;      
    }
  })

updated

updated runs only each time the state updates the DOM. This is a place to do any computations after an update.

  Litedom({
    updated() {
      //... code 
    }
  })

Example of count LI

  Litedom({
    data: {
      totalLis: 0
    },
    updated() {
      const lis = this.el.querySelectorAll('li');
      this.data.totalLis = lis.length;
    }
  })

removed

removed runs once when the Custom Element is removed from the page.

It is also the place to do some cleanup, remove intervals etc.

  Litedom({
    removed() {
      //... code here
    }
  })

Template

Litedom uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the instance’s data. All Litedom templates are valid HTML that can be parsed by spec-compliant browsers and HTML parsers.

Interpolation

To interpolate, use the single brace {...} without the the dollar-sign $ or to use it as template literals with ${...} in it.

In the template you have access to data via this.#data-property-name, where '#data-property-name' is the property name to access.

<script type="module">

  Litedom({
    el: '#root',
    data: {
      name: 'Litedom'
    },
    created() {
      // Dynamically added
      this.data.todaysDate = new Date().toLocaleString();
    }
  })
</script>

<div id="root">
  <p>Hello {this.name}</p>
  <p>Date: {this.todaysDate}
</div>

What about this

this in your template indicate the root context of the data. By not putting this, the variable will fall under the global object, which is the window in the browser. With this we keep the data in scope.

  <div id="root">
    <!-- use from data -->
    {this.firstName}

    <!-- fall under the global object/window -->
    {new Date().toLocaleString()}
  </div>

Directives

Directives are special attribute that start with : (colon) that you place in HTML elements as a normal data attribute, ie: <span :if="this.x === y">show</span>. They serve as shorthands to convert to template literals stuff that could be too challenging to write.

  // directive
  <span :if="this.index === 5">Show me</span>

  // The code above will be converted to 
  ${this.index === 5 ? `<span>Show me</span>` : ``}

  // Here's how to iterate over a list of items
  <ul>
    <li :for="item in this.items">{item}</li>
  </ul>

Values can be of any javascript conditional. Values should not be placed in ${...} or {...} inside of the directive. It should be written as normal string.

DO THIS: <span :if="this.index === 5">show me</span>

DON'T DO: <span :if="${this.index === 5}">show me</span>

:if

:if can be used to conditionally add or remove the elements.The same way you would write your conditional in javascript.

:else can also be used to indicate an "else block" for :if. The element must immediately follow the :if, or it will not be recognized.

  <div id="root">

    <div :if="this.count !== 5">The count is not {this.count}</div>

    <div :if="this.isTrue">Show me</div>
    <div :else> Show me ELSE</div>

  </div>

  <script type="module">
    Litedom({
      el: '#root',
      data: {
        isTrue: true,
        count: 5
      }
    })
  </script>


:for

:for can be used to iterate over a list of items. Underneath it will turn it into map.

The :for directive requires a special syntax in the form of item in items, where items is the source data Array and item is an alias for the Array element being iterated on.

You can also have item, index in items, where index is tracking the number.

It is recommended to provide an :key directive or id attribute with :for whenever possible, because Litedom patches the element in place. For the key, use either string or a number, or a combination of both. You may also use the index of the loop to set it as id.

Loop
  <div id="root">
    <ul>
      <li :for="location in this.locations">{location.name}</li>
    </ul>
  </div>

  <script type="module">

    Litedom({
      el: '#root',
      data: {
        locations: [
          {
            name: 'Charlotte'
          },
          {
            name: 'Atlanta'
          },
          {
            name: 'Concord'
          }
        ]
      }
    })
  </script>

Inner Loop
  <div id="root">
    <ul>
      <li :for="state in this.states">
        {state.name}

        <ul>
          <li>Cities</li>
          <li :for="city in state.cities">{city}</li>
        </ul>

      </li>
    </ul>
  </div>

  <script type="module">

    Litedom({
      el: '#root',
      data: {
        states: [
          {
            name: 'NC',
            cities: [
              'Concord',
              'Charlotte',
              'Raleigh'
            ]
          },
          {
            name: 'Florida',
            cities: [
              'Tampa',
              'Miami',
              'Jacksonville'
            ]
          },
          {
            name: 'South Carolina',
            cities: [
              'Columbia',
              'Greenville'
            ]
          }
        ]
      }
    })
  </script>
Iterate over a range

  <div :for="i in [...Array(5).keys()]">I'm {i}</div>

With :key

  <div :for="i in [...Array(5).keys()]" :key="my-div-{i}">I'm {i}</div>

:class

:class allows to conditionally toggle class names. Separates each class condition with a semi-colon, in the following format className: conditionToBeTrue; => :class="classA: this.x === y; classB: this.z > 5"


<style>
  .somAClass {
    color: blue
  }
  .myClassB {
    color: red
  }
</style>

<script>
  Litedom({
    data: {
      count: 0
    }
  })
</script>

<div :class="someClassA: this.count === 7; myClassB: this.count === 10">
  Will have .someClassA if count is 7, 
  will then have .myClassB when count is 10
</div>

:style

:style help sets inline style dynamically in the element. The data passed, must be the type of plain object which CSS style.


<script>
  Litedom({
    data: {
      myStyle: {
        backgroundColor: 'red',
        display: 'none',
        'font-size': '12px;'
      }
    }
  })
</script>

  <div :style="this.myStyle"></div>

  // will become

  <div style="background-color: red; display: none; font-size: 12px"></div>


Data

Data is at the core of the instance's reactivity. Whenever data is changed, it will trigger a re-render (if necessary).

Data is usally set during the instance's setup, under the data options.

data

[object]
Is the application state. All data in here are reactive. Whenever a property is added, updated or removed it will trigger the update of the DOM (if necessary).
Values are expected to be the type string, number, plain object, boolean, null, undefined or function.
In the case of a function, it will become a computed data.

Litedom({
  data: {
    firstName: 'Mardix',
    lastName: 'M.',
    fullName: (state) => `${state.firstName} ${state.lastName}`
  }
})

Data in Litedom is:

  • Accessible: in the template and methods you have direct access to the data
  • Mutable: in the methods you can mutate the data directly without setters, ie this.data.aNumber = 1; or this.data.someArray.pop();
  • Reactive: whenever this.data is updated it will trigger a re-render (if necessary)
  • Dynamic: you don't have to pre define properties during the instance setup, you can set new properties in some other places or when needed, and automatically it will be also become reactive.

Props

Props are simply attributes that were passed in the Custom Element. They can be retrived in the methods via this.prop or in the template {this.prop}

  <script type="module">
    const template = `counting: {this.count}`;

    Litedom({
      template,
      tagName: 'my-counter',
      data: {
        count: 0
      },
      created() {
        this.data.count = this.prop.start || 0;
        setInterval(() => {
          this.data.count++;
        }, 1000)
      }
    })
  </script>

  <my-counter start=5></my-counter>

Local state

Local state is the data that the instance will use. It is set in the data. Whenever it is updated, it will trigger a re-render (if necessary).

In the template you have access to it via {this.#data-property-name} and in your methods it's via this.data;

The state/data is mutable only in the methods of your instance, which means you can directly update the properties. No need for this.set(key, value) or this.get(key).

You can do this:

Litedom({
  el: '#root',
  data: {
    name: 'Litedom',
    count: 0
  },
  sayHello() {
    console.log(this.data.name);
  },
  changeName(name) {
    this.data.name = name;
  },
  runCounter(){
    setInterval(() => {
      this.data.count++;
    }, 1000)
  },
  created() {
    this.runCounter();
  }
})

Computed state

Computed state are data that will be created based on some other input, usually from the reactive data. Whenever the state is updated, the computed data will also be updated. Which makes computed data reactive.

Computed data are set as function that returns a value, which will be assigned to the name of the function in the data object.

  data: {
    firstName: 'Mardix',
    lastName: 'M.',

    // computed data, will be accessed via '{this.fullName}' or 'this.data.fullName'
    fullName: (state) => `${state.firstName} ${state.lastName}`,

    // computed data, will be accessed via '{this.totalChars}' or 'this.data.totalChars'
    totalChars: (state) => state.fullName.length
  }

In the example above, we now can access as properties: this.data.fullName and this.data.totalChars. In the template, {this.fullName} and {this.totalChars}

NOTE 1: You can't access the computed data as functions in your code.
NOTE 2: You can't mutate the state in the computed data funcion, nor access an instance's method in the computed data function.

Computed data function accept the current state as the only argument, and must return a value. The value will be assigned in the data with the function name. The data provided in the computed data is not mutable.

  <script type="module">

    Litedom({
      el: '#root',
      data: {
        firstName: 'Mardix',
        lastName: 'M.',
        fullName: (state) => `${state.firstName} ${state.lastName}`
      }
    })
  </script>

<div id="root">
  <p>Hello {this.fullName}</p>
</div>


Two-Way Data Binding

You can use the @bind directive to create two-way data bindings on form input, textarea, and select elements. It automatically picks the correct way to update the element based on the input type. @bind is essentially syntax sugar for updating data on user input events.

<script type="module">
  import Litedom from '//unpkg.com/litedom';

  Litedom({
    el: `#root`,
    data: {
      name: '',
      salutation: ''
    }
  })
</script>

<!-- HTML -->
<div id="root">
  <div>Hello {this.salutation} {this.name}</div>

  <!---- Form ---->

  <form>
    <div>Enter name: <input type="text" @bind="name"></div>
    <div>Salutation: 
      <input type="radio" name="salutation" @bind="salutation" value="Mr."> Mr. -
      <input type="radio" name="salutation" @bind="salutation" value="Mrs."> Mrs. 
    </div>
  </form>
</div>

Example of making Async call

The example below illustrate how we can make async call and at the same time setting the state to make it reactive.


<div id="root">

  <div $if="this.loadingStatus === 'loading'">Loading...</div>

  <div $if="this.loadingStatus === 'done'">
    <p>Data loading successfully!</p>
    <ul>
      <li $for="item in this.myData">{item}</li>
    </ul>
  </div>


</div>

<script type="module">

  Litedom({
    el: '#root',
    data: {
      loadingStatus: null,
      myData: []
    },
    async loadData() {
      this.loadingStatus = 'loading';

      const resp = await fetch('some-url');
      this.data.myData = await resp.json();

      this.loadingStatus = 'done';
    }
  })

</script>

Shared state

To share state with multiple instances, please refer to the SHARED STATE section in this guide.


Methods

You can define your own methods in the instance.

Method can be used to be accessed by other methods via this.$method-name(...args), or can be used as events methods in the instance of @click="$method-name"

Properties

*All methods have access to the following instance's properties:

this.el: Is the instance root element. It allows you to safely query, manipulate the instance's DOM elements. ie: this.el.querySelector('ul')

this.data: Gives you access to the reactive data. You can get, set and delete properties. Whenever a data is updated it will trigger re-render (if necessary), ie: console.log(this.data.name)

this.prop: Give you access to the properties that were set as attributes in the custom element.

...this.defined-methods all of the defined methods, ie: this.my-defined-method()

Defined Method

The example below showcases how methods can be used.


<div id="root">
  <a @click="sayHello" href="#">Say Hello!</a>

  <input 
    type="text" 
    name="color" 
    @call="changeColor" 
    $value="this.defaultColor"
  > 

</div>

<script type="module">

  Litedom({
    el: '#root',
    data: {
      defaultColor: '#FFFFFF'
    },

    sayHello(event) {
      console.log('Hello World!');
    },

    changeColor(event) {
      const color = event.target.value;
      this.setBgColor(color);
    },

    setBgColor(color) {
      this.el.style.background = color;
    },

  })

</script>

Async method

You can also setup Async methods with the async/await.


  Litedom({
    el: '#root',

    async loadData() {
      this.data.status = 'loading...';
      const data = await fetch('url');
      const data = await resp.data;
      this.data.status = 'loading completed!';
    },

    async created(event) {
      await this.loadData();
    },

  })



Events

You can add event listener to elements by adding the @ + the $event-name as attribute, and assign it the name of the method to bind it to: <a @click="sayHello" href="#">Say Hello!</a>

The $event-name must be the name of the event without on, ie: @click is VALID but @onclick is INVALID.

The method must be in the context of the instace that's created.

When an event is invoked, the Event object is passed to the method as the first and only argument. The Event object can be used to retrieve data attribute of the element, etc.


<div id="root">
  <a @click="sayHello" href="#">Say Hello!</a>
</div>

<script type="module">

  Litedom({
    el: '#root',
    data: {},

    sayHello(event) {
      console.log('Hello World!')
    }
  })

</script>

When the button is clicked it will 'Hello World' will be displayed on the console.

Passing values

To pass values from the element to the event, we can use html attribute and retrieve the data from there. We can't pass object directly to the method. It has to be done via data attribute. With the data attribute, we can use it to retrieve some more data from some other sources.


<div id="root">
  <button @click="sayHello" data-name="Mardix">Say Hello!</button>
</div>

<script type="module">

  Litedom({
    el: '#root',
    data: {},

    sayHello(event) {
      const name = event.target.getAttribute('data-name');
      console.log(`Hello ${name}`)
    }
  })
</script>

Will now show Hello Mardix

@call

@call is a shorthand key that will assign the right event based on the element type.

By default all @call will result into @click, except for the scenarios below:

HTMLAnchorElement

AHREF @call => @click

<a @call="something">x</a> to
<a href="javascript:void(0);" @click="something"></a>

HTMLInputElement & HTMLTextAreaElement

FORMS: Input & Textarea @call => @input + @paste

<input type="text" @call="something"> to
<input type="text" @input="something" @paste="something">

HTMLSelectElement

FORMS: Select @call => @change

<select @call="something"><options...></select>
<select @change="something"><options...></select>

HTMLFormElement

FORMS: Form @call => @submit

<form @call="something"></form>
<form @submit="something"></form>

Events Name List

Here is the list of all the events accepted by Litedom


@call
@click
@submit
@change
@input
@select
@focus
@blur
@hover
@reset
@keydown
@keypress
@keyup
@dblclick
@mouseenter
@mouseleave
@mousedown
@mousemove
@mouseout
@mouseover
@mouseup
@contextmenu
@drag
@dragend
@dragenter
@dragstart
@dragleave
@drop
@cut
@copy
@paste


Shared State

To share state with multiple instances, it's recommended to have a state manager such as *RESTATED, Redux, or look through this List of State Managers

State Manager Interface

For the store to be hooked into Litedom, it must have the following methods:

getState() : To return the full state of the store.

subscribe(callback:function): A subscription method that will execute each the state is updated.

If the state manager doesn't provide these methods by default, you can extend it yourself.

  const myStateManager = new somethingSomething()

  // Now the store contains getState() and subscribe(callback)
  const store = {
    getState() {
      return myStateManager.state;
    },
    subscribe(callback) {
      return myStateManager.onChange(callback);
    },
    ...myStateManager
  }

Setup


Litedom({
  el: '#root',
  data: {},
  $store: STORE_INSTANCE
})

In Methods

The store is exposed in the methods by this.$store, which is the object that was passed. Therefor you can access anything from it.


Litedom({
  el: '#root',
  data: {},
  $store: STORE_INSTANCE,
  doSomething() {
    this.$store.doSomething();
  }
})

In Template

To access properties from the store, this.$store is exposed and contain the values from $store.getState().

  <div id="root">
    {this.$store.fullName}
  </div>

Example with reStated


reStated

An ambitiously tiny flux-like library to manage your state.

Inspired by Redux and Vuex, reStated removes the boilerplate and keep it simple and flat.

Unlike Redux, you don't need to return a new immutable object. You can mutate the state in place, and you definitely don't need to define a reducer. The action mutator is both your action and your reducer "at the same damn time" (Future's song)

Unlike Vuex, you don't need to have actions and mutations. You can only mutate the state via your actions mutators which are just function that pass as first argument the current state to be mutated.

Learn more about RESTATED


This is how we can use shared state with reStated.


<script type="module">
  import Litedom from '//unpkg.com/litedom';
  import reStated from '//unpkg.com/restatedjs';

  const store = reStated({
    state: {
      name: '',
      lastName: '',
      fullName: (state) => `${state.name} ${state.lastName}`,
      accountDetails: []
    },
    changeName(state, name) {
      state.name = name;
    },
    changeLastName(state, lastName) {
      state.lastName = lastName;
    },
    async loadAccount(state) {
      state.status = 'loading';
      const resp = await fetch(url);
      const data = await resp.json();

      // will be shared as this.$store.accountDetails
      state.accountDetails = data;

      state.status = 'done';
    }
  });

  Litedom([
    {
      el: '#rootA',
      $store: store,
      loadAccount() {
        this.$store.doSomething();
      }
    },
    {
      el: '#rootB',
      $store: store
    }
  ]);
</script>


<div id="rootA">
  Hello {this.$store.fullName}!
  <button @call="loadAccount">Load Account</button>
</div>

<div id="rootB">
  <ul>
    <li :for="item in this.$store.accountDetails">{accountName}</li>
  </ul>
</div>