How to reference a sub-view in Handlebars? - ember.js

I've embedded a series of views into my document using the following code:
{{view App.View.PersonProfile.Name itemBinding="person"}}
And I'm using the following code for my main container view, and its two sub-views:
App.View.PersonProfile = Ember.ContainerView.extend(
{
tagName: 'a',
person: null,
childViews: ['Name', 'NameAvatar'],
click: function()
{
App.View.Window.Profile.create({ person: this.get('person') });
},
name: Ember.View.extend(
{
template: Ember.Handlebars.compile('{{ view.parentView.person.formalName }}')
}),
nameAvatar: Ember.View.extend(
{
template: Ember.Handlebars.compile
(
'<img class="avatar" {{bindAttr src="view.parentView.person.avatar"}} />' +
'<div class="name">{{ view.parentView.person.formalName }}</div>'
)
})
});
However, when I run the script, "App.View.PersonProfile.Name" apparently cannot be found. Is this expected behaviour? And if so, what is the solution? Is it better to extend the PersonProfile abstract?

The reason this not working is likely that the property "App.View.PersonProfile.Name" is not present in your app. Your code defines a property with lowercase n for name, that will exist on instances of the PersonView. So, there are multiple layers of issues here.
A better way to approach this would be to have a PersonView extending from Ember.View with a template. If you need subviews (it doesn't look like you would from your example, but maybe you have other needs), you would reference them from the template associated with PersonView.
If you try this approach and have a problem, please post a JS Fiddle that illustrates the problem in executable code. You can start from the one on emberjs.com/community.

Related

How to dynamically apply classes to objects in ember-cli?

I'm creating an app that has a listing of items and a series of filter buttons at the top. As the user applies different filters, I want the buttons to change style using CSS classes to show them as enabled/disabled.
I want to be able to write something like the code below, but it doesn't work.
{{#each category in category_options}}
<button {{action "filterCategory" category}} {{bind-attr class=":btn-small isFiltered(category):btn-active:btn-inactive"}}>{{category}}</button>
{{/each}}
In this example, isFiltered is a computed property on the controller, and it looks at the query parameters to determine whether the specified category has been applied as a filter.
From the reading I've done, it sounds like you can't pass parameters to computed properties. I've come across answers mentioning helpers, bound helpers, and components, but I haven't been able to sort out which one I need, or how I would apply it in this situation.
EDIT:
To clarify the example, imagine I have a series of buttons that filter on various tags:
Filter for: <Cats> <Dogs> <Rabbits> ... # imagine an arbitrary number of these. dozens, maybe
When a user clicks Cats, it triggers filterCategory, which sets the model.category query parameter to ['Cats']. If he then clicks Dogs, model.category becomes ['Cats','Dogs']
Following the latter case, I want the Cats and Dogs buttons to have the class btn-active.
I would like to define isFiltered like so:
isFiltered: function(buttonname) {
if (this.get('model.categories').containsObject(buttonname)) { # pseudocode
return true;
}
else { return false; }
}
Passing buttonname into the function makes it easy to do the comparison for every button and determine if it's in the filter.
If this overall approach is the wrong way to go about things, what's the right way to do it?
1)As component you can do something like below:
in template
{{#each category in category_options}}
{{category-button category=category selectedCategoies=selectedCategories action="filterCategory"}}
{{/each}}
component template
{{category}}
component
export default Ember.Component.extend({
tagName: 'button',
classNames: 'btn-small',
classNameBindings: 'isFiltered:btn-active:btn-inactive',
isFiltered: Ember.computed('category', 'selectedCategories', function(){
return this.get('selectedCategories').contains(this.get('category'));
}),
click: function(){
this.sendAction('action', this.get('category'));
}
})
2)Or you can make your categories as array of objects like so
[
{name: 'category1', isActive: false},
{name: 'category2', isActive: true},
...
]
And then change isActive flag as you need.
In controller:
categoryObjects: Ember.computed('category_options', function(){
return this.get('category_options').map(function(category){
Ember.Object.create({name: category, isActive: false});
})
}),
actions: {
filterCategory: function(category){
category.toggleProperty('isActive');
return
}
}
And in template:
{{#each category in categoryObjects}}
<button {{action "filterCategory" category}} {{bind-attr class=":btn-small category.isActive:btn-active:btn-inactive"}}>{{category.name}}</button>
{{/each}}
I'm not sure how the rest of your code looks like but in general you would use model hook in your route to get query parameter, process it, if needed, and return with your model, let's say you would return model.category, then in your controller you would have something like this:
isFiltered: function() {
var category = this.get('model.category');
// do whatever you want here with category to return true or false
}.property('model.category')
then in .hbs you would be able to write this:
{{#each category in category_options}}
<button {{action "filterCategory" category}} {{bind-attr class=":btn-small isFiltered:btn-active:btn-inactive"}}>{{category}}</button>
{{/each}}
If you were to do this by your approach, you can get it working by making a Computed Property Macro and then looping over the category_options and creating computed properties as isCategory ( isRed, isBlue etc..)
But this won't be the right way to do it, You need to make those button components, which will accept the category_options and model.category and internally decide whether it should be active or not.

Accessing component scope within template block

if I have a component called my-scope which is defined only as:
export default Ember.Component.extend({
foo: 'bar'
})
and then I use the component in a template, like so:
{{#my-scope}}
{{foo}}
{{/my-scope}}
How can I reach the scope of the surrounding block component? I have tried:
{{foo}}
{{component.foo}}
{{view.foo}}
Maybe this is a case where I need to use a view? Hoping not but maybe that is the case.
note: my use-case is far more complex but let's just say that normal binding approach to components {{#my-scope foo=outsideFoo}} ... {{/my-scope}} is not a workable example
Without knowing your specific use-case, it's hard to know if the following answer will work for you, but one thing is for sure - it is very much compliant with Ember 2.0 "data down, actions up" philosophy (see this excellent piece by Sam Selikoff).
You can create a component that sends an action up with the data that you want available in your surrounding context as follows:
App.XTestComponent = Ember.Component.extend({
foo: 'bar',
didInsertElement: function(){
this.sendAction('action', this.get('foo'));
}
});
Then, in your surrounding context (controller), you can catch the action and set the appropriate data:
App.ApplicationController = Ember.Controller.extend({
foo: 'controller',
actions: {
setFoo: function(param){
this.set('foo', param);
}
}
});
Now, you can use your foo variable in the surrounding context the way you showed in the example:
<script type="text/x-handlebars">
{{#x-test action='setFoo'}} Hmm {{ foo }} {{/x-test}}
</script>
<script type="text/x-handlebars" id='components/x-test'>
<h2>Testing</h2>
{{ yield }}
</script>
See a working example here
(In theory, you can do this using an observer and not just from inside didInsertElement, but I have not tested it.)
You can use _view.parentView to access the value of foo property inside the component. (Because there is an underscore in _view, to me that means that it's not public API, so they might get rid of it in the future - but it's around for now :))
Something like the following:
{{#x-test}} Inside c1: {{ _view.parentView.foo.name }} {{/x-test}}
Working demo here

why does using this.store.find to load a single model force me to use content.attributeName in the template

I can't understand this.
In my route I have:
model: function (params) {
return this.store.find('article', params.article_id);
}
And in my template I cannot just output my article's attributes like this: {{title}}
but I have to use {{content.title}}
Likewise when I'm in my create article route and I create a model like this:
model: function () {
return this.store.createRecord('article', {title: '', pageContent: '', urlSegment: ''});
}
I'm having to bind the inputs like this:
{{input type="text" value=content.title id="title" placeholder="Title"}}
But, when I'm loading an index route like this
model: function () {
return this.store.find('article');
}
In my template I am able to just say {{#each}} {{title}} {{/each}} and it is much nicer, I don't want to have to use content. for every item route. Am I doing something wrong?
Thanks.
EDIT
As of the latest versions - ember 1.7 and ember data 1.0000.9 I can substitute content for model, but I still can't address the attributes directly.
And in my template I cannot just output my article's attributes like this: {{title}} but I have to use {{content.title}}
Judging by this, you're not setting up your controller properly. You're likely inheriting from Ember.Controller instead of Ember.ObjectController. Ember.ObjectController will proxy properties to your model, so you can use just {{title}}.
Also, don't use the content property in controllers, use the model property. You'll run into a whole lot of subtle bugs if you use the former.
Here's a JSBin showing that behavior.

Ember ViewState alternative

What is the alternative to Ember.ViewState that has been deprecated? I tried using Ember.State instead with ContainerView etc., but could not get the view loaded. If someone can help (preferably with any example), that will be great.
(Unable to share entire code as it is Work in Progress)
newState: Ember.State.create({
...
view: Ember.ContainerView.create({
childViews: [ Dashboard.ChartView.create() ]
}),
});
Also how to debug why a view is not rendered, especially if you want to know if your layouts and outlets are the problem? Do outlets work with StateManager? Right now, assume I have only the following in my index.html, is it enough (I am using Ember AnimatedOutlet)?
<script type="text/x-handlebars" data-template-name="application">
{{animatedOutlet name="main"}}
</script>
With the new Ember specifications, how to use outlets with StateManager without using routes? I want a single-page app with only default "/" route?
Thanks,
Paddy
For this sort of problem I typically create an Ember.StateManager from the now separate ember-states project. I then have computed properties on my controller that determine whether various parts of the page should be shown. Then, in my templates, I use {{#if shouldShowPartX}} ... {{\if}} type statements that are only displayed if the StateManager is in the given state. Here's a more complete example:
App.MyController = Ember.Controller.extend({
isOpen: Ember.computed.equal('panelManager.currentState.name', 'open')
init: function() {
this._super();
# Create the state manager instance from the class defined on this controller.
this.set('panelManager', this.PanelManager.create());
},
reset: (function() {
this.get('panelManager').send('reset');
}).on('init'),
PanelManager: Ember.StateManager.extend({
initialState: 'open',
open: Ember.State.create({
# If already open do nothing.
open: function(manager) {},
# Close the panel
shrink: function(manager) {
manager.transitionTo('closed');
},
}),
closed: Ember.State.create({
# If already closed, do nothing
shrink: function() {},
# Open the panel
open: function(manager) {
manager.transitionTo('open');
},
}),
reset: function(manager) {
manager.transitionTo(manager.get('initialState'));
}
})
});
Then In my view I can do something like:
{{#if isOpen}}
<div class="panel panel-open"> ... </div>
{{else}}
<div class="panel panel-closed"> ... </div>
{{/if}}
This is an admittedly simplistic example, and one wouldn't usually use a StateManager for a simple two-state situation, but a more complicated example would probably be confusing.
Would this help? It doesn't require messing directly with a ContainerView and instead relies on an implicit ContainerView that handles the {{#if}} blocks and creates views according to the state of the StateManager.

emberjs bind data attributes

I am wondering if there is a way to bind data attributes in a template when calling a view.
For example (this doesn't work):
{{ view App.SomeView data-dateBinding="currentDate" }}
I have ended up doing it this way:
<a {{bindAttr data-date="currentDate"}}></a>
There must be a way to do it when calling the view?
More on the excellent answer from #kurt-ruppel.
An example using : to describe the property for attribute binding, entirely done from the view.
App.SomeView = Ember.View.extend({
attributeBindings: ["date:data-date"],
date: function() { return '1642-12-06' }
.... rest of view
})
The cleaner template.
{{view App.SomeView}}
You have to define in App.SomeView which attributes you want put in the HTML.
App.SomeView = Ember.View.extend({
attributeBindings: ["data-date"]
.... rest of view
})
Now data-dateBinding should work:
{{view App.SomeView data-dateBinding="currentDate" }}
FWIW - and this is in response to #sly7_7's comments from the top answer -, it is possible to specify a data-* attribute binding in the view itself, as opposed to setting it in the template.
Similar to classNameBindings, you can prepend a preferred value to the attribute, joining the values with a ':'. Best place to see this in action is probably in the related ember.js tests. Gives credence to the value of good testing, seeing as how sometimes it serves as the best documentation.