Skip to content

TEMPLATE DIRECTIVE

Overview

Directives is a way in Still.js to deal with some UI specific features on the template side of things, which also provides capabilities to handle DOM tree and elements eliminating the need for diracte manipulating it in some specific situations.

In Still.js, directives are added as HTML tag properties enclosed in parentheses (e.g., (click)), visually distinguishing them from standard HTML attributes.

There are some directives available, and they are addressing things in different categories such as event binding, form binding, looping, rendering & conditional, form validation, follow a list bellow:

Event Directive

Those directives allow to tie specific kind of event to HTML form input by specifying the component method to be called, follow the list:

Directive Applicable to Description Use context
(click) Any HTML element/tag It adds the click event to the target element by providing access to the component methods.
  • Any event click implementation calling component method
(change) HTML <select>/combobox element It binds a component method to a combo-box allowing detect change on it.
  • Any event click implementation calling component method


The (click) Directive example:

Click event binding considering a component implemented method.

This is the template code snippet
<button (click)="increment()">count-state (@countVariable) +</button>
<button (click)="decrement()">count-state (@countVariable) -</button>
This is the component code snippet
countVariable = 0;

// This is going to be bound to the template via (click) directive
increment(){
    this.countVariable = this.countVariable.value + 1;
}

// This is going to be bound to the template via (click) directive
decrement(){
    this.countVariable = this.countVariable.value - 1;
}



The (change) Directive example:

Change event binding for HTML <select> tag/combobox ti check whenever a value gets selected.

This is the template code snippet
1
2
3
4
5
6
7
8
<form>
    <select (change)="callIfChanging($event)">
        <option value="">Select the country</option>
        <option value="1">Austria</option>
        <option value="2">Bulgaria</option>
        <option value="3">Canada</option>
    </select>
</form>
This is the component code snippet
1
2
3
4
5
// This is going to be bound to the template via (change) directive
callIfChanging(e) {
    console.log(`Country selected value is: `, e.target.value);

}

Changes in the regular HTML input can be handled through the state change in the bound state as shown in the (value) directive example.

Event Binding considerations

In general, Still.js concerns especially with click event not to miss the capability to bind component methods, anyway, therefore, when it comes to other events (except for onchange for combobox) the approach to follow is regular DOM manipulation and JavaScript since Still.js is pure javaScript.



Container Directives

Those directives are placed in any HTML element that can enclose other elements thereby providing features like looping through a list, rendering/not rendering, hide/unhide:

Directive Applicable to Description Use context
(forEach) Any HTML wrapper elemen (e.g. <div>, <section>, <span>, <select>, etc.) Allow looping through a list of elements while printing each element in the UI.
  • Generating data for Datatable
  • Looping to a list for a combobox
  • Mapping a list to cards
  • etc.
(renderIf) Any HTML wrapper elemen (e.g. <div>, <section>, <span>, <select>, etc.) Render/Not render an element according to a flag value (true/false)
  • Prevent application part/section not to render
  • Suitable for user permission content render manipulation
(showIf) Any HTML wrapper elemen (e.g. <div>, <section>, <span>, <select>, etc.) Hide/Unhide render an element according to a flag value (true/false)
  • Hide/Unhide UI part according to business rules.


The (forEach) Directive example:

It loops through a list/array of any type of element allowing the maping of it to some kind of desirable output.

This is the template code snippet
1
2
3
4
5
6
7
8
9
<!-- Outermost container -->
<div>
    <!-- Looping container -->
    <span (forEach)="frutList">
        <div each="item">
            Fruit: {item.name} | Color: {item.color}
        </div>
    </span>
</div>
This is the component code snippet
1
2
3
4
5
6
// This is a state variable in the componet with an array of objects
frutList = [
    { name: 'Banana', color: 'yellow' },
    { name: 'Apple', color: 'red' },
    { name: 'Grapes', color: 'violet' },
]

Result:



The (renderIf) Directive example:

This comes into place especially in it comes to handle parts of the App that can or cannot render according to the user permission, if it does not reneders, it woun't even in the DOM tree.

This is the template code snippet
1
2
3
4
5
6
<div>
    This is a regular content which does not require permision
    <div (renderIf)="self.isUserAdmin">
        This requires user permission to render
    </div>
</div>
This is the component code snippet
1
2
3
// Flag needs to be component prop, hence they're annotated with it
/** @Prop */ 
isUserAdmin = false;

(renderIf) works with flag, which true will make it to render, and false not. The flag is defined in the component itself and needs to be annotated with @Prop annotation. When referencing the flag variable it's required to use the self. prefix.



The (showIf) Directive example:

In contrast to (renderIf), the (showIf) is to be used if we still need to render the UI/component part but keep it hidden.

