Transform label attribute in handlebars from Ember component - ember.js

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.

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.

Changing query param is not shown in the url

I want to click on a "export" button and transit to a route "home" with "export" query param set as true. I don't want this query param to refresh my route. So here is how my route looks like:
export default Route.extend(ApplicationRouteMixin, {
queryParams: {
export: {
refreshModel: false
}
}
})
In my controller, i'm trying to observe the query param and call a function which does export for me and after that i want to set the query param back to null. here is my controller:
import Ember from 'ember'
const {Controller, inject} = Ember
export default Controller.extend({
// == Dependencies ==========================================================
session: inject.service(),
// == Keyword Properties ====================================================
queryParams: ['export'],
export: null,
queryParamsObserver: function () {
if (this.get('export')) {
this.exportFile()
this.set('export', null)
}
}.observes('export'),
// == Functions =============================================================
exportFile () {
},
// == Actions ===============================================================
actions: {
}
})
But my problem is that when i set the query param to null, it won't change on the url. I'm wondering what i am missing here that is not causing that behavior.
Plus that i wonder if using observing query param is the best solution to trigger some actions.
The problem with your case is related with the fact that your setting of export within controller is too early that query params change is not reflected to url. It is not trivial to learn about Ember.run loops. You should start learning about run loops by reading following.
Take a look at the following twiddle, if you wrap the setting of export within Ember.run.scheduleOnce as in the twiddle then you will see export is cleared in the url. (In fact it is immediately removed; you never see it is becoming true; if you wrap both export function and clearing of export flag within a promise; let's say that will resolved after some seconds; then you will see it will become true then will be removed).
Regarding your question about using observers; I cannot see anything you could do with your current design. The reason is that; you are saying refreshModel is false; hence you do not have any hook methods available in route to trigger export function within controller if you are within the same route already. You need to change your design; what ykaragol suggested could be a good starting point.
First option, using a service and triggering the export via application route or via button component seems more applicable for your situation. You can use it from all of your routes. Users stay at the same route while they are exporting. I would use this option.
Second option is to convert the exportFile to a thennable function. When the exporting is finished, you can clear the queryParams. Here is a working twiddle for you.

Ember link-to gets active class for the wrong transition

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.

Accessing Ember Controller Properties within the same controller

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.

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.