This is one of those Ember issues that I'm unable to replicate anywhere but my project. The visual effect of the problem is that the active class on a link-to is one transition behind. I'll click a link and the link that goes to the page I was just on is highlighted with the active class.
I've started digging into the link-to component code to figure out how active is computed. But it is based on _routing.currentState and I'm not sure what that is. The currentState, and other bits of info, are passed to the routing's isActiveForRoute which then calls the routerState's isActiveIntent. And that function calls another isActiveIntent and compares some more things together. All this seems like a large easter egg hunt for something (the root of my problem) that is probably not in Ember's code anyways.
I feel like the following snippet sums up the problem I'm having. The targetRouteName is the route that is being directed to by the link. _routing.currentRouteName seems to be pointing to the route the browser is currently looking at. The fact these match makes me feel like the link should be active, but the active function returns false.
> link.get('targetRouteName')
"parentRoute.pageA.index”
> link.get('_routing.currentRouteName')
"parentRoute.pageA.index”
> link.get('active')
false
For reference this is after finding the link via the Chrome extension and showing all components. I then did link = $E.
For the wrong link (the one that does get the active class) I get:
> link.get('targetRouteName')
"parentRoute.pageB.index"
> link.get('_routing.currentRouteName')
"parentRoute.pageA.index"
> link.get('active')
"active"
Additional Raw Information
The routes I'm dealing with are nested. But it is a pretty standard nesting, very much like the one I have in my ember-twiddle (e.g. page-a, page-b, page-c).
There is a model hook on the parent route and on the indexs of the children routes. But the children routes reference (this.modelFor(...)) the parent.
My template is referencing the .index of those routes. They are standard link-to components. They do not include model information.
I'm running Ember-cli 1.13.8, Ember 2.0.0, and Ember Data 2.0.0-beta.1.
What I have tried so far
Upgrading to 1.13.0
Moving the file structure to pods
Removing the functions in my authentication route which a lot of these routes inherit from.
Upgrading to 2.0.0
Trying to remove/add .index on my routes
Tried replicating on ember-twiddle
Doing ember init with ember-cli to see if my router or application setup was different from the standard layout and it doesn't differ in any significant way.
Adding model information to one of the links, that didn't change anything and since it didn't call the model hooks it messed up the view.
Asked on the slack channel
Please Help
I've had this issue for a couple weeks now and I'm not sure where else to look. I'd love any suggestions on how I can resolve this.
Update
This ended up getting fixed in 2.1.0.
This is common problem when you mess around with willTransition router action. For example,
IMS.ResultDetailsEditRoute = Ember.Route.extend({
actions: {
willTransition: function() {
this.controller.clearForm();
}
}
});
In this code snipped willTransition called controller's method "clearForm()" which no longer exists. For some reason, Ember doesn't throw an error, but it causes the problem that #RyanJM explained.
I have run into something similar when using a component with a nav. Here was my approach:
I added a controller (I know, you should be steering away form these, but I needed to). My controller:
import Ember from 'ember';
const {
Controller,
inject
} = Ember;
export default Controller.extend({
application: inject.controller(),
});
Then, in my template, I could pass application to my component.
{{account/account-icon-nav currentRouteName=application.currentRouteName}}
In my component, I set set up a function to test my current route names:
import Ember from 'ember';
const {
Component,
computed,
get
} = Ember;
const activeParentRoute = function(dependentKey, parentRouteName) {
return computed(dependentKey, {
get() {
return get(this, dependentKey).indexOf(parentRouteName) > -1;
}
});
};
export default Component.extend({
isYourProfile: activeParentRoute('currentRouteName', 'account.your-profile'),
isYourActivity: activeParentRoute('currentRouteName', 'account.your-activity'),
isYourGoals: activeParentRoute('currentRouteName', 'account.your-goals')
});
Then bind the active class yourself:
<div class="icon-nav md-hidden">
{{link-to "" "account.your-profile" classBinding=":profile isYourProfile:active" title="Your Life"}}
{{link-to "" "account.your-activity" classBinding=":activity isYourActivity:active" title="Your Money"}}
{{link-to "" "account.your-goals" classBinding=":goals isYourGoals:active" title="Your Goals"}}
</div>
I know this is a bit different since we are doing it within a component, but I hope it helps. You can bind these classes yourself by passing the application around.
Related
EmberJS 3.4
I'm loading a Project entity from a backend which takes a couple of seconds. Now I would like to show a spinner during loading.
as described I created a project-loading.hbs (also tried with loading.hbs) https://guides.emberjs.com/release/routing/loading-and-error-substates/
project model class:
export default AuthenticatedRoute.extend({
model(params) {
return this.store.findRecord("project", params.projectname);
},
actions: {
refresh: function() {
this.refresh();
}
}
});
though it takes time to load the entity, the loading template seems not to be rendered/shown. Am I doing something wrong ?
For a route called project (routes/project.js), the loading template should be called project-loading.hbs.
I cloned your project and actually made it work (Ember CLI 3.4.3) by adding templates/project-loading.hbs, adding a sleep(30) call to your /api/projects/:name endpoint and going to a URL like http://localhost:4200/projects/hallo.
Do you have the problem when transitioning to the route internally (by using transitionTo or the {{link-to}} helper with a model for instance) or by entering the URL manually? Note that model hook is not executed when you transition to a route and pass in a model context (see https://guides.emberjs.com/v3.4.0/routing/specifying-a-routes-model/).
I ended up with adding the following code to the application adapter:
// not very ember way of doing this, but quite simple :)
$(document).ajaxStart(() => {
$('#spinner').removeClass('hide');
});
$(document).ajaxStop(() => {
$('#spinner').addClass('hide');
});
I really would prefer doing it the ember way. for now, this seems to do the trick.
for anyone interested, here's the complete project: https://github.com/puzzle/mailbox-watcher/tree/master/frontend
I'm very new to EmberJS 2.0 and trying to slowly understand it by building my own website with it. Anyways, I've managed to get Firebase integrated with Ember and my controller is able to authenticate correctly. However, I'd like to understand why when I execute:
this.send('toggleModal');
inside the authenticate action property function (.then()) it doesn't work but if I execute it outside then everything works fine.
1) Is the 'this' keyword getting confused with something other than the Ember controller?
Here is the sample:
// /app/controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
isShowingModal: false,
actions: {
toggleModal: function() {
this.toggleProperty('isShowingModal');
},
authenticate: function(username, pass) {
this.get('session').open('firebase', {
provider: "password",
email: username,
password: pass
}).then(function (data) {
console.log(data.currentUser);
console.log(session.isAuthenticated); //Why is 'session' not defined?
this.send('toggleModal'); //This doesn't work. Throws an error.
});
this.send('toggleModal'); //This works.
},
logOut: function() {
this.get('session').close();
}
}
});
2) Also, I've noticed that when using Emberfire I'm able to use the property 'session.isAuthenticated' within the template application.hbs however, shouldn't 'session' be an object that is injected to all routes and controllers using Torii? Why is that property inaccessible/undefined within the application.js controller? I'm using https://www.firebase.com/docs/web/libraries/ember/guide.html#section-authentication as a reference.
3) In the guide above the actions for authentication are put inside the route. However, according to this quora post the route should only handle template rendering and model interfacing. Is this post incorrect? The authentication logic should reside in the application.js controller correct? https://www.quora.com/What-is-the-best-way-to-learn-Ember-js
1) Is the 'this' keyword getting confused with something other than the Ember controller?
Yes. This is one of the most common sticking points of Javascript. There's a lot of articles out there about it, but this one looked pretty good. To solve it you'll either need to use an arrow function, bind the function to the current context, or save the context in a local variable. (Read that article first though.)
2) Also, I've noticed that when using Emberfire I'm able to use the property 'session.isAuthenticated' within the template application.hbs however, shouldn't 'session' be an object that is injected to all routes and controllers using Torii? Why is that property inaccessible/undefined within the application.js controller? ...
That's because the template pulls the property from the current context (your controller). Inside of your controller you'll have to use this.get('session') instead. (After you fix the issue I mentioned above.)
3) ... Is this post incorrect? ...
I wouldn't say incorrect, just a bit oversimplified. I would follow whatever conventions the library uses as that's probably the best way given the library's requirements.
You're partially right about this although it's not really confused. this (where you're modal call doesn't work) isn't scoped to the Controller anymore, because it's inside a function. Either:
replace the function (data) call with data => if you're using ember cli. Or
var _self = this; up top and reference _self instead.
This should at least get you started.
I am attempting to fix an issue caused by the 1.13 upgrade with my internationalization/localization implementation. Nine times out of ten, the default label attribute will appear, but occasionally, we will need to serve up an alternate label based on the user's language settings.
The handlebars template, as it is now, has the following:
{{button-widget label="sample" action="actionHandler"}}
Pre 1.13, we would add a function in the component, called on init, that will look up the current langauge setting, and set "this.label" to whatever the result is within our locale file.
i18n: function() {
...
}.on('init')
The {...} looks up the appropriate locale file for the key "test", and updates the label attribute with the result if one is found. For instance, if the user were french, a lookup of "test" might return "échantillon".
From some research, as of 1.13, Ember no longer allows updates to attributes on init. They did, however, create a few new hooks to help (including didInitAttrs and didReceiveAttrs). Unfortunately though, none of them seem to allow me to override the label attribute.
I am attempting to do this without relying on an ugly jQuery hack, and since this is a project being consumed by other applications, button labels may not always be available within the button, so just adding the label to a model and referencing a variable likely isn't a reliable solution.
Any help would be greatly appreciated.
Jason
As Kitler mentioned you could use didInsertElement for all that.
App.GenericComponent = Ember.Component.extend({
didInsertElement: function() {
// Your magic here
}
}
Furthermore, if you need to get access to the store, you can use it as a service by defining a property containing the service:
App.GenericComponent = Ember.Component.extend({
store: Ember.inject.service(),
didInsertElement: function() {
// Your magic here
// Like so
this.get("store").find("model");
}
}
If your language settings are not located in a data store, you could simply attach the object containing the information to the component like you're used to.
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 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');