Data sharing in ember - ember.js

I'm trying to understand how to share data between my controllers/routes.
I have an application that's displaying data about companies. Here are the routes I want:
/ summary info
/companies list of all companies with some more detail
/companies/:id details about a single company
Now, the data required for all three routes is contained in a single array of company data. So, I want that data to load when the app starts up, and then be used for each route. There are also additional methods I will need on the controller that should be shared.
It is clear that the second and third routes are nested, so I can share the data from the CompaniesController when I link to a specific company, by passing in that company's data:
{{#linkTo 'company' company}}{{ company.name }}{{/linkTo}}
But the summary route is where I'm getting stuck. The two options I've come up with:
Create the CompaniesController with any additional methods I need, and create the IndexController by extending it
App.IndexController = App.CompaniesController.extend({});
Then, as far as I can tell, both routes will need to find the models:
App.Router.map(function() {
this.resource('companies');
});
App.CompaniesRoute = Ember.Route.extend({
model: function() {
return App.Company.find();
}
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Company.find();
}
});
Seems like there should be a better way, since I'll have to repeat this for each new route I add (e.g. /revenue).
Nest the summary route within the companies resource, and give it a path of '/'. What I don't like about this is that the 'nesting' of my UI doesn't match the data. It also seems like I'll have to redefine the model property for each route.
Is there another option that's better?
tl;dr: How should I share data across controllers?

To share data beetwen controllers the correct way would be to use the needs API.
Assuming your CompaniesController has all the data that you want to make available to other controllers you should define it via needs, this can be a simple string, or an array of strings if you define more then one.
App.MyController = Ember.ObjectController.extend({
needs: ['companies'],
myFunction: function() {
// now you can access your companies controller like this
this.get('controllers.companies');
}
});
To make things more easy accessible you could additionally define a binding, for example:
App.MyController = Ember.ObjectController.extend({
needs: ['companies'],
companiesBinding: 'controllers.companies',
myFunction: function() {
// now you can access your companies controller like this
this.get('companies');
}
});
Hope it helps.

Related

When creating a new data object in Ember that relies on another object how do you pass it along?

I am on a page where I can see a specific customer, part of my router.js is:
this.route('customers');
this.route('customer', {path: "customers/:customer_id"});
this.route('customer.order.create', { path: "customers/:customer_id/order/create" });
customer.order.create needs to load in my main view and so is not nested. An order 'has a' customer.
I've setup my /customer/order/create controller to have
needs: "customer"
I want to access the customer in my /customer/order/create.hbs template like this:
<h3>New Order for {{controllers.customer.name}}</h3>
When I end up creating the order I will also want to set newOrder.customer = customer.
customer.hbs links like so
<div>
{{#link-to 'customer.order.create' model}}Create Order{{/link-to}}
</div>
Currently {{controllers.customer.name}} renders nothing, what piece of the puzzle am I missing to get to the customer in my order/create route?
Or putting it more generally, what route/controller/etc code do I need when I have a parent object which belongs to my child object in a /parentObject/parent_id/childObject/create type scenario.
There are many points to fix:
1) {{controllers.customer}} is Controller Object, {{controllers.customer.name}} it's name property. I think you want {{controllers.customer.model.name}}.
2) "..newOrder.customer = customer.." should be
newOrder.set('customer', this.get('controllers.customer.model'));
3) your customer.order.create route model hook shoudn't be empty, since you are using dynamic segment customer_id:
//route
model: function(params) {
return this.find('customer', params.customer_id);
}
4) Your routes are not nested, so {{controllers.customer.model.name}} would be empty if your customer route is not activated. So you should use: {{model.name}} instead of {{controllers.customer.model.name}}
When you click link you passes model directly, and model hook is not fired, so all looks good. When you visit url directly, model hook is fired. If it is empty you will see nothing.
General concept: it is dependancy injection concept, you could read here: http://guides.emberjs.com/v1.12.0/understanding-ember/dependency-injection-and-service-lookup/
You should be able to get the customer from the store. Give the following code a try:
The route:
export default Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
customer: this.store.find('customer', params.customer_id)
});
}
});
The controller:
export default Ember.Controller.extend({
customer: Ember.computed.alias('model.customer')
});
And it should be directly accessible as customer in your template, like so:
<h3>New order for {{customer.name}}</h3>
I changed needs: "customer" to needs: ["customer"] and then used {{model.name}} in my template. Seems that needs requires an array of strings and not just a string, after I fixed that Ember took compare of the rest without the need to create a /customers/order/create.js route.
For a more complete answer see Artych's answer if you don't want everything taken care of.

Temporary Non-Persistent Record with Ember-Data 1.0.0-beta

