Model-Centric View Composition


This is one of the ideas that are neither constructed a-priori nor abstracted from accumulated experiences of solving different but similar problems; this emerged naturally from only one single project. I'm not saying that this is better than what people are used to or even this is any good at all, but I believe it's worth writing down.

What is this "model-centric view composition"?

It's exactly what its name says: a GUI application written in this way would be a composition (or a combination of multiple compositions) of views with their actual content determined by models. The reason why it is "model-centric" is that views, instead of being a relatively-standalone thing like in common implementations of MVC and MVVM, are only a part (or avatars, to be more exact) of models. "Composition", most of the time, merely means "combination" and roughly corresponds to a single web page in a Multi-Page Application.

diagram.png

Figure 1: A graph demonstrating the structure of a possible MCVC app.

How this idea came into being

So I was trying to make a WYSIWYG UI editor that's like a mix of Microsoft Powerpoint and VB6; in this editor there are a lot of case of changes coming from different places and need to be synced to different places. Since it was still just a mock-up I decided to do everything in vanilla JavaScript; I ended up making something like this:

class Attribute {
    public static name = 'width';
    public static description = 'The width of the component.';
    private _value = new Observable();
    public setValue(newValue) { this._value.update(newValue); }
    public getValue() { return this._value.currentValue(); }
    public makeEditorView() {
        let res = document.createElement('div');
        // adding name & description & text field to res & setting up 2-way data binding
        return res
    }
}
function MakeAttributeTable(...) {
    // ... some code used to simplify the making of a table of attributes.
}
class Button {
    private _text = new Observable('');
    // separating publicly-available & private attributes, should you want some attributes to be hidden...
    private _attributes = MakeAttributetable(...);
    public attributes = { ... };

    // 
    public makeMainView() {
        let res = document.createElement('button');
        // setting up 2-way data binding
        return res;
    }
    public makeEditorView() {
        let res = document.createElement('button');
        // setting up 2-way data binding
        // then add things specific to the UI editor, e.g. drag to move/resize, double-click to rename, etc.
        return res;
    }
}
class CurrentEditorState {
    private static _componentList = new Observable([]);
    private static _focusedComponent = new Observable();
    private static _attribute = CurrentEditorState._focusedComponent.derive((x) => x.getAllAttributes());
    public static makeAttributeEditorView() {
        let res = document.createElement('div');
        // setting up attribute editor
        return res;
    }
    public static makeEditorView() {
        let res = document.createElement('div');
        // setting up editor
        return res;
    }
    public static makeMainView() {
        let res = document.createElement('div');
        // setting up main view
        res.appendChild(this.makeEditorView());
        res.appendChild(this.makeAttributeEditorView());
        // ...
        return res;
    }
}

The motive behind this is that: if I set up the data binding properly in all the makeView methods, I can always get a view that's guaranteed to be able to push & receive updates through these methods anywhere anytime I want.

Addendum: Command-Driven Model

The phrase "command-driven" means that the update of the model (or models) is not conceptually a singular thing but as a part of a "command" or "transaction". This idea is to solve a very specific problem; it requires the following to make sense:

  • The changes that could happen for your models can indeed be abstracted in a handful of commands;
  • These commands could and would be occuring in a non-predetermined fashion, e.g. it is expected to be configured with an external source.

Back

Last update: 2024.6.6