@doars/doars

3.1.1 • Public • Published

npm @latest version minzipped size


@doars/doars

The core library, it manages the components and plugins as well as includes the basic contexts and directives.

Table of contents

Install

From NPM

Install the package from NPM, then import and enable the library in your build.

npm i @doars/doars
// Import library.
import Doars from "@doars/doars"

// Setup an instance.
const doars = new Doars(/* options */)

// Enable library.
doars.enable()

IIFE build from jsDelivr

Add the IIFE build to the page from for example the jsDelivr CDN and enable the library.

<!-- Import library. -->
<script src="https://cdn.jsdelivr.net/npm/@doars/doars@3/dst/doars.iife.js"></script>
<script type="application/javascript">
  document.addEventListener('DOMContentLoaded', () => {
    // Setup an instance.
    const doars = new window.Doars(/* options */)

    // Enable library.
    doars.enable()
  })
</script>

ESM and IIFE builds are available via the jsDelivr CDN.

Directives overview

Name Description
d-attribute Set an attribute's value.
d-cloak Is removed after the component is initialized.
d-for Loop over a value and create elements based on a template.
d-html Set the inner html of the element.
d-if Return whether the template should be added to the document.
d-ignore Ignore the element and its children from being processed.
d-initialized Runs once when the component is initialized.
d-on Listen to events on the document tree.
d-reference Add the element to the component's references context.
d-select Set selected item of a select element or selectable input elements.
d-show Return whether the element should be displayed.
d-state Define a component and set its initial state.
d-sync Keep the value of an element in sync with a value in the state.
d-text Set the inner text or text content of the element.
d-transition Change attributes on an element when being hidden or shown.
d-watch Runs every time a used value has been changed.

Contexts overview

Name Description
$children List of contexts of child components.
$component Component's root element.
$dispatch Dispatch custom event on the element.
$element Directive's element.
$for Get variables defined in the for directive.
$inContext Call a function in context after the existing one has been revoked.
$nextSibling Context of next sibling component.
$nextTick Call a function after updates are done processing.
$parent Context of parent component.
$previousSibling Context of previous sibling component.
$references List of referenced elements in the component.
$siblings List of contexts of sibling components.
$state Access the component's state.
$store Access the data store.
$watch Call a function when the specified value changes.

Header overview

Name Description
redirect Redirects the page location to the header's value.
request Indicates the request is done via this library.
title Changes the page title to the header's value.

Directives

Directives are instructional attributes placed on elements in order to make the elements react to changes and input.

Directives consist of several parts, some are optional depending on which directive is used. The first part is the prefix, by default d-. The second part the name of the directive, for example d-on. Optionally a name can be provided after a colon d-on:click. After a stop additional modifiers can be provided d-on:click.once. Finally the attribute value is provided. What the name, modifiers, and value are used for dependents on the directive.

A directive will only be read if it is part of a component. A component is defined by setting the d-state on an element. Everything inside the element becomes part until another component is defined further down the hierarchy.

d-attribute

Set an attribute's value or multiple attributes at once by returning an object. The directive's value should be function expression. If the directive is given a name the attribute with that name will be set to the value returned by the expression. Otherwise an object needs to be returned where the keys of the object are the attribute names and the value is set as the value of the attribute. Alternatively a promise can be returned resolving into an attribute value or object if attribute name and value pairs.

d-attribute modifiers

  • {boolean} selector = false Return a CSS style selector instead of a specific value or object.

d-attribute examples

<!-- Only show if active is true. -->
<div d-attribute:style="$state.active ? '' : 'display:none'"></div>
<!-- Only show if active is true. -->
<div d-attribute="$state.active ? { style: '' } : { style: 'display:none' }"></div>
<!-- Only show if active is true. -->
<div d-attribute.selector="$state.active ? '[style]' : '[style=display:none]'"></div>

d-cloak

Is removed after the component is initialized.

<!-- Hide any components with the cloak directive. -->
<style>
  [d-cloak] {
    display: none
  }
</style>

<!-- Since the cloak directive is removed after initialization this element won't be visible until then. -->
<div d-cloak>
  Not hidden!
</div>

d-for

Loop over a value and create elements based on a template. The directive's value gets split into two parts. The first part a list of variable names and the second part should be a function expression. The split happens at the of or in keyword. The variable names are the names under which the values of the function expression are made available on the $for context. The function expression can return either a number, array, object, or promise resolving into a number, array, or object. Which variable name matches which value of the return type depends on the return type. For numbers only one variable will be set to the index of the iteration. For arrays the first variable is the value, and the second variable the index of the iteration. For objects the first variable is the key, the second variable is the value, and the third variable the index of the iteration. The directive can only be used on a template element.

d-for examples

<!-- Create four items base of a number. Make the index of the item available on the $for context under the property named 'index'. -->
<template d-for="index of 4">
  <li>Item</li>
</template>
<!-- Create two item based of an array. Make the value and index available on the $for context under the properties named 'value' and 'index' respectively.  -->
<template d-for="(value, index) of [ 'value A', 'Value b' ]">
  <li>Item</li>
</template>
<!-- Create two item based of an object. Make the key, value, and index available on the $for context under the properties named 'key', 'value', and 'index' respectively.  -->
<template d-for="(key, value, index) in { a: 'value A', b: 'Value b' }">
  <li>Item</li>
</template>

d-html

Set the inner HTML of the element. The directive's value should be a function expression returning the HTML to set, or a promise resolving into the HTML to set. The inner HTML is only updated if it differs from the current value.

d-html modifiers

  • {boolean} decode = false If the returned type is a string the value will's special HTML characters will be decoded. For example &gt; will become >.
  • {boolean} morph = false Whether to convert the old document structure to the new, or to fully overwrite the existing structure with the new.
  • {boolean} outer = false Set the result to outerHTML instead of the innerHTML.

