Complex code inside ember Route - ember.js

I have the Route
App.MyRoute = Ember.Route.extend({
model : function() {
return complexCode();
}
}
complexCode makes many asynchronous chained calls that depend of the above results.
for this reason I want complexCode is distributed into several files for easier maintenance of the code.
The problem I have is that when I'm in a method of another file I have no access to MyRoute.
One option is to pass the object as parameter but I want to avoid having to pass the object by all methods.
App.MyRoute = Ember.Route.extend({
model : function() {
var route = this;
return complexCode(route);
}
What is the best way to have gloal access a property or method of MyRoute?
It's ok the approach of separate the code in other files?

That's probably a bad pattern to use. It tightly couples your complexCode to the route. You'd be better off passing in the necessary values to execute the complex code, or creating a mixin and adding it to each route (this would then allow you to have methods that are within the scope of the route).
http://emberjs.com/api/classes/Ember.Mixin.html

Related

How do you set variable in route and display in template?

I am looking to set a variable for each page that is set to text to display as a dynamic header sort of thing. How would I set a variable in a route and display it in the corresponding template? So far everything I have tried is not working.
The context of a template in a route is its controller, not its route. Try moving the action to the controller.
See updated twiddle: https://ember-twiddle.com/b454e10355ae8c708c3b8dc24b51e44e?openFiles=controllers.my-route.js%2C
For more information about controllers: https://guides.emberjs.com/v2.16.0/controllers/
You can only pass route variable to the template by using model hook. However, model is called only once. In order to update route variable and see its final value in the template, you need to wrap the variable. After that, you need to update the variable inside wrapped and the referance of the wrapped will not be changed. Your code will be like:
route.js:
model(){
this._super(...arguments);
this.set('myWrappedVariable', {myVariable: 1});
let modelResult = {myWrappedVariable: this.get('myWrappedVariable')};
return modelResult;
},
actions:{
increaseVariables(){
Ember.set(this.get('myWrappedVariable'), 'myVariable', this.get('myWrappedVariable.myVariable')+1);
}
}
template.hbs
myWrappedVariable: {{model.myWrappedVariable.myVariable}}
Take a look at this twiddle for this usage.
#Gaurav answer is totally right and should be considered as best practice. However if you have a good reason for setting a variable, which is not the model, in a Route, you could use setupController hook therefore:
import Route from '#ember/routing/route';
export default Route.extend({
setupController(controller, model) {
// Call _super for default behavior
this._super(controller, model);
// Set a variable on controller
controller.set('foo', 'bar');
}
});
As shown in the api docs you could also use setupController to set a variable on another controller by getting an instance of it using controllerFor method. However I would not consider this a good practice, cause if it's not well documented throughout the application, it's quite hard to maintain such code.
As shown by #Ahmet Emre Kılınç's answer you could also use a hash as model. If at least one of it's properties is a Promise, you should return a RSVP.hash(). Otherwise model hook would not block the transition until the Promise is fulfilled.

Injecting into Ember views

In Ember you can inject objects into views using an initializer as follows (Ember-CLI syntax):
export default {
name: 'sayHello',
initialize: function(container, app) {
var thing = function() {
return 'Hello';
};
app.register('sayHello:main', sayHello, { instantiate: false });
app.inject('view', 'sayHello', 'sayHello:main');
}
};
This injects the method into all views including link-tos, inputs, list items in a collection view, etc. This seems like it would hinder the app's performance if the thing being injected was substantially sized. In many situations, you just want to inject something into a route-specific view or controller. What I mean by that is a view that Ember automatically associates with the current route.
Question: Is there a way to inject objects into just the route-specific views and not into the link-tos, inputs, etc, and does injecting the methods into all views noticeably inhibit the application's performance?
The ember guide says:
Injections can also be made on a specific factory by using its full
name:
application.inject('route:index', 'logger', 'logger:main');
-- http://emberjs.com/guides/understanding-ember/dependency-injection-and-service-lookup/
So the question is – which factory do you want to target.
I'm not sure if there is a factory that includes all user-defined views, but excludes all framework views. You should probably be able to target single views with view:application, etc.
An option may be to have a proxy view, which extends Ember.View, and from which you extend all your app views.

Different Ember Routes or Displaying Two Complex Views in their own context

I have an app with different related concerns. I have a list of items on the left that affect some of the items on the right (think Github Issues), but here's a mockup
All fine, I click Item One and something happens on the right. However, the list on the left has its own routing requirements, it's a resource of items with nested routes. For example when we click on Create New Item we display a new form inline (see bottom left corner)
When I do that it, even if it's on a different outlet, it overrides what is currently rendered in other outlets. In this case the products on the right.
It would be ideal if we could just have different routers for different sections. I know I could just call render and create a new view/controller, but that would require me to handle my own states (am I creating a new item, editing it, looking at the index, etc).
I'm currently looking into query-params as an option.
Any ideas?
I tried many ways to solve this and ended up having a route that encompasses all the various routes that need to coexist render the rest of the parent routes with {{render}}, and making those routes' renderTemplate hook a NOOP (you have to do this specifically, or you will get assertion errors that you are using the singleton form of render twice).
However you don't have to manage your own state -- you can still use nested routes, and their model hooks, and since you are using the singleton form of {{render}} things will still automagically render into their correct spots. To say that another way: if you are using the singleton form of {{render}}, routes can set that controllers model, via the model hook if the route has the same name as the controller, or in either the model or setupController hook of another route using controllerFor.
You can also render into named outlets in the renderTemplate hooks, but I eventually abandoned that approach because I still had problems with disconnectOutlet getting called on things I didn't want disconnected.
Discussion on some outstanding issues indicate that there may eventually be a way to control whether/when routes get torn down and their outlets disconnected, but only when a way to do it has been worked out that won't increase the chance of memory leaks for people doing things the ordinary way.
It's possible to register a different router and inject this into controllers. From ember's source code:
/**
#private
This creates a container with the default Ember naming conventions.
It also configures the container:
....
* all controllers receive the router as their `target` and `controllers`
properties
*/
buildContainer: function(namespace) {
var container = new Ember.Container();
// some code removed for brevity...
container.register('controller:basic', Ember.Controller, { instantiate: false });
container.register('controller:object', Ember.ObjectController, { instantiate: false });
container.register('controller:array', Ember.ArrayController, { instantiate: false });
container.register('route:basic', Ember.Route, { instantiate: false });
container.register('router:main', Ember.Router);
container.injection('controller', 'target', 'router:main');
container.injection('controller', 'namespace', 'application:main');
container.injection('route', 'router', 'router:main');
return container;
}
A second router could be created, registered with the container and injected into certain controllers:
App.Router = Ember.Router.extend();
App.Router.map function () {...}
Then to register
container.register('router:secondary', App.Router);
container.injection('controller:list', 'target', 'router.secondary');

Detect model/URL change for Ember Route

My EmberJS application has a ProjectRoute (/project/:project_id) and a corresponding ProjectController. When viewing a particular project, users can edit its properties, and I'd like the project to automatically be saved when the user stops looking at it.
Currently, what I'm doing is something like this:
Application.ProjectRoute = Ember.Route.extend({
...
exit: function() {
this.get('controller').saveProject();
}
...
});
This works when the user simply closest the project view. However, if the user simply switches to viewing a different project (e.g. goes directly from /project/1 to /project/2), the same route is used (it just uses a different model), and exit is not called.
What I need is a way to detect this transition and call the saveProject function before it happens. Any ideas?
I "solved" it by adding the following to my controller:
Application.ProjectController = Ember.ObjectController.extend({
...
saveOnChange: function() {
var previousProject = this.get('target.projectToSave');
if (previousProject && previousProject.get('isDirty')) {
Application.store.commit();
}
this.set('target.projectToSave', this.get('content'));
}.observes('content')
...
});
This seems to work well. Note that I'm storing the projectToSave property in the route (target) as opposed to the controller, because the controller gets wiped every time the model changes. It feels a little weird/hacky to store this in the route, but the only alternative I could think of was to store it in ApplicationController, which seemed overly broad.

Is it possible to hide substates from showing up in the URL when using the Ember Router v2?

I would like to have a route substate not show up in the URL, but still be able to take advantage of having a route class on which I can define renderTemplate, model, setupController, etc. hooks. Is this possible with the v2 router? I am using Ember release candidate 2.
Here's an example.
Suppose I have the routes:
/exercise/:exercise_id
/exercise/:exercise_id/correct
/exercise/:exercise_id/incorrect
I would like all of these to show up in the URL as:
/exercise/:exercise_id
As I don't want the student to just directly type in /correct onto the end of the ULR and get to the correct answer. And although I have a way to prevent that from working, the full route still shows up in the URL. From the student's perspective, I only want them to think about the state as /exercise/:exercise_id.
Of course I could just store the state correct vs. incorrect in some controller variable, but then I loose the convenience of having route classes, ExerciseCorrectRoute and ExerciseIncorrectRoute, which I want to behave differently, and so the hooks, like renderTemplate and setupController, are nice to have defined cleanly in separate places.
Thoughts?
Kevin
UPDATE:
I went with Dan Gebhardt's suggestion because I like to keep things as much as possible within the framework's considered design cases, as this seems to reduce headaches given Ember is still evolving. Also I didn't get a chance to try out inDream's hack.
Although I still think it would be nice if the router added a feature to mask substates from the URL.
Every route must be associated with a URL for Ember's current router.
Instead of using multiple routes, I'd recommend that you use conditionals in your exercise template to call the appropriate {{render}} based on the state of the exercise. In this way you can still maintain separate templates and controllers for each state.
You can reference to my answer in Ember.js - Prevent re-render when switching route.
Reopen the location API you're using and set window.suppressUpdateURL to true if you want to handle the state manually.
Ember.HistoryLocation:
Ember.HistoryLocation.reopen({
onUpdateURL: function(callback) {
var guid = Ember.guidFor(this),
self = this;
Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
if(window.suppressUpdateURL)return;
// Ignore initial page load popstate event in Chrome
if(!popstateFired) {
popstateFired = true;
if (self.getURL() === self._initialUrl) { return; }
}
callback(self.getURL());
});
}
});
Ember.HashLocation:
Ember.HashLocation.reopen({
onUpdateURL: function(callback) {
var self = this;
var guid = Ember.guidFor(this);
Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
if(window.suppressUpdateURL)return;
Ember.run(function() {
var path = location.hash.substr(1);
if (get(self, 'lastSetURL') === path) { return; }
set(self, 'lastSetURL', null);
callback(path);
});
});
}
});