DEV Community

Cover image for Cascading ViewController as a Facade pattern - craftkit best practice
CraftkitJS
CraftkitJS

Posted on • Updated on

Cascading ViewController as a Facade pattern - craftkit best practice

Alt Text

  • ViewController is cascaded to its sub views
  • ViewController can be described as Facade object

Cascading ViewController

When you append a sub-view to View, its ViewController is automatically cascaded.

This is done by Craft.Core.Component#appendSubView(|appendView).

if( this.viewController ){
    component.setViewController(this.viewController);
}
Enter fullscreen mode Exit fullscreen mode

When you append a sub-view to ViewController, the controller is set to the sub-view.

This is done by Craft.Core.DefaultViewController#appendSubView(|appendView).

So, to implement your own ViewController, you have to inherit DefaultRootViewControler.

component.setViewController(this);
Enter fullscreen mode Exit fullscreen mode

Appending ViewController to View or ViewController is same as above.

So, multiple ViewController on the same page(=DOM), like ModalViewController on PageController, run intuitively.

ViewController as Facade

To avoid complex object relation across your components, it is recommended to design your application by Delegate pattern.

Here is an example application using Delegate pattern.
This application places 9 panels on the screen. Click one, show selected num.

Alt Text

class PanelController extends Craft.UI.DefaultViewController {
    viewDidLoad(callback){
        for( let i=0; i<9; i++ ){
            let p = new Panel({
                delegate:this, num:i
            });
            this.appendView({
                id : 'container',
                component : p
            });
        }
        if(callback){ callback(); }
    }
    focus(num){
        this.shadow.getElementById('focus').innerHTML = num;
    }
    style(componentId){
        return `
            #container {
                width: 318px;
                display:flex; flex-direction:row; flex-wrap:wrap;
                margin-right:auto; margin-left:auto;
            }
        `;
    }
    template(componentId){
        return `
            <div>
                <div>
                    Selected: <span id="focus"></span>
                </div>
                <div id="container"></div>
            </div>
        `;
    }
}
class Panel extends Craft.UI.View {
    constructor(options){
        super(options);
        this.delegate = options.delegate;
        this.data = { num:options.num };
    }
    style(componentId){
        return `
            .root {
                box-sizing:border-box;
                width:100px; height:100px; margin:3px;
                background-color:#eee;
                display: flex;
                justify-content: center;
                align-items: center;
            }
        `;
    }
    template(componentId){
        return `
            <div class="root">
                <div onclick="${componentId}.delegate.focus(${componentId}.data.num)">
                    select: ${this.data.num}
                </div>
            </div>
        `;
    }
}
Enter fullscreen mode Exit fullscreen mode

ViewController is always cascaded.

So, you can write above sample like this.

class PanelController extends Craft.UI.DefaultViewController {
    viewDidLoad(callback){
        for( let i=0; i<9; i++ ){
            let p = new Panel({
                num:i // without delegate
            });
            this.appendView({
                id : 'container',
                component : p
            });
        }
        if(callback){ callback(); }
    }
    focus(num){
        this.shadow.getElementById('focus').innerHTML = num;
    }
    style(componentId){
        return `
            .root { box-sizing:border-box; }
            #container {
                width: 318px;
                display:flex; flex-direction:row; flex-wrap:wrap;
                margin-right:auto; margin-left:auto;
            }
        `;
    }
    template(componentId){
        return `
            <div class="root">
                <div>
                    Selected: <span id="focus"></span>
                </div>
                <div id="container"></div>
            </div>
        `;
    }
}
class Panel extends Craft.UI.View {
    constructor(options){
        // constructor without delegate
        super(options);
        this.data = { num:options.num };
    }
    style(componentId){
        return `
            .root {
                box-sizing:border-box;
                width:100px; height:100px; margin:3px;
                background-color:#eee;
                display: flex;
                justify-content: center;
                align-items: center;
            }
        `;
    }
    template(componentId){
        return `
            <div class="root">
                <!-- call via viewController -->
                <div onclick="${componentId}.viewController.focus(${componentId}.data.num)">
                    select: ${this.data.num}
                </div>
            </div>
        `;
    }
}
Enter fullscreen mode Exit fullscreen mode

In this code, PanelController looks more like implicit Facade pattern than Delegate pattern.

NOTE

Above examples are runnable on playground.

var view = new PanelController();
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)