EmberJS: How to check if a component exists (inside another component)? - ember.js

My goal is to resolve to a generic component is the component doesn't exists. It managed to do it like this:
// app/components/dynamic-widget.js
...
widgetName: function() {
var name = this.get('config.name');
if (!this.container.resolve('component:'+name)) {
name = 'generic-widget';
}
return name;
}.property('config.name')
...
Then in app/templates/components/dynamic-widget.hbs:
{{component widgetName}}
Then, I could use my dynamic-component like this:
{{dynamic-widget 'foo-widget'}}
If foo-widget is not implemented, it fallback into generic-widget.
But since EmberJS 1.11, resolving a component from a component's container is deprecated:
DEPRECATION: resolve should be called on the registry instead of the container
So my question is, how can I check if a component actually exists without using this.container.resolve ?
Thanks a lot.

Does this.container.registery.resolve work?
Looking at the code in git https://github.com/emberjs/ember.js/blob/5fd2d035b30aa9ebfe73de824b3b283ec8e589cc/packages/container/lib/registry.js
Looks like you may also be able to use this.container.registery.has

Related

Allow binding to any data-* attribute on an ember component

I am designing a library whereby I would like to allow the user to supply any data attributes they might like.
{{my-component data-type='hello' data-name='world'}}
I don't know ahead of time which data attributes they might like to bind to so can't add them to the attributeBindings array.
Is there a workaround for this?
Use the didReceiveAtts hook of your component:
didReceiveAttrs(params){
let newAttrs = params.newAttrs;
let attributeBindings = Ember.A();
Object.keys(newAttrs).forEach((attr)=>{
if(attr.indexOf('data-')>= 0){
attributeBindings.pushObject(attr);
}
});
this.set('attributeBindings', attributeBindings);
}
Look that Sample twiddle
Updated, after deprecation:
Since arguments of didReceiveAttrs function are deprecated, you need to change the code as the following:
didReceiveAttrs(){
let attributeBindings = Ember.A();
Object.keys(this).forEach((attr)=>{
if(attr.indexOf('data-')>= 0){
attributeBindings.pushObject(attr);
}
});
this.set('attributeBindings', attributeBindings);
}
See updated twiddle.
I guess after v3.10 you can do this without any hacks with the angle bracket invocation (and if required pass further using the ...attributes). So in my simplest case it was as simple as
<MyComponent data-aaa="bbb"/>

Ember template helper get-value-with-key

Does Ember have any template helper "get-value-with-key"
I found the below usage, but not sure what it does exactly ?
{{get-value-with-key item optionValuePath}}
There is an Ember Get Helper for HTMLBars.
You might have to install the Package "ember-get-helper" if you are on ember < 2.1.
{{get object key}}
Let's say you have the following object:
var obj = {
"key1": {
"subkey1": "hello world"
}
}
Using Ember 3.18, to access "hello world" from the template, you can do:
{{get obj 'key1.subkey1'}}
You can use the build-in get helper. See docs here: Ember Docs.
Usage example:
{{get object key}}
Note that the get helper will not be able to work on all JavaScript keys. For example, a key with a '.' will not work with the built-in get helper.
For example, if you have a valid JavaScript object like:
const example = {
'example.pdf': 'pdf_url'
}
// You can access this key normally via
example['example.pdf']
however, this will not work in the get helper
{{get this.example 'example.pdf'}}
One solution is to create a helper that can support the types of keys you need to support. For example, I made a helper that can work on keys with a '.' by including '.' in the key name which are escaped like with ''.
{{get this.example 'example\.pdf'}}
The ember twiddle can be found here: twiddle
Other Helpful Sources:
Dot notation for object keys with dots in the name
How do I reference a field name that contains a dot in mustache template?

computed.oneWay not working with Ember Data

