Ember 1.13 - Recompute helper when global variable change - ember.js

I got a "get-text" helper which translate my static fields depending on a config variable : ENV.LOCALE.
The thing is, I'd like to recompute my if my ENV.LOCALE gets modified. I tried several things from sending events to Ember.observer, without much success.
// controllers/application.js
locale: function() {
var locale = this.get('isFrench') === true ? 'fr' : 'en';
ENV.LOCALE = locale;
return locale;
}.property('isFrench'),
//helpers/get-text.js
import Ember from 'ember';
import ENV from '../config/environment';
export default Ember.Helper ( function(key, locale) {
var lang = {
save: {
'fr': 'Enregistrer',
'en': 'Save',
},
}
return new Ember.Handlebars.SafeString(lang[key][ENV.LOCALE]);
});
Bottom line, how can I recompute my helper when a global property changes?

After digging threw the documentation, I found this chapter about dependency injection. This combined with the API documentation on Ember.Helper I came up with the following solution :
1st I created a service "locale-manager"
// services/locale-manager.js
import Ember from 'ember';
export default Ember.Service.extend({
isFrench: true,
locale: function() {
var locale = this.get('isFrench') === true ? 'fr' : 'en';
return locale;
}.property('isFrench'),
});
Then I inject my service into my helper and recompute it everytime time the property of locale is changed.
// helpers/get-text.js
import Ember from 'ember';
export default Ember.Helper.extend({
localeManager: Ember.inject.service(),
onLocaleChange: Ember.observer('localeManager.locale', function() {
this.recompute();
}),
compute(params) {
var key = params[0];
var lang = {
enregistrer: {
'fr': 'Enregistrer',
'en': 'Save',
},
var locale = this.get('localeManager.locale');
return new Ember.Handlebars.SafeString(lang[key][locale]);
}
});

Related

How to properly use parameters in Ember query?

I have been trying to implement pagination (I've tried both ember-cli-pagination and ember-simple-pagination) for my application but I've had a lot of issues. So I decided to try custom pagination and noticed that I cannot pass parameters into my query. For instance, when visiting my api at: http://jsonplaceholder.typicode.com/posts?_start=0&_limit=10, start and limit both work properly. When calling it in my route, it seems to ignore that entirely and just give me all entries. I would appreciate all insight into what I am doing wrong or how to properly implement pagination in this case.
app/adapters/post.js
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
host:'https://jsonplaceholder.typicode.com',
pathForType(){
return 'posts';
}
});
app/models/post.js
import DS from 'ember-data';
const { Model } = DS;
export default Model.extend({
user:DS.belongsTo('user'),
title:DS.attr('string'),
body:DS.attr('string'),
});
app/routes/post.js
import Route from '#ember/routing/route';
import { set } from '#ember/object';
import { hash } from 'rsvp';
export default Route.extend({
model() {
return hash({
post: this.store.query('post', {
start: 0,
limit: 10
}),
user: this.store.findAll('user')
});
},
setupController(controller, model) {
this._super(...arguments);
set(controller, 'posts', model.post);
set(controller, 'users', model.user);
}
});
You need define the query params in both side Route and Controller.
app/routes/post.js
import Route from '#ember/routing/route';
import { set } from '#ember/object';
import { hash } from 'rsvp';
export default Route.extend({
queryParams = {
start: {
refreshModel: true
},
limit: {
refreshModel: true
}
},
model() {
return hash({
post: this.store.query('post', {
start: 0,
limit: 10
}),
user: this.store.findAll('user')
});
},
setupController(controller, model) {
this._super(...arguments);
set(controller, 'posts', model.post);
set(controller, 'users', model.user);
}
});
And inside app/controllers/post.js
import Controller from '#ember/controller';
export default class ArticlesController extends Controller {
queryParams = ['start', 'limit'];
start = 1;
limit = 5;
}
Ember by default does not call model when query params are changed. We tell it to do so any time start/limit changes, through refreshModel: true.

Serialize id in _id format with serializeId in Ember

