Skip to content

craftkit/craft-uikit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Craft-UIKit

Craft-UIKit is a JavaScript UI library for Clear Web OOP.

Try online tutorial:
https://github.com/craftkit/craftkit-playground

Component

component

The most core element of Craft-UIKit is representedy by the base class Craft.Core.Component. This wraps shadow host, shadow root, tree under the shadow, styles for them and its actions.

Instance of Component is identified by componentId. By default, this is automatically generated by its packagename defined as its class variable, with appending sequencial number to be able to identify in the global scope. If it is not defined, just used its class name for it.

The component instance is a JavaScript object. So, you can access to the instance via its componentId from anywhere you want.

example

For example, your class has packagename MyApp.Hello.
Your componentId will be something like MyApp_Hello_0.

console:

> var hello_world = new HelloWorld();
> hello_world.loadView();
> document.body.appendChild(hello_world.view);

> MyApp_HelloWorld_0
HelloWorld {packagename: "MyApp.HelloWorld", componentId: "MyApp_HelloWorld_0", view: div#MyApp_HelloWorld_0, css: Array(1), …}

> MyApp_HelloWorld_0.view
<div id="MyApp_HelloWorld_0">...</div>

> MyApp_HelloWorld_0.shadow
#shadow-root (open)

> MyApp_HelloWorld_0.root
<div class="root">...</div>

> MyApp_HelloWorld_0.css
[style#MyApp_HelloWorld_0_1]

> MyApp_HelloWorld_0.say()
(say() is invoked -> show "Hello World!" in the page)

※ Dot(.) is converted to underscore(_).
※ componentId has suffix of auto generated serial number.

View and ViewController

component

The Component is concretized by View(Craft.UI.View) and ViewController(Craft.UI.DefaultViewController). Both are sub-class of Craft.Core.Component. View is an element packing its template, style and action. ViewController is also a kind of View having some View elements in it, and manages them.

Your application may also have special ViewController called RootViewController(Craft.UI.DefaultRootViewController). This is a root element of your application, and should be set by Craft.Core.Context.setRootViewController at the start of your application. In Craft-UIKit application, the RootViewController should also manage popstate event and history by implementing appropriate interface.

Let's see a Hello World example:

class HelloWorld extends Craft.UI.DefaultViewController {
    constructor(){
        super();
        this.packagename = 'MyApp.HelloWorld';
    }
    viewDidLoad(callback){
        this.hello = new Hello();
        this.world = new World();
        if(callback){ callback(); }
    }
    say(){
        this.appendView(this.hello);
        this.appendView(this.world);
    }
    style(componentId){
        return `
            :host { width: 100%; }
            .root { display flex; flex-direction: row; }
        `;
    }
    template(componentId){
        return `
            <div class='root'></div>
        `;
    }
}

class Msg extends Craft.UI.InlineBlockView {
    style(componentId){
        return `
            .root { margin: 10px; }
            .msg { color: blue; }
        `;
    }
    template(componentId){
        return `
            <div class='root'>
                <span class='msg'>${this.msg}<\span>
            </div>
        `;
    }
}

class Hello extends Msg {
    constructor(){
        super();
        this.packagename = 'MyApp.Hello';
        this.msg = 'Hello';
    }
}

class World extends Msg {
    constructor(){
        super();
        this.packagename = 'MyApp.World';
        this.msg = 'World!';
    }
    style(componentId){
        return super.style(componentId) + `
            .msg { color: red; }
        `;
    }
}

Template and Style

Component has a DOM tree in this.root. This is based on template().

template() must return a HTML expression that starting with a single element. The returning HTML is evalutated as HTML template, and converted to a DOM fragment by Craft.Core.Component.renderView. Its first element is used for this.root.

When you define id and class for the root element, you have to name it as 'root'. This will simplify to cascade style-sheet of super class.

template(componentId){
    return `
        <div id='root' class='root'>
            ...
        </div>
    `;
}
GOOD:
    <div>
        <span>Hello world!</span>
    </div>
    
BAD:
    <div>
        <span>Hello</span>
    </div>
    <div>
        <span>world!</span>
    </div>
    
Also BAD: 
    <!-- hello message: this is also a DOM -->
    <div>
        <span>Hello world!</span>
    </div>

Component also has shadowed style tags in this.css. This is defined by style() method and it should return usual style sheet expression. You can access host element this.view by :host pseudo class name.

To cascade super class style, just append yours on it.

style(){
    return super.style() + `
        .msg { color: red; }
    `;
}

First argument for template() and style() is its componentId (same as this.componentId). In style method, you may not use it, but sometimes it may be required to cascade styles from your super class. In template method, it is used for accessing its method.

Component Method

To call instance method from its template, all you have to do is just call it via ${componentId}.

class Wow extends Craft.UI.View {
    say(msg){
        alert(`oh ${msg}`);
    }
    template(componentId){
        return `
            <div onclick='${componentId}.say("wow")'>Say wow</div>
        `;
    }
}

Traditional JavaScript programmer may like self instead of componentId.

class Wow extends Craft.UI.View {
    say(msg){
        alert(`oh ${msg}`);
    }
    template(self){
        return `
            <div onclick='${self}.say("wow")'>Say wow</div>
        `;
    }
}

Indeed, this is not default behaviour. As can be seen above, window[componentId] holds its instacne. This is enabled by setting Craft.Core.Defaults.ALLOW_COMPONENT_SHORTCUT to true. You may set this at the begenning of you app. You can select this behavior, but you may love to use this shortcut.

Without this shourcut, you can call instance method like following:

onclick="window.Craft.Core.ComponentStack.get('${componentId}').say('wow')"

Additionaly, if you know componentId for another instance, you can call any method via it, like as global shared function.

Public library developer shoud write its template by verbose mode using fully quolifiied component access, to be able to run without shourcut.

Component Lifecycle

Component lifecycle is just a contract with you. If you write your own ViewController, you have to honor those definition to keep your life safe, like as Craft.Core.Component, Craft.UI.View and Craft.UI.DefaultViewController are doing so. In other words, while you extends those classes and keep this way, lifecycle is guaranteed.

lifecycle

lifecycle method what is
loadView make this.view and this.css
viewDidLoad Called at the end of loadView
viewWillAppear Called just before appending this.view to the parent
viewDidAppear Called just after this.view appended to the parent
viewWillDisappear Called just before removing this.view from its parent
viewDidDisappear Called just after this.view removed from its parent
unloadView remove view and css

Related method:

method what is
appendSubView append sub-component view
removeSubView remove sub-component view
removeFromParent remove component view from parent

Routing and RootViewController

routing

In Craft-UIKit application, RootViewController has responsibility for routing. At the time booting application via Craft.Core.Bootstrap, listener for popstate is registered against RootViewController.didReceivePopstate.

The recieved PopState event is passed for Router. Default router is Craft.Core.HashRouter, using '#' for routing component.

You can define your original router in your application config object like described in the next section. Craft-UIKit provides both HashRouter for '#' and PathRouter for '/'. The former is default.

Router parses the PopState event and window.location, then pass it to resolveRoutingRequest. You have to implement your own routing logic in it. Or you have to override DefaultRootViewController.didReceivePopstate as you like.

class PageController extends Craft.UI.DefaultRootViewController {
    :
    resolveRoutingRequest(route){
        switch(route.path){
            case 'page1':
                this.open({ page:new Page1() });
                break;
            case 'page2':
                this.open({ page:new Page2() });
                break;
            default:
                this.open({ page:new NotFound() });
                break;
		}
    }
    :
}

Booting application

Application entry point is Craft.Core.Bootstrap.boot. You must kick this function when window.onload occured.

boot function requires an object containing a function named as didBootApplication. This is your application entry point.

To start routing at booting time, you have to call didReceivePopstate of your RootViewController. For convenience, this is implemented as DefaultRootViewController.bringup.

window.onload = function(){
    Craft.Core.Defaults.ALLOW_COMPONENT_SHORTCUT = true;
    Craft.Core.Bootstrap.boot({
        router : Craft.Core.PathRouter,
        didBootApplication : function(){
            const rootViewController = new PageController();
            Craft.Core.Context.setRootViewController(rootViewController);
            rootViewController.bringup();
        }
    });
};

License

MIT