ember: How can I set bind-attr for a link-to? - ember.js

I want to set the style of a link-to helper but don't quite understand how.
I have the following model:
App.ArtistFavorite = DS.Model.extend
name: DS.attr 'string'
image_url: DS.attr 'string'
My template:
li
link-to 'artistFavorite' this {bind-attr style="background-image: url('image-url');"}
But the bind-attr doesn't seem to work
BTW: I'm using emblemjs and coffeescript

link-to is an Ember view helper, so (inspired by this) I was originally going to suggest using attributeBindings, except that raises the following JS error:
Setting 'attributeBindings' via Handlebars is not allowed. Please subclass Ember.View and set it there instead.
It looks like if you really need to set attributes in this way, it is possible to do so by reopening the Ember.LinkView class and setting attributeBindings there, but be forewarned that this will affect every link-to on your page.
But if (as it appears) the only attribute you need to set is style, you can just create a CSS class with your desired style and then set classNames, as discussed here, i.e.:
{{#link-to 'artistFavorite' this classNames="your-class-name"}}
From a code-style perspective, I would go with this approach even if it was possible to (more easily) set the style attribute directly.
Edit: Just realized you are trying to individually set styles for each link using one of the attributes of the corresponding item, so obviously CSS classes would not work. I've thought about this a bit more though.
Although discouraged, you should be able to bind to the style attribute by reopening the LinkView class and adding style to the attributeBindings:
Ember.LinkView.reopen({
attributeBindings: ["style"]
})
Then possibly you could set a value for the style attribute:
{{#link-to 'artistFavorite' this style=favStyle}}
Where favStyle is a computed property on your model or (ideally) your controller:
favStyle: function() {
return "background-image: url('" + this.get('image_url') + "');";
}.property('image_url')
However I haven't tested this and I'm not 100% certain the binding will work correctly this way, because these bindings are typically used for plain-text, not properties.

Related

How do I set a class on a parent element when a route is the first one loaded?

I have an Ember demo app that works fine if the first route loaded is 'index', 'list' or 'list/index', but not if the first route loaded is 'list/show'. Code is at https://github.com/DougReeder/beta-list , demo is running at https://ember-demo.surge.sh To see the problem, set your window narrower than 640px and surf to https://ember-demo.surge.sh/list/5 You'll see the list panel, rather than the detail panel.
The underlying problem is that, when the route is 'list/show', the divs with class 'panelList' and 'panelDetail' should also have the class 'right'.
I can't set this in the template, because panelList and panelDetail are created by the parent 'list' template. If I move panelList and panelDetail to the child templates 'list/index' and 'list/show', then the list gets re-rendered when going from 'list/index' to 'list/show' which would be a terrible performance hit.
Currently, I use the 'didTransition' action to toggle the class 'right'. This is called both then transitioning from 'list/index' to 'list/show', and when 'list/show' is the initial route. Unfortunately, if 'list/show' is the first route, none of the DOM elements exist when 'didTransition' is called.
I can envision two routes to a solution, but don't know how to implement either:
Toggle the class 'right' on some action which happens after DOM elements exist.
Insert conditional code in the 'list' template, which sets the class 'right' on 'panelList' and 'panelDetail' if the actual route is 'list/show'.
Suggestions?
Answer current as of Ember v2.12.0
You can use the link-to helper to render elements other than links, with styles that change based on the route. Utilizing the activeClass, current-when, and tagName properties, you can basically have that element be styled however you want depending on which route you are on. For example, to render your panelList div:
{{#link-to tagName='div' classNames='panelList' activeClass='right' current-when='list/show'}}
More markup
{{/link-to}}
I love a trick with empty component. In didInsertElement and willDestroyElement hooks you can add and remove a css class from parent element or (I like it better) body. Here is a code:
import Ember from 'ember';
export default Ember.Component.extend({
bodyClass: '',
didInsertElement() {
const bodyClass = this.get('bodyClass');
if (bodyClass) {
Ember.$('body').addClass(bodyClass);
}
},
willDestroyElement() {
const bodyClass = this.get('bodyClass');
if (bodyClass) {
Ember.$('body').removeClass(bodyClass);
}
}
});
I use it in template (in my example it's a template of player route) like this
{{body-class bodyClass='player-page-active'}}
To apply classes to parent element, you can use this.$().parent(), but using body is more reliable. Note that this component will create an empty div, but it shouldn't be a problem (can be in rare cases, fix it with classNames and css if needed).
Sukima suggested looking at currentRouteName, and I thus found hschillig's solution, which I simplified for my case. In the controller, I created an isShow function:
export default Ember.Controller.extend({
routing: Ember.inject.service('-routing'),
isShow: function() {
var currentRouteName = this.get('routing').get('currentRouteName');
return currentRouteName === 'list.show';
}.property('routing.currentRouteName'),
In the template, I now use the if helper:
<div class="panelList {{if isShow 'right'}}">
RustyToms's answer eliminates the need for adding a function to the Controller, at the expense of being less semantic.

Why is that using itemController renders a collection of empty items?

I'm currently learning Ember while following the todomvc tutorial with ember-cli: http://thetechcofounder.com/getting-started-with-ember-js-using-ember-cli/
I'm in the section where in order to edit a todo, it's needed to add the editTodo action in the TodoController. So far so good, but it also says to use itemController on the each handlebars helper to tell each todo to use a specific controller
.
The thing is that when I add itemController to each in the template (using Emblem.js: each itemController='todo'), the template no longer renders the title of each item on the collection, it only renders them blank:
I cannot understand why this happens.
Template extract
section#main
ul#todo-list
each
li class={isCompleted:completed}
if isEditing
input.edit
else
= input class='toggle' type='checkbox' checked=isCompleted
label{action 'editTodo' on='doubleClick'}= title
button.destroy
input#toggle-all type='checkbox'
Controller extract
`import Ember from 'ember'`
TodoController = Ember.Controller.extend
actions:
editTodo: ->
#set 'isEditing', true
`export default TodoController`
An item controller must be an Ember.ObjectController to successfully render each item and its associated data. ObjectControllers are used to decorate individual items within an ArrayController. Use the itemController property in the 'TodosListController' ArrayController to declare the item controller:
itemController: 'todo',
Then, when creating the 'todo' item controller class definition as suggested in the referenced tutorial, observe that the Ember CLI 'generate controller' command will create a standard Ember Controller. Standard Controllers and ArrayControllers represent multiple items (like the 'TodosController' or 'TodosListController'). Thus, the TodoController should extend Ember.ObjectController to represent singular items:
`import Ember from 'ember'`
TodoController = Ember.ObjectController.extend
actions:
editTodo: ->
#set 'isEditing', true
`export default TodoController`
A standard Ember.Controller, as posted with the question, fails to display each of the individual todos properly, when passed via the 'each' helper, because the model for the standard controller is referencing a filtered set of all records of type 'todo', instead of a particular, single todo record.
I’ve created a JS Bin to illustrate - just toggle between using Ember.Controller and using Ember.ObjectController for the 'TodoController', to see the standard controller fail.
Also, not the cause of the issue, but just in case it was overlooked, the ‘isEditing:editing’ is missing from the list-item class attribute declaration:
section#main
ul#todo-list
each itemController='todo'
li class={isCompleted:completed, isEditing:editing} // <-- here
if ...

Custom Handlebars block helper

How do I define a custom block helper in Handlebars (for use with Ember.js)? I've tried using the method described on the Handlebars site, but it doesn't seem to work. I get this error from Ember.js:
Assertion failed: registerBoundHelper-generated helpers do not support use with Handlebars blocks.
Here's the code for my helper. The idea is that the block will only be rendered if the models that I pass in are the same:
Ember.Handlebars.helper 'sameModel', (model1, model2, options) ->
if model1 is model2
new Handlebars.SafeString(options.fn(this))
else
''
6 months later, it looks like this is possible now, at least to a certain extent. You can view the discussion here. I agree with the pull request that this should usually be handled using computed properties, but this is still very useful in some cases.
I'm going to accept this answer to keep this post updated. If I've broken any SO etiquette, let me know. :)
Assertion is correct. You cannot do that, at least not it RC6 and before.
You may want to create a view with a template and bind some properties to it:
some.hbs
{{#if model1}}
This is model1 {{model1.name}}
{{/if}}
{{#if model2}}
This is model2 {{model2.name}}
{{/if}}
views/some.js
App.SomeView = Ember.View.Extend({
templateName: "some"
})
different template
<h3>{{view App.SomeView model1Binding=someModel1 model2Binding=someModel2}}</h3>

How do I setup an Ember View class to be appended to a particular container?

Ember.View has a nice method called .appendTo("#container") which would allow me to specify a container div for the view. However, when I use the router and .connectOutlet method, an instance of my view is created automatically based on convention and is added to the page body element by default. Is there a way to configure the class definition of the view so that upon creation it will be inside my desired #container. Here is my view:
Jimux.BuildsView = Em.View.extend({
templateName: 'builds',
appendTo: '#jimux-header', #this was just a guess and did not work. but does some propery like this exist for the view?
tagName: 'div',
listVisible: true,
...
Another way to ask this question is: how do I tell Ember router to append a view to a particular item in the dom? By default the router appends the view to the body.
And here is the router bit:
# Connect builds controller to builds view
router.get('applicationController').connectOutlet("builds","builds", Jimux.buildsController)
To clarify, I dont want to put my whole Ember app in a container. I have several views in my application, and most of them are fine directly in the body. But there are a couple like the one mentioned in this question, which I want to put inside "#application-header" div.
You can specify the root element for your application object.
window.App = Ember.Application.create({
rootElement: '#ember-app'
});
Edit:
Having re-read your question, I think you should look into named outlets, so you could do something like:
<div id="application-header">
{{outlet builds}}
</div>
{{outlet}}
well..after understanding your question, i remember having same trouble. Also, thing is i didn't find any way to do this even after going through the Ember code. But later i understood that its for good purpose only. I know you already might have come across with handlebars with which we can acheive this. If we give a view a ID to get appended, we are constraining the application and the whole use of ember becomes useless. Ok coming to you question, as far as i know, we can acheive that appending mustache templates in you div element of HTML.
<div id="jimux-header">
{{view Jimux.BuildsView}}
</div>
This way we can use the Jimux.BuildsView where ever you want and as many times possible. The Beauty of Ember you have to say...
Just add rootElement in the application object.
var App = Ember.Application.create({
rootElement: '#container'
});

How can I bind the element ID for an Ember View?

My model "content.id" contains a string, e,g "123":
{{view Em.TextArea idBinding="content.id"}}
Instead of just setting the id of this view to "123", I'd like it to be "message-123", basically customizing the string being used. Sadly, Ember does not allow bindings to be functions, which would solve my problem (I could define such a function on the controller).
What's the best way to achieve this?
You could define a computed property in the controller (or elsewhere):
The controller
MyApp.ApplicationController = Ember.Controller.extend({
content: "a-content",
editedContent: function() {
return "message-" + this.get('content');
}.property('content')
});
The view
MyApp.FooView = Ember.View.extend({
    tagName: 'p'
});
The template (where content is a String, here)
{{#view MyApp.FooView elementIdBinding="editedContent"}}
{{content}}
{{/view}}
And the JSFiddle is here.
EDIT
How can the view see the property editedContent since it belongs on the ApplicationController controller?
The router, after started, automatically render the ApplicationView, or its template when there is no ApplicationView defined. If you want more detail, I suggest you to read the Ember guide: Understanding the Ember.js Router: A Primer.
And {{editedContent}} directly get the controller editedContent property, because the default view context is its controller, as you can read in Ember Blog - 1.0 Prerelease:
The {{#view}} helper no longer changes the context, instead maintaining the parent context by default. Alternatively, we will use the controller property if provided. You may also choose to directly override the context property. The order is as follows:
Specified controller
Supplied context (usually by Handlebars)
parentView's context (for a child of a ContainerView)