In my response from server i have id attribute like 'id'. But to update record i need to send in request id like '_id'. I try to use serializeId serializeId method. My adapter looks like
import DS from 'ember-data'
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-
mixin'
import Ember from 'ember'
import Inflector from 'ember-inflector'
export default DS.JSONAPIAdapter.extend(DataAdapterMixin, {
authorizer: 'authorizer:token',
pathForType (modelName) {
let underscored = Ember.String.underscore(modelName)
return Inflector.inflector.pluralize(underscored)
}
})
and voicemail serializer
import ApplicationSerializer from './application'
import DS from 'ember-data'
import Ember from 'ember'
export default ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
primaryKey: 'id',
attrs: {
history: {embedded: 'always'}
},
serializeIntoHash (data, type, snapshot, options) {
let root = Ember.String.decamelize(type.modelName)
data[root] = this.serialize(snapshot, options)
},
serializeId (snapshot, json, primaryKey) {
let id = snapshot.id
json['_id'] = id
}
})
But serializeId method did not called during serializing. And in my payload i still get id like 'id' instead '_id'. How to solve my issue?
UPDATED
I find in ember code that serializeId runs only if ds-serialize-id feature is enabled.
serialize(snapshot, options) {
let json = {};
if (options && options.includeId) {
if (isEnabled('ds-serialize-id')) {
this.serializeId(snapshot, json, get(this, 'primaryKey'));
} else {
const id = snapshot.id;
if (id) {
json[get(this, 'primaryKey')] = id;
}
}
}
To run serializeId method i turn on ds-serialize-id property in enviroment.js
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
'ds-serialize-id': true
}
}
I think that isues solved.

Use Mixin property in a Controller

This is a crappy example, but I am merely trying to use a mixin's property in a controller. I did the same thing in a route and could access that property. I've tried every way to reference a property I know... What am I misunderstanding?
// app/mixins/author-data.js
import Ember from 'ember';
export default Ember.Mixin.create({
authorName: 'Example author name',
});
// app/controllers/application.js
import Ember from 'ember';
import AuthorDatas from 'app-name/mixins/author-data';
export default Ember.Controller.extend(AuthorDatas, {
siteTitle: `Site title`,
fromAuthorData: this.get('authorName'),
// returns 💩 - what is the proper syntax?
actions: {
showAuthor() {
var author = this.get('fromAuthorData');
console.log(`Author from controller: ${author}`);
},
},
});
// app/templates/application.hbs
{{fromAuthorData}}
This works...
// app/routes/application.js
import Ember from 'ember';
import AuthorDatas from 'app-name/mixins/author-data';
export default Ember.Route.extend(AuthorDatas, {
afterModel() { // arbitrary
var intro = `Author from route:`;
console.log(`${intro} this.authorName`, this.authorName );
console.log(`${intro} this.get('author-name')`, this.get('authorName') );
},
});
(I would have made an ember-twiddle - but I wasn't sure if Mixins would work the same way ~ since they aren't on the list and there is 0 documentation)
The fromAuthorData property on your controller should be defined like this (I think):
fromAuthorData: Ember.computed('authorName', function() {
return this.get('authorName'); // or whatever derived value you need
}
To understand the problem we need to talk about scope, when you extend/create an object you are merely passing in options, your code is no different than:
let options = {
siteTitle: `Site title`,
// `this` is undefined since we are in strict mode
fromAuthorData: this.get('authorName'),
actions: {
showAuthor() {
var author = this.get('fromAuthorData');
console.log(`Author from controller: ${author}`);
},
}
};
export default Ember.Controller.extend(AuthorDatas, options);
Now to access properties that rely on this being the object holding it you will need a function that is run with the object as it's context that returns that value, enter computed properties.
Your code becomes:
// app/controllers/application.js
import Ember from 'ember';
import AuthorDatas from 'app-name/mixins/author-data';
const { computed } = Ember;
export default Ember.Controller.extend(AuthorDatas, {
siteTitle: `Site title`,
// We add `authorName` as the dependent key, should it change `fromAuthorData` will update
fromAuthorData: computed('authorName', function() {
// your author data stuff
let authorName = this.get('authorName');
// ...
return authorDetails;
}),
actions: {
showAuthor() {
var author = this.get('fromAuthorData');
console.log(`Author from controller: ${author}`);
},
},
});

How to pass errors in template from route?

I do 'server side validation'. In route in method 'catch' get errors from server. And I want pass this errors in template.
How to pass errors in template from route?
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
model() {
return this.store.createRecord('project');
},
actions: {
save(project) {
var router = this;
var errors = router.controllerFor('project.new').get('errors')
project.save().then(function(project){
router.transitionTo('projects.show', project);
}).catch(function(resp) {
// how to pass this errors in template ????
console.log(resp.errors);
});
}
},
});
From router.js
this.route('projects', function() {
this.route('new');
this.route('show', { path: '/:project_id' });
});
From Component
import Ember from 'ember';
import DS from 'ember-data'
export default Ember.Component.extend({
actions: {
save() {
this.project.set('colors', colors);
this.sendAction('save');
}
},
...
});
Closure Actions! (Assuming a recente version of - Ember 1.13+). Closure actions can have a return value, unlike regular actions.
On your template you do:
{{my-component mySave=(action 'save')}}
And in your component you do
import Ember from 'ember';
import DS from 'ember-data'
export default Ember.Component.extend({
actions: {
save() {
this.project.set('colors', colors);
let result = this.attrs.mySave();
//do something with result
}
},
...
});
And then in your controller:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Controller.extend({
actions: {
save(project) {
var router = this;
var errors = router.controllerFor('project.new').get('errors')
project.save().then(function(project){
router.transitionTo('projects.show', project);
}).catch(function(resp) {
return resp.errors;
});
}
},
});
I would also recommend this article on Closure Actions which is very helpful.
EDIT: I initially replied with the action being on the route (as in your example) but #Kitler correctly reminded that closure actions communicate with the controller or another component. I don't know if that's an option for the OP?

