Accessing global application state inside a custom Handlebars helper - ember.js

In an ember.js application, I'm looking for an elegant way to access global application state (e.g. configuration/session data, information about the logged in user, ect) from within a custom handlebars helper. This is easy to do in routes/controllers using Ember.Application.initializer like so:
App.initializer({
name: 'registerSession',
initialize: function(container, application) {
application.register(
'app:session',
Ember.Object.extend({userId: 1, dateFormat:"MMMM Do, YYYY", /* ... */}),
{singleton: true}
);
application.inject('controller', 'session', 'app:session');
application.inject('route', 'session', 'app:session');
}
});
However there doesn't seem to be any equivalent of this in the Handlebars helper registration api, where you can essentially inject an external dependency.
My use case for example, is that the session data holds the user's date format preference, and I have a custom helper, formatDate where I want to be able to pull in their settings to use as the default format, eg:
Ember.Handlebars.helper('formatDate', function(timestamp) {
//need to be able to access the global session data here
//in order to get the user's date format preference
return moment.unix(timestamp).format(session.dateFormat);
});

Helpers are isolated (like components), You'll need to pass in any external dependencies needed in order to use them.
Ember.Handlebars.helper('formatDate', function(timestamp, format) {
//need to be able to access the global session data here
//in order to get the user's date format preference
return moment.unix(timestamp).format(format);
});

You can if you use Ember.Handlebars.registerHelper which bring different function parameters. Once, you get the container you could look for any registered instance like your session.
I have not tested, but I think something similar to this example must work:
import {handlebarsGet} from "ember-handlebars/ext";
registerHelper('formatDate', function(value, options) {
var container = options.data.keywords.controller.container;
var session = container.lookup('app:session');
var propertyValue;
if ( options.types[0] !== 'STRING' ) {
var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this;
propertyValue = handlebarsGet(context, value, options);
} else {
propertyValue = value;
}
return moment.unix(propertyValue).format(session.dateFormat);
});
Take in consideration that helpers created with this method will not re-render their content when data changes. If you need to define a "bound helper", take a look at Ember Handlebars Helpers.

Related

Passing data from controller to model in emberjs

