Where should I set defaults on components with attributeBindings? - ember.js

I have a component {{upload-image src=foo.bar}}. When foo.bar is falsy I want to give it a different value. I setting the value in init like so:
import Ember from 'ember';
export default Ember.Component.extend({
attributeBindings: ['src'],
tagName: 'img',
init: function(){
this.set('src', 'http://www.placecage.com/40/40');
this._super();
this.set('src', 'http://www.placecage.com/40/40');
},
click: function(){
console.log(this.get('src'));
}
});
However it doesn't work. The image gets rendered with the value I pass in, not the new one. When I click the image it does log the placecage image. Instead if I try to override it later on say didInsertElement it works as expected:
import Ember from 'ember';
export default Ember.Component.extend({
attributeBindings: ['src'],
tagName: 'img',
didInsertElement: function(){
this.set('src', 'http://www.placecage.com/40/40');
},
click: function(){
console.log(this.get('src'));
}
});
It seems a bit silly to have it render out and only then be able to change the value causing (I presume) a re-render. What's the appropriate place to check if src is falsy and set it to a default and get the expected results?

In Ember 1.13 you can do that in
attributeBindings: ['src:src'],
didInitAttrs() {
this.set('src', <new value>);
}
Or you could do the falsy logic outside of the component and pass it in.

