Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Align subviews life-cycle with parent view #113

Open
cquinders opened this issue Apr 11, 2015 · 0 comments
Open

Align subviews life-cycle with parent view #113

cquinders opened this issue Apr 11, 2015 · 0 comments

Comments

@cquinders
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants