@doars/doars
v3.1.1
Published
A front-end library for declaring functionality in your markup.
Downloads
30
Maintainers
Readme
@doars/doars
The core library, it manages the components and plugins as well as includes the basic contexts and directives.
Table of contents
- Install
- Directives overview
- Contexts overview
- Directives
- Contexts
- Simple contexts
- Browser support
- Expression processors
- API
- Writing contexts
- Writing directives
- Writing plugins
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>
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>
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 toouterHTML
instead of theinnerHTML
.
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="'<h1>Hello world!</h1>'"></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 thecapture
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 thekeydown
,mousedown
, andpointerdown
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 thekeydown
,mousedown
, andpointerdown
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 theonce
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 thepassive
option needs to be enabled when listening to the event.{boolean} prevent = false
Whether to callpreventDefault
on the event invoking the route change.{boolean} repeat = false
Whether to allow repeat calls of the event. Repeat calls are detriment by theKeyboardEvent.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 callstopPropagation
on the event invoking the route change.{boolean} super = false
Seemeta
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
{boolean} content = false
Whether to write totextContent
instead ofinnerText
. See the MDN docs for the differences betweeninnerText
andtextContent
.
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 thecloak
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 theexecuteExpression
andevaluateExpression
function located on the Doars constructor, with a preferences for the execute function if both are available. To set the preferred processor toevaluateExpression
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.