Where to store l20n ctx object in ember-cli app - ember.js

I'm using the bower version of l20n to localize an ember-cli app.
I'm localizing the app using the following flow:
I've created an initializer which creates an l20n context object based off of the user's language setting.
Using an ember helper, I pass each word from the DOM onto the l20n context object to be translated.
In handlebars template :
{{l20n-helper 'stringVariable'}}
In ember helper:
export function translate(word){
return Ember.l20n.getSync(word);
}
export default Ember.Handlebars.makeBoundHelper(translate);
This works, but I've just tagged a global variable onto Ember in order to make the l20n context accessible to the helper. I know that is not best practice. I'm trying to figure out how to create an ES6 module that will serve only to store the l20n context object. That way, inside my ember helper, I could just do:
import l20n from "/????"
and prevent the l20n object from bogging down the rest of the app.
Thanks in advance for any help!!!
Update:
One solution is to use application.register, like so:
application.register('l20n:main', ctx, {instantiate: false});
BUT, then my problem would be accessing the container from my helper. This:
var l20n = this.container.lookup('l20n:main');
won't work because container is not available to helpers!

So, it turns out that my helper already had access to the container!
This is all I had to do to get it to work:
import Ember from 'ember';
export function translate(word) {
var ctx = this.container.lookup('l20n:main');
return ctx.getSync(word);
}
export default Ember.Handlebars.makeBoundHelper(translate);

Related

How works Embers `toString` and how to give my Ember Objects fancy names?

I write some Ember code (an ember-cli addon) where I have custom helper Objects.
There I have some Computed Properties that return helper object instances.
For example a simple Mixin:
// my-addon/cool-mixin
import Ember from 'ember';
import CoolThing from 'my-addon/cool-thing';
export default Ember.Mixin.create({
coolThing: Ember.computed('foo', {
return CoolThing.create({
foo : this.get('foo')
});
})
});
// my-addon/cool-thing
import Ember form 'ember';
export default Ember.Object.create({});
Now everything works fine but when I use this Mixin I always get instances of (subclass of Ember.Object), which is not nice:
import Ember form 'ember';
import CoolMixin from 'my-addon/cool-mixin';
Ember.Controller.extend(CoolMixin, {
actions: {
debug() {
alert(this.get('coolThing').toString()); // here I cant something like "myAddon.CoolThing" or anything usefull
}
}
});
What is the best way to give my Object a fancy name?
It works nice for Ember internal Objects (like ObjectProxy), but I cant find the code how they do it!
I know that if I lookup the Object with the container everything gets a fancy name, but how to get it for static imports?
So:
How does Ember.ObjectProxy get its cool name?
What is the best way for me to give Objects a name in my addon?
I believe you can implement toString for your classes and it'll be called when outputting. See Ember.Object for reference.
You can set a value for the key Ember.NAME_KEY. This will be used instead of (subclass of X), unless you override toString.
See this similar discussion: https://stackoverflow.com/a/29588126/1911487

Inject a service into an Ember Object [not an Ember Controller]

I'm trying to inject an Ember service into an Ember Object but keep getting the following error:
"Assertion Failed: Attempting to lookup an injected property on an
object without a container, ensure that the object was instantiated
via a container."
My code looks essentially something like the following:
const Model = Ember.Object.extend({
store: Ember.inject.service(),
destroyRecord() {...},
serialize() {...},
deserialize() {...},
});
let newModel = Model.create();
newModel.get('store');
Note: it does work if I inject the service into a Controller, but not an object. Haven't had any luck trying to figure out how to register the Object with the Ember container.
It works for an Ember.Controller because Ember controls the lifecycle of the object. In short, when Ember needs an instance of a certain controller, it asks the container for one, and the container will provide the instance, initializing if necessary.
What this implies is that for dependency injection to work, you would need to get a new instance of Model through the container. Assuming Ember 2.3 because of getOwner, and that this is somewhere inside the Ember application:
let owner = Ember.getOwner(this);
let newModel = owner.lookup('object:model');
newmodel.get('store');
You can consult the lookup documentation here.
But how to register? Use an application initializer:
$ ember generate initializer register-model
Then, open up the generated initializer and, assuming that your file is in app/folder/model.js, put something like:
import Model from 'app-name/folder/model';
export function initialize(application) {
application.register('object:model', Model);
}
export default {
name: 'register-model',
initialize
};
You can consult the register documentation here.
Hope this is helpful!
Well you need to passing in the container instance when you create a instance of your model. The container is accessible in the route, controllers, components with this.get('controller'). AFAIK basically anything created with the container gets the container property set. Thats why service injections work in controllers etc..
So if you are creating the model in a route's method. The code will look like below
App.IndexRoute = Ember.Route.extend({
model: function() {
var newModel = Model.create({
container: this.get('container')
});
return newModel.get('test').getText();
}
});
Here is a working demo.

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.

Ember CLI generate component in an addon

