I build a basic emberjs app, the app lists posts, and every post has a star / unstar event.
I would like to list all starred posts in the sidebar, without server side communications. What is the best way to do that? My first idea is that: I create a star action to PostsController, which add starred posts to an array, and I will list this array in the template.
The simplest solution I can think for this is to set an attribute star on the post model which will be false by default, then you can set star to true when you want and render in the sidebar all post filtered by star attribute.
Code would be something like this (coffeescript) :
App.Post = DS.Model.extend
title: DS.attr('string')
star: false
App.Post.reopenClass
stared: ->
#filter (post) -> post.get('star') is true
From this you can render App.Post.stared() array in your sidebar.
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.
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.
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.
I have two simple models in my Ember application that work like so:
App.Book = DS.Model.extend({
tags: DS.hasMany('tag')
});
App.Tag = DS.Model.extend({
books: DS.hasMany('book', { async: true })
});
Users can easily add and remove tags to a given book via our UI. The code that runs when a user removes a tag, for example, is:
book.get('tags').removeRecord(tagToRemove);
book.save();
This works just fine in removing the tag from the book. However, the tag model itself still references the book. In other words, the inverse relationship is not automatically updated.
EDIT:
After doing a JS Bin (http://jsbin.com/ucanam/1574/edit), it seems the reason the tag's books property is not updating automatically is because of the { async: true } option I passed. That said, I'm not sure I can really afford to remove that option, because a tag may have millions of books and I don't want to load all of them with the tag.
Any way to get things working as-is?
The only way Ember Data would 'magically' remove from both is if you deleted the record. If you just remove the record from one of the other records, technically Ember Data isn't positive you don't want to still have the other relationship from the other record to this record.
I'm trying to make this ember app, where one controller depends on another like so:
App.BooksController = Ember.ArrayController.extend({
itemController: 'book'
});
App.BookController = Ember.ObjectController.extend({
needs: 'books',
formattedPrice: function(){
return "$"+this.get('price').toFixed(2);
}.property('price'),
imageUrl: function(){
var img = this.get('image');
return "http://localhost/book-store-restful-api/images/book_covers/"+img;
}.property('image')
});
And there is 2 routes: #/books and #/books/book_id
When I navigate to #/books I get a list with all the books, along with a link pointing to each individual book controller. When I click it, it works as expected some individual book information is shown. But if I would browse to 1 #/books/book_id (it renders inside books template) then the list of the books get rendered, but it looks like the subtemplate get rendered before the associated model is fetched, therefore when navigating directly, all the individual book properties are undefined. If I click on corresponding link after it is loaded, the data becomes available.
So in a nutshell, is there anyway to tell the BookController to say that it only renders the associated single book sub-view after the model data was fetched?