Please see: http://emberjs.jsbin.com/xakok/1/edit
How do I do the following? The categories display. When user clicks on a category, I want to bring up a list of links that belong to the category that was clicked. Ember seems to be by-passing my LinkRoute all together. Thanks
When you provide a model to the link-to helper it will skip the model hook (it built the url based on the model, and assumes that's the model that would be used for that route).
That being said you need to handle the case where you refresh the page instead of hitting the page using the link-to.
Solving the first, we can now assume the category model is being sent to the links route as its model. So we can update the template to iterate over the links on the category sent in. (you could also say each link in model.links, where the category is your model).
{{#each link in links}}
{{link.title}}<br/>
{{/each}}
But you need to be able to handle the case where we refresh the page as well. So we change the link route to mimic the behavior and return the same type of model the link-to is passing in.
App.LinkRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('category', params.category_id);
}
});
Lastly, using the fixture adapter, when you define a hasMany or belongsTo as 3 or [1,2,3] you need to specify those relationships as async.
App.Category = DS.Model.extend({
name: DS.attr('string'),
links: DS.hasMany('link', {async:true})
});
Example: http://emberjs.jsbin.com/fexelera/1/edit
Lastly, thanks for providing source and all that was necessary for your problem, the jsbin really helps people answer questions easily.
Related
I have a simple app to show a list of clients, and a detail page for each specific client.
So basically, two URLs:
/clients/
/clients/:slug/
I've been having some trouble rendering the results from my Client model into a template, specifically when using the #link-to helper with dynamic segments.
I've found two methods for doing so, and while I can get them both to work, I don't know which one is the best and why.
Here is some (simplified) code from my app to explain.
// app/models/client.js
export default DS.Model.extend({
name: DS.attr('string'),
slug: DS.attr('string')
});
(my actual model is larger, but these are the only relevant fields)
and here are my routes:
// app/router.js
Router.map(function() {
this.route('clients');
this.route('client', { path: 'clients/:slug' });
});
NOTE: I didn't nest the routes, because I didn't want to use the {{outlet}} nested template feature.
Here is the route for Clients, where I retrieve my list of clients
// app/routes/clients.js
export default Route.extend({
model: function() {
return this.get('store').findAll('client'); // fetch all Clients
}
});
Finally, here is the route to fetch info for a single Client:
// app/routes/client.js
export default Route.extend({
model: function(params) {
return this.store.query('client', {
filter: { slug: params.slug } // fetch one specific client, by slug
});
}
});
Everything works fine up to here, but my issue starts when displaying the model data in the templates.
There are two "ways", which one is correct??
OPTION A:
//app/templates/clients.hbs
// list all clients using #each
{{#each model as |client|}}
{{#link-to "client" client}} // pass the "client" object to generate my dynamic routes
{{client.name}}
{{/link-to}}
{{/each}}
Clicking on any of the generated links will render the client detail template, client.hbs
//app/templates/client.hbs
<h1>Client - {{model.name}}</h1>
In this example, I can use model.name to render my model object. That's fine, until I refresh the page! Then the info returned by model.name is obliterated. Same thing if I try to visit the URL directly. I have to go back to /clients and click on the link again to see my client's name.
I then looked for another way to display my data, where the model information would survive a page reload.
OPTION B:
After much reading I found a suggestion to use the specific client slug/id as param for #link-to
// app/templates/clients.hbs
// list all clients using #each
{{#each model as |client|}}
{{#link-to "client" client.slug}} // specifically use the actual slug/id, and not the object
{{client.name}}
{{/link-to}}
{{/each}}
But... by passing client.slug instead of client as parameter to #link-to, I can no longer use model.name to display my data. It simply returns nothing!
//app/templates/client.hbs
<h1>Client - {{model.name}}</h1> <-- model.name now returns nothing D:
However, using a loop DOES work for some reason:
//app/templates/client.hbs
{{#each model as |client|}}
<h1>Client - {{client.name}}</h1> <-- using loop even though I only have one record :(
{{/each}}
So option B works, and the information is displayed correctly after a page reload, or a direct visit from the URL.
But I'm now using a loop to display a single record! And I can't find an alternative solution that actually works.
To summarize:
option A feels like the correct way, but reloading the page obliterates the data.
option B actually works and returns the data, but I have to use a loop to iterate through a single record, which doesn't feel idiomatic / efficient.
I'm extremely confused, any clarification would be greatly appreciated.
Thanks in advance!
This is one of those fun 'magic' things that is supposed to simplify onboarding and make Ember seem easy - but really just caused the most confusion for what should be the most common use-case for the router.
At least it's in the guides now.
https://guides.emberjs.com/v2.12.0/routing/specifying-a-routes-model/#toc_dynamic-models
"Note: A route with a dynamic segment will always have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), and a model context is provided (second argument to link-to), then the hook is not executed. If an identifier (such as an id or slug) is provided instead then the model hook will be executed."
So, that's one thing...
But the other thing - is that you are getting back an array - because you are using query + filter - to create an array of records.
So, if you use queryRecord() - which is meant to get 1 record - then you'll get what you want. : )
I'd just add that {{outlets}} are cool. Here's how I normally do it - but t I can see that my route always loads all of the data this way... / which I normally want - but could see how in many cases - it wouldn't be desired.
And also - if you run into any more whacky params issues... investigate the underscore in the dynamic segment - because it's probably trolling you.
I have a model which contains a single data but it's inside an array. I want to retrieve this data from inside my controller and making it a property of the controller so I can use it in other controllers. For example :
App.CurrentsubuserController = Ember.ArrayController.extend({
currentsubuser: function() {
return this.get('model'); <-------** not working **
}.property()
});
Basically I want to get the whole associated model so I can access it's datas. What is the syntax I have to use? Thank you
I'm not 100% sure of what your goal is here, but from another controller you can do a someAttribute: Ember.computed.alias('controllers.someController.model')
There is no need to create any local attribute in the controller that is being provided model data unless you are transforming it in some way.
You will need to specify a needs in that controller to reference the one you are pulling model data from like (adjust for your global style javascript)
export default Ember.Controller.extend({
needs: ['someController'],
someAttr: Ember.computed.alias('controllers.someController.model')
})
I know that will work fine, but thats not to say you should be doing any of this. And, obviously, make sure the model data is in the originating controller as you expect. A quick way to validate this is tossing a logging helper into your handlebars like {{log model}} or using the Ember Inspector in the browser.
UPDATE: Based on your comment below, this will work
export default Ember.Controller.extend({
currentSubUser: Ember.computed.readOnly('model.firstObject'),
})
Then, in your template you can use {{ currentSubUser.foo }}
Maybe this will help:
Getting the model inside a controller emberjs
Basically the model is loaded asynchronously. You can use this.get('model').then(function(data) { ... }) to work with the data, once it's loaded. Although I suggest using Ember.computed macros, like .mapBy:
currentsubuser: Ember.computed.mapBy('model', 'subuserproperty')
http://emberjs.com/api/classes/Ember.computed.html#method_mapBy
There is no need to store the model into an attribute.
Check this about the dependencies between controllers.
But to answer your question, do so:
On the controller you want to retrieve the ** CurrentsubuserController** model you define the need of this controller:
export default Ember.ArrayController.extend({
needs: "currentsubusercontroller"
currentSubUserController: Ember.computed.alias("controllers.CurrentsubuserController")
});
And then you can access this controller and his model with this.get('currentSubUserController.model')
I'm happy that multiple models can be loaded into a route using something like this (although feel free to correct if there's a better way!):
model: function() {
var self = this;
return Ember.RSVP.hash({
foos: self.store.find('foos'),
bars: self.store.find('bars'),
});
However, if the model hook is set by a model being passed by, for example, a link-to helper, how should I add the additional model?
Thanks!
EDIT
So in my case I have a unit and objective, which are related. A unit can have many objectives - although its a one way relationship.
In my units route, I click on a unit which links-to units/unit:ID route. So the unit is set as the model, but I also want all objectives loaded into the model, so that I can select and add these to the unit.
Not everything in your page must be in the route's model. For example, you may have a Route that contains a Unit object. To get a list of all Objective objects, you could add a simple property to your controller:
allObjectives: function() {
return this.store.find('objective');
}.property()
Your template could then render the model as usual:
<div>Unit name: {{model.name}}</div>
<div>All objectives:
<ul>
{{#each objective in allObjectives}}
<li>{{objective.name}}</li>
{{/each}}
</ul>
So I had a route: /tournaments/setup/:tournament_id in my ember app, and it displayed various fields from a model.
But then I find that I'll need other models on the same page. For example, suppose I want the list of all players, so that I can make a dropdown for selecting players to join a tournament.
I can pass an object:
this.transitionTo('tournaments.setup', {tournament: tournament, players: players});
and that works once I change the handlebars template to reflect the change of context.
But now the URL is wrong. The URL is /tournaments/setup/undefined. I'm sure there is a way to explicitly handle this also, but I don't know what it is ...
... and I feel like I'm losing the path. Is there a more conventional way to do what I'm doing?
Update: I've found that I can get the URL to work by adding an id:
this.transitionTo('tournaments.setup', {id: tournament.id, tournament: tournament, players: players});
but I'm still wondering if I'm doing this the right way.
What you want to do is load the models in your route and stick them in a controller to user them. I usually do this in the beforeModel hook. So in your App.TournamentsSetupRoute do something like this:
App.TournamentSetupRoute = Ember.Route.extend({
beforeModel: function() {
var self = this;
var players = this.store.find('player');
players.then(function() {
self.controllerFor('players').set('model',players);
});
}
});
So now, when ever this route is entered, the players will be fetched from your API and stored in a controller. In your controller, just add the players controller to needs so you can access it.
App.TournamentSetupController = Ember.ObjectController.extend({
needs: ['players']
});
You can access the players models anywhere in your controller by doing this.get('controllers.players.model); and iterate over it in your template like so:
<select>
{{#each player in controllers.players.model}}
<option>{{player.name}}</option>
{{/each}}
</select>
Hope that helps! I haven't tested the above code, so there may be a few typos, but it should help get ya there.
In my app I have the following setup:
Router
this.resource('types');
this.resource('type', {path: 'types/:type_id'})
When a user navigates to types he gets a list with all the different types. When he clicks on a link he navigates to the specific type:page. Here I would like to show an array of products. Each type has an array of products. I tried to get the products doing so:
this.resource('types');
this.resource('type', {path: 'types/:type_id'}, function(){
this.resource('products', {path: '/products'});
});
Router
model: function(){
return this.store.find('product');
}
but that didn't work. I also tried with the params but that doesn't work.
model: function(params){
return this.store.find('product', {id: params_id});
}
Templates
Type-template
<h3>{{name}}</h3>
<hr>
{{outlet}}
Product-template
{{#each}}
{{name}}
{{/each}}
But nothing gets loaded in the template. I don't know if this is the way to go. I had some success with removing the nested route and simply loading the two models in the afterModel hook on the typeController, but that doesn't give me access to some other models, linked to the product.
It's very frustrating that some "easy" things, like loading an array of products that belong to a type, is such a hassle.
Edit
What I try to achief is that when you visit the homepage you can
click a link "types" that takes you to the url ..../types and shows you a list of all the types. No problem there.
When you click on a type it takes you to types/type_id (e.g. types/1) and show you some info about the specific type (that works too).
On that same page I want to show a list of all the products associated with this type. Here is where I'm struggling
Models
type
name: DS.attr('string'),
products: DS.hasMany('product', {async: true})
product
name: DS.attr('string'),
prodcuts: DS.belongsTo('type', {async: true})
I tried this first by passing two models on the same route as suggested in this question. That works fine to some extend, but I want to put an action and computed property on each of the products and because the models are loaded in the afterModel hook, they don't get updated. I thought the way to go was to input a nested resource that returns me all of the products based on the type_id but I can't get that to work somehow.