DEV Community

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

Posted on • Edited on

1

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

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

If this article connected with you, consider tapping ❤️ or leaving a brief comment to share your thoughts!

Okay