DEV Community

Cover image for Data injection - How to update a view by remote data - craftkit best practice
CraftkitJS
CraftkitJS

Posted on • Updated on

Data injection - How to update a view by remote data - craftkit best practice

Data will always come from outside of application asynchronously. So we are always plagued by way of loading data, and making its view. If you don't care about lag of data load, your interface easily stack.

Best practice: Data injection

Alt Text

The best practice for this problem is "load data then make its view" pattern. (called as "Data injection" pattern)

It is best to implement controller class to load remote data, that makes its factorized view (Factorize pattern) after loaded data.

In other word, this is a kind of GoF Facade pattern, or minimized MVC structure. Usually, this controller represents reusable package.

If factorized view classes need to load remote data, the request should be delegated to the controller class. (Delegate pattern)

This will make complexity aggregated in the controller.

Example

Here is a sample remote data same as craftkit best practice - How to manage sub views for array data.

https://craftkit.dev/craftkit-playground/sampledata/simple_products.json

[
    { name: 'Apple', price: 10 },
    { name: 'Orange', price: 13 },
    { name: 'Strawberry', price: 20 },
    { name: 'Pear', price: 9 }
]
Enter fullscreen mode Exit fullscreen mode

Here is a sample implementation to load and show remote product list.

class ProductListController extends Craft.UI.View {
    constructor(options){
        super(options);
        this.data = { products:[] };
        this.views = { current:null };
    }
    viewDidLoad(callback){
        this.loadData();
        this.renderReloadBtn();
        if( callback ){ callback(); }
    }
    loadData(){
        this.renderLoading();

        let xhr = new XMLHttpRequest();
        xhr.onload = (e) => {
            this.data.products = JSON.parse(xhr.response);
            this.renderProductList();
        };
        xhr.open('GET','https://craftkit.dev/craftkit-playground/sampledata/simple_products.json');
        xhr.send();
    }
    renderLoading(){
        let view = new Loading();
        this.replaceView({id:'list',component:view});
        this.views.current?.unloadView(); // for chrome
        this.views.current = view;
    }
    renderProductList(){
        let view = new ProductList({delegate:this});
        this.replaceView({id:'list',component:view});
        this.views.current?.unloadView(); // for chrome
        this.views.current = view;
    }
    renderReloadBtn(){
        this.appendView({
            id: 'btn',
            component: new ReloadBtn({delegate:this})
        });
    }
    style(componentId){
        return `
            #btn { background-color:#eee; }
        `;
    }
    template(componentId){
        return `
            <div id="root" class="root">
                <div id="list"></div>
                <div id="btn"></div>
            </div>
        `;
    }
}

class Loading extends Craft.UI.View {
    template(componentId){
        return `
            <div>Loading...</div>
        `;
    }
}
class ReloadBtn extends Craft.UI.View {
    constructor(options){
        super(options);
        this.delegate = options.delegate;
    }
    reloadData(){
        this.delegate.loadData();
    }
    style(componentId){
        return `
            .root { cursor:pointer; }
        `;
    }
    template(componentId){
        return `
            <div id="root" class="root" 
                onclick="${componentId}.reloadData();">
                Reload data
            </div>
        `;
    }
}

class ProductList extends Craft.UI.View {
    constructor(options){
        super(options);
        this.delegate = options.delegate;
        this.views = { products:[] }
    }
    viewDidLoad(callback){
        this.delegate.data.products.forEach( p => {
            let view = new Product(p);
            this.appendView(view);
            this.views.products.push(view);
        });
    }
}
class Product extends Craft.UI.View {
    constructor(options){
        super(options);
        this.data = options;
    }
    template(componentId){
        return `
            <div id="root" class="root">
                ${this.data.name} : ${this.data.price}
            </div>
        `;
    }
}
Enter fullscreen mode Exit fullscreen mode

Sometimes, controller is represented as Page object to be shown in navigation stack. Sometimes, controller is represented as micro-fronend UI component.

NOTE

Above examples are runnable on playground.

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

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

Top comments (0)