d-html examples

<!-- Write a string to the inner HTML of the element. -->
<div d-html="'<h1>Hello world!</h1>'"></div>
<!-- Decodes the special HTML characters before writing the string to the inner HTML of the element. -->
<div d-html.decode="'&lt;h1&gt;Hello world!&lt;/h1&gt;'"></div>
<!-- Write a string to the outer HTML of the element, replacing this element in the process. -->
<div d-html.outer="'<h1>Hello world!</h1>'"></div>
<!-- Write a value from the state to the inner HTML of the element. -->
<div d-html="$state.message"></div>

The inner HTML is only updated if it is different from the current inner HTML.

d-if

Return whether the template should be added to the document. The directive's value should be a function expression. If the result is truthy then the element will added to the document otherwise. If the result was previously truthy and is not any more then the element added by the directive will be removed. Alternatively a promise can be returned. After it has been resolved its truthiness will be checked and the directive will update then. The directive can only be used on a template element.

d-if examples

<!-- Adds the span tag to the document. -->
<template d-if="true">
  <span>Hello world!</span>
</template>
<!-- Toggles adding and removing the span element when the button is clicked. -->
<div d-state="{ active: false }">
  <button d-on:click="$state.active = !$state.active">
    Toggle
  </button>

  <template d-if="$state.active">
    <span>Active</span>
  </template>
</div>

d-ignore

Ignore the element and its children from being processed.

d-ignore examples

<!-- The text directive will never run due to the ignore attribute on the same element. -->
<p d-ignore d-text="'This message will not be displayed.'">
  This message will not be updated.
</p>

<!-- The text directive will never run due to the ignore attribute on the parent element. -->
<div d-ignore>
  <p d-text="'This message will not be displayed.'">
    This message will not be updated
  </p>
</div>

d-initialized

Runs once when the component is initialized. The directive's value should be a function expression.

d-initialized examples

<!-- Logs initialized to the console when the element's component is initialized. -->
<div d-initialized="console.log('initialized')"></div>

d-on

Listen to events on the document.

The directive's name is the event name to listen to. When listen to the keydown or keyup events a hyphen after the event name can be used to specify which key to filter on. For example d-on:keydown-h, or d-on:keyup-space. The directive's value should be a function expression. It will processed when the event is triggered.

d-on modifiers

  • {number} buffer = null Buffer multiple events together whereby the value is the amount of calls to bundle together. All events will be made available in an $events context and the most recent event is also available in the $event context. If set without a specific value then 5 will be used.
  • {boolean} capture = false Whether the capture option needs to be enabled when listening to the event.
  • {boolean} cmd = false See meta modifier.
  • {boolean} code = false Whether the keyboard event's key or code property should be checked.
  • {number} debounce = null Only fire the event if another event hasn't been invoked in the amount of time in milliseconds specified. All events will be made available in an $events context and the most recent event is also available in the $event context. If set without a specific value then 500 will be used.
  • {number} delay = null Amount of time to delay the firing of the directive by. If set without a specific value then 500 will be used.
  • {boolean} document = false Listen for the event on the document, instead of the element the directive is placed on.
  • {number} held = null Only fire the event if the key, mouse, or pointer was held down for the amount of time in milliseconds specified. This modifier can only be used in combination with the keydown, mousedown, and pointerdown events.
  • {number} hold = null Only fire the event after the amount of time specified has been elapsed and the key, mouse, or pointer has been held down. This modifier can only be used in combination with the keydown, mousedown, and pointerdown events. The key difference with the held modifier is this fires as soon as the time has elapsed.
  • {boolean} meta = false Whether the meta (command or windows) key needs to held for the directive to fire.
  • {boolean} once = false WWhether the once option needs to be enabled when listening to the event.
  • {boolean} outside = false Whether the event needs to have happened outside the element it is applied on.
  • {boolean} passive = false Whether the passive option needs to be enabled when listening to the event.
  • {boolean} prevent = false Whether to call preventDefault on the event invoking the route change.
  • {boolean} repeat = false Whether to allow repeat calls of the event. Repeat calls are detriment by the KeyboardEvent.repeat property.
  • {boolean} self = false Whether the target of the event invoking the route change must be the directive's element itself and not an underlying element.
  • {boolean} stop = false Whether to call stopPropagation on the event invoking the route change.
  • {boolean} super = false See meta modifier.
  • {number} throttle = null Prevent the event from firing again for the amount of time in milliseconds specified. All events will be made available in an $events context and the most recent event is also available in the $event context. If set without a specific value then 500 will be used.
  • {boolean} window = false Listen for the event on the window, instead of the element the directive is placed on.

Only one of the following five modifiers can be used at a time buffer, held hold, debounce, or throttle.

Only one of the following three modifiers can be used at a time document, outside, or window.

d-on examples

<input d-on:change.debounce-250="updateSearchResults($event)" type="text" />
<div d-state="{ open: false }">
  <!-- Show the opened div when the button is clicked. -->
  <button d-on:click="open = true">
    Open
  </button>

  <!-- Close the opened div when clicking outside the div. -->
  <div d-show="open" d-on:click.outside="open = false">
    Opened!
  </div>
</div>

d-reference

Add the element to the component's references context. The directive's value should be the variable name under which to make the reference available in the $references context.

d-reference examples

<!-- Make the element accessible under $references.myElement. -->
<div d-reference="'myElement'"></div>

d-select

Set selected item of a select element or selectable input elements. Selectable input elements are input elements with the type checkbox or radio. The directive's value should be a function expression. The function expression should return the value of the item to select, an array of values to select if the multiple attribute is applied, an input element with the type checkbox is used, or a promising resolving into one the previously mentioned types.

d-select examples