Ember Services - Create default settings for application

My app has a lot of reports, and I have dateBegin and dateEnd in all of then.
The desired behaviour is:
app first load, dateBegin = month begin (jun-01) / dateEnd = today (jun-11)
when user change dates (let's say to mai-01 / mai-31), all controllers get the new dates
The code that I have now:
// app/services/defaults.js
import Ember from 'ember';
export default Ember.Service.extend({
init: function () {
this._super();
var dateEnd = moment().format('YYYY-MM-DD');
var dateBegin = moment().startOf('month').format('YYYY-MM-DD'));
if (!this.get('dateEnd')) { this.set('dateEnd', dateEnd); }
if (!this.get('dateBegin')) { this.set('dateBegin', dateBegin }
}
});
// app/components/select-dates-in-reports.js
import Ember from 'ember';
export default Ember.Component.extend({
defaults: Ember.inject.service(),
displayDateBegin: null,
displayDateEnd: null,
dateBegin: Ember.computed.alias('defaults.dateBegin'),
dateEnd: Ember.computed.alias('defaults.dateEnd'),
setInitialParams: Ember.on('init', function () {
this.set('displayDateBegin', this.get('dateBegin'));
this.set('displayDateEnd', this.get('dateEnd'));
}),
actions: {
chooseParams: function () {
this.set('dateBegin', this.get('displayDateBegin'));
this.set('dateEnd', this.get('displayDateEnd'));
}
}
});
// app/mixins/query-params-for-reports.js
import Ember from 'ember';
export default Ember.Mixin.create({
queryParams: ['dateBegin', 'dateEnd'],
defaults: Ember.inject.service(),
dateBegin: Ember.computed.alias('defaults.dateBegin'),
dateEnd: Ember.computed.alias('defaults.dateEnd')
});
// app/mixins/routes-query-params-for-reports.js
import Ember from 'ember';
export default Ember.Mixin.create({
queryParams: {
dateBegin: {
refreshModel: true
},
dateEnd: {
refreshModel: true
}
},
model: function(params) {
return this.store.find(this.get('modelName'),
{
dateBegin: params.dateBegin,
dateEnd: params.dateEnd
}
);
}
});
It works as desired just after each controller is initialized:
user enter the app, and visit controller1. dateBegin = jun-01 / dateEnd = jun-11
on the same controller1, user change dates to dateBegin = mai-01 / dateEnd = mai-31
user visit controller2. Here is the Problem. The dates are set to dateBegin = jun-01 / dateEnd = jun-11
on the same controller2, user change dates to dateBegin = apr-01 / dateEnd = apr-30
user visit controller1 again. Now it works. The dates are set to dateBegin = apr-01 / dateEnd = apr-30
I tried all I could find over the net.
Create Initializers, used localStorage, etc. Nothing works.
Can anyone helps me??
thanks!
Ember Dependency Injection to the rescue!
You can create a singleton object to hold the dates:
App.SelectedDates = Ember.Object.extend({
dateBegin: null,
dateEnd: null // or whatever value you want...
});
Then, inject that object into ALL controllers like this:
Ember.Application.initializer({
name: 'register-global-dates',
initialize: function(container,app) {
app.register('my:globalDate', App.SelectedDates.create(), {
singleton: true,
instantiate: false
});
app.inject('controller', 'dateglobal', 'my:globalDate');
}
});
Now, in your controller(s), you can do this:
this.dateglobal.set('dateBegin', '2015/01/12');
It's the same object in all controllers.
I hope I am understanding your problem correctly and that this is the solution...