How do you set variable in route and display in template? - ember.js

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.

Related

EmberJs : How to pass data to a component without using a controller

The documentation for emberjs clearly states that you should not use controllers, however sometimes you need to pass data into a component that is not the model for the corresponding route. For instance in an application I am working on I want to retrieve a list of records from the store and display them in a component so the user can select them as an attribute of the model for that route.
The advice I have received on this is to either create a controller and use it to retrieve the list in question or to add the list of records as an attribute of the model for that route, but since the former is inadvisable and the latter only makes sense if the item in question is a logical part of the model's schema (and therefore should probably be in there anyway) I am left feeling confused about how this apparently simple thing ought to be done. Can anyone help?
You can use Ember.RSVP.hash in your routes model hook. When the promise resolves, the results get passed as the second param in setupController.
// This would be in a route file like app/blogs/edit/route.js
model: function() {
return Ember.RSVP.hash({
blog: this.store.findRecord('blog', 1),
categories: this.store.findAll('category'),
});
},
setupController: function(controller, models) {
this._super(controller, models);
controller.set('model', models.blog);
controller.set('categories', models.categories);
},
OR
If you wanted all the data logic to exist in the component you can inject the data store service. This goes against the DDAU mantra (data down, actions up) but IMO it's a clean, modular solution. Useful if the extra content isn't visible immediately ie: components that open modal windows.
// This would live within the actual component
store: Ember.inject.service(),
loadCategories: function() {
this.get('store').findAll('category').then((categories) => {
this.set('categories', categories);
});
}.on('init'),
However, I would advise against this if the data (categories in this example) were immediately visible in the layout. Ember won't wait for these requests to complete before rendering so you would see blank spaces/whatever with the actual values loading in a half second later.
just be aware that components don't know anything about outside them self. The way I would solve the problem is by creating a bridge between controller and component by passing the property that you want to access to your component.
<script type="text/x-handlebars" data-template-name="sample-com">
{{sample-com
sampleRequests=sampleRequests
}}
</script>
App.MainController = Ember.Controller.extend({
//bridged properties that the controller must communicate between components/view
sampleRequests: 'hello world'
});
App.SampleComComponent = Ember.Component.extend({
sampleRequests: null
});
if there is a better way please feel free to suggest.

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.

Transform label attribute in handlebars from Ember component

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.

Model/SetUpController hook not always being called in ember when using TransitiontoRoute

I am returning some static json from my ajax call for test purpose before the bakend is ready. Nut when I use transitionToRoute from some function I can see the model hook of the route is not always called. I guess it is caching the static json and I see the route rendering properly. But I am also setting some other properties of the controller in the setUpController hook which also doesn't get executed when the model hook is not called.
This variable needs to set whenever I am changing to this route. If setUpController is not the place to set it where should I set it . So it doesn't fail to get set when ember doesn't call model hook as part of caching process.
setupController : function(controller, model ) {
controller.set('isEditing',false);
controller.set('messages', model.messages);
controller.set('params', this.get('params'));
console.log('Set Up controller' );
},
model: function( routeParams) {
this.set('params',routeParams);
// return data omitted code
});
}
So the isEdiding field doesn't get set when model hook is bypassed. One get around solution is set it before transitioning like this
this.controllerFor("messages").set('isEditing',false);
// then do tranisitioning
Is there any better way to acheive the same thing? Like ideally where should this variable setting be done if done properly in Ember ?
Sorry I'm late and this might not be of any use for you. I just wanted to post it over here, if in case it might be of any use for others.
This link helped me, clear my problem.
Approach 1:
We could supply a model for the route. The model will be serialized into the URL using the serialize hook of the route:
var model = self.store.find( 'campaign', { fb_id: fb_id } );
self.transitionToRoute( 'campaign', model);
This will work fine for routing, but the URL might be tampered. For this case, we need to add extra logic to serialize the object passed into the new route and to correct the URL.
Approach 2: If a literal is passed (such as a number or a string), it will be treated as an identifier instead. In this case, the model hook of the route will be triggered:
self.transitionToRoute( 'campaign', fb_id);
This would invoke the model() and would correctly display the required URL on routing. setupController() will be invoked immediately after the model().
2nd one worked fine for me fine. Hope it's useful and answered the above question.

Ember.js: ensure parent is loaded

This might be a simple question, but I can't seem to find any current (RC1) example on how to achive this.
So let's say I've got the following two routes:
App.PostRoute = Ember.Route.extend({
});
App.PostCommentRoute = Ember.Route.extend({
});
How do I ensure that the controller of the PostRoute is loaded when I access the PostComment route directly. I.e. by calling #/post/comment directly from the browser?
You need the PostRoute model hook to return a promise. If you are using ember-data, this is done automatically for you.
The router checks, and if the object returned by the model hook implements the then function (which means it's a promise), it will transition the router into a loading state until the promise is resolved (which means the data was fetched). Then it will continue to the PostCommentRoute.