This is the template code snippet
1
2
3
4
5
6
7
8
9
<div>
    <p>
        Click to hide/unhide the date
    </p>
    <button (click)="handleShowDate()">Hide/Unhide date</button>
    <div (showIf)="self.shouldShowDate">
        Today is @todayDate
    </div>
</div>
This is the component code snippet
// Because we don't need to listen to changes, the variable is turned to Prop
/** @Prop */
todayDate = new Date().toDateString();

/** @Prop */
shouldShowDate = true;

handleShowDate() {
    this.shouldShowDate = !this.shouldShowDate;
}

(renderIf) works with flag, which true will make it to render, and false not. The flag is defined in the component itself and needs to be annotated with @Prop annotation. When referencing the flag variable it's required to use the self. prefix.

Animated result:



Form Binding Directives

Directives in the Form binding category allows to handle 2-way binding as wey as handle validation as needed when dealing with form submit, follow hte list:

Directive Applicable to Description Use context
(formRef) dependent Depends on validator directives:
(required) or (validator)
HTML form/form tag Privides a form referance variable to allow handling form validation, however, only makes sense if validation is in place
  • Form validation on submit
(value) HTML form input element (e.g. <input>, <select>, except for radio and checkbox, as thier bind is done with (field) directive instead) Provides two-way binding for a form input against a component state
  • Two-way between component state variable and a Form input.
  • On the Radio button and Checkbox only serves to bind value, but not property.
(field) HTML form, Radio button and Checkboxes Provides two-way binding for a form input against a component state
  • Two-way between component state variable and a Form Radiobutton and/or Checkbox input
(labelAfter) Radio button and Checkboxes text label Puts the specified text label right after (beside) the Radiobutton/Checkbox
  • Presenting label to the used
(labelBefore) Radio button and Checkboxes text label Puts the specified text label right before (beside) the Radiobutton/Checkbox
  • Presenting label to the used


The (formRef) Directive example:

For (formRef) to be effective, form inputs must use validation directives, be bound to component state variables via (value), and include either (required) and/or (validator) directives to enable validation.

This is the template code snippet
<div>
    <!-- formRef is a variable inside the component -->
    <form (formRef)="myForm">
        <!-- Both (value) and (required) are present and mantadory for validation -->
        <select (required)="true" (value)="myCountry">
            <option value="">Select the country</option>
            <option value="1">Austria</option>
            <option value="2">Bulgaria</option>
            <option value="3">Canada</option>
        </select>
        <!-- Both (value) and (required) are present and mantadory for validation -->
        <input type="text" (value)="myField" (required)="true">
    </form>
    <!-- sendForm() method will use the (formRef) to check validation -->
    <button (click)="sendForm()">Submit Form</button>
</div>
This is the component code snippet
//Because we don't need to listen to changes we're turning form-ref to Prop
/** @Prop  */
myForm;
//This state is bound to form combobox
myCountry;
//This state is bound to form input
myField;
//This state is bound the template
submissionResult = `Fill the form`;

sendForm() {
    //Validate method runs and returns validation result
    const isValid = this.myForm.validate();
    if (isValid) {
        this.submissionResult = `
            <span style="color: green;">
                Form subitted successfully.
            </span>
        `;
    }
}

At the end of the day, the validation variable which is assigned to the (formRef) then is provided with validate() method among others, when it gets call, if all validations are passing it'll return true, otherwise it's false, see the result:

Animated result:



The (value) Directive example:

In addition to be a complement for the (formRef) when it comes to validation, (value) directive allows 2-way biding with component state variables.

This is the template code snippet
<div style="padding: 10px;">
    <form>
        <select (value)="myCountry">
            <option value="">Select the country</option>
            <option value="Austria">Austria</option>
            <option value="Bulgaria">Bulgaria</option>
        </select>
        <input type="text" (value)="myField">
    </form>
    <p><b>Selected country:</b> @myCountry</p>
    <p><b>My Field value:</b> @myField</p>
    <button (click)="autoFill()">Auto-fill the form</button>
    <button (click)="emptyCountry()">Clear Country</button>
</div>
This is the component code snippet
// We need to listen to changes of both, hence they're state (not @Prop annotated)
myCountry = '';
myField = '';

autoFill() {
    this.myCountry = 'Bulgaria';
    this.myField = 'Default value';
}

emptyCountry() {
    this.myCountry = '';
}

(renderIf) works with flag, which true will make it to render, and false not. The flag is defined in the component itself and needs to be annotated with @Prop annotation. When referencing the flag variable it's required to use the self. prefix.

Animated result:



Form Validation Directives

Validation Directives make it quite easy to handle validation in Still.js very few lines where only the directive concerning the specific validation needs to be used, follow hte list:

Directive Applicable to Description Use context
(required) HTML form input element (e.g. <input>, <select>) Mark a form elemen as required which gets triggered when validation checked through (formRef)
  • Form validation on submit
(validator) HTML <input> tag Specify of valid data type for an input field
  • Hide/Unhide UI part according to business rules.
