signalizejs/directives

Attribute Directives inspired by Vue and Svelte.

Directives can be used only within web components and are initialized on DOM ready and after the component is constructed.

Installation

Required import map dependencies:
const {
	directive,
	getPrerenderedNodes,
	processDirectives
 } = await signalize.resolve('directives');

 /*
	If you do not want to use any directives module functions but still want to use the directives on elemnents,
	simply resolve it to initialize the module.
*/
await signalize.resolve('directives');

Javascript Evaluation

Directives use javascript javascript evaluator

  • This evaluator is able to process only inline javascript.
  • There is no unsafe eval (eval, new Function, Async function Prototype).
  • It doesn’t support full javascript syntax. Make sure to read about the supported syntax.

Passing data to directives from Web Components

To pass data to the directives, return the data from a component setup. Any created property will also be passed to the directives as data:

<my-component>
    <button @click="count(count() + 1)">
        Click:  <span :html="count"></span>
    </button>
</my-component>

<script type="importmap">{
  "imports": {
    "signalizejs": "https://cdn.jsdelivr.net/npm/signalizejs/+esm",
    "signalizejs/bind": "https://cdn.jsdelivr.net/npm/signalizejs/bind/+esm",
    "signalizejs/directives/for": "https://cdn.jsdelivr.net/npm/signalizejs/directives/for/+esm",
    "signalizejs/directives/if": "https://cdn.jsdelivr.net/npm/signalizejs/directives/if/+esm",
    "signalizejs/dom/traverser": "https://cdn.jsdelivr.net/npm/signalizejs/dom/traverser/+esm",
    "signalizejs/evaluator": "https://cdn.jsdelivr.net/npm/signalizejs/evaluator/+esm",
    "signalizejs/event": "https://cdn.jsdelivr.net/npm/signalizejs/event/+esm",
    "signalizejs/mutation-observer": "https://cdn.jsdelivr.net/npm/signalizejs/mutation-observer/+esm",
    "signalizejs/scope": "https://cdn.jsdelivr.net/npm/signalizejs/scope/+esm",
    "signalizejs/signal": "https://cdn.jsdelivr.net/npm/signalizejs/signal/+esm",
    "signalizejs/strings/cases": "https://cdn.jsdelivr.net/npm/signalizejs/strings/cases/+esm",
    "signalizejs/component": "https://cdn.jsdelivr.net/npm/signalizejs/component/+esm",
    "signalizejs/directives": "https://cdn.jsdelivr.net/npm/signalizejs/directives/+esm"
  }
}</script>

<script type="module">
    import Signalize from 'signalizejs';

    const { resolve } = new Signalize();
    const { component, signal } = await resolve('component', 'directives', 'signal');

    component('my-component', () => {
        const count = signal(0);
        return { count }
    });
</script>

API

directive

Add a custom directive

directive('name', {
	matcher: /reg exp to match the attribute/,
	callback: async ({ scope, attribute, matches }) => {
		const { $data, $el } = scope;
		// This method is called, when the directive is matched on any element
	}
});

Matcher is a function. This way we can check if the directive should be executed for the element or not.

directive('name', {
	matcher: ({ scope, attribute }) => {
		// Checks if directive can be processed for the element
		// Return, if the directive should not be processed
		if (false) return;

		return /reg exp to match the attribute/;
	},
	callback: async ({ scop, data, attribute, matches }) => {
		// This method is called, when the directive is matched on any element
	}
});

getPrerenderedNodes

This method returns a Node array of elements that are prerendered between <!-- prerendered -->...<!-- /prerendered --> after the current element.

const nodes = getPrerenderedNodes(element);

processDirectives

This method traverses over the DOM tree of the root and initializes directives.

  • directives: If directives are passed as an argument, only the selected directives will be processed.
await processDirectives({
	root: element,
	// Optional
	directives: ['my-directive']
});

Configuration

  • prerenderedBlockStart: The default is prerendered
  • prerenderedBlockEnd: The default is /prerendered It will be matched like this:
<template :for="..."></template>
<!-- prerendered -->
<!-- /prerendered -->

Attributes

Directives use attributePrefix and attributeSeparator, which you can specify during Signalize initialization.

  • The default attributeSeparator is - (dash).
  • By default, there is no prefix, so you write, for example, signal="", bind-value="".
  • However, if you define the attribute prefix to be data-, then you use data-signal="", data-bind-value="".

Directives

Directives available by default:

  • bind - all elements
  • on - all elements
  • for - templates only
  • if - templates only

Each directive has access to the following data:

  • $el - current element
  • $ - current signalize instance
  • $refs - returns an element or array of elements <input ref="field">

bind

The bind directive is used to bind attributes and properties to elements.

  • It can bind normal value types as well as Signals.
  • If you pass a signal on its own into the directive, you don’t have to call it to get the value.
<!-- Pass string -->
<input :value="'Hello World'">

<input data-bind-value="someSignal">

<input :value="'text' + custom suffix">

<!--
	Shortcut - If the name any key in public data
	matches the name of binded attribute
-->
<input {value}>

interpolation

Signalize doesn’t support interpolation. Instead, use bind html or text.

<div :html=""></div>
<div :text=""></div>
<div>Count is: <span :text="count"></span></div>

for

The for loop is used for rendering elements within a for loop.

  • The supported syntax is for of and for in.
  • If you just need to trigger the iteration multiple times, you can use the value of 1000 / value in 1000 shortcut.
<template :for="item of items"></template>
<template :for="key in items"></template>
<template :for="item of 100"></template>
<template :for="[key, value] of Object.entries(data)"></template>

The key should always be bound inside the loop. This is because the for loop allows you to render multiple roots:

<template :for="i of 100">
	<li :key="i"></li>
</template>

<template :for="i of 100">
	<label :key="key + '-label'">Text</label>
	<input :key="key + '-input'">
</template>

Inside the for directive, the iterator variable is initialized. It holds important information about the current loop: counter: iteration counter first: is this the first iteration? last: is this the last iteration? odd: is this iteration odd? even: is this iteration even?

<template :for="i of count">
	<div>
		<template :if="iterator.last">
			<span> Last item</span>
		</template>
		<template :if="!iterator.last">
			<span :text="'Item' + iterator.counter"></span>
		</template>
	</div>
</template>

if

The if directive is used for conditional rendering.

  • It can be used only on a template element.
  • Inside the directive, there can be multiple roots.
  • It doesn’t need a wrapping element inside.
<template :if="true">
	Hello World!
</template>

However, if you plan to use it in loops, you might want to wrap it in a div or some other neutral element.

<div>
	<template :if="true">...</template>
</div>

on

The on directive is used to bind listeners to an element.

<button @click="alert('Hello World!')"></button>
<button on-click="alert('Hello World!')"></button>

<button @click="callback($event)"></button>