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'));
}
});
Related
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.
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
using
Ember : 1.13.11,
Ember Data : 1.13.8,
ember-cli : 1.13.12
I want to add a component dynamically to webpage - this webpage is template of another component don't think that it will make any difference-. Here is my code snippet in which I try to add a component named LyricsEditorLine to <div> tag, somehow like this
agenda-alpha/components/lyrics-editor.js
import Ember from 'ember';
import LyricsEditorLine from 'agenda-alpha/components/lyrics-editor-line';
export default Ember.Component.extend({
afterRenderEvent:function(){
LyricsEditorLine.create().appendTo($("#in"));
},
init:function(){
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
this._super();
}
});
agenda-alpha/templates/components/lyrics-editor.hbs
<div id='in'> </div>
every time this gives me
'Uncaught Error: Assertion Failed: You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead'
Looked for ContainerViewhere found that it is deprecated
Most of the answers that I found are not using ember-cli and being a beginner makes it harder to understand
I want to be able to add components as much as the user needs
I think you probably want the {{component}} helper which allows to dynamically render a component.
{{component "componentName" param1=paramValue param2=anotherParamValue}}
Which means you can have (made up example)
{{component "lyrics-editor-line" line=line}}
One the best things is that componentName can be a bound property
{{component componentName line=line}}
And in your controller/component:
componentName: Ember.computed('prop1','prop2', function() {
if (this.get('prop1') === 'A') {
return 'my-component-a';
}
return 'default-component';
}),
line: computed('prop3', function() {
return this.get('prop2');
})
Also, you can have the component helper inside an each loop (example taken from the Ember documentation)
{{#each model as |post|}}
{{!-- either foo-component or bar-component --}}
{{component post.componentName post=post}}
{{/each}}
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.
I'm using Twitter Bootstrap for navigation in my Ember.js app. Bootstrap uses an active class on the li tag that wraps navigation links, rather than setting the active class on the link itself.
Ember.js's new linkTo helper will set an active class on the link but (as far as I can see) doesn't offer any to hook on to that property.
Right now, I'm using this ugly approach:
{{#linkTo "inbox" tagName="li"}}
<a {{bindAttr href="view.href"}}>Inbox</a>
{{/linkTo}}
This will output:
<li class="active" href="/inbox">Inbox</li>
Which is what I want, but is not valid HTML.
I also tried binding to the generated LinkView's active property from the parent view, but if you do that, the parent view will be rendered twice before it is inserted which triggers an error.
Apart from manually recreating the logic used internally by the linkTo helper to assign the active class to the link, is there a better way to achieve this effect?
We definitely need a more public, permanent solution, but something like this should work for now.
The template:
<ul>
{{#view App.NavView}}
{{#linkTo "about"}}About{{/linkTo}}
{{/view}}
{{#view App.NavView}}
{{#linkTo "contacts"}}Contacts{{/linkTo}}
{{/view}}
</ul>
The view definition:
App.NavView = Ember.View.extend({
tagName: 'li',
classNameBindings: ['active'],
active: function() {
return this.get('childViews.firstObject.active');
}.property()
});
This relies on a couple of constraints:
The nav view contains a single, static child view
You are able to use a view for your <li>s. There's a lot of detail in the docs about how to customize a view's element from its JavaScript definition or from Handlebars.
I have supplied a live JSBin of this working.
Well I took what #alexspeller great idea and converted it to ember-cli:
app/components/link-li.js
export default Em.Component.extend({
tagName: 'li',
classNameBindings: ['active'],
active: function() {
return this.get('childViews').anyBy('active');
}.property('childViews.#each.active')
});
In my navbar I have:
{{#link-li}}
{{#link-to "squares.index"}}Squares{{/link-to}}
{{/link-li}}
{{#link-li}}
{{#link-to "games.index"}}Games{{/link-to}}
{{/link-li}}
{{#link-li}}
{{#link-to "about"}}About{{/link-to}}
{{/link-li}}
You can also use nested link-to's:
{{#link-to "ccprPracticeSession.info" controller.controllers.ccprPatient.content content tagName='li' href=false eventName='dummy'}}
{{#link-to "ccprPracticeSession.info" controller.controllers.ccprPatient.content content}}Info{{/link-to}}
{{/link-to}}
Building on katz' answer, you can have the active property be recomputed when the nav element's parentView is clicked.
App.NavView = Em.View.extend({
tagName: 'li',
classNameBindings: 'active'.w(),
didInsertElement: function () {
this._super();
var _this = this;
this.get('parentView').on('click', function () {
_this.notifyPropertyChange('active');
});
},
active: function () {
return this.get('childViews.firstObject.active');
}.property()
});
I have just written a component to make this a bit nicer:
App.LinkLiComponent = Em.Component.extend({
tagName: 'li',
classNameBindings: ['active'],
active: function() {
return this.get('childViews').anyBy('active');
}.property('childViews.#each.active')
});
Em.Handlebars.helper('link-li', App.LinkLiComponent);
Usage:
{{#link-li}}
{{#link-to "someRoute"}}Click Me{{/link-to}}
{{/link-li}}
I recreated the logic used internally. The other methods seemed more hackish. This will also make it easier to reuse the logic elsewhere I might not need routing.
Used like this.
{{#view App.LinkView route="app.route" content="item"}}{{item.name}}{{/view}}
App.LinkView = Ember.View.extend({
tagName: 'li',
classNameBindings: ['active'],
active: Ember.computed(function() {
var router = this.get('router'),
route = this.get('route'),
model = this.get('content');
params = [route];
if(model){
params.push(model);
}
return router.isActive.apply(router, params);
}).property('router.url'),
router: Ember.computed(function() {
return this.get('controller').container.lookup('router:main');
}),
click: function(){
var router = this.get('router'),
route = this.get('route'),
model = this.get('content');
params = [route];
if(model){
params.push(model);
}
router.transitionTo.apply(router,params);
}
});
You can skip extending a view and use the following.
{{#linkTo "index" tagName="li"}}<a>Homes</a>{{/linkTo}}
Even without a href Ember.JS will still know how to hook on to the LI elements.
For the same problem here I came with jQuery based solution not sure about performance penalties but it is working out of the box. I reopen Ember.LinkView and extended it.
Ember.LinkView.reopen({
didInsertElement: function(){
var el = this.$();
if(el.hasClass('active')){
el.parent().addClass('active');
}
el.click(function(e){
el.parent().addClass('active').siblings().removeClass('active');
});
}
});
Current answers at time of writing are dated. In later versions of Ember if you are using {{link-to}} it automatically sets 'active' class on the <a> element when the current route matches the target link.
So just write your css with the expectation that the <a> will have active and it should do this out of the box.
Lucky that feature is added. All of the stuff here which was required to solve this "problem" prior is pretty ridiculous.