Handlebars linkTo renders empty link - ember.js

I came accross a wierd situation, using Ember and Handlebars. I have a table of records. The user should click to any record and be redirected to the item detail - quite a usual use case.
However, Ember does not renders the link correctly. It works when I wrap only a single word or element by linkTo tag, but it does not work when I wrap whole table row.
{{#each item in controller.content}}
{{#linkTo "detail" item}}
<tr>
this is part of the link, correctly
<td>and this is not</td>
<td>{{item.someInfo}}</td> <!-- this neither -->
</tr>
{{/linkTo}}
{{/each}}
How can I fix this, when I want whole tr to work as a link?

Tables should only have <tr> fields in them, and <tr> fields should only have <td> fields in them. You're trying to add an ember tag (and an anchor) outside the table row, which shouldn't be done in a table.
If you want something done when clicking a table, rather add an action to the row. Something like the following should work.
<tr {{action 'details' on="click"}}>
details will, of course, be a function in the corresponding controller from where you can transition to wherever you like.

Related

Ember - actions within link-to blocks

I am building a component in Ember.js which includes a table. Each tr in that table is created using {{#link-to ...}} so that clicking anywhere on the row will take a user to another route.
However, I'd like to make a td element in those rows clickable to open a link in a new window. I'm doing this as an action.
Right now, clicking the proper td element will both trigger the {{#link-to}} redirect and activate the action on the td element as well.
Instead, I'd like a click on the proper td element to only trigger that element's action, and ignore the {{#link-to}} event above. How would I go about doing this?
This is what my code looks like:
<table>
{{#link-to 'widget' widget.id tagName='tr'}}
<td>Go to link-to</td>
<td {{action 'sendMail' widget.email}}>Send Email</td>
{{/link-to}}
</table>
Look at this twiddle implemented your case.
You need to call event.stopPropagation for that you need to have event object, to get it I used onclick={{action
<table>
{{#link-to 'my-route' tagName='tr' bubble=false}}
<td>Go to link-to</td>
<td onclick={{action 'sendMail' }}>Send Email</td>
{{/link-to}}
</table>
In sendMail, you need to stop event propagation.
actions:{
sendMail(event){
console.log('sendMail');
event.stopPropagation();
}
}

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.

custom Handlebars helper - parameter is not resolved

# 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.

ember.js does not {{bindAttr}} the <label> For attibute to the correct inputField.elementId in a collection

I'm trying to link a label to an input field using the {{bindAttr}} and the input field's [viewName].elementId. It works on a single entry view, but not when there are several records being displayed: it just links the label to the last input field in the collection. (This used to work in a previous iteration using an older ember library but now it doesnt.) I've created a fiddle but the gist of it is:
{{#each controller}}
<fieldset>
<label {{bindAttr for="view.tbFullName.elementId"}}>Full Name</label>
{{view App.DetailTextField viewName="tbFullName" placeholder="Full Name" valueBinding="fullName" readonly="readonly"}}
</fieldset>
{{/each}}
I thought maybe I could create a collectionView and create a calculated property for viewName which would generate a unique ID for each item in the collection, sort of mentioned in answer to another problem here. But that is getting WAY too complicated - just so that I can have the input field highlight itself if the user clicks on the corresponding label.
Any help appreciated.
Create a wrapper Ember.View around the label and input field. Let's call it App.FieldView:
App.FieldView = Ember.View.extend({
tagName: 'fieldset'
});
Then in your template:
{{#each controller}}
{{#view App.FieldView}}
<label {{bindAttr for="view.tbFullName.elementId"}}>Full Name</label>
{{view App.DetailTextField viewName="tbFullName" placeholder="Full Name" valueBinding="fullName" readonly="readonly"}}
{{/view}}
{{/each}}
Fiddle: http://jsfiddle.net/NQKvy/26/
Panagiotis Panagi, has answered the question correctly. I'll just add why this is happening, ie:- linking to the incorrect view.
The view property inside a template refers to the Ember View wrapping the html markup. This property however has different value depending on the context it is in.
This value is dependent on the view block it placed in. By default the template itself corresponds to a view in this case, ListOfPeopleTemplateView.
So when you are binding to view.tbFullName.elementId, you are actually binding to an {instance of ListOfPeopleTemplateView}.tbFullName.elementId. And when the loop finishes the only tbFullName visible is the last one.
Panagiotis Panagi's solution is to wrap the label inside another view, so the value of view changes to within that block, and hence points to the correct tbFullName
Finally an even easier way to achieve the same result is to wrap the textfield inside the label. Then you do not need the label for binding at all.
<label>Full Name
{{view App.DetailTextField viewName="tbFullName" placeholder="Full Name" valueBinding="fullName" readonly="readonly"}}
</label>
See this jsfiddle
Forms are somewhat tricky I must admit if you want to do things right. But there are is an ember add-on that comes to the rescue, for example easyForm.
Have a look it might helps you solving exact the problems you are facing, like the ones on having unique labels for your form fields etc.
Hope it helps.

how do you catch a 'click' on a {{linkTo}} using a view?

(Note: I am using Ember version 1.0.0-rc.3)
I'm trying to catch the 'click' of a {{linkTo}} using a view, so that I can do additional stuff (basically scroll the list of users in the sidebar) besides merely loading the new template. Me being relatively new to this (but having read the documentation!), I thought the following would just work:
"users" template:
{{#each user in users}}
{{#view App.ClickView}}
{{#linkTo user user}}{{ user.name }}{{/linkTo}}
{{/view}}
{{/each}}
the view code:
App.ClickView = Ember.View.extend({
click: function(evt) {
// do stuff
}
});
and for context, the layout template:
<div id='sidebar'>
{{#each user in users}}
{{#linkTo user user}}{{ user.name }}{{/linkTo}}
{{/each}}
</div>
<div id='main'>
{{ outlet }}
</div>
Referring back to the users template, you can see that each {{linkTo}} is contained within a view. I'm expecting for a click on that {{linkTo}} to therefore bubble up to, and caught by the view (App.ClickView). Unfortunately, it doesn't. It seems like the click is somehow not being bubbled up to the view when it's happens on a {{linkTo}}... What should I do?
Note #1:
If I replace the {{linkTo}} (only in div#main! I don't intend to replace the ones in div#sidebar) with an <a> element, it works, and the click gets caught by the view. However, I'm not so sure that i want to go down this route (I'd have to replicate the functionality of the {{linkTo}}!). And I'm thinking that there ought to be a better way to do this. Is there?
Note #2:
*Note that I'm also aware that i can put my intended "do stuff" code in renderTemplate() of the UserRoute, but the problem with that is that the effect will happen for every link to that route (including the ones in the sidebar - which is not what I want). I want the scroll to only trigger for specific {{linkTo}}s - specifically the {{linkTo}}s in div#main.
I would suggest using an anchor (<a>) tag with {{action ... target="view"}} in it instead of linkTo, apply your conditional logic in the view, and then if appropriate, re-send to the controller (this.get('controller').send(actionName), let it bubble to the router, and do a transitionTo in a router event handler.