style attribute with emberjs - ember.js

Is there a way to set css properties by using emberjs properties' auto binding ?
Something like:
<div {{bindAttr style="background-color: divColor;"}}>
...
</div>

Please note that as of ember 1.13. binding attributes (bind-attr) is deprecated. You would need to use code similar to this to bind to the class:
<div class={{myClass}}></div>
Furthermore, style binding in this fashion is not recommended because it can introduce XSS vulnerabilities. The HTML templating automatically escapes HTML to prevent XSS when using {{...}}, but style attributes have additional vulnerabilities outside of the scope of the built-in escaping.
The recommended approach is to escape the CSS yourself (i.e. creating the escapeCSS function that would escape the specific CSS appropriately to prevent XSS - this is not a built-in function. You could start with Ember.Handlebars.Utils.escapeExpression and add any additional checking from that base.) More information can be found here:
https://guides.emberjs.com/v2.2.0/templates/writing-helpers/#toc_escaping-html-content
Then you tell Ember that the string is "safe" by using Ember.String.htmlSafe, and Ember will not try to escape that content.
controller:
myStyle: Ember.computed('color', function() {
var color = escapeCSS(this.get('color'));
return new Ember.String.htmlSafe("color: " + color);
})
template:
<div style={{myStyle}}></div>
Reference: http://emberjs.com/deprecations/v1.x/#toc_binding-style-attributes

