I'm having a part of my applications split away from the main template in a sepparate handlebars template using {{render "header"}} to render that at the right place.
This sepparate template also has its own controller to display some data from the model (App.Notification).
What I want to do is display a limited amount of notifications and the number of new notifications in total.
Ember data loads 10 entries from the server when the controller's notifications property is called in an each loop but as soon as I try to limit the amount of shown notifications via splice, it won't return any data.
Basically, I'm having trouble handling model data if I can't set the controllers model in a route, I assume that's why the normal syntax with slice isn't working in this case.
I already searched Stackoverflow and the Ember docs but only found examples with normal Route -> Controller setup but nothing on this particular topic, so the question is, how to correctly handle model data in this setup?
Header Controller:
App.HeaderController = Ember.Controller.extend
notificationNew: (->
#get('notifications').filterBy('seen', false).get('length')
).property('notifications')
notifications: (->
#store.find('notification').slice(0, 2)
).property('#each.notification')
Template:
{{#each notifications}}
...
{{/each}}
To set properties on a controller not affiliated directly with a route by naming convention refer to the following jsbin.
http://emberjs.jsbin.com/gugajaki/3/edit
App = Ember.Application.create();
notifications = [
{seen: false, message: "tornado siren!!"}
{seen: true, message: "Omg lol security breach"}
{seen: false, message: "BFF Rite?"}
{seen: false, message: "steve kane 4 prez?"}
]
App.HeaderController = Ember.Controller.extend
newNotifications: Ember.computed.filterBy("notifications", "seen", false)
App.ApplicationRoute = Ember.Route.extend
setupController: (controller, model) ->
headerController = this.controllerFor("header")
#this can be a remote fetch via find. will work the same
headerController.set("notifications", notifications)
There are several issues in your posted code will are addressed in the link but I will enumerate here for clarity:
You can use the length property directly in the template
Use the Ember.computed.filterBy to get very efficient array observations
Configure your singleton instance of the header controller by using the application route's "controllerFor" method
I don't understand the splice but I wouldn't do it anyway
Hope this helps.
Related
I have a search feature that allows the user to filter out by products. Typically, I would have the following:
// Routes
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
keyword: params.keyword,
products: this.store.find('product', { name: params.keyword, status: 'available' })
});
},
setupController: function(controller, model) {
controller.set('content', model);
}
});
In the template:
// Template
{{#if products.length}}
<ul>
{{#each products}}
<li>
Name: {{name}}
</li>
{{/each}}
</ul>
{{else}}
<p>No products</p>
{{/if}}
Which works. However, what if I wanted a search API endpoint to handle filtering of products...
How will the model hook in the routes look like? Should I create an Ember search model? Is that the right approach?
If not, should I be using Ember.$.ajax instead? Other approach?
Ember.js provides facilities to deal with _query_ing data at resource level. Here is a suggested solution followed by some other options that are closer to your expectations.
Expected Use Case
In Ember, you don't have to map your "representation" of data to your server endpoint. You don't have to be RESTful with your Ember routes (as in URLs).
In Ember, one "representation" of data (say a Controller and View set) can consult multiple data sources.
Use "q" parameter for full text search on your resource.
The point behind all of this is, to decrease complexity of your backend API. If you build a search and query system for one resource, you can reuse it for all other resources of your backend.
Create a search URL
// in your router
this.route('search')
Create a search Route to fetch required data
Ember.RSVP.hash({
stores: this.store.find('store', {
rate_minimum: 4.0,
near: 'lat:long',
q: 'local store name'
}),
products: this.store.find('product', {
price_maximum: '$50',
near: 'lat:long',
q: 'famous shoe brand'
})
});
Fast Response Way
Use Store#pushPayload
Download data with Ember.$.ajax from your search endpoint. Keep in mind as long as your data has a name, you can use Store#pushPayload to load multiple unrelated data types into Ember Data's Store.
In your Route's model hook, use this.store.all('product') to return all products in memory without sending a request. At some point in future Ember will update this array. You can use Ember.RSVP to "represent" more than one resource.
In your Controller, use array operations like filterBy to provide real time update to users' queries beside issuing a new request to API for more relevant data.
This results in immediate response while waiting for backend to reply and immediately updating "representation" of data with backend response.
Ember Data-less Way
Create a Search Route
Use Ember.$.ajax to get data
Pass it as your Route's model hook
Send another ajax request whenever user changes search criteria
I don't think you would do anything different. When you execute a store.find with query parameters, in your case { name: params.keyword, status: 'available' }, the result that is return by the query is what will show in the controller; assuming you are using an ArrayController. Your model and controller should not need to change, you just control what you want based on how you structure your query.
// display all models that are in the store
store.find('someModel')
// display a single model
store.find('someModel', 123) // will display a single model
// display only models that match the query
store.find('someModel', { name: params.keyword, status: 'available' }
Now if you are dealing with endpoints that don't conform to the norm, ie. your search endpoints are not just at /products?name=some-name&status=available, then you may have to modify your adapter slightly to deal with the differences. Doing this in the adapter means you still have a simple controller/route/model and you are hiding the complexity which is very specific to the endpoint in the adapter.
I'm making a simple web chat system with Ember.
I have a route /chatrooms that lists a few chatrooms, and then I also have /chatrooms/:chatroom_id that should show the actual chatroom with messages.
The second route is within the first one, like this:
this.resource('chatrooms', function() {
this.route('show', {
path: ':chatroom_id'
});
});
When I access /chatrooms, a call is made to the server (/api/chatrooms) is a list of rooms is returned and displayed, like expected.
When I click a room, the application transitions to /chatrooms/id, but no call is made to retrieve the messages (available at /api/chatrooms/id), even when I try to define a model.
I have a similar scenario with the users. A list of users is retrieved, then displayed. When a name is clicked, the profile is shown. No second call is made, but that's okay since Ember knows everything about the user already.
In my current case, when a list is first returned, it includes all the information except the messages. I believe that would be too much otherwise (10 chatrooms * 100 last messages = 1000 elements in my JSON for each request). So I want to call the server for messages only when a chatroom is selected.
Do you know how to do it, or maybe there's something wrong I'm doing in the first place?
Updates
Template code from app/templates/chatrooms.hbs
<h1>Chatrooms</h1>
<ul class="sub-menu nobullet flex mas">
{{#each chatroom in model}}
<li class="mrs">{{link-to chatroom.name "chatrooms.show" chatroom class="pat"}}</li>
{{/each}}
</ul>
{{outlet}}
In this case, model is an array of chatrooms.
My routes:
app/routes/chatrooms.js
export default Ember.Route.extend({
model: function() {
return this.store.find('chatroom');
}
});
app/routes/chatrooms/show.js
export default Ember.Route.extend({
model: function(params) {
return this.store.get('chatroom', params.chatroom_id);
},
actions: {
send: function() {
...
}
}
});
As discussed in this thread, when you link-to a route and the model is already loaded, the model hook of the route is not fired because there’s no need to reload the data.
If you transition to a route and all the context objects -the objects which will serve as models to templates- are passed in, the beforeModel and model hooks will not be called.
Later in the thread balint corrects:
In fact, the beforeModel hook still gets called in that case, it is only the model hook that does not.
If you want to force the model to be reloaded, you can change your link to use the ID instead of the model:
{{link-to chatroom.name "chatrooms.show" chatroom.id class="pat"}}
You could also load the data in the beforeModel or afterModel hooks, or setupController.
Also, in the chatrooms/show route, you are getting the already-loaded model from the Ember Data store rather than loading it from the server. Try this:
return this.store.find('chatroom', params.chatroom_id);
I ended up adding a links property to the JSON response for chatrooms. When the content of a chatroom has to be displayed, the link is used and the messages retrieved. It only requires two requests, and there's not need to preload all the messages from all the chatrooms and no need to make a request for each message.
Ive got little basic problem with ember.
Here is app: http://emberjs.jsbin.com/qivamuzu/5 (click test - working like a charm, because model is in memory - loaded in index page)
But when you try page #test directly http://emberjs.jsbin.com/qivamuzu/5#/test all the data disappear (and that's bad - does not fired index route and load model). I follow this question Why isn't my ember.js route model being called? but doesn't help me.
I need to use template with model in other templates - I use {{render index}} but I'm not sure what to use and how. Please help me I am stuck.
I'm a little unclear on exactly what you're trying to do.
If you're just trying to use the same model data with a different route (and template) then you can explicitly set the model data to be the same in the route definition:
App = Ember.Application.create();
App.Router.map(function() {
this.route('test');
});
var myModelData = ['red', 'yellow', 'blue'];
App.IndexRoute = Ember.Route.extend({
model : function(){
return myModelData;
}
});
App.TestRoute = Ember.Route.extend({
model : function(){
return myModelData;
}
});
Here's a working JSBin example:
http://emberjs.jsbin.com/qivamuzu/8/edit
EDIT: Additional Information That May Help
Okay, one more stab at it =) When you navigate directly to the test page there is no data because the TestRoute is using the model data from the IndexRoute which hasn't been loaded yet. What you can do is force the creation of the IndexController and model by initializing it from the ApplicationRoute which will always be invoked when you first go to any route in your application.
First you have to generate the controller since it doesn't exist yet.
this.generateController('index');
Then you can get the controller and set its model data:
this.controllerFor('index').set('model', ['red','green','blue']);
Here's a working fiddle and I actually tested it this time to make sure it works when you go straight to #/test. I removed the extra routes and things that aren't actually needed from my previous example.
http://emberjs.jsbin.com/qivamuzu/11#/test
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.
So I'm making a little toy ember app that will present a user with some improv prompts to create a comic strip out of. After a certain amount of time, the user will be able to upload their completed comic.
I've got the whole thing working up to the point of preseting the form to the user to upload their completed comic.
So far, everything is one route, controller and model (prompts). At the end though, they really should be working with a comic record. At the present, everything is in the same template, and I put the upload form into a view:
App.UploadComicView = Ember.View.extend
templateName: "upload_comic"
tagName: "form"
classes: ['form-horizontal']
submit: ->
#get('controller').uploadComic(#get('comicTitle'))
false
For the following model:
App.Comic = DS.Model.extend
title: DS.attr('string')
And I attempted to do the following in the prompts_controller:
uploadComic: (title) ->
App.Comic.createRecord(title: title)
#get('store').commit()
However, that attempts to sent a update action to the prompts controller on the backend, not a create request to the comics controller.
So it seems like I really should have a separate ember controller/template/model/route for comics, but I'm not sure how to get from the prompts controller at the end of the challenge to the comics controller in order to create a new comic object in the database. To make matters worse, I also really need to send the selected prompts from the current prompts controller to the new context.
What would be the best way to proceed?
When you do a App.Comic.createRecord it will do a POST to /comics with the request body {title: 'your title'}. This is regardless of which controller/location you do it from. Are you saying that it goes elsewhere? Please post a jsbin if this is the case.
Regarding the need to communicate with/access other controllers, you can use the needs declaration inside the other controller.
needs: 'comics'
comicsBinding: 'controllers.comics'
You can now access the comics controller via this.get('comics') inside it.