CKN Technology | UI

UI Component
(DOM State Management Library)

This guide explains how to use the Component class as an application developer. It focuses only on what you write in your own components (class Counter extends Component, etc.) – no internal engine details.

1. What is a Component?

A Component is a UI unit that:

  • Manages its own state (e.g. this.count, this.createdAtFrom).
  • Defines its HTML template using onTemplateBuilding().
  • Handles events (click, change, input, etc.).
  • Supports binding, loops, conditions, and two-way binding.
  • Can call backend APIs (via ckn.api) and update the UI.

Typical pattern:

import { Component } from "@ckn-technology/ui";

class Counter extends Component {
    constructor() {
        super();
        this.count = 0;
    }

    increment() {
        this.count++;
    }

    async onTemplateBuilding() {
        return /*html*/`
            <div>
                Count: {{ this.count }}
                <button click()="{{ this.increment() }}">+</button>
            </div>
        `;
    }
}

2. Lifecycle You Actually Use

2.1 constructor()

Use the constructor to set initial state (simple synchronous values).

constructor() {
    super();
    this.createdAtFrom = new Date().addDays(-7).dateDataFormat();
    this.createdAtTo   = new Date().addDays(1).dateDataFormat();
    this.searchText    = "";
}

Good for initial default values that don’t need async operations.

2.2 onInitializing()

Use this for async initial loading, e.g. call APIs and build data.

async onInitializing() {
    let data = await ckn.api.get("/company");
    // prepare this.companies = ...
}

2.3 onTemplateBuilding()

This is the core of your Component – return HTML as a string.

Example inspired by WEB06_ResourceIssueHistory:

async onTemplateBuilding() {
    return /*html*/`
        <div>
            <h3>Resource Issue History</h3>

            <div class="filter-controls">
                <input type="text"
                       placeholder="Search by resource name..."
                       @value="{{this.searchQuery}}">
                <button click()="{{this.applySearch()}}">Search</button>
            </div>

            <table class="issue-table">
                <thead>
                    <tr>
                        <th>Issue ID</th>
                        <th>Resource Name</th>
                        <th>Date Reported</th>
                        <th>Status</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    <tr :for="{{issue of this.filteredIssues}}">
                        <td>{{issue.id}}</td>
                        <td>{{issue.resourceName}}</td>
                        <td>{{issue.reportedDate}}</td>
                        <td>{{issue.status}}</td>
                        <td>
                            <button click()="{{this.showIssueDetails(issue.id)}}">
                                View
                            </button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    `;
}

2.4 Optional Hooks

You may override these when needed:

async onFinalizing() {
    // Called after component is rendered into DOM
}

async onProcessExternalScript() {
    // Called whenever state changes and extra processing is needed
}

3. Data Binding – {{ ... }}

Anywhere in your template, you can use:

{{ this.someProperty }}
{{ this.someMethod() }}
{{ expression }}

Examples:

<td>{{coupon.code}}</td>
<td>{{coupon.qty}}</td>
<td>{{coupon.planReturnDate}}</td>
<div>Count: {{ this.count }}</div>

The framework evaluates the expression and renders the value into the DOM.

4. Attribute Binding & Shorthands

4.1 Direct attribute binding

You can bind expressions directly to attributes:

<input value="{{ this.searchText }}">

4.2 Shorthands supported

To make writing templates easier, the framework supports shorthand attributes:

Shorthand Expanded to Usage
:for="..." ckn-for="..." Loop rendering
@value="..." ckn2wb-value="..." Two-way binding for value
change()="x" cknevent-change="x" Bind change event
click()="x" cknevent-click="x" Bind click event

You don’t need to write the low-level ckn-* attributes directly; just use these shorthands.

<tr :for="{{coupon of this.completeWithdrawAndWithdrawBy}}">
    {{coupon.code}}
</tr>

<input type="date" @value="{{this.createdAtFrom}}">
<button click()="{{this.filter()}}">Search</button>

5. Loop Rendering – :for / ckn-for

Use :for in your template to render a list:

<tr :for="{{coupon of this.completeWithdrawAndWithdrawBy}}">
    <td>{{coupon.index}}</td>
    <td>{{coupon.code}}</td>
    <!-- ... -->
</tr>

In your component, you simply prepare an array:

this.completeWithdrawAndWithdrawBy = [];
let index = 1;

for (let coupon of result.coupons.where(c => c.statusCode === "OK")) {
    this.completeWithdrawAndWithdrawBy.push({
        index: index++,
        code: coupon.couponNumber,
        // ...
    });
}

When the array changes, the UI is re-rendered automatically after event handlers finish.

6. Event Handling

6.1 Basic event usage

Use click() / change() shorthands in the template:

<button click()="{{this.filter()}}">Search</button>
<input type="text" change()="{{this.searchResourceIssueHistory(target)}}">
  • click() → triggers when the element is clicked.
  • change() → triggers when the input value changes.
  • target is the DOM event target (HTML element).

6.2 Define handler methods

In your class, implement handlers as normal async functions:

async filter() {
    this.createdAtFrom = new Date(this.createdAtFrom).dateDataFormat();
    this.createdAtTo   = new Date(this.createdAtTo).dateDataFormat();

    let i = 1;
    this.results = this.originalList
        .where(c => c.createAt.dateDataFormat() >= this.createdAtFrom
                 && c.createAt.dateDataFormat() <= this.createdAtTo)
        .where(c => c.index = i++);
}

async searchResourceIssueHistory(target) {
    if (target != null) this.searchText = $(target).val();
    // filter based on date range + text search
}

You do not need to call any manual “reload” function; the framework re-processes bindings automatically after the handler finishes.

