Skip to content

Commit

Permalink
implementing shorthand syntax for subviews. Fixes #43
Browse files Browse the repository at this point in the history
  • Loading branch information
HenrikJoreteg committed Sep 16, 2014
1 parent f31e60f commit f707134
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 10 deletions.
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module.exports = AmpersandView.extend({
});
```

**Note:** if you are using a template function (and not a string) the template function will get called with a context argument that looks like this, giving you access to `.model`, `.collection` and any other props you have defined on the view from the template.
**Note:** if you are using a template function (and not a string) the template function will get called with a context argument that looks as follows, giving you access to `.model`, `.collection` and any other props you have defined on the view from the template.

```javascript
this.renderWithTemplate(this, this.template);
Expand Down Expand Up @@ -192,10 +192,31 @@ var PersonView = AmpersandView.extend({
hook: 'avatar'
},

//no selector, selects the root element
// no selector or '' (empty string) selects the root element
'model.selected': {
type: 'booleanClass',
name: 'active' //class to toggle
},

// you can also pass an array if you wish to do multiple bindings
// for a single property
'model.username': [
{
type: 'text',
hook: 'username'
},
{
type: 'attribute',
name: 'data-username',
hook: 'username-tag'
}
],

// if you use a CSS selector that matches multiple elements
// all of them will get the binding
'model.active': {
type: 'booleanClass',
selector: 'a, li'
}
}
});
Expand Down Expand Up @@ -517,8 +538,15 @@ module.exports = AmpersandView.extend({
}
},
tab: {
// note this will also get it's parent view's
// model and collection if present
container: '[data-hook=switcher]',
constructor: ViewSwitcher
},
otherThing: {
hook: 'other',
constructor: OtherView,
arbitraryPath: 'model.submodel'
}
}
});
Expand All @@ -530,7 +558,8 @@ subview declarations consist of:
* 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.
* 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.
* other arbitrary object paths {String} Any other keys you include (i.e. 'model.submodel.name') will get resolved and passed through to the subview constructor. Note that the parent's `model` and `collection` will get passed through by default.
* prepareView {Function} If you want total control of how your subview gets instantiated, use this. It will get 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 first argument. Your function should return an instantiated view instance. Note that in this case you're controlling how the subview instantiates, therefore nothing from the parent will get passed through to the subview unless you do it here.


### delegateEvents `view.delegateEvents([events])`
Expand Down Expand Up @@ -559,6 +588,7 @@ You usually don't need to use this, but may wish to if you have multiple views a
## Changelog
- 7.2.0 Adding support for shorthand subviews and passing through `model` and `collection` to declared subviews from parent by default. [issue #24](https://github.com/AmpersandJS/ampersand/issues/24)
- 7.0.0 Replacing use of `role` in lieu of `data-hook` for [accessibility reasons discussed here](https://github.com/AmpersandJS/ampersand/issues/21)
- [insert period of poor changelog management here], this will not happen again now that ampersand is public.
- 1.6.3 [diff](https://github.com/HenrikJoreteg/ampersand-view/compare/v1.6.2...v1.6.3) - Move throw statment for too many root elements inside non `<body>` case.
Expand Down
19 changes: 13 additions & 6 deletions ampersand-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ function View(attrs) {
}
this._initializeSubviews();
this.initialize.apply(this, arguments);
this.set(_.pick(attrs, viewOptions));
if (this.autoRender && this.template) {
this.render();
}
Expand Down Expand Up @@ -76,9 +75,7 @@ var BaseState = State.extend({

// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;

// List of view options to be merged as properties.
var viewOptions = ['model', 'collection', 'el'];
var excludedSubviewOpts = ['container', 'hook', 'waitFor'];

This comment has been minimized.

Copy link
@pgilad

pgilad Jun 18, 2015

Member

selector, constructor ?


View.prototype = Object.create(BaseState.prototype);

Expand Down Expand Up @@ -257,10 +254,20 @@ _.extend(View.prototype, {
selector: subview.container || '[data-hook="' + subview.hook + '"]',
waitFor: subview.waitFor || '',
prepareView: subview.prepareView || function (el) {
return new subview.constructor({
var opts = {
el: el,
parent: self
});
};
// pass in parent's model/collection by default
opts.model = self.model;
opts.collection = self.collection;
// allow arbitrary paths to be passed through to child
for (var key in subview) {
if (!_.contains(excludedSubviewOpts, key) && typeof subview[key] === 'string') {

This comment has been minimized.

Copy link
@pgilad

pgilad Jun 19, 2015

Member

So user cannot pass a string property to the subview via the shorthand syntax?

opts[key] = getPath(this, subview[key]);

This comment has been minimized.

Copy link
@pgilad

pgilad Jun 18, 2015

Member

this should be self, no?

This comment has been minimized.

Copy link
@HenrikJoreteg

HenrikJoreteg Jun 19, 2015

Author Member

for consistency, yeah, though i'm pretty sure prepareView always is called with the correct context (which is why this still works). By the way, you'll note this PR was opened ages ago and kinda stalled due to disagreements about how this should be done, ideally.

It's possible that the best approach is to close this PR entirely. I guess what I'm saying is, you've got a close eye on this and opinions on how it should work so feel free to modify it or take other action.

}
}
return new subview.constructor(opts);
}
};
function action() {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"browser": "./ampersand-view.js",
"bugs": "https://github.com/ampersandjs/ampersand-view/issues",
"dependencies": {
"ampersand-collection-view": "^1.0.0",
"ampersand-collection-view": "^1.1.2",
"ampersand-dom-bindings": "^3.0.0",
"ampersand-state": "^4.2.7",
"component-classes": "^1.0.0",
Expand Down
29 changes: 29 additions & 0 deletions test/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,35 @@ test('declarative subViews basics', function (t) {
var view = new View();

t.equal(view.el.innerHTML, '<span></span>');
t.equal(view.sub1.parent, view, 'subview should have parent reference');

t.end();
});

test('subviews should support passing object paths for things like model/collection', function (t) {
var Sub = AmpersandView.extend({
template: '<span></span>',
props: {
fireDanger: 'string'
}
});

var View = AmpersandView.extend({
template: '<div><div class="container"></div></div>',
autoRender: true,
subviews: {
sub1: {
container: '.container',
constructor: Sub,
something: 'model.fireDanger'
}
}
});
var model = new Model();
var view = new View({model: model});

t.equal(view.el.innerHTML, '<span></span>');
t.equal(view.sub1.model, model, 'Subview should have property from model set directly');

t.end();
});
Expand Down

0 comments on commit f707134

Please sign in to comment.