I'm new to Ember and Ember-data and am deciding whether to use Ember-Data or one of the other persistence libraries. In order to evaluate, I'm experimenting with writing a small Rails-backed app.
One of my routes can be considered similar to the Todo MVC app that is frequently used in examples.
In my template, I have a number of input fields that represent attributes within the model. Furthermore, I also have one element in the model that represents a hasMany relationship.
Models:
App.CompanyModel = DS.Model.extend
company: DS.attr()
desc: DS.attr()
contacts: DS.hasMany('company_contact')
App.CompanyContactModel = DS.Model.extend
firstname: DS.attr()
lastname: DS.attr()
...
Within my controller, I want to be able to create a new CompanyModel record (and by virtue, add one or more contacts models to it), but not have it appear within the controller's instance of the CompanyModel until I'm ready to do so.
Currently, when a user wants to add a new record, I have a component that calls an action in my controller as follows:
#set('new_company',
#store.createRecord('company')
)
This actually works fine, except for one thing. My view has to populate the individual attributes within "new_company", which it does, however, the record is immediately added to the controller's model instance and appears in the list of records; I only want the newly created record to be visible in the table once a particular action has taken place.
Instead of instantiating new_company with createRecord, I could do something like this:
#set('new_company',
Ember.Object.create
companyname: ''
desc: ''
contacts: [
firstname: ''
lastname: ''
]
)
And then do a #store.createRecord('company', #get('new_company')), however, given I've already defined my attributes in the model, it doesn't feel very DRY to me.
I'm using Ember 1.5.0 and Ember-Data 1.0.0-beta.7.
It appears I'm not the first person to have this issue (create temporarty non persistent object in Ember-Data), but it appears that Ember-Data has sufficiently changed to make all of these solutions inoperable.
Thanks for your help!
You're real issue is you're using what's considered a live collection. I'm going to assume in your route you've done something like this:
App.FooRoute = Em.Route.extend({
model: function(){
return this.store.find('company');
}
});
find with no parameters says, hey Ember Data, find me all the records that are company. Well Ember Data shoots off a request to your back-end, then returns store.all('company'). all is a live collection that will always have all the records of that type currently in the store. In your case, you are saying I want to avoid any record that is new. There are a couple of ways to handle this.
Create a static list. (You'll need to manually add/remove objects to/from this list).
App.FooRoute = Em.Route.extend({
model: function(){
return this.store.find('company').then(function(companies){
return companies.toArray();
});
}
});
Example: http://emberjs.jsbin.com/OxIDiVU/641/edit
Create a computed property that only shows records that aren't new
App.FooRoute = Em.Route.extend({
model: function(){
return this.store.find('company');
}
});
App.FooController = Em.ArrayController.extend({
savedRecords: function(){
return this.get('model').filterBy('isNew', false);
}.property('model.#each.isNew')
// shorthand this could be written like this
// savedRecords: Ember.computed.filterBy('model', 'isNew', false)
});
Then in your template you would iterate over the computed property
{{#each item in savedRecords}}
{{/each}}
Example: http://emberjs.jsbin.com/OxIDiVU/640/edit

Ember organize routes & resources?

Hi guys i have bunch of images that i want to sort by 'Recent' or 'Popular' or 'Hot'.
For now i have a route which is defined like this:
App.Router.map(function () {
this.route("browse");
});
I wanted to do something like browse/recent to show the images by recent and browse/popular for the popular but I cant nest routes.
Shall I change my code so instead of the browse route ill have images resource?
And nest into it my filters? so ill have something like images/recent images/popular...
It seems like too many routes, maybe ill have in the future 10 filters does it mean ill have to create 10 different routes & controllers? cant i just use 1 controller and set a logic to filter(with ember-data)?
You should probably use a noun (images) as a resource name. You can then create multiple routes, each applying different filter on your data (different model hook), but each using the same controller / template. A simplified example:
First, create an images resource, with individual routes for your filters:
App.Router.map(function() {
this.resource('images', function () {
this.route('hot');
this.route('new');
});
});
Then, create a shared route, which will use hardcoded template and controller. The part with setupController is needed because the default controller will be (probably auto-generated) controller for ImagesNew or ImagesHot. You must take the given model and use it to set up shared ImagesController.
App.ImagesRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('images', {
controller: 'images'
});
},
setupController: function (_, model) {
this.controllerFor('images').set('content', model);
}
});
App.ImagesController = Ember.Controller.extend({
// your shared logic here
});
Finally, you can create filtering routes. Each should inherit the base ImagesRoute and provide its own filtered data in the model hook.
App.ImagesHotRoute = App.ImagesRoute.extend({
model: function () {
return this.store.getHotImages();
}
});
App.ImagesNewRoute = App.ImagesRoute.extend({
model: function () {
return this.store.getNewImages();
}
});
Working jsbin example here.
It's a best practice to start with a resource and then nest routes within it.
App.Router.map(function() {
this.resource('images', { path: '/' }, function() {
this.route('browse');
this.route('hottest');
this.route('popular');
});
});
As far as creating ten different controllers, that is not necessary. I'd imagine that the route logic will be different (HottestRoute will load the hottest photos, PopularRoute will load the most popular), but the controller logic should be the same. It is probably best to have named controllers, but they can just extend an already defined controlled.
App.ImagesPopularController = ImagesController.extend();

