Skip to content

Align subviews life-cycle with parent view #113

@cquinders

Description

@cquinders

After playing around with the subviews property of ampersand-view I feel like it’s making it harder to work with a given subview than it has to be.

If I want to listen to an event from a subview for instance, it is not really clear where I would have to put my listenTo code, because the subview is initialized after some change event from the parent view.

My ideal scenario would look like this:

var AmpersandView = require("ampersand-view");
var CollectionView = require("ampersand-collection-view");
var ViewSwitcher = require("ampersand-view-switcher");

module.exports = AmpersandView.extend({
    template: "<div><div></div><ul data-hook=\"collection-container\"></ul></div>",

    subviews: {
        stuffView: {
            container: "[data-hook=collection-container]",
            constructor: CollectionView
        },
        tabSwitcher: {
            container: "[data-hook=switcher]",
            constructor: ViewSwitcher
        }
    },

    initialize: function () {
        AmpersandView.prototype.initialize.apply(this, arguments);
        // Because subviews are initialized with the view it’s save to
        // rely on them everywhere in the containing view.
        this.listenTo(this.stuffView, "some:event", this._handleSomeEvent);
        this.listenTo(this.tabSwitcher, "show", this._handleTabShow);
        // Alter subview properties or call subview methods without checking
        // if the subview is already initialized and having to handle it async.
        this.stuffView.collection = this.model.stuffCollection;
    },

    rerender: function () {
        // Maybe establish a `rerender` which only renders all the subviews but
        // leaves this views dom setup untouched ...
        this._renderSubviews();
        return this;
    },

    _handleSomeEvent: function (target, value) {
        // handle `some:event` in here
    },

    _handleTabShow: function (target, value) {
        // handle the `show` event in here
    }
});

So basically I’m proposing to align the subview’s life-cycle with that of the parent view:

  • initialize calls _initializeSubviews() which initializes the configured subviews, registerSubview() them and assignes them to a property on this view.
  • render calls _renderSubviews() which calls renderSubview() for all initialized subviews. This could also be used for rerendering subviews.
  • remove calls remove() on all registered subviews.

Now if you want to do some work on a view before or after subviews are rendered you can override render and call View.prototype.render from there (In some cases this could address the need for events demanded in #70).

I think all of this could be implemented in a backwards compatible manner. So if this doesn’t suit your need you could call registerSubview() or renderSubview() like before.

One possible use case would be mitigating problems with binding server rendered views. Currently, if you want to attach a view instance to an existing element, subviews are a nightmare because they immediately render after a view is initialized.

It would be cool if we could establish a bind() method or something:

// bindable-view.js
var AmpersandView = require("ampersand-view");

module.exports = AmpersandView.extend({

    bind: function (el) {
        // Attach view to given el and bind configured subviews
        this.el = el;
        this._bindSubviews();
        return this;
    },

    bindSubview: function (view, el) {
        // Query this view for el selector
        el = (typeof el === "string") ? this.query(el) : el;
        // Bind subview to found el
        view.bind(el);
        return view;
    },

    _bindSubviews: function () {
        if (!this.subviews) {
            return;
        }
        each(this.subviews, function (config, name) {
            var subview = this[name];
            this.bindSubview(subview, config.selector);
        }, this);
    }
});

And then we could use it to take over an existing DOM element:

<html>
<body>
    <div id="my-view">
        <h1 data-hook="title"></h1>
        <p>This is some static text</p>
    </div>
</body>
</html>
var BindableView = require("./bindable-view");

var PageView = BindableView.extend({
    bindings: {
        "model.title": {
            type: "text",
            hook: "title"
        }
    }
});

var myModel = new Model({title: "Hello"});
var myView = new PageView({model: myModel});

// Attach myView to `#my-view` DOM element
myView.bind(document.querySelector("#my-view"));

// Altering the model now triggers DOM change as if this view was rendered
// through javascript.
myModel.title = "Hi";

// Rendering the whole thing would also work as expected
myView.render();

So to sum things up: I think aligning the life-cycle phase of subviews with their parent views will give us a lot of flexibility.

So what’s everybody’s thought on this?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions