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.
Related
Ember.View is deprecated, in favor of Components. That's great but I'm having trouble making sense of the 2.0 release.
Most often, I used the didInsertElement hook to run some jQuery code etc. But now that the Em.View class has been deprecated, how can I achieve the same thing? I don't want to create a component or anything like that. It doesn't make sense to create components for normal pages(routes). Simply because its not a re-usable thing plus component's scopes are isolated.
Say we have a about route, and when the template is rendered I just want to sun some jQuery code. How can I do this in Ember 2.0+?
You could take advantage of didTransition hook and Ember.run.next. Check my solution:
export default Ember.Route.extend({
actions: {
didTransition() {
Ember.run.next(this, 'initParticles');
}
},
initParticles() {
let ammount = (window.matchMedia('(max-width: 456px)').matches) ? 40 : 100;
particlesJS('particles-js', {
// my options
});
}
});
While it doesn't seem like the best approach, creating a component is probably your best option. Soon we'll have routeable components which will take over much of what controllers and views use to do. Creating a component and just inserting it into your template should put you on a good path to be ready for routeable components.
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
Cannot get my head around the following problem. I got a Uncaught TypeError: Cannot read property 'send' of undefined whatever I try. I think it must be the controllerBinding in the itemsViewClass, however I think it is defined correctly.
In the code below there are two showMenu actions. The first one works, but the last one in the itemsViewClass does not.
Please take a look at my code below (I show only the relevant code):
//views/menu.js
import Ember from "ember";
var MenuitemsView = Ember.View.extend({
template: Ember.Handlebars.compile('<div{{action "showMenu" target="view"}}>this works already</div>\
much more code here'),
contentBinding: 'content',
itemsView: Ember.CollectionView.extend({
contentBinding: 'parentView.subCategories',
itemViewClass: Ember.View.extend({
controllerBinding: 'view.parentView.controller', // tried to add controllerBinding but did not help
// this is where the question is all about
template: Ember.Handlebars.compile('<div {{action "showMenu" target="parentView"}}>dummy</div>')
}),
actions: {
showMenu: function(){
// dummy for testing
console.log('showmenu itemsView');
}
}
}),
actions: {
showMenu: function() {
console.log('showMenu parentView!'); // how to reach this action?
}
}
});
export default MenuitemsView;
I have tested with {{action "showMenu" target="view"}} and without a target. It seems not to help.
Do someone have a clue why the second showMenu action cannot be reached?
OK, so this is by no means the only way to do logic separation in Ember, but it's the method I use, and seems to be the method generally used in the examples across the web.
The idea is to think of events and actions as separate logic pools, where an event is some manipulation of the DOM itself, and an action is a translatable function that modifies the underlying logic of the application in some way.
Therefore, the flow would look something like this:
Template -> (User Clicks) -> View[click event] -> (sends action to) -> Controller[handleLogic]
The views and the controllers are only loosely connected (which is why you can't directly access views from controllers), so you would need to bind a controller to a view so that you could access it to perform an action.
I have a jsfiddle which gives you an idea of how to use nested views/controllers in this way:
jsfiddle
If you look at the Javascript for that fiddle, it shows that if you use the view/controller separation, you can specifically target controllers to use their actions, utilising the needs keyword within the controller. This is demonstrated in the LevelTwoEntryController in the fiddle.
At an overview level, what should happen if your bindings are correct, is that you perform an action on the template (either by using a click event handler in the view, or using an {{action}} helper in the template itself, which sends the action to the controller for that template. Which controller that is will depend on how your bindings and routing are set up (i've seen it where I've created a view with a template inside a containerView, but the controller is for the containerView itself, not the child view). If the action is not found within that controller, it will then bubble up to the router itself (not the parent controller), and the router is given a chance to handle the action. If you need to hit a controller action at a different level (such as a parent controller or sibling), you use the needs keyword within the controller (see the fiddle).
I hope i've explained this in an understandable way. The view/controller logic separation and loose coupling confused me for a long time in Ember. What this explaination doesn't do, is explain why you are able to use action handlers in your view, as I didn't even know that was possible :(
I have an app with many similar views which I instantiate programmatically to "DRY-up" my app.
The problem is that controllers instantiated programmatically do not delegate actions in the actions hash further. This is clear because there is nothing from which the controller can derive the hierarchy. There should be a way, however, to tell a controller which parent controller it has to use for event bubbling. Does anyone know it?
You shouldn't be initializing controller's on your own. All controller initialization should be handled by Ember itself. Another interesting note, controller's are intended to be singletons in the application. The only exception to this being the itemController when looping over an ArrayController. You can read more about it in the guides. Quote from the guides:
In Ember.js applications, you will always specify your controllers as
classes, and the framework is responsible for instantiating them and
providing them to your templates.
This makes it super-simple to test your controllers, and ensures that
your entire application shares a single instance of each controller.
Update 1:
An example of how to do routing for a wizard:
App.Router.map(function() {
this.resource('wizard', function() {
this.route('step1');
this.route('step2');
this.route('step3');
});
});
This way, you can have a separate controller/view/template per step of the wizard. If you have logic around how much of each step should be completed prior to transitioning to the next one, you can handle that in the individual routes.
Update 2:
In the event that the number of steps aren't predetermined, but are based on the data being fed to the app, you can make a WizardController that is an ArrayController where each item in the array is a step in the wizard. Then, use the lookupItemController hook on the ArrayController, kind of like this:
App.WizardRoute = Ember.Route.extend({
model: function() {
return [
{controllerName: 'step1', templateName: 'step1'},
{controllerName: 'step2', templateName: 'step2'},
{controllerName: 'step3', templateName: 'step3'}
];
}
});
App.WizardController = Ember.ArrayController.extend({
lookupItemController: function(modelObject) {
return modelObject.controllerName;
}
});
{{#each step in controller}}
{{view Ember.View templateName=step.templateName}}
{{/each}}
As another, probably better, alternative, you can override the renderTemplate hook in the route where you're pulling down the model for the next step in the wizard and pass in the appropriate templateName and controller in the render call, kind of like you see here.
Point being, I think it should be possible to do this without having to instantiate controllers yourself.
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');