I would create another property defined as follows:
effectiveSrc: function() {
return this.get('src') || 'http://www.placecage.com/40/40';
}.property('src')
then use effectiveSrc in your logic and templates.
Another alternative is to give src a default:
src: 'http://www.placecage.com/40/40'
Then when calling your template, omit src if its value is falsy, something like
{{#if foo.bar}}
{{upload-image src=foo.bar}}
{{else}}
{{upload-image}}
{{/if}}
The following might also work, need to try it:
{{upload-image src=(if foo.bar foo.bar)}}

With ember 2.x (I've tested with 2.4.2, 2.4.3 and 2.5.0), I've achieved this functionality with the mentioned default by torazaburro :
export default Ember.Component.extend({
attributeBindings: ['src'],
tagName: 'img',
src: 'http://www.placecage.com/40/40',
click() {
console.log(this.get('src'));
}
}
And the templates:
{{upload-image src="http://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon.png"}}
{{upload-image}}
The Twiddle to test it

There's an open issue over on Github https://github.com/emberjs/ember.js/issues/11637 where they seem to be saying that I shouldn't set the value using the two-way data binding this way.
I suppose I should make it read from a computed property instead and set my default from that.

Related

How do I make dynamic classNames in an ember 2.0 component?

For example:
Ember components allow you to add a classNames array and those classes will be added to the main div of the component.
say we have this component called new-div
export default Ember.Component.extend({
classNames: ['container']
});
then if you inspect this component when rendered you will see:
<div id="ember595" class="ember-view container">
...
<div>
this is fine but my issue is what if i want to use this component as a fluid container sometimes and sometimes I might want to make it a jumbotron etc.
Is there a way to do this in the html and have the component.js apply it correctly?
{{new-div extra-classes='class1,class2'}}
then in the component.js:
export default Ember.Component.extend({
classNames: [this.get('class-names')]
});
The #dmk'solution is the cleanest one, but if your scenario it is not working you can use classNameBindings:
export default Ember.Component.extend({
classNameBindings: ['getClassNames'],
getClassNames: Ember.computed('extra-classes', function(){
return this.get('extra-classes').replace(',', ' ');
})
})
You can add class names simply by specifying them inside the class attribute on your component:
{{new-div class="class1 class2"}}
If you're not adding too many classes, it's easy enough with class name bindings:
export default Ember.Component.extend({
classNameBindings: [
'foo:bar',
'foo:baz',
],
});
And set the value of foo:
{{new-div foo=true}}
This will toggle on all the above class names.
See: https://api.emberjs.com/ember/release/classes/Component/properties/classNameBindings?anchor=classNameBindings
Of course, you could get tricky with computed properties and mapping an array. Also: I like to avoid assigning dynamic class names to components explicitly. Things become messy rather quickly.
Just as an alternative one could use something like this
export default Ember.Component.extend({
attributeBindings: ['style'],
style: function(){
return new Ember.Handlebars.SafeString('class="foo bar"');
}.property(),
});
// NOT sure on this one untested
export default Ember.Component.extend({
attributeBindings: ['classNames'],
classNames: function(){
return 'foo bar';
}.property(),
});
If someone is using ember-component-css, you may want to try out the join-classes or the local-class attribute helper.
{{join-classes styles.myclass1 attributeValue}}
attributeValue can be a value from the component's controller (I mean component.js), or an item inside an each block.
If styles.myclass1 = .class1, and attributeValue = .dummy, then the selectors would be available as .class1.dummy in the styles.css.
local-class={{(concat "myclass-" myvalue)}}
If myvalue = 'part', then with this, the generated classname would include tree-to-component_myclass-part__sdfje2jbr2 (last part is generated id), and would be accessible in the stylesheet as .myclass-part.

Ember Component classNameBinding not binding

I have a component that is a button which needs to change it's classNames via a property of it parent component/controller:
// components/my-button/component.js
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'button',
classNameBindings: ['classNames'],
// some other unrelated stuff following....
});
it's template:
// components/my-button/template.hbs
{{text}}
// nothing important here exept the test-output of:
{{classNames}}
and I insert it like this in another component:
// component/parent-component/template.hbs
{{my-button
classNames=variableClassNames
text='foo'
}}
// components/parent-component/component.js
import Ember from 'ember';
export default Ember.Component.extend({
isSortableDown: Ember.computed('track.sort', 'maxSort', function() {
return this.get('track.sort')<this.get('maxSort');
}),
variableClassNames: Ember.computed('isSortableDown',function() {
if(this.get('isSortableDown')) {
return 'btn btn-xs btn-default';
} else {
return 'btn btn-xs btn-default push-20-r';
}
}),
// other unrelated stuff...
});
Now here's my problem:
when isSortableDown is changing, variableClassNames in parent AND classNames in child (component/my-button) IS updated (also the test-output in my-button template).
BUT the classNameBindings are NOT updated, instead the classNames appear multiple times (when looking at the actual outputted DOM).
Well, that's not 100% right, they do get added, but never removed.
So if the className push-20-r once gets added, it'll stay there (in the DOM), but never removed, even if the property classNames doesn't include it anymore.
Finally my question is if I'm doing something wrong,
or if the classNameBindings should not be updated (but why the name 'bindings' then??),
or if this is eventually a bug?
I'm on
Ember 2.0.1
jQuery 1.11.3
The only maybe relevant issues I found are:
https://github.com/emberjs/ember.js/issues/11980
https://github.com/emberjs/ember.js/issues/11556
but they don't have an answer... or are only party related
Sidenote:
Yes, I want the component itself to be a button, not a div, because otherwise I'd have to change all the css.... I know I could do it by leaving the component a div and wrap that button and adjust it's classNames there.
you are using special property of ember component classNames as bound variable which was causing the problem instead you can take following approach
export default Ember.Component.extend({
tagName: 'button',
classNameBindings: ['isSortableDown::push-20-r'], // class added when isSortableDown is false
classNames: ['btn', 'btn-xs', 'btn-default'], // classes that don't change
isSortableDown: true
// some other unrelated stuff following....
});
in template
{{my-button
isSortableDown=isSortableDown
text='foo'
}}
in your parent component
export default Ember.Component.extend({
isSortableDown: Ember.computed('track.sort', 'maxSort', function() {
return this.get('track.sort')<this.get('maxSort');
}),
// other unrelated stuff...
});
classNames is a special property within components. You might try changing the name to something else and see if that helps?
http://emberjs.com/api/classes/Ember.Component.html#property_classNames

Ember 2.0 - views for pages

I am used to put some jquery code in View files, that I create for pages.
For instance:
I have the route this.route('buildings');
Next create the view file app/views/buildings.js
import Ember from "ember";
export default Ember.View.extend(Ember.TargetActionSupport, {
didInsertElement: function () {
//jquery here
}
});
But now with Ember 2.0 we have no such ability, what should I do?
In Ember 2.0.0 you can still do the following:
App.ApplicationView = Ember.Component.extend({
classNames: ['customClassName'],
didInsertElement: function() {
alert('did insert element')
}
});
App.BuildingsView = Ember.Component.extend({
classNames: ['customClassName2'],
didInsertElement: function() {
alert('did insert element2');
}
});
See this jsbin for a working example.
P.S. Comment by Robert Jackson: "Using a component as ApplicationView will allow customization of classNames and whatnot, but is definitely going to have a number of negative results as well (for example controller is not correct)." https://github.com/emberjs/ember.js/issues/11486#issuecomment-131366332
Put it in a component. In components/my-component.js
import Ember from 'ember';
export default Ember.Component.extend({
didInsertElement: function(){
// jquery here.
}
});
In your template:
{{my-component}}
Or:
{{#my-component}}
Stuff
{{/my-component}}

Ember JS: using a component to wrap accounting.js library

I am trying to wrap accounting.js into a component but i am missing something fundamental and getting an error.
Here is the each loop in my template:
{{#each item in model}}
{{#with item}}
{{#link-to 'debtor' debtor_id}}
<div>{{debtor_id}}</div>
<div>{{debtor_legacy_account_number}}</div>
<div>{{debtor_full_name}}</div>
<!-- below is the component call -->
<div>{{currency-widget value=debtor_balance}}</div>
<div>{{debtor_debt_number}}</div>
{{/link-to}}
{{/with}}
{{/each}}
and here is the component:
// app/components/currency-widget.js
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'span',
classNames: ['currency-widget'],
didInsertElement: function(value) {
return accounting.formatMoney(value);
}
});
as you can see, i want to have this component loop through and return a formatted value for each debtor_balance property passed to it.
Update: I don't have any errors now.. but the component is not returning the new value.
I added the dependency in my brocfile
app.import('vendor/accounting.js/accounting.min.js');
And I also included accounting and formatMoney to global in my .jshintrc file
It must be the the logic inside the component? maybe its something simple
I don't believe you can pass a value to the didInsertElement function, but you can still access properties that you set in the template when you define the component. When you say:
{{currency-widget value=debtor_balance}}
You're effectively setting a value property on the component that is equal to the debtor_balance. So update your component code to access the components "value" attribute, as opposed to trying to pass the value to the didInsertElement function.
export default Ember.Component.extend({
tagName: 'span',
value: 0,
classNames: ['currency-widget'],
didInsertElement: function() {
return accounting.formatMoney(this.get('value'));
}
});

Ember template displayed twice

There is a simple ember.js app with one view displayed in a particular place on the webpage. Have a look at this jsfiddle: http://jsfiddle.net/jkkK3/9/
App = Ember.Application.create({
ready: function(){
this._super();
this.ApplicationView.create().appendTo(".content");
},
ApplicationController: Ember.Controller.extend({
}),
ApplicationView: Ember.View.extend({
templateName: 'application'
}),
Router: Ember.Router.extend({
root: Ember.Route.extend({
})
})
});
My question is: Why is the "some content here" element displayed twice? It works when I remove the router, but that's exactly what I cannot do, since I try to add Router to my Ember app. Could you please help me to display application view only once, inside the red box?
When using router, applicationController/view are used by default. In your ready method you append it explicitly. So 'application' template is appended twice. Remove appending it in ready method and it will be appended only once.
By default it's appended to body but if you want to override use rootElement property of Ember.Application
Ember.Application.create( {
rootElement : '.content',
....
})