(validator-max) dependent Depends (validator) HTML numeric <input> tag Specify the max value allowed in the field
  • Limit the maximun numeric threshold.
(validator-max-warn) dependent Depends (validator) HTML numeric <input> tag Specify message to display if max allowed value is violeted
  • Customize max limit warning message.
(validator-min) dependent Depends (validator) HTML numeric <input> tag Specify the min value allowed in the field
  • Limit the minimun numeric threshold.
(validator-min-warn) dependent Depends (validator) HTML numeric <input> tag Specify message to display if min allowed value is violeted
  • Customize min limit warning message.
(validator-trigger) dependent Depends (validator) HTML <input> tag Specify (validator) should trigger (e.g. when typing, when focus, when blur)
  • Customize min limit warning message.



The (required) Directive example:

It defines the HTML form field/input element as mandatory thereby making it marked for validation, therefore, proper validation requires the form to have a reference (see this example), and validation is verified through it.



The (validator) Directive example:

This directive works with (value) to define valid input types for form elements. Still.js offers 7 default validators, and custom validators can also be implemented.

Follow the built-in Validators:

  • number - Allow only number in the target filed
  • alphanumeric - Allow both number and text but not special characters
  • text - Allow anything
  • email - Allow email only
  • phone - Allow Telephone number format
  • date - Allow data
  • dateUS - Allow date in USA format



Using a validator

To use it we only need to assign its name to the directive (e.g. (validator)="number") hence, every name has to be unique.

This is the template code snippet
<div style="padding: 10px;">
    <form>
        <div class="input-group">
            <label for="">Country:</label>
            <select 
                (validator)="text"
                (value)="myCountry" 
            >
                <option value="">Select the country</option>
                <option value="Austria">Austria</option>
                <option value="Bulgaria">Bulgaria</option>
            </select>
        </div>
        <div class="input-group">
            <label for="">Shoe Size:</label>
            <input 
                (value)="shoeSize" 
                (validator)="number" 
                placeholder="Type the shoe size"
            >
        </div>
    </form>
</div>
This is the component code snippet
1
2
3
// We only need the state variable that are bound to the form inputs
myCountry = '';
shoeSize;

Animated result:

When used in an input text field, unless we override the trigger (by defining (validator-trigger) directive) the validation happens as we type the value. When it comes to combo-box, the validator only takes affect through the (formRef) by calling .validate() as in this example.



Implementing Custom Validator

Custom validators are typically added at the application level in StillAppSetup (app-setup.js). However, they can also be registered elsewhere, like directly within a component, using the .addValidator() utility method.

Custom validator - first 2 are implemented inside the AppSetup, the last one is in the component itself
<div style="padding: 10px;">
    <form>
        <div class="input-group">
            <label for="">Shoe Size:</label>
            <input 
                (value)="shoeSize" 
                (validator)="twoNumberOnly" 
                placeholder="Type the shoe size">
        </div>
        <div class="input-group">
            <label for="">Car Speed capacity:</label>
            <input 
                (value)="speedCapacity" 
                (validator)="max3Chars" 
                placeholder="Type cad speed limit">
        </div>
        <!-- 
         The above validators are implemented in the StillAppSetup, 
         whereas the bellow one is in the component 
        -->
        <div class="input-group">
            <label for="">Name prefix:</label>
            <input 
                (value)="namePrefix" 
                (validator)="prefixValidator" 
                placeholder="Type you name prefix">
        </div>
    </form>
</div>
This is the component code snippet
// The states that are bound to the form
shoeSize;
speedCapacity;
namePrefix;

stOnRender() {
    // A custom validator added before the component is rendered
    StillAppSetup.addValidator('prefixValidator', (value) => {
        if (value === 'First-' || value === 'Sec-')
            return true;
        return false;
    });
}
This is the component code snippet
import { StillAppMixin } from "./@still/component/super/AppMixin.js";
import { Components } from "./@still/setup/components.js";
import { AppTemplate } from "./app-template.js";
import { HomeComponent } from "./app/home/HomeComponent.js";

export class StillAppSetup extends StillAppMixin(Components) {

    constructor() {
        super();
        this.setHomeComponent(HomeComponent);

        //This is a regular expression type of validaor 
        this.addValidator('twoNumberOnly', /^[0-9]{2}$/);

        //This is a function type of validaor
        this.addValidator('max3Chars', (val) => {
            return val.length >= 1 && val.length <= 3;
        });

    }

    async init() {
        return await AppTemplate.newApp();
    }

}

Animated result:



The (validator-trigger) directive

This state when the validation will happen if (required) and/or (validator) is/are defined, therefore, there are 3 type as follow:

  • typing - Default for input text, it makes the validation to trigger as we're typing.
  • losefocus - Check validation when the cursor is moved out from the target input.
  • focus - Checks validation when the cursor is placed inside the target input.