<!-- Will select the second option only. -->
<select d-select="'b'">
  <option value="a" selected>Option A</option>
  <option value="b">Option B</option>
  <option value="c">Option C</option>
</select>
<!-- Will select the second and third options. -->
<select d-select="[ 'b', 'c' ]" multiple>
  <option value="a" selected>Option A</option>
  <option value="b">Option B</option>
  <option value="c">Option C</option>
</select>
<!-- Will select the second checkbox only. -->
<div d-state="{ selected: 'b' }">
  <input type="checkbox" name="checkbox-name" d-select="$state.selected" value="a" checked>
  <input type="checkbox" name="checkbox-name" d-select="$state.selected" value="b">
  <input type="checkbox" name="checkbox-name" d-select="$state.selected" value="c">
</div>
<!-- Will select the second and third checkboxes. -->
<div d-state="{ selected: [ 'b', 'c' ] }">
  <input type="checkbox" name="checkbox-name" d-select="$state.selected" value="a" checked>
  <input type="checkbox" name="checkbox-name" d-select="$state.selected" value="b">
  <input type="checkbox" name="checkbox-name" d-select="$state.selected" value="c">
</div>
<!-- Will select the second toggle. -->
<div d-state="{ selected: 'b' }">
  <input type="radio" name="radio-name" d-select="$state.selected" value="a" checked>
  <input type="radio" name="radio-name" d-select="$state.selected" value="b">
  <input type="radio" name="radio-name" d-select="$state.selected" value="c">
</div>

d-show

Return whether the element should be displayed. The directive's value should be a function expression. The directive applies the inline styling of display: none to the element if the directive's value returns a non truthy value (false, or null, etc.), otherwise the inline styling of display: none is removed. Alternatively a promise can be returned. After it has been resolved its truthiness will be checked and the directive will update then.

d-show examples

<div d-show="true">
  Shown!
</div>
<div d-state="{ visible: false }">
  <div d-show="$state.visible">
    Hidden!
  </div>
</div>

d-state

Define a component and set its initial state. The directive's value should be a function expression returning an object. If no value is given an empty state of {} will be used.

d-state examples

<!-- Define a component. -->
<div d-state="{ message: 'Hello there.' }">

  <!-- Define a child component. -->
  <div d-state="{ message: 'General Kenobi!' }">
    <!-- The span will output the message of the child component. -->
    <span d-text="$state.message"></span>
  </div>
</div>

<!-- Define another component unrelated the former. -->
<div d-state="{ message: 'The Force will be with you. Always.' }">
  <span d-text="$state.message"></span>
</div>

d-sync

Keep the value of an element in sync with a value in the state. It works on input, checkbox, radio, select, and text area elements, as wel as div's with the content editable attribute. The directive's value should be a dot separated path to a property on the state of the component.

d-sync examples