I have a controller where I get the value from the hbs, which sends me the selected country value. I need this selected country in the model to compute and return back some results back to the hbs. How set this value in controller and get it in the model so I can compute using that value?
Well, there may be some different approaches to achieve this. However, I will give you some example which will hopefully help you.
//Controller.js
notes: Ember.computed('model.notes.[]', 'model.notes.#each.date', function() {
return this.get('model.notes').sortBy('date').reverse(); //This is an example of Computed function which in this case it's sorting notes based on date.
}),
blink: null,
actions: {
taskChangeColor: function() {
this.set('blink', 'blinker'); // this is another example that set new data by action which can be retrive from model and set to property
}
}
or another thing that you can do is to use Computed function in Model itself like
// model.js which is using ember-data and moment
timeZone: DS.attr(), //for example one property coming from server
utcOffsetFormat: Ember.computed(function() {
let time = moment.tz(this.get('timeZone')).format('hh:mm a');
return time;
// using a computed function to instantiate another value based on existing model property which means you can simpley use this property instead of direct one.
})
Additionally, you still are eligible to use action in Route.js instead of controller an example would be :
//route.js
actions: {
changeSave: function(step) {
var something = {
contact: this.currentModel,
};
this.currentModel.set('step', something.contact);
this.currentModel.save().then(d => {
// set your alert or whatever for success promise
return d;
}).catch(e => {
console.log(error(e.message));
return e;
});
},
in above example you can see that I have set an action to save notes in model which easily can set() to the model with exact same property name and if you do this you will get the result back immediately in your view.
hope it can help you. I recommend to read Ember-Docs
I would say, for your requirement you don't need controller properties for selectedCountryValue. You can keep this value in model itself.
In route,
setupController(model,transition){
this._super(...arguments); //this will set model property in controller.
Ember.set(model,'selectedCountryValue','US'); //you can set default value
}
and inside controller, you create computed property with dependent on model.selectedCountryValue. and compute some results
result:Ember.Computed('model.selectedCountryValue',function(){
//compute something return the result
}
In template, you can use {{model.selectedCountryValue}} directly.

Push record into local store and update template

I'm using this.store.push to push a record into the store from with the application controller (this action is being called from a socket service that is initialized in the application controller), using ember 2.2.1 I am achieving this like
var newStoreRecord = this.store.push({
data: {
id: id,
type: 'cart',
attributes: newCartItem
}
});
This adds this new item into the store but the template doesn't update to show the new item, I also tried adding something like this
this.get('cart.model').pushObject(newStoreRecord); assuming that I had something like cart: Ember.inject.controller(), at the top of the controller, might have had that one wrong anyway.
In the cart route I have my model being defined as so
model(params) {
this.set('routeParams',params.event_url);
return Ember.RSVP.hash({
event: null,
items: null
});
},
actions: {
didTransition() {
this.store.findRecord('event',this.get('routeParams')).then((result)=>{
this.controller.set('model.event',result);
});
this.controller.set('noItems',false);
this.store.query('cart',{auction_id:this.get('routeParams'),user:this.get('user.user.user_id'),combine:true}).then((result)=>{
if(!result.get('length')){
this.controller.set('noItems',true);
return null;
}else{
this.controller.set('model.items',result);
}
});
},
}
Not sure if I'm having troubles with getting the template to update because I'm not use the model hook? (btw, we're not using the model hook because of the bad performance on android we'd rather load an empty template with a loader and THEN load data rather than the other way around.
I have several thoughts here:
To answer your question specifically, when you set a variable from the store, like you're doing, it will only reference what was in the store at that time. It will not update automatically.
Your best bet is to add two new computed properties to your controller:
items: Ember.computed(function() {
return this.store.peekAll('cart');
}),
// You'll need to flesh this one out further
filteredItems: Ember.computed('items.#each.auction_id', function() {
return this.get('items').filter(...);
})
Reference filteredItems in your template and it should work.
Sidenote, I'd highly recommend refactoring a couple things.
I would use the setupController hook instead of didTransition. It runs after the model hook is complete so will be similar to what you're looking for
You can access the params at any time in the route, so you don't need to save them in the model hook
You don't need to return an a promise in the model hook if you're not doing any async data. Just return the object. You may need even need to do that.
Hope this helps.

How to create a inject helper for something other than service?

https://github.com/emberjs/ember.js/blob/5fd2d035b30aa9ebfe73de824b3b283ec8e589cc/packages/ember-runtime/lib/system/service.js#L31
In the line I reference above the ember-core team imports this createInjectionHelper and uses it to add a clean/simple api for injecting services like so
App.ApplicationRoute = Ember.Route.extend({
authManager: Ember.inject.service('auth'),
model: function() {
return this.get('authManager').findCurrentUser();
}
});
How can I create something like this myself for a non service?
Your example usage will change slightly from what you have above. We will cover what the injectRepositories does in a little bit.
import injectRepositories from 'app/utils/inject';
export default Ember.Route.extend({
repository: injectRepositories('person'),
model: function() {
var repository = this.get('repository');
return repository.find();
}
});
The initializer can be improved with the following changes:
import registerWithContainer from "ember-cli-auto-register/register";
export function initialize(_, application) {
registerWithContainer("repositories", application);
application.inject("repositories", "store", "store:main");
}
export default {
name: "repositories",
after: "store",
initialize: initialize
};
Let's break down what is happening in each line.
registerWithContainer("repositories", application);
In the line above, we are deferring to the ember-addon ember-cli-auto-register. This addon will take a directory, in this situation, the repositories directory and register each object into the Ember container to able to be accessed via a lookup. They will be inserted as if doing the following:
application.register("repositories:person", PersonRepository);
Then we add a function to do the injection using the ember-addon ember-cli-injection:
// app/utils/inject.js
import inject from "ember-cli-injection/inject";
var injectRepositories = inject("repositories");
export default injectRepositories;
This then allows us the opportunity to use the newly created function above to access these objects with the code below:
import injectRepositories from 'app/utils/inject';
export default Ember.Route.extend({
repository: injectRepositories('person'),
model: function() {
var repository = this.get('repository');
return repository.find();
}
});
Since each object is now in the container, we can look it up and inject at runtime instead of during the initialization of the application. We register the repositories key in the function and this then returns a computed property (see code below from ember-cli-injection). We do this as a computed property to allow lazy loading. The object is not fetched from the container until the property is accessed.
import Ember from 'ember';
var injection = function(key) {
return function(name) {
return Ember.computed(function(propertyName) {
var objectName = name || propertyName;
return this.container.lookup(key + ':' + objectName);
});
};
};
export default injection;
We also allow for a name to passed to the repositories function, for example repository: injectRepositories('person'). This allows you to name your object whatever you would like when injecting it.
If you would like to just name the object the same as the name of the repository injected into the container you can alternatively do person: injectRepositories(). This will pass the person key to the computed property as the propertyName and since the name was left null when injecting, the objectName will instead be person. This matches the API produces similar results but is not the same as that of the Ember.inject.service and Ember.inject.controller API that is available as of Ember 1.10.
I don't think it's their intention for you to use it this way. The standard way is to use App.inject() if you're using plain ember, or do this in an initializer if you're using ember-cli.
In your case:
// Register the service
App.register('service:auth', {
findCurrentUser: function() {
// Do your magic
}
}, { instantiate: false });
App.inject('route', 'auth', 'service:auth');
Then in your model hook you can use this.auth.findCurrentUser();. You can also inject this into controllers and components if you need. Also note, to keep it clean, that you might want to include a separate module instead of defining your auth module in the service registration.
More info here:
http://emberjs.com/guides/understanding-ember/dependency-injection-and-service-lookup/#toc_dependency-injection-with-code-register-inject-code
NOTE
A service is also not a "special" thing. You can inject anything you want into pretty much anything you want using this method.

how to store json response into model ember.js

I'm not sure how to store json data into a model in the controller. Here is my code.
App.LoginController = Ember.ObjectController.extend({
actions: {
login: function() {
var data = this.getProperties("email", "password");
console.log(data);
return $.post('/', {
email: data.email,
password: data.password
}).then(function(data) {
if(data.isFail) {
return data;
} else {
}
});
}
}
});
after the line if(data.isFail) {.... I want to store the json data into a model. How do I do this?
UPDATE
I went with Josh's suggestion of putting the action into the Route
App.LoginRoute = Ember.Route.extend({
actions : {
login: function() {
var data = this.getProperties("email", "password");
console.log(data);
return $.post('/', {
email: data.email,
password: data.password
}).then(function(data) {
if(data.isFail) {
store.createRecord('login', data);
} else {
}
return data;
});
}
}
});
No I'm getting an error Uncaught TypeError: Cannot read property 'normalize' of undefined.
If you have defined a DS.Model and if the contents of your JSON are a subset of the properties defined in your DS.Model then you can just do this:
store.createRecord('my-model-name', data);
This assumes that you have a DS.Model defined called my-model-name.js and that your JSON is in the data var. If you have some properties in your JSON that are not defined in your DS.Model, I'm not sure how Ember Data reacts.
UPDATE: You asked about how to connect the route and the controller. You can use this idiom:
Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
controller.set('myProperty', 'hello');
controller.set('meta', this.store.metadataFor('org-user'));
}
})
Note that this doesn't really relate much to your original question.
UPDATE #2:
Ok, it looks like you want to attempt a login, and then if the POST request succeeds, but the login itself fails (as indicated by the presence of an isFail property in the json response), then you want to create a new record in your local store? Did I describe your intentions right?
My first question is, are you sure you want to create an Ember record here? An Ember record is basically a "facsimile" of a "real" object that comes from your backend / database. It makes sense to create a new local record if you want to eventually persist that somewhere (e.g. by calling myRecord.save). Maybe you would create a local record for cacheing purposes only, but I personally have not seen that in the wild yet (but don't let me disqualify your usage if you've thought it through).
With that out of the way, let's assume you DO want to create a local record. Then first we actually need a DS.Model that represents the record this will be. I'll define mine like this:
models/login.js
DS.Model.extend({
username: DS.attr('string'),
password: DS.attr('string')
});
Note that I made sure to copy your properties from this.getProperties() because the way you're initializing your model with createRecord('login', data) means that what's in data needs to be a subset of what I just defined above.
Now that I have a model definition, I an get instances of this model from a backend via JSON (or any other format as long as I have the right serializer; Ember by default uses `RESTSerializer, which expects JSON). I can also locally instantiate a new model, or in Ember speak, create a record. Again, my goal in creating a new record is probably that I eventually want to persist it to my backend. But since you're doing your own AJAX calls, I'll leave that part out.
Now onto your code, with slight revisions:
App.LoginRoute = Ember.Route.extend({
actions : {
login: function() {
var _this = this;
var data = this.getProperties("email", "password");
$.post('/', {
email: data.email,
password: data.password
}).then(function(data) {
if(data.isFail) {
var loginModel = _this.store.createRecord('login', data);
loginModel.save(); // not sure if you want to do this?
} else {
}
});
I took out the return statements because I'm guessing you don't need them (I may be wrong). I also decided I would do something with the model instance we just created, in this case save() it, which will trigger a POST request to your backend, as determined by your adapter (by default Ember uses RESTAdapter to determine this).
Note also that I needed access to the current route instance via this but this takes on different meanings as I descend down the code, so I define var _this = this; at the top so I can reference the "real" this when I need it.
Does this solve your issue?

Where is my store?

I am migrating from ember-data 0.0.14 to ember-data 1.0.0-beta.6. I have been following the guide
I am preloading some data needed by my application, triggering this pre-load when the application is ready. But I have lost the store!
/// application.js
var App = Ember.Application.createWithMixins({
...
ready: function () {
this.preLoadData();
},
...
});
/// load_data.js
function preLoadData() {
var store = this.Store;
if (DEBUG) { console.log('preLoadData > this=%o store=%o', this, store); }
store.find('node'); // was this.Node.find();
}
App.preLoadData = preLoadData;
But this.Store is not the store (I do not know what that is!). Also tried with this.store, but it is undefined, so I get:
Uncaught TypeError: Cannot call method 'find' of undefined
I have even tried doing:
// inject the store into all components
SettingsApp.inject('component', 'store', 'store:main');
Whatever that magic means (what is 'component'? Is it component 'component', or any component? What is a component?), but didn't help.
How can I access an instance of the store directly from my App?
Edit: I have found component in the documentation, but this is not what I need: I want to access the store from the App.
You are trying to access store from Application object which is not possible. You could use something like this,
var store = App.__container__.lookup('store:main')
To initialize your app
But I believe you want to initialize your app with some preloaded data. You could use initializer to do such work.
Ember.Application.initializer({
name: "preload data",
initialize: function(container, application) {
var store = container.lookup('store:main');
store.find('node');
}
});