I try to make an addon using ember-cli. Here it is step by step what I have done so far:
sudo ember addon test-addon
cd test-addon
sudo ember serve
now the server runs and on localhost:4200 I can see the test/dummy app's application hbs.
Welcome to Ember.js
Its time to make some components for the addon:
sudo ember g component my-form
In the tests/dummy/app/templates/application.hbs I added
{{my-form}}
And now I'm getting the following js error:
Uncaught Error: Could not find module test-addon/components/my-form imported from dummy/components/my-form
edit
After struggling a little bit with npm, I tried it again (without sudo) and the same thing happened. I'm on Ember CLI 0.2.1. Here are my files, but they should be the same since they are auto-generated. The error is thrown from bower-components/loader.js/loader.js line 110.
addon/components/my-form.js
import Ember from 'ember';
import layout from '../templates/components/my-form';
export default Ember.Component.extend({
layout: layout
});
addon/templates/components/my-form.hbs
{{yield}}
app/components/my-form.js
import myForm from 'test-addon/components/my-form';
export default myForm;
It looks like (as of July 2015, anyway) that the fact templates don't work in addons is partially by design. Philosophically, I guess the justification is that the styling should be app-specific, but the JS logic can be shared. Or it's just a bug/oversight.
It turns out that if you simply remove that layout line and the import layout, it will work.
So the result looks like:
<app-name>/app/templates/includes-a-shared-component.hbs:
What follows is my shared component! {{my-shared-component}}
<addon-name>/addon/components/my-shared-component.js:
import Ember from 'ember';
export default Ember.Component.extend({
valueFromProperty:function() { // simple hello-world-style function
return 5;
}.property()
});
<app-name>/app/templates/components/my-shared-component.hbs:
Hey look I'm the app-specific style for your component <marquee>Hello world</marquee>
Here's a value from a property: {{valueFromProperty}}
My versions:
Ember: 1.13.1
node: 0.12.0
npm: 2.12.1
And here's a very different (and IMO better, but not perfect) answer than my earlier one.
<addon-name>addon/components/test-component.js:
import Ember from 'ember';
export default Ember.Component.extend({
valueFromProperty:function() {
return 5;
}.property(),
layout:Ember.HTMLBars.compile("I'm the addon's layout {{valueFromProperty}}")
});
You will need to add the template compiler to your app:
<app-name>/app/ember-cli-build.js:
/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function(defaults) {
var app = new EmberApp(defaults, {
// Add options here
});
app.import('bower_components/ember/ember-template-compiler.js');
... other stuff...
Note that the layout property in the addon's component completely overrides your in-app template, so per-app customizing becomes more difficult. But if you want your template customizable, you could probably use my other answer where you don't specify layout at all and just let the resolver find the template in your app.
One more approach I've found that works and is different than my other answers.
Let's say you've done ember g component my-addon-component in your addon.
This will result in you having a component at: <addon-name>/addon/components/my-addon-component.js and a template at <addon-name>/addon/templates/my-addon-component.hbs (at least with my current ember-cli).
You'll also have a tiny component stub at <addon-name>/app/components/my-addon-component.js
The fix:
1. Move the component guts from <addon-name>/addon/components to <addon-name>/app/components (replacing the stub).
2. Move the template from <addon-name>/addon/templates/components to <addon-name/app/templates/components
After 1: The import layout from ../templates/components/my-addon-component will now have a different meaning: it'll be importing from the including-app's namespace instead of the addon's namespace.
After 2: The template's import location will be in the including-app's namespace. This also seems to mean it gets compiled by the app, so you won't throw the "addon templates were detected, but there are no template compilers registered"

Using Ember CLI Addon Models in Ember Application?

I want to allow something like the following to work in my application:
store.find('my-addon.my-addon-model', 1)
store.find('my-addon/my-addon-model', 1)
store.find('my-addon:my-addon-model', 1) (unlikely)
The thing is I want it to search for a model that is 100% defined in an addon.
import MyAddonModel from 'my-addon/models/my-addon-model' works from within my app - but container resolution doesn't...
How would I do/allow this?
This question is the same as:
Registering models from another namespace with the Ember Data store
However the naming there is a bit confusing. When trying this out I also seem to have hit a bug in ember-data#1.0.0-beta.15.
What you need to do in your module initializer is register the model(s) to your application.
import Ember from 'ember';
import MyModel from my-module/models/my-model';
export default {
name: 'my-module-models',
initialize: function(container, application) {
//one of these calls for each model you need to register
application.register('model:myModule.myModel',MyModel);
}
};
Now according to my experience with Ember and Ember-Data, this is supposed to work just like that. But in my setup with ember-data#1.0.0-beta.15 there seems to be an issue determining the models "key" after creation. The model instance is created fine, but you will hit an error trying to save the model.
I've found a quick workaround, but it's a hack and I would need to investigate further whether I'm missing a step or it's a bug. The workaround involves setting the "typeKey" of the class, resulting in:
import MyModel from my-module/models/my-model';
export default {
name: 'my-module-models',
initialize: function(container, application) {
//one of these calls for each model you need to register
application.register('model:myModule.myModel',MyModelreopenClass({typeKey:'myModule.myModel'}));
}
};
Finally, there is another way around. When creating modules in ember-cli, you have the app folder which will be merged with the app using your module. You could place default extends of your model(s) in the app folder.