How to structure a multi-record Ember app with named outlets?

I'm trying to build a Tweetdeck-like UI to arrange items from a central library into categories. I really need help wrapping my head around the canonical way of using Ember's router.
Essentially, I have a search UI, which allows the user to open zero or more categories simultaneously. The categories show a list of items, which the user can add to from a central library on the right. By completely ignoring the router and the URL, I have managed to hack together a semi-working proof of concept. Now I want to go back and try to do it the Ember way. Below is a high level sketch of what I am trying to accomplish:
If I understand correctly, the desired URL scheme would be a comma-separate list of model IDs that are currently open. I got a good idea of how to approach that from another question, How to design a router so URLs can load multiple models?.
Unfortunately, there are a few concepts I do not understand:
How do I construct my templates and router, such that the library is displayed with its own model and controller? I assume a named {{outlet}} is the way to go, but I am completely lost when it comes to the renderTemplate configuration. Or perhaps I should use {{render}} instead? In either case, I do not understand the router's role in this situation.
EDIT 1/28: I've added an updated fiddle that includes a standalone library route/template and documents my attempts to render it into the categories template. How does Ember expect me to give the library template its model when I try to embed it into another route? I've tried both {{outlet}} with renderTemplate and {{render}}, but in both cases, I am stuck when it comes to specifying the model.
Using renderTemplate:
App.CategoriesRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('categories');
this.render("library", {
into: "categories",
outlet: "library",
controller: "library",
});
},
});
When my controller receives a request to open a category, how do I communicate that to the router? How is the hash path updated? Who is responsible for loading the appropriate model(s)? I assume I should start with transitionTo or transitionToRoute, but I do not understand the router's role here either. Specific questions:
How do I de-serialize multiple, comma-separated models from the URL? Do I just split on the comma or is there a better way?
Once I get the IDs from the URL, how do I make my model hook return multiple records? Do I just shove them all into an Ember array?
When the controller gets the ID of a new record to open, how do I communicate that to the router?
I've tried to work this out on my own and have read the Ember documentation many times, but I am afraid it is simply over my head. I put together a minimal (currently non-functional) fiddle to outline my thoughts and point out where I am stuck. I would appreciate any help anyone could offer.
this.render does not accept a model parameter, but you could pass the model through the controller property instead, this makes sense to do since the Controller is really a proxy for the model at any rate
App.IndexRoute = Ember.Route.extend({
var self = this,
notesController = self.controllerFor('notes').set('content', self.store.find('notes'));
renderTemplate: function() {
this.render('notes', {
controller: notesController,
into: 'index',
outlet: 'notes'
});
}
});
You could also try something like this from this link.
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
books: this.store.findAll('book'),
genres: this.store.findAll('genre')
});
},
setupController: function(controller, model) {
controller.set('books', model.books);
controller.set('genres', model.genres);
}
});
Here, they load multiple models into one route using Ember.RSVP.hash and then using setupController they set each model (Rails: instance variable?) individually.
I'm assuming using this method that you could load as many models as you needed.

ObjectController and ArrayController

I am learning emberjs form trek.github.com. That tutorial used both Em.ObjectController and Em.ArrayController. And There is also Em.Controller.
I am confused when to use them, I guess Em.ObjectController is for single object, Em.ArrayController is for array and Em.Controller is just for ApplicationController.
Is there any blessed rule for when to use which?
Usually, if your Controller represent a list of items, you would use the Ember.ArrayController, and if the controller represents a single item, you would use the Ember.ObjectController. Something like the following:
MyApp.ContactsController = Ember.ArrayController.extend({
content: [],
selectedContact: null
});
MyApp.SelectedContactController = Ember.ObjectController.extend({
contentBinding: 'contactsController.selectedContact',
contactsController: null
});
Then in your Ember.Router (if you use them), you would connect the two inside the connectOutlets function:
connectOutlets: function(router) {
router.get('selectedContactController').connectControllers('contacts');
}
Edit: I have never used the Ember.Controller. Looking at the source code, it seems like you might want to use this if you are building a custom controller that doesn't fit in with the two other controllers.
The general rule is that it depends on model from route.
If model is an array then you should use ArrayController. It will allow you to implement in easy way sorting or filtering in future. ArrayController is connecting usually ObjectControllers.
When your model is an instance of Ember Object then you should use ObjectController. It takes place when you are using for instance ember data. With Objectcontroller you can access model properties directly. You don't have to write model.property each time.
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return Ember.Object.create({name: 'Mathew'});
}
});
My name is {{name}}
Finally, when one doesn't have model there is an ideal situation to use just Ember.Controller. It is not going to allow direct access to model properties as ObjectController.