<input type="text" name="message" d-sync="$state.message" />
<input type="text" name="status" d-sync="$state.messenger.status" />
<input type="text" name="message" d-sync:state="message" />
```
<input type="text" name="status" d-sync="$store.messenger.status" />
<input type="text" name="message" d-sync:store="message" />
<input type="text" name="message" d-sync="message" />

d-text

Set the inner text or text content of the element. The directive's value should be a function expression returning the text to set, or a promise resolving into the text to set. The inner text or text content is only updated if differs from the current value.

d-text modifiers

d-text examples

<!-- Write a string to the inner text fo the element. -->
<div d-text="'Afterwards'"></div>
<!-- Write a value from the state to the inner text fo the element. -->
<div d-text="$state.message"></div>
<!-- Write a value from the state to the text content of the element. -->
<div d-text.content="$state.message"></div>

d-transition

Change attributes on an element when being hidden or shown. The directive's name should either be in or out. Where in is used when an element is being show, and out when a element will be hidden. The directive's value should be a CSS selector. This selector will be applied when another directive is transition the element away from being hidden or will become hidden. Differing selectors can be used during each type of transitions, and different selectors can be applied during each phase of the transition using modifiers.

The duration of the transition depends on the transition duration or animation duration set on the element after the first frame.

d-transition modifiers

One of the following modifiers can be applied. If both are applied the directive is ignored.

  • {boolean} from = false Will only be applied on the first frame of the transition.
  • {boolean} to = false Will only be applied on the last frame of the transition.

Not using a modifier means the selector is applied during the entire transitioning period.

d-transition examples

<!-- When this element is made visible by the show directive. the first-frame and transition classes are applied then the next frame the first-frame class is removed. On the last frame of the transition the last-frame class is applied and then the next frame the transitioning and last-frame classes are removed. -->
<div d-show="true" style="display: none" d-transition:in.from=".first-frame" d-transition:in=".transitioning" d-transition:in.to=".last-frame"></div>

d-watch

Runs every time a used value has been changed. The directive's name is ignored so multiple watch directive's can be applied to the same element. The directive's value should be a function expression.

d-watch examples

<div d-state="{ message: null }">
  <!-- Store the value of this input in the state. -->
  <input type="text" d-sync="$state.message" />

  <!-- Log the message to the console when it changes. -->
  <div d-watch="console.log(message)"></div>
</div>

Contexts

Contexts are the variables available to directive expressions during execution.

$children

List of contexts of child components.

  • Type: Array<Object>

$children examples

<!-- Sets the amount of child components of the directive's component to the innerText of the directive's element. -->
<div d-text="$children.length"></div>
<!-- Logs the first child component of the directive's component to the console. -->
<div d-initialized="console.log($children[0])"></div>

$component

Component's root element.

  • Type: HTMLElement

$component examples

<!-- On initialization sets the id of the directive's component's element to 'myComponent'. -->
<div d-initialized="$component.id = 'myComponent'"></div>

$dispatch

Dispatch custom event on the element.

  • Type: Function
  • Parameters:
    • {string} name Name of the event.
    • {object} detail = {} Detail data of the event.

$dispatch examples

<!-- On click dispatches the 'beenClicked' event. -->
<div d-on:click="$dispatch('beenClicked')"></div>

$element

Directive's element.

  • Type: HTMLElement

$element examples

<!-- On initialization sets the id of the directive's element to 'myElement'. -->
<div d-initialized="$element.id = 'myElement'"></div>

$for

Get variables defined in the for directive. This context gets deconstruct automatically so when accessing the properties you do not need to prefix it with $for.

$for examples

<!-- Loops over and adds the indices and values of the array to the page. -->
<template d-for="(value, index) of [ 'Hello world!' ]">
  <div>
    <span d-text="$for.index"></span> - <span d-text="$for.value"></span>
  </div>
</template>
<!-- Loops over and adds the indices and values of the object to the page. -->
<template d-for="(key, value, index) in { message: 'Hello world!' }">
  <div>
    <span d-text="$for.index"></span> - <span d-text="$for.value"></span>
  </div>
</template>
<!-- Loops over and adds the indices and values of the object to the page. The same as the previous example, but it makes use of the fact that the context gets automatically deconstructed. -->
<template d-for="(key, value, index) in { message: 'Hello world!' }">
  <div>
    <span d-text="index"></span> - <span d-text="value"></span>
  </div>
</template>

$inContext

Call a function in context after the existing one has been revoked. Whereby the first parameter of the callback method will be an object containing the contexts. Useful for accessing a component's context after running an asynchronous function.

  • Type: Function
  • Parameters:
    • {Function} callback Callback to invoke.

$inContext examples

<!-- On initialization runs an asynchronous method and sets the result on the state of the component. -->
<div d-initialized="
  doSomething().then((result) => {
    $inContext(({ $state }) => {
      $state.value = result
    })
  })
"></div>

$nextSibling

Context of next sibling component.

  • Type: object

$nextSibling examples

<div d-state="{}">
  <!-- On initialization sets the message property on the sibling component's state. -->
  <div d-initialized="$nextSibling.$state.message = 'hello world'"></div>
</div>
<div d-state="{ message: null }"></div>

$nextTick

Call a function after updates are done processing. Whereby the first parameter of the callback method will be an object containing the contexts.

  • Type: Function
  • Parameters:
    • {Function} callback Callback to invoke.

$nextTick examples

<!-- On initialization sets the initialized property on the component's state to true. -->
<div d-initialized="
  $nextTick(({ $state }) => {
    $state.initialized = true
  })"></div>

$parent

Context of parent component.

  • Type: object

$parent examples

<div d-state="{ message: null }">
  <div d-state="{}">
    <!-- On initialization sets the message property on the parent component's state. -->
    <div d-initialized="$parent.$state.message = 'hello world'"></div>
  </div>
</div>

$previousSibling

Context of previous sibling component.

  • Type: object

$previousSibling examples

<div d-state="{ message: null }"></div>
<div d-state="{}">
  <!-- On initialization sets the message property on the sibling component's state. -->
  <div d-initialized="$previousSibling.$state.message = 'hello world'"></div>
</div>

$references

List of referenced elements in the component.

  • Type: object<string, HTMLElement>

$references examples

<!-- On initialization logs the reference named 'otherElement' to the console. -->
<div d-reference="'otherElement'"></div>
<div d-initialized="console.log($references.otherElement)"></div>

$siblings

List of contexts of sibling components

  • Type: object

$siblings examples

<div d-state="{ message: 'hello' }"></div>
<div d-state="{}">
  <!-- Set the text to the joined messages of the sibling components. -->
  <div d-text="$siblings.map(({ $state }) => $state.message).join(' ')"></div>
</div>
<div d-state="{ message: 'world' }"></div>

$state

Access the component's state. This context gets deconstruct automatically so when accessing the properties you do not need to prefix it with $state. Do note the $state context will be checked after the $for context since the $state context is inserted before the for context. This means that when a property exists on both the state and the for contexts the value from the for will be returned.

  • Type: object

$state examples

<!-- On initialization sets the property property on the component's state to true. -->
<div d-initialized="$state.property = true"></div>
<!-- Sets the message of the state as innerText on the directive's element. -->
<div d-text="$state.message"></div>
<!-- Sets the message of the state as innerText on the directive's element. The same as the previous example, but it makes use of the fact that the context gets automatically deconstructed. -->
<div d-text="message"></div>

$store

Access the data store. This context does not automatically get deconstructed this requires the storeContextDeconstruct option to be set to true. Doing this will allow accessing the properties to do not need the $store prefix. Do note the $store context will be checked after the $for and $state context since the $store context is inserted before the for and state context. This means that when a property exists on both the state and the for or store contexts the value from the for or state will be returned.

  • Type: object

$store examples

<!-- Read from the data store. -->
<div d-text="$store.message"></div>
<!-- Write to the data store. -->
<div d-on:click="$store.message = 'Hello there!'"></div>
<!-- Access directly if the deconstruct option is set to true. -->
<div d-text="message"></div>
<!-- If the deconstruct option is set, but the same key exists on the state. -->
<div d-state="{ message: 'Hello there!' }">
  <!-- Then 'Hello there!' will be read instead of the value from the data store. -->
  <div d-text="message"></div>
</div>

$watch

Call a function when the specified value changes. The function takes in a string which resolves to the value to watch. Then when the value is changed the second parameter, the callback function is called. Do note the callback is no invoked when defined, if this needs to be done simply call the function returned by the $watch(...) call again like so $wath(...)().

$watch examples

<!-- Keep track of the count, and log to the console whenever the count changes. -->
<div d-state="{ count: 0 }" d-initialized="$watch('count', ({ $state }) => { console.log($state.count) })">
  <!-- Increment the count whenever the button is clicked. -->
  <button type="button" d-on:click="count++">Increment</button>
</div>

Headers

Some contexts and directives can perform requests, headers can be used to inform the server of the requests origin as well as the response headers can perform additional actions in the client. The most notable examples are the fetch and navigate plugins. Because these share the same headers they are specified in the options of the main library.

Redirect

Response header that is able to redirects the page to the header's value. Useful when you want to redirect the user to a different webpage after the request has been completed.

Request

Request header that indicates the request is done via this library. This way the server can take a different action depending on the origin of the request. The value of the header is the name of the context or directive on who is handling the request.

Title

Response header that changes the page title to the header's value. Useful when the request changes the page's content enough where it should be seen as a different page.

Simple contexts

A simple context is a context that is added using the setSimpleContext function on the Doars instance. The advantage are they can be easily added or removed using a single function. The disadvantages are they do not have access to the attribute and component, as well as a destroy function called after every expression processed. Therefore simple contexts are best used for values that need to be accessible to every context and do not require any life cycle management or information about the attribute or component the value is used in.

The simple contexts are used as the base to build the full context from. This means they will be overwritten by any other context or property on any deconstructed context, like the component's state, that the simple context has a name in common with.

<!-- Create the component's state with the message by calling the simple context. -->
<div d-state="createState()">
  <!-- Call the simple context with the message when the button has been clicked. -->
  <button d-on:click="handleButtonClick($state.message)">
    Log message
  </button>
</div>

<!-- Import library. -->
<script src="https://cdn.jsdelivr.net/npm/@doars/doars@3/dst/doars.iife.js"></script>
<script type="application/javascript">
  document.addEventListener('DOMContentLoaded', () => {
    // Setup an instance.
    const doars = new window.Doars()

    // Set the simple contexts on the instance.
    doars.setSimpleContext('createState', () => {
      // Return an object with our message.
      return {
        message: 'Hello there!'
      }
    })
    doars.setSimpleContext('handleButtonClick', (message) => {
      // Log the message to the console.
      console.log('The element has been clicked!', message)
    })

    // Enable library.
    doars.enable()
  })
</script>

Browser support

Internet Explorer and older versions of other browsers are not supported as the library heavily relies on proxies for its state management. See proxy browser compatibility on MDN Web Docs or the can i use statistics.

Expression processors

Some directives can return JavaScript expressions. The expressions are given to a function that processes it. There are three different processors provided by default each having a separate build. You can also specify a custom processor function using the options.

Execute processor

The execute function uses the Function constructor to process the expressions. Which is similar to the eval function in that it is not very safe to use, nor recommended. This processor function does not work when a Content Security Policy is set that does not contain unsafe-eval. That being said this process method does allow for running any expression you might want, since it uses the JavaScript interpreter directly.

<script src="https://cdn.jsdelivr.net/npm/@doars/doars@3/dst/doars.iife.js"></script>
import Doars from '@doars/doars'
// Or the file directly.
import Doars from '@doars/doars/src/DoarsExecute.js'

Interpret processor

The interpret function uses a custom interpreter that parses and runs the expression. The interpret does not support all JavaScript features, but any expression that it runs is also valid JavaScript. To see what features are supported see the interpreter's supported features section.

Because the interpret processor does not use the Function constructor it can be used when a Content Security Policy is setup without unsafe-eval. However the interpreter is essentially a way to get around the policy and should not be used without taking the accompanying risks into consideration.

<script src="https://cdn.jsdelivr.net/npm/@doars/doars@3/dst/doars-interpret.iife.js"></script>
import Doars from '@doars/doars/src/DoarsInterpret.js'

Call processor

The call function is the simplest processor and also the most limiting one. Instead of trying to the evaluate the expression, instead it assumes the expression is a dot separated path to a value in the contexts. If the value at the path is a function it will run it and given the contexts object as a parameter. This means it also the most limiting build-in processor function, however in combination the simple contexts functions you can still accomplish anything you might want to do.

Because the call processor does not try to run an arbitrary expression it is the most secure option out of all of the build-in.

<script src="https://cdn.jsdelivr.net/npm/@doars/doars@3/dst/doars-call.iife.js"></script>
import Doars from '@doars/doars/src/DoarsCall.js'

Call processor examples

<!-- Instead of -->
<div d-state="{ message: 'Hello there!' }">
  <button d-on:click="console.log('The element has been clicked!', $state.message)">
    Log message
  </button>
</div>
<script src="https://cdn.jsdelivr.net/npm/@doars/doars@3/dst/doars.iife.js"></script>
<script type="application/javascript">
  document.addEventListener('DOMContentLoaded', () => {
    // Setup an instance.
    const doars = new window.Doars()
    // Enable library.
    doars.enable()
  })
</script>

<!-- You need to do -->
<div d-state="createState">
  <button d-on:click="handleButtonClick">
    Log message
  </button>
</div>
<script src="https://cdn.jsdelivr.net/npm/@doars/doars@3/dst/doars-call.iife.js"></script>
<script type="application/javascript">
  document.addEventListener('DOMContentLoaded', () => {
    // Setup an instance.
    const doars = new window.Doars()

    // Set the simple contexts on the instance.
    doars.setSimpleContext('createState', () => {
      // Return an object with our message.
      return {
        message: 'Hello there!'
      }
    })
    doars.setSimpleContext('handleButtonClick', ({ $state }) => {
      // Log the message to the console.
      console.log('The element has been clicked!', $state.message)
    })

    // Enable library.
    doars.enable()
  })
</script>

API

EventDispatcher

Base class extended by several other classes in order to dispatch events.

  • constructor Create instance.
    • @returns {EventDispatcher}
  • addEventListener Add callback to event.
    • @param {string} name Event name.
    • @param {Function} callback Callback to invoke when the event is dispatched.
    • @param {object} options Event listener options.
      • {boolean} once = false Removes the callback after it has been invoked.
  • removeEventListener Remove callback from event.
    • @param {string} name Event name.
    • @param {Function} callback Callback to remove.
  • removeEventListeners Remove all callbacks to an event.
    • @param {string} name Event name.
  • removeAllEventListeners Remove all callbacks of the event dispatcher.
  • dispatchEvent Trigger event callbacks.
    • @param {string} name Event name.
    • @param {Array<Any>} parameters List of parameters to pass to the callback.
    • @param {object} options Dispatch options.
      • {boolean} reverse = false Invokes event callbacks in reverse order from which they were added.

ProxyDispatcher

Sends out events when an object it keeps track of get accessed of mutated. Extends the EventDispatcher.

  • constructor Create instance.
    • @param {object} options = {} Options.
      • {boolean} delete = true Whether to dispatch delete events.
      • {boolean} get = true Whether to dispatch get events.
      • {boolean} set = true Whether to dispatch set events.
    • @returns {ProxyDispatcher}
  • add Add object to proxy dispatcher.
    • @param {object} target Object to add.
    • @returns {Proxy}
  • remove Remove object from proxy dispatcher.
    • @param {object} target Object to remove.

ProxyDispatcher events

  • delete When an property is deleted from a tracked object.
    • @param {object} target The root object the property has been deleted from.
    • @param {Array<String>} path Path segments leading to the deleted property.
  • get When a property is retrieved on a tracked object.
    • @param {object} target The root object the property has been retrieved from.
    • @param {Array<String>} path Path segments leading to the retrieved property.
    • @param {any} receiver
  • set When a value is set on a tracked object.
    • @param {object} target The root object the property has been set on.
    • @param {Array<String>} path Path segments leading to the set property.
    • @param {any} value Value of the set property.
    • @param {any} receiver

Doars

Extends the EventDispatcher.

  • constructor Create instance.
    • @param {object} options = null See options.
    • @returns {Doars}
  • getEnabled Whether this is currently enabled.
    • @returns {boolean} Whether the library is enabled.
  • getId Get the unique identifier.
    • @returns {Symbol} Unique identifier.
  • getOptions Get the current options.
    • @returns {object} Current options.
  • enable Enable the library.
    • @returns {Doars} This instance.
  • disable Disable the library. Disabling the library does not return everything back to the state is was before enabling it. Listeners will be removed, modifications to the document will not be undone. For instance the cloak attribute once removed will not return.
    • @returns {Doars} This instance.
  • getSimpleContexts Get simple contexts.
    • @returns {object} Stored simple contexts.
  • setSimpleContext Add a value directly to the contexts without needing to use an object or having to deal with indices.
    • @param {string} name Property name under which to add the context.
    • @param {any} value = null The value to add, null removes the context.
    • @returns {boolean} Whether the value was successfully set.
  • setSimpleContexts Adds simple contexts by looping through the object and calling the the setSimpleContext function with the data.
    • @param {object} contexts An object where the key is the name for the simple context and the value the simple context.
    • @returns {object} Which simple context was successfully set.
  • getContexts Get list of contexts.
    • @returns {Array<Object>} List of contexts.
  • addContexts Add contexts at the index. Can only be called when NOT enabled.
    • @param {number} index Index to start adding at.
    • @param {...Object} contexts List of contexts to add.
    • @returns {Array<Object>} List of added contexts.
  • removeContexts Remove contexts. Can only be called when NOT enabled.
    • @param {...Object} contexts List of contexts to remove.
    • @returns {Array<Object>} List of removed contexts.
  • getDirectives Get list of directives.
    • @returns {Array<Object>} List of directives.
  • getDirectivesNames Get list of directive names.
    • @returns {Array<String>} List of directive names.
  • getDirectivesObject Get object of directives with the directive name as key.
    • @returns {object} Object of directives.
  • isDirectiveName Check whether a name matches that of a directive.
    • @param {string} attributeName Name of the attribute to match.
    • @returns {boolean} Whether the name matches that of a directive.
  • addDirective Add directives at the index. Can only be called when NOT enabled.
    • @param {number} index Index to start adding at.
    • @param {...Object} directives List of directives to add.
    • @returns {Array<Object>} List of added directives.
  • removeDirectives Remove directives. Can only be called when NOT enabled.
    • @param {...Object} directives List of directives to remove.
    • @returns {Array<Object>} List of removed directives.
  • update Update directives based on triggers. Can only be called when enabled.
    • @param {Array<Object>} triggers List of triggers to update with.

Doars options

  • {string} prefix = 'd' The prefix of the directive's attribute names.
  • {function|string} processor = 'execute' The expression processor to use. By default it will grab either the executeExpression and evaluateExpression function located on the Doars constructor, with a preferences for the execute function if both are available. To set the preferred processor to evaluateExpression use 'evaluate'. If a function is set that function will be used instead.
  • {HTMLElement|string} root = document.body.firstElementChild The element or selector of an element to scan and keep track of.
  • {boolean} allowInlineScript = false When setting the innerHTML or outerHTML inline scripts are not automatically ran. Enabling this wil ensure the inline scripts are executed.
  • {boolean} forContextDeconstruct = true Whether to require the $for prefix when trying to accessing data from the for context.
  • {boolean} stateContextDeconstruct = true Whether to require the $state prefix when trying to accessing data from the state context.
  • {boolean} storeContextDeconstruct = false Whether to require the $store prefix when trying to accessing data from the store context.
  • {object} storeContextInitial = {} The initial data of the data store context.
  • {boolean} indicatorDirectiveEvaluate = true If set to false the indicator directive's value is read as a string literal instead of an expression to process.
  • {boolean} referenceDirectiveEvaluate = true If set to false the reference directive's value is read as a string literal instead of an expression to process.
  • {boolean} selectFromElementDirectiveEvaluate = true If set to false the select from element directive's value is read as a string literal instead of an expression to process.
  • {boolean} targetDirectiveEvaluate = true If set to false the target directive's value is read as a string literal instead of an expression to process.
  • {string} childrenContextName = '$children' The name of the children context.
  • {string} componentContextName = '$component' The name of the component context.
  • {string} dispatchContextName = '$dispatch' The name of the dispatch context.
  • {string} elementContextName = '$element' The name of the element context.
  • {string} forContextName = '$for' The name of the for context.
  • {string} inContextContextName = '$inContext' The name of the inContext context.
  • {string} nextSiblingContextName = '$nextSibling' The name of the next sibling context.
  • {string} nextTickContextName = '$nextTick' The name of the nextTick context.
  • {string} parentContextName = '$parent' The name of the parent context.
  • {string} previousSiblingContextName = '$previousSibling' The name of the previous sibling context.
  • {string} referencesContextName = '$references' The name of the references context.
  • {string} siblingsContextName = '$siblings' The name of the siblings context.
  • {string} stateContextName = '$state' The name of the state context.
  • {string} storeContextName = '$store' The name of the store context.
  • {string} watchContextName = '$watch' The name of the watch context.
  • {string} attributeDirectiveName = 'attribute' The name of the attribute directive.
  • {string} cloakDirectiveName = 'cloak' The name of the cloak directive.
  • {string} forDirectiveName = 'for' The name of the for directive.
  • {string} htmlDirectiveName = 'html' The name of the html directive.
  • {string} ifDirectiveName = 'if' The name of the if directive.
  • {string} ignoreDirectiveName = 'ignore' The name of the ignore directive.
  • {string} indicatorDirectiveName = 'indicator' The name of the indicator directive.
  • {string} initializedDirectiveName = 'initialized' The name of the initialized directive.
  • {string} onDirectiveName = 'on' The name of the on directive.
  • {string} referenceDirectiveName = 'reference' The name of the reference directive.
  • {string} selectDirectiveName = 'select' The name of the select directive.
  • {string} selectFromElementDirectiveName = 'select' The name of the select from element directive.
  • {string} showDirectiveName = 'show' The name of the show directive.
  • {string} stateDirectiveName = 'state' The name of the state directive.
  • {string} syncDirectiveName = 'sync' The name of the sync directive.
  • {string} targetDirectiveName = 'target' The name of the target directive.
  • {string} textDirectiveName = 'text' The name of the text directive.
  • {string} transitionDirectiveName = 'transition' The name of the transition directive.
  • {string} watchDirectiveName = 'watch' The name of the watch directive.
  • {string} redirectHeaderName = 'redirect' The name of the redirect header.
  • {string} requestHeaderName = 'request' The name of the request header.
  • {string} titleHeaderName = 'title' The name of the title header.

Doars events

The following events are dispatched by the library and can be listened to by calling the addEventListener(/* name, callback, options */) function on the instance.

  • enabling When enabling, but before enabling is done.
    • @param {Doars} doars Library instance.
  • enabled After enabling is done.
    • @param {Doars} doars Library instance.
  • updated After enabling is done and when an update has been handled.
    • @param {Doars} doars Library instance.
  • disabling When disabling, but before disabling is done.
    • @param {Doars} doars Library instance.
  • disabled After disabling is done.
    • @param {Doars} doars Library instance.
  • components-added When one or more components are added.
    • @param {Doars} doars Library instance.
    • @param {Array<HTMLElements>} addedElements List of added components.
  • components-removed When one or more components are removed.
    • @param {Doars} doars Library instance.
    • @param {Array<HTMLElements>} removedElements List of removed components.
  • contexts-added When one or more contexts are added.
    • @param {Doars} doars Library instance.
    • @param {object} addedContexts List of added contexts.
  • contexts-removed When one or more contexts are removed.
    • @param {Doars} doars Library instance.
    • @param {object} removedContexts List of removed contexts.
  • simple-context-added When a simple context is added.
    • @param {Doars} doars Library instance.
    • @param {string} name Name of simple context.
    • @param {any} value Value of simple context.
  • simple-context-removed When a simple context is removed
    • @param {Doars} doars Library instance.
    • @param {string} name Name of simple context.
  • directives-added When one or more directives are added.
    • @param {Doars} doars Library instance.
    • @param {object} addedDirectives List of added directives.
  • directives-removed When one or more directives are removed.
    • @param {Doars} doars Library instance.
    • @param {object} removedDirectives List of removed directives.

Component

  • getAttributes Get the attributes in this component.
    • @returns {Array<Attribute>} List of attributes.
  • getChildren Get child components in hierarchy of this component.
    • @returns {Array<Component>} List of components.
  • getElement Get root element of the component.
    • @returns {HTMLElement} Element.
  • getId Get component id.
    • @returns {Symbol} Unique identifier.
  • getLibrary Get the library instance this component is from.
    • @returns {Doars} Doars instance.
  • getParent Get parent component in hierarchy of this component.
    • @returns {Component} Component.
  • getProxy Get the event dispatcher of state's proxy.
    • @returns {ProxyDispatcher} State's proxy dispatcher.
  • getState Get the component's state.
    • @returns {Proxy} State.

Component events

The following events are dispatched by the component and can be listened to by calling the addEventListener(/* name, callback, options */) function on the component's root element.

  • d-destroyed When this instance is destroyed.
    • @param {CustomEvent} event Event data.
      • {object} detail Event details.
        • {HTMLElement} element Component's root element.
        • {Symbol} id Component's unique identifier.
  • d-updated When one or more attributes on the component have been updated.
    • @param {CustomEvent} event Event data.
      • {object} detail Event details.
        • {HTMLElement} element Component's root element.
        • {Symbol} id Component's unique identifier.
        • {Array<Attribute>} updatedAttributes List of updated attributes.

Attribute

Extends the EventDispatcher.

  • getComponent Get the component this attribute is a part of.
    • @returns {Component} Attribute's component.
  • getElement Get the element this attribute belongs to.
    • @returns {HTMLElement} Element.
  • getId Get attribute id.
    • @returns {Symbol} Unique identifier.
  • getDirective Get the directive this attribute matches.
    • @returns {string} Directive name.
  • getKey Get the optional key of the attribute.
    • @returns {string} Key.
  • getKeyRaw Get the optional key of the attribute before being processed.
    • @returns {string} Raw key.
  • getModifiers Get the optional modifiers of the attribute.
    • @returns {object} Modifiers object
  • getModifiersRaw Get the optional modifiers of the attribute before being processed.
    • @returns {Array<String>} List of raw modifiers.
  • getName Get attribute's name.
    • @returns {string} Attribute name.
  • getValue Get the attribute's value.
    • @returns {string} Value.
  • accessed Mark an item as accessed.
    • @param {Symbol} id Unique identifier.
    • @param {string} path Context path.
  • hasAccessed Check if attribute accessed any of the item's paths.
    • @param {Symbol} id Unique identifier.
    • @param {Array<String>} paths Contexts path.
    • @returns {boolean} Whether any item's path was accessed.
  • clone Creates a clone of the attribute without copying over the id and accessed values.
    • @returns {Attribute} Cloned attribute.

Attribute events

The following events are dispatched by an Attribute and can be listened to by calling the addEventListener(/* name, callback, options */) function on the instance.

  • changed When the value is changed.
    • @param {Attribute} attribute The attribute instance.
  • destroyed When the instance is destroyed.
    • @param {Attribute} attribute The attribute instance.
  • accessed When a new item is marked as accessed.
    • @param {Attribute} attribute The attribute instance.
    • @param {Symbol} id The accessed's unique identifier.
    • @param {string} path The accessed's context path.

Writing contexts

Contexts can be added to the Doars instance using the addContexts function where the first parameter is the index to add them to in the list, and the rest of the parameters the contexts you want to add.

Technically a context is nothing more than an object with a name property and a create property. The name must be a valid variable name, and create a function that returns an object containing the value that will be made available under the context's name when executing an expression.

The create function is given several arguments, the first is the Component, the second the Attribute.

Take for example the $element context. All it needs to do is the return the element of the attribute that is being processed, simple enough.

export default {
  // The name of the context.
  name: '$element',

  // The function to process in order to create the context.
  create: (component, attribute) => {
    return {
      // Set the value to make available under the context's name.
      value: attribute.getElement(),
    }
  },
}

In addition to the Component and Attribute arguments the create function is also given a third and fourth argument. The third is an update function, and the fourth an object containing several utility classes and functions.

The update function can be called to trigger an update of the main library instance. In order for the library to know which directives need to be updated it will need to be given where something has updated as well as what has been updated. The where is taken care of by providing a Symbol, and the what is a String.

The utilities arguments has the following properties:

  • createContexts:Function Create component's contexts for an attributes expression. See the ContextUtils for more information.
  • createContextsProxy:Function Create component's contexts only after the context gets used. See the ContextUtils for more information.
  • RevocableProxy:RevocableProxy A Proxy.revocable polyfill.

Besides the name and create properties, an additional deconstruct property can be set. If deconstruct is set to a truthy value then the value returned by the context will be deconstructed using the with statement. The result is that the context's name will not be needed in order to get the properties on the context. For example $state.something.or.another will also be accessible via something.or.another. Do note that the with statement is called in the same order that the contexts are added to by the addContexts function. In other words if two context both have the deconstruct property set and both contain the same property then the one later in the list will be used.

A more advanced context example is the $state context. It needs to get the state from the component and trigger an update if the state is changed as well as mark any properties accessed on it as accessed by the attribute. Finally when the contexts is no longer needed it will need to remove the listeners and revoke access to it.

export default {
  // Mark the context for deconstruction.
  deconstruct: true,

  // The name of the context.
  name: '$state',

  // The function to process in order to create the context.
  create: (component, attribute, update, { RevocableProxy }) => {
    // Get and check values from the component.
    const proxy = component.getProxy()
    const state = component.getState()
    if (!proxy || !state) {
      return
    }

    // Create event handlers that trigger an update if a property on the state is deleted or set, and mark a value as accessed if a value is retrieved.
    const onDelete = (target, path) =>
      update(component.getId(), '$state.' + path.join('.'))
    const onGet = (target, path) =>
      attribute.accessed(component.getId(), '$state.' + path.join('.'))
    const onSet = (target, path) =>
      update(component.getId(), '$state.' + path.join('.'))

    // Add event listeners.
    proxy.addEventListener('delete', onDelete)
    proxy.addEventListener('get', onGet)
    proxy.addEventListener('set', onSet)

    // Wrap in a revocable proxy.
    const revocable = RevocableProxy(state, {})

    return {
      // Set the value to make available under the context's name.
      value: revocable.proxy,

      destroy: () => {
        // Remove event listeners.
        proxy.removeEventListener('delete', onDelete)
        proxy.removeEventListener('get', onGet)
        proxy.removeEventListener('set', onSet)

        // Revoke access to state.
        revocable.revoke()
      },
    }
  },
}

And there you have it, most of what you need to know about writing your own custom contexts. For more examples see the build-in contexts and plugin packages.

Writing directives

TODO: See the build-in directives and plugin packages for now.

Writing plugins

TODO: See the plugin packages for now.

Package Sidebar

Install

npm i @doars/doars

Weekly Downloads

1

Version

3.1.1

License

MIT

Unpacked Size

4.61 MB

Total Files

79

Last publish

Collaborators

  • redkenrok