Can I let a user customize a component's template? - ember.js

I'm making a sortable component. You could imagine a simple implementation having the following API:
{{sortable-list content=orderedQuestions tag='ul' itemTag='li'}}
But, that offers pretty limited template customization. Ideally I'd like the API to allow something like this:
{{#sortable-list content=orderedQuestions tag='ul'}}
<li>
<h2>{{title}}</h2>
<div>Some more {{details}}</div>
</li>
{{/sortable}}
where this block template gets used for each sortable item. Is this possible? I tried something like this for the component's template:
{{#each item in content}}
{{#with item}}
{{yield}}
{{/with}}
{{/each}}
but this doesn't work, since the block template the user passes into the component has the controller as its context.
Is what I'm after possible?

The exact thing you're trying to do is not currently possible.
Take a look at this Github issue to see a lot of discussion around component use cases like this. In particular take a look at Trek's first comment.
An alternative is to break your component into multiple components:
{{#sortable-list content=orderedQuestions tag='ul'}}
{{#each orderedQuestions}}
{{#sortable-item tag='li'}}
<h2>{{title}}</h2>
<div>Some more {{details}}</div>
{{#sortable-item}}
{{/each}}
{{/sortable}}
If you think about components in terms of existing HTML elements you can see that it kind of looks a little bit like a select which has options inside. To make this work it's likely that the sortable-items would need to communicate with the sortable list. One option is to use parentView in your component to get access to the parent.
Take a look at this talk from Ryan Florence to get more ideas about how to deal with composable components.
Update
Updated my answer to include an each around {{sortable-item}} to change the context.
Reading through the Ember each helper it seems that it would be possible for a component to do what you want but it looks really hairy.

Related

Wrapper Component for Form Controls

I'm looking for a pattern to make a wrapper component for form controls.
Its role: wrap any kind of component, (handle error states, messages)
Syntax:
{{form-control-group type="my-input" label="Some fancy label:"}}
templates/components/form-control-group.hbs
{{#if hasError }}
{{error}}
{{/if}}
{{label}}
{{component type}}
It works nice, as long as I don't need any attribute for my-input component. Usually it is not the case. Let's say I need pass the value. I could do
{{component type value=value}}
But as not that flexible as I wanted to have it. I will gonna use it with different form controls, ex.: select, checkbox etc. They all need different attributes.
I also could prepare for this different attributes:
{{component type
value=value
content=content
contentPath=contentPath
disabled=disabled
checked=checked
...
}}
In this way it can be used only with the component it is prepared for. If it is published as part of a form addon, and its consumer want to use with a datapicker component, it won't pass down the attributes.
Question:
Is there a way to stream down ALL the attributes passed to form-control-group component?
Maybe the whole concept is wrong, and there is a better pattern to handle this. Any critics and / or ideas are most welcome.
Edit
To be clear: in this question: "setProperties" does not pass "attrs" as expected I tried to solve this very problem, passing down the attrs hash, which contains all the attributes. Maybe Ember is not ready for this, but I'm sure there is a workaround.

Ember.js: Passing a template's current context object to an action

I'm working on an Ember.js app similar to this:
http://jsfiddle.net/rzb2m/
Now this does pretty much what I want except for one crucial problem. The setFavorite action at the bottom has a hard-coded 0. This means that if you change Carol's favorite activity, it actually changes Alice's favorite activity. I haven't been able to figure out how to structure the application so that it has the correct behavior. How do I pass the correct object to be changed (this.content.people[i] in this case) to the setFavorite function?
Note: My real application is actually a bit more complicated than this because I have two separate lists of people on the page. So the solution shouldn't just involve passing an index into setFavorite because that won't scale to the more complex situation with multiple lists. This is also why the person template looks unused. In the real app, there is complexity that justifies it.
You are relying on implicit binding resolution from controllers to views. But this breaks down because when you separate things out into partials you have no means to lookup the parent object in the #each iteration.
While #each controller works, you are better off with #each person in controller. Then you can access the properties from the view in the partial view, and pass it forward as needed.
Change the main #each in index template to,
{{#each person in people}}
<li>{{view App.PersonView contentBinding=person}}</li>
{{/each}}
Then in person template,
{{view App.PersonDetailsView contentBinding=view.content}}
And in personDetails template.
{{#each fave in view.content.getActivities}}
All this gives you the ability to pass the person to the setFavorite method,
<a {{action setFavorite view.content fave}} href="#">
So the setFavorite switches to a simple assignment on that person.
setFavorite: function(person, fave) {
person.set('favoriteActivity', fave.name);
},
Here's the updated jsfiddle.

Updating backed array in Ember.js rc1 does not propogate to view

I am trying to upgrade a partially built UI to the latest Ember.js rc1 and it has turned into a very big rewrite job thanks to the dramatically changed API. Most info out there (and here) has been rendered useless. I've had to go through the documentation again several times to get things partially working but there are a lot of loose ends. Here is a biggie. The views do not update like they did under the previous version. I'm missing something that must have to do with rerender, {{outlet}} or something else that I'm not aware of. The ember guides seem to need updates.
The template is very simple:
<script type="text/x-handlebars" data-template-name="index">
<button {{action "addOne"}}>add one</button>
<ul>
{{#each item in controller}}
<li>{{item.title}}</li>
{{/each}}
</ul>
</script>
When clicked, the button adds a new element to the backed array. The console logs show that the array is growing, but the template does not change. Here is a jsfiddle to illustrate how far I've gotten. Can anyone figure out what needs to be added?
I modified your example to highlight the fact when we use arrays in Ember, that we are using Ember arrays (Ember.A() or Em.A() if you want to make explicit this fact). From my understanding, you can use the methods Em.A().addObject and Em.A().removeObject to achieve the basic functionality using the Ember.Object getter and setter methods, (i.e. .get() & .set()) .
In order be properly observed by the Ember application, it is important to use the Ember getters and setters.
A modified version of your fiddle.

Instantiate a controller class using the {{view}} helper?

While connectOutlet("basename") automatically creates an instance of BasenameController, I was wondering if there's a way to do the same using the {{view}}-helper.
I have tried several things I've seen in examples, but non of them seem to work:
{{view controllerBinding=App.BasenameController}}
{{view controllerBinding=App.basenameController}}
{{view controllerBinding="App.BasenameController"}}
{{view controllerBinding="App.basenameController"}}
I have also tried to do the same using controller instead of controllerBinding, unfortunately without success, and I was also unable to find out where exactly the difference is between the two of them.
Does anybody know how to achieve my goal?
You probably want to use an outlet. The connectOutlet/outlet functions are meant for rending other controller/view pairs.
Lets say we have a person view, but inside that view we want to have another controller/view pair. For this, we need to use a named outlet, our template would look like this.
Person View!
{{name}} = the person's name!
{{controller}} = PersonController!
{{outlet other}} = our outlet
Then inside the router when you want to attach another controller/view to that outlet you can simple use connectOutlet on the personController.
router.get('personController').connectOutlet('other', 'other');
That will wire OtherController and OtherView together and display them in your template. The first param is the outlet name, the 2nd is the controller/view.
This allows you to easily swap different controllers and views onto that outlet. For example, using a different connectOutlet api, we could
router.get('personController').connectOutlet({
outletName: 'other',
controller: router.get('carsController'),
viewClass: App.CarsView
});
...
Btw, to answer you original question. You can get access to other controllers from your view by doing this: {{view controllerBinding="controller.target.otherController"}}. Every controller will have a target property that points back to the router. However, I do not recommend using this code. It's brittle, hard to test, hard to debug, and will come back and bite you in the future.

View management with Ember.StateManager (and not Ember.Router)

I'd like to user Ember to create an 'informational sign' type of application; The data displayed will be updated on a regular basis, but there is no user input or interaction. The application is configured and initialized based on URL parameters provided by an administrator. The layout and content of the application will change depending on the data in the feed the application is monitoring.
Ember.Router seems like overkill for this task because I don't need/can't have URL-based routes mapped to application state. Ember.StateManager seems perfect because it allows me to programmatically transition between states depending only on conditions met within the logic of my application, ignoring the current URL. The deprecated Ember.ViewState seems like it was the way to manage views when using Ember.StateManger.
What is the current best practice for managing views in the DOM with Ember.StateManger?
Is there a way to do what I'm describing with Ember.Router instead?
Thanks,
Aaron
After reading your primary explanation, I feel that you dont need any states at the moment. You need diferent application stated defined when application needs different behaviour in those states. Looks like your application behaviour is not changing but just view.
So you can just use some handlebars condition helpers to make this work along with some derived attribute in your controller indicating whether there is any current advisory.
{{#view App.myView}}
{{#if hasAdvisory}}
<div class="col-2">
<div class="col1">
{{#each bus}}
//bus details
{{/each}}
</div>
<div class="col2">
//advisory details
</div>
</div>
{{else}}
<div class="col-1">
<div class="col">
{{#each bus}}
//bus details
{{/each}}
</div>
</div>
{{/if}}
{{/view}}
Please explain more like if you just need this or do you plan any different application behaviours when you have advisory and when you dont have.
In general, the same techniques used to manage views and controllers in Ember.Router-based apps can be used with Ember.StateManager. Specifically, using outlets with Em.Controller#connectController is an excellent way to switch views that is independent of state management.