@doars/doars
v3.1.1
Published
A front-end library for declaring functionality in your markup.
Downloads
27
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 = falseReturn 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 = falseIf the returned type is a string the value will's special HTML characters will be decoded. For example>will become>.{boolean} morph = falseWhether to convert the old document structure to the new, or to fully overwrite the existing structure with the new.{boolean} outer = falseSet the result toouterHTMLinstead 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 = nullBuffer 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 = falseWhether thecaptureoption needs to be enabled when listening to the event.{boolean} cmd = falseSee meta modifier.{boolean} code = falseWhether the keyboard event's key or code property should be checked.{number} debounce = nullOnly 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 = nullAmount of time to delay the firing of the directive by. If set without a specific value then 500 will be used.{boolean} document = falseListen for the event on the document, instead of the element the directive is placed on.{number} held = nullOnly 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, andpointerdownevents.{number} hold = nullOnly 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, andpointerdownevents. The key difference with the held modifier is this fires as soon as the time has elapsed.{boolean} meta = falseWhether the meta (command or windows) key needs to held for the directive to fire.{boolean} once = falseWWhether theonceoption needs to be enabled when listening to the event.{boolean} outside = falseWhether the event needs to have happened outside the element it is applied on.{boolean} passive = falseWhether thepassiveoption needs to be enabled when listening to the event.{boolean} prevent = falseWhether to callpreventDefaulton the event invoking the route change.{boolean} repeat = falseWhether to allow repeat calls of the event. Repeat calls are detriment by theKeyboardEvent.repeatproperty.{boolean} self = falseWhether the target of the event invoking the route change must be the directive's element itself and not an underlying element.{boolean} stop = falseWhether to callstopPropagationon the event invoking the route change.{boolean} super = falseSeemetamodifier.{number} throttle = nullPrevent 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 = falseListen 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 = falseWhether to write totextContentinstead ofinnerText. See the MDN docs for the differences betweeninnerTextandtextContent.
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 = falseWill only be applied on the first frame of the transition.{boolean} to = falseWill 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} nameName 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} callbackCallback 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} callbackCallback 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.
constructorCreate instance.@returns {EventDispatcher}
addEventListenerAdd callback to event.@param {string} nameEvent name.@param {Function} callbackCallback to invoke when the event is dispatched.@param {object} optionsEvent listener options.{boolean} once = falseRemoves the callback after it has been invoked.
removeEventListenerRemove callback from event.@param {string} nameEvent name.@param {Function} callbackCallback to remove.
removeEventListenersRemove all callbacks to an event.@param {string} nameEvent name.
removeAllEventListenersRemove all callbacks of the event dispatcher.dispatchEventTrigger event callbacks.@param {string} nameEvent name.@param {Array<Any>} parametersList of parameters to pass to the callback.@param {object} optionsDispatch options.{boolean} reverse = falseInvokes 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.
constructorCreate instance.@param {object} options = {}Options.{boolean} delete = trueWhether to dispatch delete events.{boolean} get = trueWhether to dispatch get events.{boolean} set = trueWhether to dispatch set events.
@returns {ProxyDispatcher}
addAdd object to proxy dispatcher.@param {object} targetObject to add.@returns {Proxy}
removeRemove object from proxy dispatcher.@param {object} targetObject to remove.
ProxyDispatcher events
deleteWhen an property is deleted from a tracked object.@param {object} targetThe root object the property has been deleted from.@param {Array<String>} pathPath segments leading to the deleted property.
getWhen a property is retrieved on a tracked object.@param {object} targetThe root object the property has been retrieved from.@param {Array<String>} pathPath segments leading to the retrieved property.@param {any} receiver
setWhen a value is set on a tracked object.@param {object} targetThe root object the property has been set on.@param {Array<String>} pathPath segments leading to the set property.@param {any} valueValue of the set property.@param {any} receiver
Doars
Extends the EventDispatcher.
constructorCreate instance.@param {object} options = nullSee options.@returns {Doars}
getEnabledWhether this is currently enabled.@returns {boolean}Whether the library is enabled.
getIdGet the unique identifier.@returns {Symbol}Unique identifier.
getOptionsGet the current options.@returns {object}Current options.
enableEnable the library.@returns {Doars}This instance.
disableDisable 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 thecloakattribute once removed will not return.@returns {Doars}This instance.
getSimpleContextsGet simple contexts.@returns {object}Stored simple contexts.
setSimpleContextAdd a value directly to the contexts without needing to use an object or having to deal with indices.@param {string} nameProperty name under which to add the context.@param {any} value = nullThe value to add, null removes the context.@returns {boolean}Whether the value was successfully set.
setSimpleContextsAdds simple contexts by looping through the object and calling the the setSimpleContext function with the data.@param {object} contextsAn 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.
getContextsGet list of contexts.@returns {Array<Object>}List of contexts.
addContextsAdd contexts at the index. Can only be called when NOT enabled.@param {number} indexIndex to start adding at.@param {...Object} contextsList of contexts to add.@returns {Array<Object>}List of added contexts.
removeContextsRemove contexts. Can only be called when NOT enabled.@param {...Object} contextsList of contexts to remove.@returns {Array<Object>}List of removed contexts.
getDirectivesGet list of directives.@returns {Array<Object>}List of directives.
getDirectivesNamesGet list of directive names.@returns {Array<String>}List of directive names.
getDirectivesObjectGet object of directives with the directive name as key.@returns {object}Object of directives.
isDirectiveNameCheck whether a name matches that of a directive.@param {string} attributeNameName of the attribute to match.@returns {boolean}Whether the name matches that of a directive.
addDirectiveAdd directives at the index. Can only be called when NOT enabled.@param {number} indexIndex to start adding at.@param {...Object} directivesList of directives to add.@returns {Array<Object>}List of added directives.
removeDirectivesRemove directives. Can only be called when NOT enabled.@param {...Object} directivesList of directives to remove.@returns {Array<Object>}List of removed directives.
updateUpdate directives based on triggers. Can only be called when enabled.@param {Array<Object>} triggersList 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 theexecuteExpressionandevaluateExpressionfunction located on the Doars constructor, with a preferences for the execute function if both are available. To set the preferred processor toevaluateExpressionuse'evaluate'. If a function is set that function will be used instead.{HTMLElement|string} root = document.body.firstElementChildThe element or selector of an element to scan and keep track of.{boolean} allowInlineScript = falseWhen setting the innerHTML or outerHTML inline scripts are not automatically ran. Enabling this wil ensure the inline scripts are executed.{boolean} forContextDeconstruct = trueWhether to require the$forprefix when trying to accessing data from the for context.{boolean} stateContextDeconstruct = trueWhether to require the$stateprefix when trying to accessing data from the state context.{boolean} storeContextDeconstruct = falseWhether to require the$storeprefix when trying to accessing data from the store context.{object} storeContextInitial = {}The initial data of the data store context.{boolean} indicatorDirectiveEvaluate = trueIf set to false the indicator directive's value is read as a string literal instead of an expression to process.{boolean} referenceDirectiveEvaluate = trueIf set to false the reference directive's value is read as a string literal instead of an expression to process.{boolean} selectFromElementDirectiveEvaluate = trueIf set to false the select from element directive's value is read as a string literal instead of an expression to process.{boolean} targetDirectiveEvaluate = trueIf 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.
enablingWhen enabling, but before enabling is done.@param {Doars} doarsLibrary instance.
enabledAfter enabling is done.@param {Doars} doarsLibrary instance.
updatedAfter enabling is done and when an update has been handled.@param {Doars} doarsLibrary instance.
disablingWhen disabling, but before disabling is done.@param {Doars} doarsLibrary instance.
disabledAfter disabling is done.@param {Doars} doarsLibrary instance.
components-addedWhen one or more components are added.@param {Doars} doarsLibrary instance.@param {Array<HTMLElements>} addedElementsList of added components.
components-removedWhen one or more components are removed.@param {Doars} doarsLibrary instance.@param {Array<HTMLElements>} removedElementsList of removed components.
contexts-addedWhen one or more contexts are added.@param {Doars} doarsLibrary instance.@param {object} addedContextsList of added contexts.
contexts-removedWhen one or more contexts are removed.@param {Doars} doarsLibrary instance.@param {object} removedContextsList of removed contexts.
simple-context-addedWhen a simple context is added.@param {Doars} doarsLibrary instance.@param {string} nameName of simple context.@param {any} valueValue of simple context.
simple-context-removedWhen a simple context is removed@param {Doars} doarsLibrary instance.@param {string} nameName of simple context.
directives-addedWhen one or more directives are added.@param {Doars} doarsLibrary instance.@param {object} addedDirectivesList of added directives.
directives-removedWhen one or more directives are removed.@param {Doars} doarsLibrary instance.@param {object} removedDirectivesList of removed directives.
Component
getAttributesGet the attributes in this component.@returns {Array<Attribute>}List of attributes.
getChildrenGet child components in hierarchy of this component.@returns {Array<Component>}List of components.
getElementGet root element of the component.@returns {HTMLElement}Element.
getIdGet component id.@returns {Symbol}Unique identifier.
getLibraryGet the library instance this component is from.@returns {Doars}Doars instance.
getParentGet parent component in hierarchy of this component.@returns {Component}Component.
getProxyGet the event dispatcher of state's proxy.@returns {ProxyDispatcher}State's proxy dispatcher.
getStateGet 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-destroyedWhen this instance is destroyed.@param {CustomEvent} eventEvent data.{object} detailEvent details.{HTMLElement} elementComponent's root element.{Symbol} idComponent's unique identifier.
d-updatedWhen one or more attributes on the component have been updated.@param {CustomEvent} eventEvent data.{object} detailEvent details.{HTMLElement} elementComponent's root element.{Symbol} idComponent's unique identifier.{Array<Attribute>} updatedAttributesList of updated attributes.
Attribute
Extends the EventDispatcher.
getComponentGet the component this attribute is a part of.@returns {Component}Attribute's component.
getElementGet the element this attribute belongs to.@returns {HTMLElement}Element.
getIdGet attribute id.@returns {Symbol}Unique identifier.
getDirectiveGet the directive this attribute matches.@returns {string}Directive name.
getKeyGet the optional key of the attribute.@returns {string}Key.
getKeyRawGet the optional key of the attribute before being processed.@returns {string}Raw key.
getModifiersGet the optional modifiers of the attribute.@returns {object}Modifiers object
getModifiersRawGet the optional modifiers of the attribute before being processed.@returns {Array<String>}List of raw modifiers.
getNameGet attribute's name.@returns {string}Attribute name.
getValueGet the attribute's value.@returns {string}Value.
accessedMark an item as accessed.@param {Symbol} idUnique identifier.@param {string} pathContext path.
hasAccessedCheck if attribute accessed any of the item's paths.@param {Symbol} idUnique identifier.@param {Array<String>} pathsContexts path.@returns {boolean}Whether any item's path was accessed.
cloneCreates 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.
changedWhen the value is changed.@param {Attribute} attributeThe attribute instance.
destroyedWhen the instance is destroyed.@param {Attribute} attributeThe attribute instance.
accessedWhen a new item is marked as accessed.@param {Attribute} attributeThe attribute instance.@param {Symbol} idThe accessed's unique identifier.@param {string} pathThe 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:FunctionCreate component's contexts for an attributes expression. See the ContextUtils for more information.createContextsProxy:FunctionCreate component's contexts only after the context gets used. See the ContextUtils for more information.RevocableProxy:RevocableProxyA 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.
