custom Handlebars helper - parameter is not resolved - ember.js

# here is CreditCards controller context
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
{{#is property.symbol 'your_savings'}}
<td class="td">{{yourSavings}}</td>
{{else}}
<td class="td">{{cardProperty this property.symbol}}</td>
{{/is}}
{{/each}}
{{/with}}
I create the table dynamically. All content comes from ArrayController except one that is a computed property that comes from the controller. symbol' field is underscored likeannual_fee' and belongs to CreditCardProperty. Each card category has different set of card properties. Only two categories have properties (category has many card properties) and one record has computed field set to true. That means that the template should look up the corresponding controller.
As symbol (e.g age_to_apply) relates to one of the CreditCard fields (ageToApply) all I could to figure out was to use the cardProperty helper which takes current card in the context (this) and resolves property.symbol, e.g:
camelizedProperty = categoryProperty.camelize()
card.get 'camelizedProperty'
Ideally I'd like to do without the helper and use it somehow like this:
# *** {{property.symbol}} should look up this context
# and become e.g {{annual_fee}} or {{annualFee}} - is it possible?
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
<td class="td">{{property.symbol}}***</td>
{{/each}}
{{/with}}
But the problem is that I don't know how can I render that '{{yourSavings}}' part. The helper you can see comes from swag collection of Handlebars helpers. The helper, unfortunately does not resolve properties so that property.symbol becomes a string.
Here it is:
Handlebars.registerHelper 'is', (value, test, options) ->
if value is test then options.fn(#) else options.inverse(#)
I think it is possible but with the right helper - don't know which one, though.
What I do would like to avoid is to resort to computed property like if isYourSavings.

I am not certain about the context of your code, but it seems like you are looking for registerBoundHelper with a block helper. This isn't supported. You will run into this warning,
Assertion failed: registerBoundHelper-generated helpers do not support use with Handlebars blocks.
An alternative way to do what you are doing is to use a view helper instead. A view helper is like a helper but can render with a custom template.
For instance a CardItemView would be,
App.CardItemView = Em.View.extend({
templateName: 'cardItemTemplate'
});
Em.Handlebars.registerHelper('cardItem', App.CardItemView);
Where the cardItemTemplate is,
<script type='text/x-handlebars' data-template-name='cardItemTemplate'>
{{#if view.property.symbol}}
<td class="td">{{yourSavings}}</td>
{{else}}
<td class="td">{{cardProperty view.property.symbol}}</td>
{{/if}}
</script>
And you could use the helper like so,
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
{{cardItem property=property etc}}
{{/each}}
{{/with}}
You can pass in any number of properties as attributes. These will be bound to the CardItemView. And since it's a view anything a view does, like custom computed properties, can be done in CardItemView.

Related

How to use a component from within a component in Ember

G'day all,
I'm trying to wrap a component with another to provide a simplified editing wrapper.
The component is to conditionally show either a label or a select component that allows the user to pick the right value.
I want to wrap the power-select component, and pass it's values through to the sub-component, so the page template component reference looks like this:
{{cm-select
title="Country ##"
options=countries
selected=selectedCountry
searchField="name"
action="selectCountry"
}}
"countries" is an array of country objects, and selectedCountry is one of those country objects.
The component template has the following:
<td>{{title}}</td>
<td>
{{#if edit}}
{{#power-select
options=options
selected=selected
searchField=searchField
as |object|}}
{{object.name}}
{{/power-select}}
{{else}}
<small>{{modelElement}}</small>
{{/if}}
</td>
Unfortunately the power-select component renders with an empty list of options.
I thought wrapping those parameters in handlebars might do the trick, but it seems that handlebars in handlebars isn't a valid syntax.
Does anyone have any ideas?
Thanks,
Andy
That should work, I created a twiddle for you, demonstrating your use case. You'll see I updated the your cm-select template to this:
{{title}} |
<button {{action 'toggleEdit'}}>Toggle Edit</button>
<br/>
{{#if edit}}
Search for a Item via {{searchField}}
<br/>
{{power-select
options=options
selected=selected
searchField=searchField
onSelect=(action "itemSelected")
}}
{{else}}
{{search-list
options=options
searchField=searchField
onSelect=(action "itemSelected")
}}
{{/if}}
Where you iterated over options for power-select in the cm-select component, I moved that down into the power-select template. It's better to try and encapsulate some functionality there, instead of having everything in cm-select. I wasn't sure what you had in mind with {{modelElement}} so I just demonstrate what it would look like, using two different components in cm-select.

how to set an itemView for an itemController in Ember?

I have successfully implemented an ArrayController an defined an ItemController for it like this:
export default Ember.ArrayController.extend(InboxTab, {
itemController: 'messages.message-list-item'
});
then in the template for the array controller I just do
{{#each}}
<li {{action 'someActionFromItemController'}}>{{someComputedPropertyFromItemController}}</li>
{{/each}}
This works great and I can handle a lot of actions and computed for each item, but I'm running into difficulties associating a view to each item. The docs are not helpful. The only instance of itemView is in this article:
http://emberjs.com/api/classes/Ember.CollectionView.html#sts=Specifying itemViewClass
and here the example seems to revolve around adding the view to the template and specifying the content from there and I'm not sure how that applies to the way I'm doing it.
A different way you could do it:
{{#each itemController='messages.message-list-item'}}
{{#view your-view}}
{{action}}{{computed property from view/controller}}
{{/view}}
{{/each}}
specify the item controller in the loop and wrap the actions and properties in the view - which means either of these could also be called or set in the view.
I think way to do this best is with itemViewClass, see the api for the full details.
{{#each message itemController='messageListItem' itemViewClass='messageListItem'}}
{{! Assumes you have view defined in an App.MessageListItemView defined in your JavaScript}}
{{action}}
{{/each}}

how to pass parameters to Handlebars helper? What's the difference between options.hash & options.data

Here's a typical Handlebars helper:
Ember.Handlebars.helper 'myHelper', (value, options) ->
...
According to this protip you can pass hash to Handlebars helpers. I looked over the source and found out that it provides both options.hash and options.data. I'm a bit confused as this wouldn't work as expected:
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
<td class="td">{{cardProperty this property=property.symbol}}</td>
{{/each}}
{{/with}}
this is the current Card record. Here I got property.symbol as string
But this worked:
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
<td class="td">{{cardProperty this property.symbol}}</td>
{{/each}}
{{/with}}
and the value was accessible via options.
But now I can't do this:
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
<td class="td">{{cardProperty this property.symbol anotherParam yetAnotherParam}}</td>
{{/each}}
{{/with}}
My question is: how to pass other parameters to the helper and what's the difference between options.hash and options.data in the helper?
Parameters passed to a helper become arguments to the helper function. The values you provide in the template immediately after the {{helperName become the arguments. The last argument passed to the helper is an options object that provides additional information to helper like, an options.hash and options.contexts, etc. Key value pairs provided after the parameters correspond to the options.hash property.
For a hello helper that takes 3 arguments, the helper would be,
Ember.Handlebars.helper('hello', function(a, b, c, options) {
return '%# - %# - %#'.fmt(a, b, c);
});
The hello helper can be used in a template like so,
{{hello lorem ipsum dolor}}
Here the values of lorem, ipsum, and dolor properties would be used and returned as a combined string.
In addition to the required arguments, if you pass in additonal parameters they would be available in options.hash. These properties are treated as strings and aren't resolved by default. You would need to use, options.data.view to lookup their values first. See this answer for an example if you need to do this.
Finally options.data is special property provided to helpers. It's the raw handlebars Frame that holds variables, contexts and so on. It is mostly for use with block helpers. Since block helpers don't do rendering themselves but call other helpers, options.data allows such block helpers to inject additional variables into the child helpers frame. For details see the docs here.
Here's a jsbin example.

Combining view class with layout and {{#each}} multiple times gives incorrect behavior

I'm seeing some very strange behavior in this jsfiddle.
I am building an accordion control (hopefully eventually a good contribution to ember-bootstrap), and so I built a view class that uses layout to wrap the contents of the view:
Bootstrap.Accordion = Ember.View.extend({
tagName: 'div',
classNames: 'accordion',
layout: Ember.Handlebars.compile('{{yield}}')
});
Then I use it like so, with the {{#view}} helper, and include an {{#each}} block which will eventually include other views to set up the inside of the accordion control. And in one case so far, I do this twice in the same template, to display different information in two different accordion controls, sort of like this:
{{#view Bootstrap.Accordion}}
{{#each content}}
<div><strong>Field 1:</strong> {{field1}}</div>
{{/each}}
{{/view}}
{{#view Bootstrap.Accordion}}
{{#each content}}
<div><strong>Field 2:</strong> {{field2}}</div>
{{/each}}
{{/view}}
But, as you can see in the fiddle, this produces a very unexpected result. Basically, the second instance of the view is an exact copy of the first. Even the static content inside the {{#each}} block is not right:
Field 1: Instance 1 Field 1
Field 1: Instance 2 Field 1
Field 1: Instance 1 Field 1
Field 1: Instance 2 Field 1
However, if I put something between the {{#view...}} and {{#each}} helpers, it behaves as expected:
{{#view Bootstrap.Accordion}}
Fourth try...
{{#each content}}
<div><strong>Field 4:</strong> {{field4}}</div>
{{/each}}
{{/view}}
So, it looks like something about the similarity of the content directly within the {{#view}} helper causes the result to be cached by Handlebars...or something. That's just a wild hypothesis. Can anyone see what's going wrong here?
(Note that the Bootstrap library is not included in the fiddle, so it can't be that Bootstrap is goofing something up.)
This looks like a bug.
This github issue was created today, only with the {{#linkTo}} helper.
Looks like this is occurring with all block helpers.

Selected item in a template, is there any solution for a context aware bindAttr?

The problem is as follows:
In our application we have several buttons, navigation icons etc., which we want to be 'selected' when they have been clicked. We can have multiple elements marked at the same time.
The secondary reason for me wanting to do this is that when I read the new Guides on emberjs.com I get the feeling that templates should be used more than stated before and that templates should have the responsibility of rendering the DOM, while the views should be used to handle sophisticated events (if any) or to create common/shared components to be reused in the application.
Currently the view is handling this:
app.NavView = Ember.CollectionView.extend({
...
itemViewClass: Ember.View.extend({
...
classNameBindings: ['isSelected:selected']
isSelected: function () {
return this.get('controller.selected') === this.get('content');
}.property('controller.selected')
})
});
But that is all the View basically is doing, I would like to drop the entire View and just use a template for this
I have tried with a template approach, and dropped the entire View concept.
<div id="main-menu">
{{#each content}}
<div {{bindAttr class="controller.isSelected:selected"}}>
{{{iconsvg}}}
{{name}}
</div>
{{/each}}
</div>
But my problem here of course is that bindAttr doesn't know about the context it’s in, and cannot 'send' this to the isSelected property on the controller to evaluate if it is this element that is selected or not.
Is there a good solution to do this without a view, or am I forced to use a view?
Or am I thinking the design part and responsibility of Templates/views/controllers wrong?
Any response is appreciated!
In the current documentation: http://emberjs.com/guides/templates/displaying-a-list-of-items/ there is a mention explaining how to use the {{each}} helper which doesn't override the current context.
In your case, this would be something like:
<div id="main-menu">
{{#each item in controller}}
<div {{bindAttr class="isSelected:selected"}}>
{{{item.iconsvg}}}
{{item.name}}
</div>
{{/each}}
</div>
Note I have remove the reference to 'controller' in the {{bindAttr}} since I assume it's an ember controller, then it's the current context, so basically isSelected is equivalent to controller.isSelected