ember concerns implementation - ember.js

How would you implement concerns in ember. For instance, send invite functionality:
user have 5 invites (store involved from fetching data)
invite available from any application state
it appears in modal
it can be more than one more - thus {{outlet modal}} doesnt work as well
user can enter email and send invite (available invites number decreased)
current modal implementation - thus cannot assign controller to a view...
openModal: function(modal) {
var modalView = this.container.lookup(modal);
modalView.appendTo(MS.rootElement);
}
Component approach doesnt work as for me: content (model) should be setuped by routed, i dont know event in component which can be useful for data fetching.
Nested routes doesnt work as well - to much to change.
I cant find any working approach for this, please help.
Thanks in advance.

You can assign a controller to a view. Here's a simple example where I can inject a view from anywhere in the page, assign it a controller, and when I repop up the view it has the same controller (if that was the intended outcome). If this doesn't get you going in the right direction, let me know.
You can use bubbling to allow it to be called from anywhere in the app as well.
http://emberjs.jsbin.com/uhoFiQO/4/edit
App.InviteView = Ember.View.extend({
templateName: 'invite'
});
App.InviteController = Ember.Controller.extend({
actions: {
pretendToSend: function(){
var invites = this.get('invites');
this.set('invites', invites-1);
}
}
});
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model){
var inviteController = this.controllerFor('invite');
inviteController.set('invites', 5);
},
actions:{
popInvite: function(){
var inviteController = this.controllerFor('invite');
// create/insert my view
var iv = App.InviteView.create({controller: inviteController});
iv.appendTo('body');
}
}
});

Related

Ember - How to send data down to a component?

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

Emberjs go back on cancel

I have a link to User displayed from various screens(From User List, User Groups etc.). When the link is clicked, User is presented to edit. When cancel button is pressed in the edit form, I would like to transition to previous screen userlist/group. How is this generally achieved in Emberjs.
Thanks,
Murali
You need nothing more than
history.back()
One of the main design objectives of Ember, and indeed most OPA frameworks, is to work harmoniously with the browser's history stack so that back "just works".
So you don't need to maintain your own mini-history stack, or global variables, or transition hooks.
You can put a back action in your application router to which actions will bubble up from everywhere, so you can simply say {{action 'back'}} in any template with no further ado.
Here's my solution, which is very simple and high performance.
// file:app/routers/application.js
import Ember from 'ember';
export default Ember.Route.extend({
transitionHistory: [],
transitioningToBack: false,
actions: {
// Note that an action, like 'back', may be called from any child! Like back below, for example.
willTransition: function(transition) {
if (!this.get('transitioningToBack')) {
this.get('transitionHistory').push(window.location.pathname);
}
this.set('transitioningToBack', false);
},
back: function() {
var last = this.get('transitionHistory').pop();
last = last ? last : '/dash';
this.set('transitioningToBack', true);
this.transitionTo(last);
}
}
});
There is probably a way to DRY(don't repeat yourself) this up, but one way of doing it is to have 2 actions: willTransition which Ember already gives you and goBack which you define yourself. Then, there is a "global" lastRoute variable that you keep track of as follows:
App.OneRoute = Ember.Route.extend({
actions: {
willTransition: function(transition){
this.controllerFor('application').set('lastRoute', 'one');
},
goBack: function(){
var appController = this.controllerFor('application');
this.transitionTo(appController.get('lastRoute'));
}
}
});
And your template would look as follows:
<script type="text/x-handlebars" id='one'>
<h2>One</h2>
<div><a href='#' {{ action 'goBack' }}>Back</a></div>
</script>
Working example here

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

Access data from controller in view in didInsertElement

I am trying to access data from DashboardIndexController in DashboardIndexView
JP.DashboardIndexController = Ember.Controller.extend({
users: []
});
Is it possible to access users in JP.DashboardIndexView in didInsertElement?
didInsertElement : function(){
console.log(this.get("controller.users").objectAt(0));
}
This is my DashboardIndexRoute:
JP.DashboardIndexRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('users', JP.User.find());
}
});
Thank you
EDIT
console.log(this.get("controller.users").objectAt(0));
Returns data only when I go to UsersIndex and then back to DashboardIndex... I think it's something with initialization, but I don't know, how to fix it.
Yes, this is how to access the list of users.
This happens because the DashboardIndexView is inserted into the DOM before the controller.users is populated with data from the server.
So when the view is rendered, controller.users is still empty, and will asynchronously be populated after the ajax request finishes, problem is, didInsertElement already fired.
In order to solve this, you need to access controller.users from another hook. DidInsertElement is the wrong place, because it will fire irrespective of whether JP.User.find() finished loading.
You just need to make sure that it re-fires when JP.User.find() is loaded even if view is already in the DOM. Something like this:
JP.DashboardIndexView = Ember.View.extend({
didInsertElement: function() {
this.usersChanged();
},
usersChanged: function() {
if (!this.$()) { return; } // View not in DOM
console.log(this.get('controller.users'));
}.observes('controller.users.#each')
});

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