I have an EmberData snapshot which I'd like decorate with a few additional attributes before handing over to the UI for presentation. This decoration will be setting "properties" of the Ember-Data record not actual attributes. It looks like this:
let foo = Ember.computed.oneWay(this.get('store').find('activity'));
foo.map(item => {
item.set('foobar', 'baz');
return item;
}
I would then hope that foo would be the beneficiary of the promised record (it is) and the setting of the foobar property would be localized to the foo property (it's not, it seems to be globally scoped to the record's properties).
As for me this is expected behavior.
1) OneWay means only you are not bind set method on foo. It used to work between properties of objects, but this.get('store').find('activity') is a just promise.
2) foo is store.find() result of DS.RecordArray type (assuming promise is resolved). So you are iterating on records returned and set foobar property on them.
Achieving your goal, you could decorate activity record by component (Ember 2.0 way) for UI presentation.
So far the only way I've been able to do this is using Ember's ObjectProxy like so:
const FooDecorator = Ember.ObjectProxy.extend({
foobar: 'baz'
});
let foo = Ember.computed(function() {
return this.get('store').find('activity').map(item => {
FooDecorator.create({content: item});
});
}
This works but I thought I'd been hearing things about Object proxies not being a welcome part of Ember 2.0 roadmap so not sure if this approach is best. I'll keep this open for a few days before closing.

Ember.js: Is it possible to inject a dependency on a specific Route/Controller Mixin?

Let's say I have a SessionManager instance which I want to be accessible in every Route extending my ProtectedRoute Mixin, is it possible to inject this dependency into a "group of routes" as I can reference a single Route instance?
So instead of:
App.inject('route:protected1', 'sessionManager', 'session_manager:main');
App.inject('route:protected2', 'sessionManager', 'session_manager:main');
....
I could do something like
App.inject('route:protectedmixin', 'sessionManager', session_manager:main);
You certainly can, but it might involve a bit of juggling. You could define any logic to decide what to inject and where if you want to rely on the default conventions you could manually find this objects and then use the fullname when injecting.
Another option would be to do it for each route, regardless of whether they include the Mixin or not. Inject doesn't need the full name, if you call `App.inject('route', ...) it would work by default.
If going with option one, it would look something like this. You basically need to find those routes implementing their mixins and then inject into all of those.
var guidForMixin = Ember.guidFor(App.YourMixin);
var routesToInjectInto = Ember.keys(App).filter(function (key) {
var route, mixins;
if (key.match(/Route$/))
route = App[key];
mixins = Ember.meta(route).mixins;
if (mixins) {
!!mixins[guidForMixin];
}
return false;
);
routesToInjectInto.each( function (key) {
var keyForInjection = Ember.decamelize(key);
App.inject('route:' + keyForInjection, 'sessionManager', 'session_manager:main');
});
Also I would suggest doing all of this inside an initializer, but that might be a minor consideration.
Ember.onload('Ember.Application', function(Application) {
Application.initializer {
name: "sessionManager"
initialize: function (container, application) {
// do the above here. Refer to app as the namespace instead of App.
// use the container instead of App.__container__ to register.
};
});

Force a controller to always act as a proxy to a model in Ember

I'm looping through a content of an ArrayController whose content is set to a RecordArray. Each record is DS.Model, say Client
{{# each item in controller}}
{{item.balance}}
{{/each}}
balance is a property of the Client model and a call to item.balance will fetch the property from the model directly. I want to apply some formatting to balance to display in a money format. The easy way to do this is to add a computed property, balanceMoney, to the Client object and do the formatting there:
App.Client = DS.Model({
balance: DS.attr('balance'),
balanceMoney: function() {
// format the balance property
return Money.format(this.get('balance');
}.property('balance')
});
This serves well the purpose, the right place for balanceMoney computed property though, is the client controller rather than the client model. I was under the impression that Ember lookup properties in the controller first and then tries to retrieve them in the model if nothing has been found. None of this happen here though, a call to item.balanceMoney will just be ignored and will never reach the controller.
Is it possible to configure somehow a controller to act always as a proxy to the model in all circumstances.
UPDATE - Using the latest version from emberjs master repository you can configure the array controller to resolve records' methods through a controller proxy by overriding the lookupItemController method in the ArrayController. The method should return the name of the controller without the 'controller' suffix i.e. client instead of clientController. Merely setting the itemControllerClass property in the array controller doesn't seem to work for the moment.
lookupItemController: function( object ) {
return 'client';
},
This was recently added to master: https://github.com/emberjs/ember.js/commit/2a75cacc30c8d02acc83094b47ae8a6900c0975b
As of this writing it is not in any released versions. It will mostly likely be part of 1.0.0.pre.3.
If you're only after formatting, another possibility is to make a handlebars helper. You could implement your own {{formatMoney item.balance}} helper, for instance.
For something more general, I made this one to wrap an sprintf implementation (pick one of several out there):
Ember.Handlebars.registerHelper('sprintf', function (/*arbitrary number of arguments*/) {
var options = arguments[arguments.length - 1],
fmtStr = arguments[0],
params = Array.prototype.slice.call(arguments, 1, -1);
for (var i = 0; i < params.length; i++) {
params[i] = this.get(params[i]);
}
return vsprintf(fmtStr, params);
});
And then you can do {{sprintf "$%.2f" item.balance}}.
However, the solution #luke-melia gave will be far more flexible--for example letting you calculate a balance in the controller, as opposed to simply formatting a single value.
EDIT:
A caveat I should have mentioned because it's not obvious: the above solution does not create a bound handlebars helper, so changes to the underlying model value won't be reflected. There's supposed to be a registerBoundHelper already committed to Ember.js which would fix this, but that too is not released yet.