DEV Community

Cover image for How to update view - craftkit code pattern
CraftkitJS
CraftkitJS

Posted on • Edited on

How to update view - craftkit code pattern

Currently, Craft.UI.View mainly provides UI classes to encapsulate its method, data and DOM structure. View component is automatically rendered by ViewController according to view's lifycycle method implementation.

After you append view on the screen, you have to update its view (=DOM) by your self. (Therefore this is named as craft kit)

Shadow access pattern

Most primitive pattern is finding DOM element via this.shadow.

class CounterView extends Craft.UI.View {
    constructor(options){
        super(options);
        this.data = { count:0 };
    }
    countup(){
        this.data.count++;
        this.shadow.getElementById('counter').innerHTML = this.data.count;
    }
    template(componentId){
        return `
            <div id="root" class="root">
                <span id="counter">${this.data.count}</span>
            </div>
        `;
    }
}
Enter fullscreen mode Exit fullscreen mode

In the latest chrome, you can use replaceChildren instead of innerHTML.

renderView pattern

Most basic pattern is using Craft.UI.View#renderView method.

This example is just replacing above this.shadow.getElementById to renderView.

class CounterView extends Craft.UI.View {
    constructor(options){
        super(options);
        this.data = { count:0 };
    }
    countup(){
        this.data.count++;
        this.renderView();
    }
    template(componentId){
        return `
            <div id="root" class="root">
                ${this.data.count}
            </div>
        `;
    }
}
Enter fullscreen mode Exit fullscreen mode

Every time you call countup() method, template is rendered again. This pattern is useful while your data is simple.

If your view does not have this.data at the time it is instantiated, your template will be defined by optional chain somthing like this: ${this.data.YourDataSet?.count}.

Swap and renderView pattern

In more complex application, you might swap template dynamically.

class CounterView extends Craft.UI.View {
    constructor(options){
        super(options);
        this.data = { count:0 };
        this.templates = {
            even : `
                <div id="root" class="root">
                    Some complex even view: ${this.data.count}
                </div>`,
            odd  : `
                <div id="root" class="root">
                    Some complex odd view: ${this.data.count}
                </div>`,
        };
        this.crr_template = this.templates.even;
    }
    countup(){
        this.data.count++;
        if( this.data.count % 2 == 0 ){
            this.crr_template = this.templates.even;
        }else{
            this.crr_template = this.templates.odd;
        }
        this.renderView();
    }
    template(componentId){
        return this.crr_template;
    }
}
Enter fullscreen mode Exit fullscreen mode

renderView() renders template defined by this.crr_template.

Factorize pattern

This pattern defines more macro structure.

Also described in the post "How to manage sub views for array data", let's break down your template structure until it is almost atomically resolved.

class CounterView extends Craft.UI.View {
    constructor(options){
        super(options);
        this.data = { count:0 };
        this.views = { numbox:null };
    }
    countup(){
        this.data.count++;
    }
    updateNumBoxView(){
        let view = new NumBox({num:this.data.count});
        this.replaceView({
            id        : 'container',
            component : view,
        });
        this.views.numbox.unloadView();
        this.views.numbox = view;
    }
    viewDidLoad(callback){
        this.updateNumBoxView();
        if(callback){ callback(); }
    }
    template(componentId){
        return `
            <div id="root" class="root">
                <div id="container"></div>
            </div>
        `;
    }
}

class NumBoxView extends Craft.UI.View {
    constructor(options){
        super(options);
        this.data = options;
    }
    template(componentId){
        return `
            <div id="root" class="root">
                ${this.data.count}
            </div>
        `;
    }
}
Enter fullscreen mode Exit fullscreen mode

Every time you update count, new view component will be created and placed in the #id:container. Call unloadView if you have to expire its memory.

Combined

Actual web application will load its data from remote api, or will be generated by user in run time, or so on. So you may have to combine several patterns.

class CounterView extends Craft.UI.View {
    constructor(options){
        super(options);
        this.data = { count:0 };
        this.views = { 
            even : new EvenNumBoxView({ delegate:this }),
            odd  : new OddNumBoxView({ delegate:this })
        };
    }
    viewDidLoad(callback){
        this.updateNumBoxView();
        if(callback){ callback(); }
    }
    countup(){
        this.data.count++;
        this.updateNumBoxView();
    }
    updateNumBoxView(){
        if( this.data.count % 2 == 0 ){
            this.views.even.renderView();
            this.replaceView({
                id        : 'container',
                component : this.views.even,
            });
        }else{
            this.views.odd.renderView();
            this.replaceView({
                id        : 'container',
                component : this.views.odd,
            });
        }
    }
    template(componentId){
        return `
            <div id="root" class="root">
                <div id="container"></div>
            </div>
        `;
    }
}

class EvenNumBoxView extends Craft.UI.View {
    constructor(options){
        super(options);
        this.delegate = options.delegate;
    }
    template(componentId){
        return `
            <div id="root" class="root">
                Some complex even view structure: 
                ${this.delegate.data.count}
            </div>
        `;
    }
}

class OddNumBoxView extends Craft.UI.View {
    constructor(options){
        super(options);
        this.delegate = options.delegate;
    }
    template(componentId){
        return `
            <div id="root" class="root">
                Some complex odd view structure: 
                ${this.delegate.data.count}
            </div>
        `;
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, EvenNumBoxView and OddNumBoxView delegate data management to CounterView. (This is called as "delegate pattern")

NOTE

Above examples are runnable on playground.

var view = new CounterView();
view.loadView();
Craft.Core.Context.getRootViewController().appendSubView(view);
view.countup();
Enter fullscreen mode Exit fullscreen mode

🛺 Try CraftKit Playground:
https://github.com/craftkit/craftkit-playground

Top comments (0)