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

waitfor array #202

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ subview declarations consist of:
* selector {String} Selector that describes the element within the view that should hold the subview.
* hook {String} Alternate method for specifying a container element using its `data-hook` attribute. Equivalent to `selector: '[data-hook=some-hook]'`.
* constructor {ViewConstructor} Any [view conventions compliant](http://ampersandjs.com/learn/view-conventions) view constructor. It will be initialized with `{el: [Element grabbed from selector], parent: [reference to parent view instance]}`. So if you don't need to do any custom setup, you can just provide the constructor.
* waitFor {String} String specifying they "key-path" (i.e. 'model.property') of the view that must be "truthy" before it should consider the subview ready.
* waitFor {String|Array} String or Array of Strings specifying the "key-path" (i.e. 'model.property') of the view that must be "truthy" before it should consider the subview ready.
* prepareView {Function} Function that will be called once any `waitFor` condition is met. It will be called with the `this` context of the parent view and with the element that matches the selector as the argument. It should return an instantiated view instance.

### delegateEvents `view.delegateEvents([events])`
Expand Down
44 changes: 23 additions & 21 deletions ampersand-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,24 @@ var State = require('ampersand-state');
var CollectionView = require('ampersand-collection-view');
var domify = require('domify');
var uniqueId = require("lodash/uniqueId");
var pick = require("lodash/pick");
var assign = require("lodash/assign");
var forEach = require("lodash/forEach");
var result = require("lodash/result");
var last = require("lodash/last");
var isString = require("lodash/isString");
var bind = require("lodash/bind");
var flatten = require("lodash/flatten");
var invokeMap = require("lodash/invokeMap");
var events = require('events-mixin');
var matches = require('matches-selector');
var bindings = require('ampersand-dom-bindings');
var getPath = require('lodash/get');
var castArray = require('lodash/castArray');

function View(attrs) {
this.cid = uniqueId('view');
attrs || (attrs = {});
var parent = attrs.parent;
delete attrs.parent;
BaseState.call(this, attrs, {init: false, parent: parent});
BaseState.call(this, attrs, { init: false, parent: parent });
this.on('change:el', this._handleElementChange, this);
this._upsertBindings();
this.template = attrs.template || this.template;
Expand Down Expand Up @@ -75,7 +73,7 @@ var BaseState = State.extend({
},
rendered: {
deps: ['_rendered'],
fn: function() {
fn: function () {
if (this._rendered) {
this.trigger('render', this);
return true;
Expand Down Expand Up @@ -141,7 +139,7 @@ assign(View.prototype, {

// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function () {},
initialize: function () { },

// **render** is the core function that your view can override. Its job is
// to populate its element (`this.el`), with the appropriate HTML.
Expand Down Expand Up @@ -267,12 +265,16 @@ assign(View.prototype, {
subview.selector = subview.container;
}
var opts = this._parseSubviewOpts(subview);
var hasViewPath = function (path) {
return !!getPath(this, path);
};


function action() {
var el, subview;
// if not rendered or we can't find our element, stop here.
if (!this.el || !(el = this.query(opts.selector))) return;
if (!opts.waitFor || getPath(this, opts.waitFor)) {
if (!opts.waitFor.length || opts.waitFor.every(hasViewPath, this)) {
subview = this[name] = opts.prepareView.call(this, el);
if (!subview.el) {
this.renderSubview(subview, el);
Expand All @@ -299,7 +301,7 @@ assign(View.prototype, {
var self = this;
var opts = {
selector: subview.selector || '[data-hook="' + subview.hook + '"]',
waitFor: subview.waitFor || '',
waitFor: subview.waitFor ? castArray(subview.waitFor) : [],
prepareView: subview.prepareView || function () {
return new subview.constructor({
parent: self
Expand Down Expand Up @@ -383,13 +385,13 @@ assign(View.prototype, {
return this.registerSubview(collectionView);
},

_setRender: function(obj) {
_setRender: function (obj) {
Object.defineProperty(obj, 'render', {
get: function() {
get: function () {
return this._render;
},
set: function(fn) {
this._render = function() {
set: function (fn) {
this._render = function () {
this._upsertBindings();
fn.apply(this, arguments);
this._rendered = true;
Expand All @@ -399,13 +401,13 @@ assign(View.prototype, {
});
},

_setRemove: function(obj) {
_setRemove: function (obj) {
Object.defineProperty(obj, 'remove', {
get: function() {
get: function () {
return this._remove;
},
set: function(fn) {
this._remove = function() {
set: function (fn) {
this._remove = function () {
fn.apply(this, arguments);
this._rendered = false;
this._downsertBindings();
Expand All @@ -415,18 +417,18 @@ assign(View.prototype, {
});
},

_downsertBindings: function() {
_downsertBindings: function () {
var parsedBindings = this._parsedBindings;
if (!this.bindingsSet) return;
if (this._subviews){
invokeMap(flatten(this._subviews), 'remove');
this._subviews = [];
if (this._subviews) {
invokeMap(flatten(this._subviews), 'remove');
this._subviews = [];
}
this.stopListening();
this.bindingsSet = false;
},

_upsertBindings: function(attrs) {
_upsertBindings: function (attrs) {
attrs = attrs || this;
if (this.bindingsSet) return;
this._parsedBindings = bindings(this.bindings, this);
Expand Down
23 changes: 16 additions & 7 deletions test/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -864,10 +864,11 @@ test('make sure subviews dont fire until their `waitFor` is done', function (t)
});

var View = AmpersandView.extend({
template: '<div><span class="container"></span><span data-hook="sub"></span></div>',
template: '<div><span class="container"></span><span data-hook="sub"></span><span data-hook="subthree"></span></div>',
autoRender: true,
props: {
model2: 'state'
model2: 'state',
subThreeReady: 'boolean'
},
subviews: {
sub1: {
Expand All @@ -879,22 +880,30 @@ test('make sure subviews dont fire until their `waitFor` is done', function (t)
waitFor: 'model2',
hook: 'sub',
constructor: Sub
},
sub3: {
waitFor: ['model', 'model2','subThreeReady'],
hook: 'subthree',
constructor: Sub
}
}
});
var view = new View();
t.equal(view._events.change.length, 2);
t.equal(view.el.outerHTML, '<div><span class="container"></span><span data-hook="sub"></span></div>');
t.equal(view._events.change.length, 3, 'three change events change events on instantiation');
t.equal(view.el.outerHTML, '<div><span class="container"></span><span data-hook="sub"></span><span data-hook="subthree"></span></div>', 'the subviews should be empty');
view.model = new Model();
t.equal(view._events.change.length, 1);
t.equal(view.el.outerHTML, '<div><span class="container"><span>yes</span></span><span data-hook="sub"></span></div>');
t.equal(view._events.change.length, 2, 'triggers change');
t.equal(view.el.outerHTML, '<div><span class="container"><span>yes</span></span><span data-hook="sub"></span><span data-hook="subthree"></span></div>', 'the subview sub1 should have rendered');
view.model2 = new Model();
t.equal(view.el.outerHTML, '<div><span class="container"><span>yes</span></span><span data-hook="sub"><span>yes</span></span></div>');
t.equal(view.el.outerHTML, '<div><span class="container"><span>yes</span></span><span data-hook="sub"><span>yes</span></span><span data-hook="subthree"></span></div>','the subviews sub2 should have rendered');
view.subThreeReady = true;
t.equal(view.el.outerHTML, '<div><span class="container"><span>yes</span></span><span data-hook="sub"><span>yes</span></span><span data-hook="subthree"><span>yes</span></span></div>','the subviews sub3 should have rendered');
t.notOk(view._events.change);

t.end();
});


test('make sure template can return a dom node', function (t) {
var Sub = AmpersandView.extend({
template: function () {
Expand Down