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

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.

Related

Can I let a user customize a component's template?

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.

How to prevent views from being destroyed in Ember.js

Quick note:
I don't believe this is a duplicate of Ember.js: Prevent destroying of views. Other related questions that I've found are out-of-date.
In case this becomes out-of-date later, I am using Ember 1.7.0 with Handlebars 1.3.0.
Context for the question:
As the title states, I am wondering how to transition between views without destroying them. Using queryParams does not solve my issue.
I am creating a calculator with the following nested views:
>>Calculator View
>>Report View (hasMany relationship to Calculator)
--School Partial (I am using queryParams here)
I am able to navigate between the Report views just fine without destroying my School partial, since I am using queryParams and using a displaySchoolPartial boolean to show/hide the partial. Example below:
Report template (stripped to only show the essential part):
<script type="text/x-handlebars" data-template-name="calculator/report">
...
{{#link-to "calculator.report" (query-parameters displaySchoolPartial="true")}}
{{render "_school"}}
</script>
School template (also stripped down):
<script type="text/x-handlebars" data-template-name="_school">
{{#with controllers.calculatorReport}}
<div {{bind-attr class=":schoolPartialWrapper displaySchoolPartial::hide-element"}}>
...
</div>
{{/with}}
</script>
This works as expected. Navigating between different Report views and School partials, as stated before, does not destroy the view.
The problem:
My problem comes when navigating to the Calculator view, the Report view is destroyed, which then destroys my School view. I do not want to also use queryParams to replace my Report views.
The reason I need to make sure the views aren't destroyed is because I have a select box with 3,000 schools in my School partial. It takes too long to re-render this. It would be a much better UX to simply show/hide the Report views.
Don't fight with Ember. You will lose.
Views are instantiated and rendered when needed and torn down when done.
Why do you have a 3000-element dropdown, anyway?
If you really, really want to do this, what I would suggest is putting a {{render}} on your application page, and hide it. The view will be created and rendered when the app comes up and persist as long as the app is alive. Then, in the didInsertElement of your view, do a cloneNode of that hidden element and insert it into the view's DOM somewhere. You may have to muck around getting event handlers wired up correctly.
My suggestion is not using "render" but using "partial", so you only need to drop in the template that you want. Have a control variable that set show/hide via css class. And control that variable using you controllers.
Using "partial" will allow you to have school template independent from report, thereby removing report will not affect school.
Just make sure you define the outlet and partial correctly.
Hope it helps!

Concurrent states with Ember.router

I'd like to implement this statechart in Ember.js v1.5.x using Ember.Router, but I have problems with the concurrency and history mechanism.
Basically when I activate the Summary route/state I'd like to transition to No changes made and Transient state in the same time. How can I achieve this?
P.S. I know that e.g. stativus have these capabilities but don't know how to use it with Ember.js routing. An example would bee good.
(image source: Ian Horrocks: Constructing the User Interface With Statecharts p.153).
:)
Yeah statecharts are lovely and all, but Ember actually affords sub-states through computed properties.
I'm not overly familiar with state charts, and I'd really need to consume the resources (horrocks) you mentioned here (https://github.com/emberjs/ember.js/issues/4767#issuecomment-41458710) before I'd be fully conversant in the nomenclature of that particular example (which I can do if you'd like).
To that end, and having said that, please take my answer with a grain of salt, because I may not fully understand the context. I just hope to help.
So in Ember you have routes. Those routes explain the interface of your application. These will effectively be your states. Routes are not your actions, or events. They provide a URL for your app to present itself to the world.
So, state A seems to be presenting the Students. You have two sub-states in there... 0 students and >0 students. You would handle these with the same Route (call it StudentsRoute), because they're both about the same set of data, just different substates of it. The route would have a path called /students probably. At that point, you'd have a controller gets fed a model by the router (the list of students), so to that end, this controller would be an extension of Em.ArrayController.
This array controller (auto-named StudentsController, extends Em.ArrayController) automatically has a 'model', and that model, once resolved, is the students "array".
In StudentsController, you could easily have a computed property called zeroCount which represents the state of zero or not about the model. Computed properties automatically stay up to date. That'd be defined like this:
App.StudentsController = Em.ArrayController.extend({
zeroCount: function() {
// zeroCount is true if zero, otherwise false
return this.get('model.length') == 0;
}.property('model.length');
});
In your students template, you could conditionally render one of two sub-templates depending on this zeroCount state... you'd do that like this:
{{#if zeroCount}}
{{partial "noStudents"}}
{{else}}
{{partial "someStudents"}}
{{/if}}
Mind you, for this example, that'd be somewhat overkill... you probably don't need to render other templates (partials) like that.. there's an easier simpler way to do it because this is a common pattern in ember (rendering a list, and optionally rendering something else if there are no items in it, without needing the zeroCount property).
{{#each model}}
<p>This renders against each student... <br>
so if your students have a property called name, <br>
then you could just write {{name}} and it'd render the
students name</p>
{{else}}
<p>This renders when there are no students</p>
{{/each}}
You'd put a delete link on each of those items... and the live bound properties handle all the states for you... (thus, when model has zero items in it, the template goes into the else block of the each... otherwise it goes into the main block).
The delete action, handled by something like Delete inside your #each model template (handlebars) directive goes to the controller and looks for an action inside of it called, unsurprisingly, delete... and that'd look like this:
App.StudentsController = Em.ArrayController.extend({
actions: {
delete: function(itemToDelete) {
itemToDelete.delete();
// assuming the model "class" understands delete
}
}
});
The edit state would have its own route... possibly a nested route on the students, called edit, possibly not depending on if you wanted the list to appear on the screen while the edit page appears...
The "changes made" state is effectively handled not on the route, but on the model... as it should be... the model is responsible for persisting the object graph, or telling the view and controller whether or not the model has changed (Ember Data, for example, afford isDirty as a state on each model instance that can tell you whether it has changed or not)...
Hopefully this whets your appetite. I recommend going through some of the examples on the Ember site... they really do help, and following the Ember TODOMVC app if you haven't checked that out...
Ember thrives on these kind of flow-based state driven UIs... check out Tom and Yehuda's keynote at confreaks if you haven't already... they talk about flows in exactly the same way you're talking about these states and sub-states.
Hope that helps.

Best practice for dynamically rendering element outside of template

What is the best way of toggling the presence of some element (represented by a template) that is a direct child of <body>?
I'm talking about a modal box, notification, light-box etc. that is triggered either by some user event or by a route.
Examples: Dialog for newsletter sign-up that is shown after a user clicks a button. Overlay for content editing that is triggered by appending /edit to the item's route
Edit:
The first solution I though of was using Session to control state and then lining up all the application's modals and messages inside #if statements at the end of my main layout template like this:
<template name="layout">
<!-- yields and stuff -->
{{#if isActiveModal 'editArticle'}}{{> editArticle}}{{/if}}
{{#if ...
</template>
The problem is modularity; if a teammate is working inside some page template and needs to display a specific message or dialog, he or she has to edit the main layout to add it. I would have liked some easy way of conditionally appending a template to <body>.
Should be possible with the tools at hand, shouldn't it?
I actually use bootbox throughout several of my Meteor apps and it works great and does not interfere with any of Meteor's rendering. You can use the normal alert, confirm, prompt, as well as a custom dialog function, in a non-blocking way. There is a smart package available:
https://github.com/TimHeckel/meteor-bootboxjs
See some of my apps and smart packages for examples (grep for bootbox.):
https://github.com/mizzao/CrowdMapper/
https://github.com/HarvardEconCS/turkserver-meteor/
If you use the latest version of the new template engine, you can work with the body tag as its own special template through UI.body:
meteor update --release template-engine-preview-10.1
HTML:
<body>
{{#with alert}}
{{> modal}}
{{/with}}
</body>
<template name="modal">
<div class="modal">
X
<p>{{message}}</p>
</div>
</template>
JS:
if (Meteor.isClient) {
UI.body.alert = function() {
return Session.get("modal-alert");
};
UI.body.events({
"click .close": function() {
Session.set("modal-alert", null);
}
});
}
Since I posted the question, Blaze has become more sophisticated. Elements can now be rendered anywhere in the DOM while keeping a logical view hierarchy. See this discussion:
https://forums.meteor.com/t/most-useful-meteor-apis/1560/8
I've inserted the main outline below – the full thread also has code examples.
...
[Use] Blaze.render to output a template anywhere in the DOM – such as in a dimmable wrapper directly inside <body> – while maintaining a parent-child relationship between the view of the opening template and the view of the modal. This relationship is required for them to communicate directly.
(Bear in mind that a Session variable, among other limitations, can only hold serializable data...)
With the hierarchy intact, it's possible to set up shared reactivity between the modal and the background and call methods on the other, too. If we volunteered to use Blaze.renderWithData, then the two templates can even share the same data context!
Manually traversing the view hierarchy is pretty tedious, but with the parent() method from aldeed:template-extension, it isn't an issue.
Depending on whether one likes to use template instances as a 'view-model' or prefers to pass stuff around, reciprocal reactivity between the two templates can either be mediated by a new ReactiveVar assigned to a property on the parent template instance or view, or by a more retrieve-on-demand TemplateVar.
The entire exercise with Blaze.render can be duly incapsulated by using tricks like block helper templates and keyword arguments, so that everything is kept declarative.

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.