Ember - How to send data down to a component? - ember.js

I'm building an Ember app that needs to fade out a background DIV when a form input becomes focused.
I have defined actions on my Application route, and set a property in my model (because I'm trying to do this without a controller, like the Ember 2.0 way). I'm trying to do Action Up, Data Down. I have the actions going up to the Application route, but the data just isn't making it back down to the component.
I have the actions bubbling up to the application route just fine, but when I update the property this.controllerFor('application').set('showBackground', true); it never makes it back down to the component.
I have this fading out background image on every route of my site, so moving all the actions to each route seems like a lot of code duplication.
What am I doing wrong?
// Application route.js
var ApplicationRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
showBackground: false
});
},
setupController: function(controller, models) {
controller.setProperties(models);
},
action: {
showBackground: function(){
// This runs fine
this.controllerFor('application').set('showBackground', true);
},
hideBackground: function(){
// This runs fine
this.controllerFor('application').set('showBackground', false);
}
}
});
// Background component.js
var BackgroundImage = Ember.Component.extend({
// This never runs for some reason!?
controlImage: function(){
if( this.get('showBackground') ) {
// Open menu!
console.log('show image');
} else {
// Close menu!
console.log('hide image');
}
}.observes('showBackground')
});
// Login template.hbs
{{background-image showBackground=showBackground}}
Is this the correct way to replace "properties" and controllers with routes? All the "move to Ember 2.0" advice I can find doesn't mention how to replace high level properties.
EDIT
I created a JSbin, but I'm not sure if it's setup correctly for the 2.0 style (no controllers), as the import/export (ES6?) stuff doesn't work on JSbin.
http://emberjs.jsbin.com/wunalohona/1/edit?html,js,console,output
I couldn't actually get any of the actions to bubble correctly.