Another simple way to do this is to add a computed property to your model.
Model ----
App.Photo = Em.Object.extend(
objectId: null
url: ""
style: (->
"background-image:url('" + #get("url") + "')"
).property("url")
)
Template -----
{{#each item in App.photoController}}
<div {{bindAttr style="item.style"}}></div>
{{/each}}
I got this working, and seems to be the simplest way to go about it.

Not exactly like that but close. You'll have to build the style string yourself. Look at this jsFiddle.
App = Ember.Application.create();
/**************************
* Models
**************************/
/**************************
* Views
**************************/
App.View = Ember.View.extend({
style: function() {
return "background-color:" + this.get('color');
}.property('color').cacheable()
});
/**************************
* Controllers
**************************/
App.set('controller', Ember.Object.create({
color: "transparent",
red: function() {
this.set('color', 'red');
},
blue: function() {
this.set('color', 'blue');
},
style: function() {
return "background-color:" + this.get('color');
}.property('color').cacheable()
}));
/**************************
* App Logic
**************************/
$(function() {
template:
{{#view Ember.Button target="App.controller" action="blue"}}BLUE{{/view}}
{{#view Ember.Button target="App.controller" action="red"}}RED{{/view}}
{{#view App.View colorBinding="App.controller.color" attributeBindings="style"}}
Color is {{App.controller.color}}
{{/view}}
<hr>
<div {{bindAttr style="App.controller.style"}}>And another way...</div>

Recent Ember version (2.3.0 as of this writing) allows straight-forward embedding of computed style.
// bar-graph.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['bar-graph'],
inlineStyle: Ember.computed('attrs.min', 'attrs.max', 'attrs.value', function() {
let min = this.get('attrs.min');
let max = this.get('attrs.max');
let value = this.get('attrs.value')
let percentage = Math.round(value / (max - min) * 100);
return new Ember.Handlebars.SafeString(`width: ${percentage}%`);
})
});
<!-- bar-graph.hbs -->
<div class="bar" style={{inlineStyle}}>{{value}}</div>
Live example

I have tried using answer provided by #WallMobile, but it has some syntax issues. So this is the correct syntax to be used.
App.Photo = Em.Object.extend({
objectId: null,
url: "",
style: function() {
return "background-image:url('" + this.get("url") + "')"
}.property("url")
})

HTMLBars now lets you do this - upgrade to the latest ember/ember-cli to take advantage of the new stuff.

There's a new addon which allows you to declare styles as JS objects and bind them to your component's style attribute. Check out ember-computed-style
import computedStyle from 'ember-computed-style';
export default Ember.Component.extend({
style: computedStyle('backgroundStyle'),
attributeBindings: ['style'],
backgroundStyle: function(){
return {
backgroundColor: this.get('divColor')
};
}.property('divColor'),
divColor: 'red'
});
This will produce:
<div style="background-color:red;"></div>

Another approach you could use is CSS custom properties.
ember-cli-custom-properties is an Ember add-on that binds component properties to CSS custom properties (variables). Its fairly simple to use. Once you install the add-on, the add-on makes the customProperties and customPropertyBindings property available on the #ember/component class.
For example, you could turn the raw HTML above into a Ember component, and give it a class name.
import Component from '#ember/component';
export default Component.extend ({
classNames: ['my-component'],
// Map the backgroundColor attribute to a CSS custom property
customProperties: ['backgroundColor']
});
You can then reference this class name in the styles in for your application.
.my-component {
background-color: var(--background-color);
}
Lastly, just set the backgroundColor attribute on the component to your desired color.
{{my-component backgroundColor="red"}}

Related

Specifying which properties to use in a custom drop-down Ember component

I've created a reusable drop-down component in Ember:
/app/components/dropdown/component.js
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'select',
classNames: ['form-control'],
placeholder: null,
items: null,
selected: null,
change: function(event) {
var items = this.get('items');
var index = event.target.selectedIndex;
var selected = items ? items[index - 1] : null;
this.sendAction('selectedChanged', selected);
}
});
/app/components/dropdown/template.js
{{#if placeholder}}
<option value="">{{placeholder}}</option>
{{/if}}
{{#each items as | item |}}
<option value="{{item.id}}" selected={{is-equal item selected}}>{{item.name}}</option>
{{/each}}
The component currently uses the 'name' property as the label for the option. However, I want the ability to specify what property to use, in order to make the component more flexible (so that I can sometimes use, for example, 'displayName').
With the old Ember Select component, you could do the following:
{{view "select"
content=programmers
optionValuePath="content.id"
optionLabelPath="content.firstName"
value=currentProgrammer.id
}}
...and tell it which properties to use for both the value and label. I'd like to do something similar, but I'm not sure how. (I tried reading through the source but it was a bit beyond me). Thanks in advance.
<option value="{{ember-get item optionValuePath}}" selected={{is-equal item selected}}>{{ember-get item optionLabelPath}}</option>
You will need to implement the ember-get helper, in Ember 2.0 there is a default heper called get which will make the following redundant.
import Ember from 'ember';
export function emberGet(params/*, hash*/) {
return Ember.get(params[0], params[1]);
}
export default Ember.HTMLBars.makeBoundHelper(emberGet);
Also take a look at this jsbin which has an example of what you are trying to achieve.

Trigger an action on the change event with Ember.js checkbox input helper?

How can I fire a named action upon changing a checkbox in Ember.js? Any help will be greatly appreciated.
Here is what I have. Checking or unchecking the checkbox has no effect.
Template:
{{input type="checkbox" on="change" action="applyFilter"}}
Controller:
actions: {
applyFilter: function() {
console.log("applyFilter");
}
}
I'd like to post an update to this. In Ember 1.13.3+, you can use the following:
<input type="checkbox"
checked={{isChecked}}
onclick={{action "foo" value="target.checked"}} />
link to source
using an observer seems like the easiest way to watch a checkbox changing
Template
{{input type='checkbox' checked=foo}}
Code
foo:undefined,
watchFoo: function(){
console.log('foo changed');
}.observes('foo')
Example
http://emberjs.jsbin.com/kiyevomo/1/edit
Or you could create your own implementation of the checkbox that sends an action
Custom Checkbox
App.CoolCheck = Ember.Checkbox.extend({
hookup: function(){
var action = this.get('action');
if(action){
this.on('change', this, this.sendHookup);
}
}.on('init'),
sendHookup: function(ev){
var action = this.get('action'),
controller = this.get('controller');
controller.send(action, this.$().prop('checked'));
},
cleanup: function(){
this.off('change', this, this.sendHookup);
}.on('willDestroyElement')
});
Custom View
{{view App.CoolCheck action='cow' checked=foo}}
Example
http://emberjs.jsbin.com/kiyevomo/6/edit
Post Ember version >= 1.13 see Kori John Roys' answer.
This is for Ember versions before 1.13
This is a bug in ember's {{input type=checkbox}} helper.
see https://github.com/emberjs/ember.js/issues/5433
I like the idea of having a stand-in. #Kingpin2k's solution works, but accessing views globally is deprecated and using observers isn't great.
In the linked github ember issue, rwjblue suggests a component version:
App.BetterCheckboxComponent = Ember.Component.extend({
attributeBindings: ['type', 'value', 'checked', 'disabled'],
tagName: 'input',
type: 'checkbox',
checked: false,
disabled: false,
_updateElementValue: function() {
this.set('checked', this.$().prop('checked'));
}.on('didInsertElement'),
change: function(event){
this._updateElementValue();
this.sendAction('action', this.get('value'), this.get('checked'));
},
});
Example usage in a template ('checked' and 'disabled' are optional):
{{better-checkbox name=model.name checked=model.checked value=model.value disabled=model.disabled}}
For those using Ember > 2.x:
{{input
change=(action 'doSomething')
type='checkbox'}}
and the action:
actions: {
doSomething() {
console.warn('Done it!');
}
}
In Ember v1.13 it can be done simply by creating a component named j-check in my occasion(no layout content required):
import Ember from 'ember';
export default Ember.Checkbox.extend({
change(){
this._super(...arguments);
this.get('controller').send(this.get('action'));
}
});
And then you just call it from your template like this:
{{j-check checked=isOnline action="refreshModel" }}

Ember: how to set classNameBindings on parent element of view

See http://jsfiddle.net/4ZyBM/6/
I want to use Bootstrap for my UI elements and I am now trying to convert certain elements to Ember views. I have the following problem:
I embed an input element in a DIV with a given class (control-group). If a validation error occurs on the field, then I want to add an extra class "error" to the DIV.
I can create a view based on the Ember.TextField and specify that if the error occurs the ClassNameBinding should be "error", but the problem is that class is the set to the input element and not to the DIV.
You can test this by entering a non alpha numeric character in the field. I would like to see the DIV border in red and not the input field border.
HTML:
<script type="text/x-handlebars">
<div class="control-group">
{{view App.AlphaNumField valueBinding="value" type="text" classNames="inputField"}}
</div>
</script>
JS:
App.AlphaNumField = Ember.TextField.extend({
isValid: function () {
return /^[a-z0-9]+$/i.test(this.get('value'));
}.property('value'),
classNameBindings: 'isValid::error'
})
Can I set the classNameBindings on the parent element or the element closest to the input ? In jQUery I would use:
$(element).closest('.control-group').addClass('error');
The thing here is that without using jQuery you cannot access easily the wrapping div around you Ember.TextField's. Also worth mentioning is that there might be also a hundred ways of doing this, but the simplest solution I can think of would be to create a simple Ember.View as a wrapper and check the underlying child views for validity.
Template
{{#view App.ControlGroupView}}
{{view App.AlphaNumField
valueBinding="value"
type="text"
classNames="inputField"
placeholder="Alpha num value"}}
{{/view}}
Javascript
App.ControlGroupView = Ember.View.extend({
classNameBindings: 'isValid:control-group:control-group-error',
isValid: function () {
var validFields = this.get('childViews').filterProperty('isValid', true);
var valid = validFields.get('length');
var total = this.get('childViews').get('length')
return (valid === total);
}.property('childViews.#each.isValid')
});
App.AlphaNumField = Ember.TextField.extend({
isValid: function () {
return /^[a-z0-9]+$/i.test(this.get('value'));
}.property('value')
});
CSS
.control-group-error {
border:1px solid red;
padding:5px;
}
.control-group {
border:1px solid green;
padding:5px;
}
Working demo.
Regarding bootstrap-ember integration and for the sake of DRY your could also checkout this ember-addon: https://github.com/emberjs-addons/ember-bootstrap
Hope it helps.
I think that this is the more flexible way to do this:
Javascript
Boostrap = Ember.Namespace.create();
To simplify the things each FormControl have the properties: label, message and an intern control. So you can extend it and specify what control you want. Like combobox, radio button etc.
Boostrap.FormControl = Ember.View.extend({
classNames: ['form-group'],
classNameBindings: ['hasError'],
template: Ember.Handlebars.compile('\
<label class="col-lg-2 control-label">{{view.label}}</label>\
<div class="col-lg-10">\
{{view view.control}}\
<span class="help-block">{{view.message}}</span>\
</div>'),
control: Ember.required()
});
The Boostrap.TextField is one of the implementations, and your component is a Ember.TextField. Because that Boostrap.TextField is an instance of Ember.View and not an Ember.TextField directly. We delegate the value using Ember.computed.alias, so you can use valueBinding in the templates.
Boostrap.TextField = Boostrap.FormControl.extend({
control: Ember.TextField.extend({
classNames: ['form-control'],
value: Ember.computed.alias('parentView.value')
})
});
Nothing special here, just create the defaults values tagName=form and classNames=form-horizontal, for not remember every time.
Boostrap.Form = Ember.View.extend({
tagName: 'form',
classNames: ['form-horizontal']
});
Create a subclass of Boostrap.Form and delegate the validation to controller, since it have to be the knowledge about validation.
App.LoginFormView = Boostrap.Form.extend({
submit: function() {
debugger;
if (this.get('controller').validate()) {
alert('ok');
}
return false;
}
});
Here is where the validation logic and handling is performed. All using bindings without the need of touch the dom.
App.IndexController = Ember.ObjectController.extend({
value: null,
message: null,
hasError: Ember.computed.bool('message'),
validate: function() {
this.set('message', '');
var valid = true;
if (!/^[a-z0-9]+$/i.test(this.get('value'))) {
this.set('message', 'Just numbers or alphabetic letters are allowed');
valid = false;
}
return valid;
}
});
Templates
<script type="text/x-handlebars" data-template-name="index">
{{#view App.LoginFormView}}
{{view Boostrap.TextField valueBinding="value"
label="Alpha numeric"
messageBinding="message"
hasErrorBinding="hasError"}}
<button type="submit" class="btn btn-default">Submit</button>
{{/view}}
</script>
Here a live demo
Update
Like #intuitivepixel have said, ember-boostrap have this implemented. So consider my sample if you don't want to have a dependency in ember-boostrap.

How do I bind to the active class of a link using the new Ember router?

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.

Ember valueBinding to Redactor WYSIWYG

I'm using the Redactor WYSIWYG editor and it allows you to use minimal markup before initializing its code like this:
<textarea id="redactor" name="content">
…
</textarea>
However during initialization Redactor will wrap this textarea with the following content:
<div class="redactor_box">
<div class="redactor_ redactor_editor" contenteditable="true" dir="ltr">
…
</div>
<textarea id="redactor" name="content" style="display: none;">
…
</textarea>
</div>
I currently have done this in Ember
Template:
{{ view App.RedactorView valueBinding='contentAttributes.headerContent' class='header-redactor' name='headerContent' }}
View extending Ember.TextArea:
App.RedactorView = Ember.TextArea.extend({
didInsertElement: function() {
$("#"+this.elementId).redactor();
}
});
This still holds a binding to the textarea (now hidden), but I now need to bind the redactor_editor class instead. How can I do this?
After a bit of digging in the Redactor code I found out that if your element destined to be an editor is not a textarea element, Redactor will do the reverse thing and add the textarea if your a using a div instead for example.
Updated my view and tweaked it based on code from Ember.TextArea and Ember.TextSupport so it would get the correct value, this will probably work fine if you're using a contenteditable enabled element as well.
App.RedactorView = Ember.View.extend({
tagName: 'div',
init: function() {
this._super();
this.on("focusOut", this, this._elementValueDidChange);
this.on("change", this, this._elementValueDidChange);
this.on("paste", this, this._elementValueDidChange);
this.on("cut", this, this._elementValueDidChange);
this.on("input", this, this._elementValueDidChange);
},
_updateElementValue: Ember.observer(function() {
var $el, value;
value = Ember.get(this, "value");
$el = this.$().context;
if ($el && value !== $el.innerHTML) {
return $el.innerHTML = value;
}
}, "value"),
_elementValueDidChange: function() {
var $el;
$el = this.$().context;
return Ember.set(this, "value", $el.innerHTML);
},
didInsertElement: function() {
this.$().redactor();
this._updateElementValue();
}
});
Here's a JSBin demonstrating it: http://emberjs.jsbin.com/cefebepa/1/edit
I'm using Ember + Foundation, and kroofy's solution worked like a charm for me.
The height of the contentEditable were loading without paddings (were missing a paragraph), so ive changed to redactor's insertHtml method to update the value.
from:
return $el.innerHTML = value;
to:
return this.$().redactor('insertHtml', value);
thanks kroofy.
First, when you want to access the Ember View's DOM element, you should use this:
this.$()
instead of
$("#"+this.elementId)
About the Redactor issue... I am not sure how wise it is to tie up the Ember code with the WYSIWYG editor's functionality, but if you are determined about it, you can do the following:
App.RedactorView = Ember.TextArea.extend({
didInsertElement: function() {
var box = this.$().closest('.' + this.elementId + '_box');
box.find('.' + this.elementId + '_redactor_editor').redactor();
}
});