7. Two-Way Binding – @value

Two-way binding is convenient for forms. It keeps the component state and input value in sync.

7.1 Date input example

<input type="date" @value="{{this.createdAtFrom}}">
<input type="date" @value="{{this.createdAtTo}}">
  • When the component updates this.createdAtFrom, the input is updated.
  • When the user changes the input, this.createdAtFrom is updated automatically.

Then you simply use those properties in your logic:

async filter() {
    this.createdAtFrom = new Date(this.createdAtFrom).dateDataFormat();
    this.createdAtTo   = new Date(this.createdAtTo).dateDataFormat();
    // filter logic ...
}

8. Working with Backend APIs

The framework works nicely with ckn.api to load or submit data.

async reloadDataFromBackend() {
    const output = await ckn.api.post("/transaction/list", {
        date: this.date,
        type: this.type,
        status: this.status,
        search: this.searchText,
    });

    this.results = [];
    let index = 1;

    for (let coupon of output.data) {
        this.results.push({
            id: coupon.id,
            index: index++,
            code: coupon.couponNumber,
            // other fields...
        });
    }

    this.originalList = this.results;
}

Typical usage:

  • Call await this.reloadDataFromBackend() inside onInitializing().
  • Bind this.results with :for in your template.

9. Using External Libraries & DOM

You can freely use DOM APIs and libraries (like XLSX, jQuery) inside your component methods.

9.1 Export table to Excel

async exportTableToExcel() {
    let date = new Date().dateDataFormat();
    const table = document.getElementById("myTable");
    const workbook = XLSX.utils.table_to_book(table, { sheet: "Sheet1" });

    XLSX.writeFile(workbook, `report - (${date}).xlsx`);
}

Template:

<button click()="{{this.exportTableToExcel()}}">
    Export to Excel
</button>

10. Putting It All Together – Page-Level Component

Example: an IssueList page component that uses state, initialization, backend calls, loops, filters, and export.

import { ckn } from "@ckn-technology/core";
import { Component } from "@ckn-technology/ui";

class IssueList extends Component {
    constructor() {
        super();
        this.createdAtFrom = new Date().addDays(-7).dateDataFormat();
        this.createdAtTo   = new Date().addDays(1).dateDataFormat();
        this.searchText    = "";
        this.results       = [];
        this.originalList  = [];
    }

    async onInitializing() {
        await this.reloadDataFromBackend();
    }

    async reloadDataFromBackend() {
        const result = await ckn.api.post("/issue/list", {
            createdAtFrom: this.createdAtFrom,
            createdAtTo:   this.createdAtTo,
            searchText:    this.searchText,
        });

        let index = 1;
        this.results = [];

        for (let issue of result.issues || []) {
            this.results.push({
                id:           issue.id,
                index:        index++,
                resourceName: issue.resourceName,
                reportedDate: issue.reportedDate,
                status:       issue.status,
            });
        }

        this.originalList = this.results;
    }

    async filter() {
        this.createdAtFrom = new Date(this.createdAtFrom).dateDataFormat();
        this.createdAtTo   = new Date(this.createdAtTo).dateDataFormat();

        let i = 1;
        this.results = this.originalList
            .where(c => c.reportedDate >= this.createdAtFrom
                     && c.reportedDate <= this.createdAtTo)
            .where(c => c.index = i++);
    }

    async searchResourceIssueHistory(target) {
        if (target != null) this.searchText = $(target).val();
        // apply text filtering on this.originalList + date range...
    }

    async exportTableToExcel() {
        let date = new Date().dateDataFormat();
        const table = document.getElementById("issueTable");
        const workbook = XLSX.utils.table_to_book(table, { sheet: "Issues" });
        XLSX.writeFile(workbook, `issue-list-(${date}).xlsx`);
    }

    async onTemplateBuilding() {
        return /*html*/`
            <div>
                <h3>Issue List</h3>

                <div class="filters">
                    <input type="date" @value="{{this.createdAtFrom}}">
                    <input type="date" @value="{{this.createdAtTo}}">
                    <input type="text"
                           placeholder="Search..."
                           @value="{{this.searchText}}">

                    <button click()="{{this.filter()}}">Filter</button>
                    <button click()="{{this.exportTableToExcel()}}">
                        Export
                    </button>
                </div>

                <table id="issueTable">
                    <thead>
                        <tr>
                            <th>#</th>
                            <th>Resource</th>
                            <th>Date</th>
                            <th>Status</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr :for="{{issue of this.results}}">
                            <td>{{issue.index}}</td>
                            <td>{{issue.resourceName}}</td>
                            <td>{{issue.reportedDate}}</td>
                            <td>{{issue.status}}</td>
                        </tr>
                    </tbody>
                </table>
            </div>
        `;
    }
}

export { IssueList };

This is the typical way you build screens/pages with @ckn-technology/ui: state in properties, data load with ckn.api, table rendering with :for, filtering via events, and export/utility actions with normal JavaScript.

11. Summary

This guide outlines how to build interactive components and full pages using @ckn-technology/ui.

  • Manage component state with simple properties on this.
  • Use onInitializing() for async data loading and onTemplateBuilding() for HTML templates.
  • Render dynamic data with {{ ... }} bindings and :for loops.
  • Handle user interaction with click() and change() shorthands.
  • Use @value for convenient two-way binding in forms.
  • Integrate backend APIs via ckn.api and update component state.
  • Combine with libraries and DOM APIs (e.g. Excel export) inside normal methods.

With these patterns, you can quickly build robust, data-driven UI components like Counter, WEB06_ResourceIssueHistory, or full page components such as IssueList, while keeping your code clean, testable, and easy to maintain.