Here is the working demo.
There were multiple issues in the jsbin you provided. Here are some of the issue I fixed.
You need to specify the routes, components on the App namespace or Ember will not be able to find it. The resolver used in ember-cli is custom.
var ApplicationRoute = Ember.Route.extend({ should be
App.ApplicationRoute = Ember.Route.extend({
var BackgroundImage = Ember.Component.extend({ should be
App.BackgroundImageComponent = Em.Component.extend({
More about it here.
You don't need to specify the setupController method in the route. By default the model returned from the model hook is set to the model property of the controller.
https://github.com/emberjs/ember.js/blob/v1.11.1/packages/ember-routing/lib/system/route.js#L1543
The proxying behavior of ObjectController along with ObjectController has been deprecated.
Now refer model property by adding model.+modelPropertyName
You can read more about this in the deprecation page for v1.11
action in the ApplicationRoute should be actions

Related

Implementation of Site-wide arrayController in Ember

I have been looking to a solution to this for about a week now with no luck. We have an ember application which has a sidebar that is present on all routes which displays a list of user posts. It is important that the posts update in real-time as they are submitted as well as sort with the newest post at the top of the list, which from what I've read will require an array controller. The problem is, I cant find any way (or rather dont understand) to use an array controller and specific model that is not directly referenced to the current route. I have tried rendering the sidebar with the following code in the application route:
Destination.ApplicationRoute = Ember.Route.extend({
model: function(model) {
var self = this;
return new Em.RSVP.Promise(function (resolve, reject) {
new Em.RSVP.hash({
post : self.store.find('post')
}).then(function (results) {
resolve({
post: results.post
});
});
});
},
renderTemplate: function(controller, model) {
this.render();
this.render('sidebars/postBar', {
outlet: 'postbar',
into: 'application',
controller: 'posts',
model: 'post'
});
}
Then I have the following code for my array controller
Destination.PostsController = Ember.ArrayController.extend({
itemController: 'post',
sortProperties: ['id'],
sortAscending: false
});
However this doesnt work at all and I'm having trouble finding any examples of how to accomplish this.
The approach you can use is to load whatever models you need for the entire application in the ApplicationRoute. You don't have to create the RSVP.Promise as you have done, simply return an RSVP.all or RSVP.hash as follows:
Destination.ApplicationRoute = Ember.Route.extend({
model: function(model) {
return Em.RSVP.Hash({
post : self.store.find('post')
// fetch other models as required
});
}
});
Now there are two options for the controller setup and rendering.
Option 1: Outlets and route based controller setup.
The next thing is to setup the appropriate controller and render the view. Assuming you have defined an {{outlet 'sidebar'}} in your application template, the ApplicationRoute can render the sidebar as follows:
Destination.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model, transition) {
// perform default application controller setup
this._super(controller, model, transition);
// setup sidebar controller model
this.controllerFor('side-bar').set('model', model.posts);
// setup other controllers as required...
},
renderTemplate: function(controller, model) {
// render `posts` template into `side-bar` outlet with `side-bar` controller.
var c = this.controllerFor('side-bar');
this.render('side-bar', { outlet: 'side-bar, controller: c });
// other top level outlet rendering as required...
}
});
Option 2: View helper based controller setup and rendering.
Instead of using additional outlets, we can avoid the need to override setupController or renderTemplate in the route entirely. We can use the handlebars render helper to specify both the model and controller to use directly from our template.
So given your application controller will be setup with the result of the RSVP hash by default, it will contain a 'posts' property on its model/content. Just add the following to your application template:
{{render 'side-bar', posts}}
The above will render the sidebar template and setup the singleton SideBar controller using the posts model for you. I think this is cleaner than messing about with outlets given it doesn't sound like you going to be rendering different views into the sidebar based on your question.
API documentation on the render helper is here, with an overview of the rendering helpers here.
Note I have used Ember-cli resolver naming conventions which use a dasherized naming convention. If you're not using Ember CLI (which I highly recommend) then you may have to use the PascalCased string names ie 'SideBar' instead of 'side-bar'.

Emberjs: Resetting controller based on dynamic segment

I have this route structure in my app:
Profile.Router.map(function() {
this.resource('profile', { path: '/:user' }, function(){
this.route('followers');
});
});
My /:user/followers page is supposed to show the list of followers of :user. The controller for profile.followers is setup for infinite scroll - so it has properties like curPage, resultsPerPage.
Profile.ProfileFollowersController = Ember.ArrayController.extend(InfiniteScroll.ControllerMixin,{
curPage: 1,
resultsPerPage: 20,
//other code for infinite scroll which increments curPage as user scrolls down
})
Now, since controllers are singleton in emberjs, when I move from /tom/followers to /jin/followers, the properties for infinite scroll that were set for /tom/followers get preserved and applied for /jin/followers as well.
For eg. say I am on /tom/followers and scroll down 4 pages, curPage property of 'profile.followers' controller gets set as 4. Now when I move to /jin/followers, though the model hook of the route would return list of followers for jin, but would pick curPage as 4 since ember's controllers are singleton and I had scrolled down to 4th page on /tom/followers.
How is this supposed to be handled?
Here is my profile.followers route as well:
Profile.ProfileFollowersRoute = Ember.Route.extend({
model: function(params) {
console.log("fetching followers model");
return Ember.$.getJSON('/'+this.modelFor('profile').user+'/followers?new=1');
},
});
You can use the route's setupController hook for this. This hook is fired every time the route is entered.
App.ProfileFollowersRoute = Ember.Route.extend({
setupController: function(controller, model) {
// Call _super for default behavior
this._super(controller, model);
// Reset the desired controller properties
controller.setProperties({
curPage: null,
someOtherProp: null
});
}
});
I've made a jsbin demonstrating it:
http://emberjs.jsbin.com/fiyetefeno/6/
You can read the API documentation here:
http://emberjs.com/api/classes/Ember.Route.html#method_setupController

Loading Routes in nested route Hierachy

I am working on a mobile application with Ember. I want to make the user experience as good as possible and try to take into account that on mobile the connection is not always as good, that is why I want to utilize the loading routes with a loading spinner. Unfortunately in one case it is not behaving as I would expect:
In my Nested route Setup:
UserRoute:
UserIndexRoute (=Profile)
UserFriendsRoute
On the UserRoute I only load a small version (=different model) of the user. In 95% of the cases this model is already loaded when I want to navigate there. And in the Subroutes (e.g. UserIndexRoute and UserFriendsRoute I only need the full user.
What I want to achieve is that the UserRoute with its template is directly rendered when navigating to e.g. UserIndexRoute and then in the outlet for the Index part I want the UserLoadingView to be rendered. But the rendering always waits for all promises to be resolved and the UserLoadingView is never shown.
How can I force Ember to render the UserRoute and then the UserLoadingView in the outlet until the UserIndexRoute Model is resolved?
How I implemented it:
afterModel: function(model, transition){
var _this = this,
params = Ember.get(transition, 'params.user');
this.get('store').find('user', params.user_id).then(function(user){
_this.transitionTo('user.profile', user);
});
}
Don't use the index route for fetching the full model, just use it as a means for redirection.
Do something like this:
UserRoute:
UserIndexRoute
UserFooIndexRoute (=Profile) (Naming is up to you)
UserFriendsRoute
Then hook up your index route to fetch the full model and transition to FooIndex when it's completed getting the model, this depends on it being a route with a dynamic segment (:id).
App.UserIndexRoute = Em.Route.extend({
redirect: function(){
var self = this;
fetchTheFullModel.then(function(model){
self.transitionTo('user.fooIndex', model);
}
}
});
If it isn't like that you can do just transition to the other route after the transition and page has finished rendering.
App.UserIndexRoute = Em.Route.extend({
redirect: function(model, transition) {
var self = this;
transition.then(function(){
Ember.run.scheduleOnce('afterRender', function(){
self.transitionTo('user.fooIndex');
});
});
}
});
http://emberjs.jsbin.com/zohav/1/edit
You can read more about the transition promise, and afterRender here Ember transition & rendering complete event

Find query concept for route and controller

my question is a little bit general. What is the best concept for route and controller with findQuery in ember.
I have api with data filtering. Data request is executed by
this.store.findQuery('dataModel', {"q": JSON.stringify({"filters": filters})});
after that I show them in table view. The filter is updated by form views in a template.
My current solution:
Form views set controller parameters and a button call action from controller. Controller action loads parameter, executes findQuery and set('content',data).
In most cases I saw concept with a defining model: function() .. in the Route and setupController: function(controller, model) with controller.set('content',model). I like this "set" because 'content' is RecordArray (not PromiseArray) and I can easily use that for datatables and another JavaScript plugins. I think my solution isn't good.
I think your concept is correct, I have been using the following flow:
In your router:
App.Router.map(function() {
this.resource('search', { path: '/query/:filters' });
});
App.SearchRoute = Ember.Route.extend({
model: function(params) {
return this.store.findQuery('dataModel', {"q": JSON.stringify({"filters": params.filters})});
});
In your html, just bind the action which will lead to the new Search Route,
something like below :
<button {{action "doSearch"}}>Search</button>
In your controller:
App.SearchController = Ember.ArrayController.extend({
...
actions: {
doSearch: function() {
var query = buildYourQueryObject();
this.transitionToRoute("search", query);
}
}
Upon clicking on the button, the app will transition into your search route, and "query" will be serialized and sent into the Route, and the Route.model() will attempt to be populated based on the serialized parameters provided.
Note: The code has been simplified, you might need to add more stuff in order to make it work

Ember.js pre4, how to do the previous pre2 connectOutlet stuff

In pre2, suppose I had this application code, outside the router:
var controller = App.MyController.create();
controller.content = [...];
App.get('router').get('applicationController').connectOutlet({
outletName: 'modal',
controller: controller,
viewClass: App.MyView,
context: controller
});
That is, I fill an outlet named 'modal' added to the 'application' template, with my data.
Now, in pre4 I have no reference to the controllers created by the router. How would you fill an outlet from outside the router?
I could ask the router for a transition, but I don't want to modify the URL, as I'm just opening a modal over the current content.
EDIT:
This is what I came up with for a temp fix, by looking up the application view from the App.Router.router object.. obviously it's a dirty hack, anyone know the best & right way to do it in pre4?
var controller = App.MyController.create();
controller.content = this.get('content');
var theView = App.MyView.create();
theView.set('controller', controller);
App.Router.router.currentHandlerInfos[0].handler.router._activeViews.application[0].connectOutlet('modal', theView);
If you just need to add your view into the app you can use my solution in this question:
What's the right way to enter and exit modal states with Ember router v2?
But if you need to add it too an outlet, you can do it by sending an event to the router and just render it in the event without transitioning it to another route.
events: {
showModal: function(){
this.render('modal', {into: 'index', outlet: 'modalOutlet', controller = this.controllerFor('modal')});
}
}
See fiddle for an example:
http://jsfiddle.net/Energiz0r/gChWa/1