Components - set style width of component - ember.js

I can't figure out how to do some stuff with the components, ie:
This is an example of a working rendered progress bar from the dom:
<div class="progress-bar bg-color-teal" aria-valuetransitiongoal="25" aria-valuenow="25" style="width: 25%;">25%</div>
This is what I get in the dom, rendered from the component (never mind the values of attributes):
<div id="ember294" class="ember-view progress-bar bg-color-teal" aria-valuetransitiongoal="77" aria-valuenow="77" width="25">
25%
</div>
Difference and problem: the style attribute that holds a width attribute.
And the component.js:
App.ProgBarComponent = Ember.Component.extend({
classNames: ['progress-bar', 'bg-color-teal'],
attributeBindings: ['aria-valuetransitiongoal:aria-valuetransitiongoal', 'aria-valuenow:aria-valuenow', 'percent:width'],
didInsertElement: function () {
//$('#' + this.elementId).css('width', this.get('percent') + '%');
}
});
But I cant bind the width in %, based on the percent attribute, to the style attribute.
Now, the didinsertelement hook works (I mean setting the width), but I want to do (and learn) how to do this with a normal approach - just like binding the aria-values and percent.
Setting the width to percent does not work - either because it is not in the style attribute, or because it is not in percent. How could I bind an attribute with the following logic (or similar):
attributeBindings: ['someString:style'],
//someString: 'width:' + this.get('percent') + '%'
//someString: 'width:' + ['percent'] + '%'
//someString: 'width:' + percent + '%'
Neither of the commented lines work : the first one errors undefined is not a function (for get), the second one sets the width to "percent%", and the third one errors 'percent is not defined'...
The only workaround I could think of is using the routes model to return extra data, basically adding a new attribute:
styleString = 'width: ' + percent + '%';

Your attempts to define someString didn’t work because they’re set when the component is defined, rather than at runtime. Change it to a computed property:
someString: function() {
return "width: " + this.get('percent') + "%";
}.property('percent')
That defines someString as a property that depends on the value of percent. When percent changes, so does someString.

Related

Ember: SafeString and htmlSafe not removing "Binding style attributes" deprecation warning

I'm trying to figure out how to eliminate this warning
WARNING: Binding style attributes may introduce cross-site scripting vulnerabilities; please ensure that values being bound are properly escaped. For more information, including how to disable this warning, see http://emberjs.com/deprecations/v1.x/#toc_binding-style-attributes.
Controller:
percentComplete: function() {
var percent = this.get('numProcessed') / this.get('numToDo') * 100;
return percent.toString().htmlSafe();
}.property('numProcessed', 'numToDo')
Template:
<div style="width:{{percentComplete}}"></div>
Versions:
Ember : 1.13.7
Ember Data : 1.13.8
jQuery : 1.11.3
Other attempted solutions...
I've also tried following the steps at http://emberjs.com/deprecations/v1.x/#toc_binding-style-attributes however they don't elaborate on escapeCSS() nor does the Ember.Handlebars.SafeString seem to work from their example.
On a related note, I could see this being used often, so I first tried creating a helper with the template looking like this without success:
<div style="width:{{safe-css percentComplete}}"></div>
Helper attempts:
return Ember.String.htmlSafe(params[0]);
return Ember.Handlebars.SafeString(params[0]);
var safeInput = Ember.Handlebars.Utils.escapeExpression(params[0]);
return new Ember.String.htmlSafe(safeInput);
What am I missing here?
You cannot concatenate the style in the template:
<div style="width: {{percentComplete}}"></div>
Instead, wrap the entire style attribute in htmlSafe
<!-- Template -->
<div style={{percentCompleteCss}}></div>
// Controller
percentCompleteCss: function() {
var num = this.get('numProcessed') / this.get('numToDo') * 100;
return ('width: ' + num.toString() + '%').htmlSafe();
}.property('numProcessed', 'numToDo'),

In an Ember Component where should you put reusable code that isn't an action?

I have a standard component defined something like:
export default Ember.Component.extend({
hideIfEmptyChange: function() {
var thingOfInterest = ...;
var otherThingOfInterest = ...;
...
// Perform some logic
...
// Perform some logic using thingOfInterest
// Perform exactly the same logic using otherThingOfInterest
}.observes('hideIfEmpty.#each')
});
I want to move the logic for the last two pieces into their own function to prevent writing out the same code twice just for two different variables.
I could write an action on my component and use this.send('myAction',...) - but would it be best practice to have this as an action if it isn't going to be used (or even usable) from the component's template? If you shouldn't do it this way then how should you?
My other thought was mixin's - but the code here will be completely specific to the component, so again this doesn't feel 100% right.
Edit
The component is used here to observe an array of widgets, which are displayed in a sidebar. To start with the sidebar is hidden away as there are no widgets. When a widget is added the sidebar then slides out using a css3 transition between a bootstrap class I added (col-md-0) and something like col-md-2, at the same time the main column shrinks from something like col-md-10 to col-md-8. Because of the nature of it coming out from zero width the widgets inside squish around during the animation and so need to be hidden during it. So the generic piece of code will be:
function(element, classes) {
var lowWidth = 50;
// Transition looks awful if columns start from small width, so hide inner content until css3 transition complete if starting from low width
if ($(element).width() < lowWidth) {
$('div', element).hide();
$(element).toggleClass(classes);
$(element).on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function(e) {
$('div', element).fadeIn(250);
$(this).off(e);
});
}
else {
$(element).toggleClass(classes);
}
}
My component here is in block form and actually has the sidebar within it in the template.
{{#hideable-cols hideIfEmpty=rightSidebarWidgets leftId="det-main" leftMinClass="col-md-8" leftMaxClass="col-md-10" rightId="det-sidebar-right" rightMinClass="col-md-0" rightMaxClass="col-md-2"}}
<div id="det-main" class="col-md-10 resizes">
{{outlet}}
</div>
<div id="det-sidebar-right" class="det-sidebar resizes col-md-0">
{{widget-area widgets=rightSidebarWidgets saveObjectFromWidget="saveObjectFromWidget" removeWidget="removeRightWidget"}}
</div>
{{/hideable-cols}}
The only other way to do what I wanted would have been if I could have set 'inner' components to only be able to react to changes in the array of widgets AFTER the parent block component is happy/done. I've used something similar in knockout js but couldn't see this kind of feature in Ember, so my workaround was to hide the div where the widgets are added, do the transition and show the div again afterward.

Accessing the DOM representation of a template instance that is calling a helper

I am trying to access a DOM element corresponding to the particular template instance that is calling a helper function. As I read the Meteor documentation, Template.instance() should return the template instance object that called the helper, and something like Template.instance().$() would allow me to grab DOM elements within that instance.
However, the following code (and similar variations) is not working for me:
* HTML *
<template name="input_container">
<div class="small-12 medium-12 large-6 columns empty {{isActive}}"></div>
</template>
* JS *
Template.input_container.helpers({
isActive: function() {
if (Template.instance().$('.empty') && [some Session variable logic] {
return 'active';
}
}
});
When I do something like:
if (some Session logic) {
console.log(Template.instance())
}
I get the helper properly logging multiple versions of:
Blaze.TemplateInstance {view: Blaze.View, data: 7,
firstNode: div.small-12.medium-12.large-6.columns.empty.active-container,
lastNode: div.small-12.medium-12.large-6.columns.empty.active-container, $: function…}
(With data: values going from 1-12 appropriately, but otherwise each seems to be the same)
How do I get from this to being able to used the template methods such as template.$ or template.find?
EDIT:
While not a perfect solution, I did manage to work around some of these issues by using Template.currentData() and setting an identifier on each instance of the input.
Template.create_form.helpers(
# Create 12 input containers
inputContainer: () ->
[0..11]
And then:
Template.input_container.helpers(
isActive: () ->
# Get which template instance we are working with, will return the number 0-11 that was used to create it
current = Template.currentData()
# Now I can do $(".input-container").eq(current) to grab the correct DOM element
)
But it seems a little dirty to need to use so much jQuery.
I'm not sure if I've interpreted the question correctly, but if you're trying to access another DOM element on the page - I was able to use a jquery selector.
For example, given html of
<input type="textfield" id="initials" value=" ">
and a simple meteor template of
<template name="demo">
<input type="button" div id="s0">
</template>
I can successfully access the initials field when the s0 button is clicked as follows
Template.demo.events({
'click .cell': function(event, template) {
if ($('#initials').val().trim().length > 0) {
console.log($('#initials').val().trim() + ' - you clicked button '+$(event.target).attr('id'));
}
},

ember version 1.6.1 best to way to bind css style

I am using ember version 1.6.1. I would like to show an error message if user doest not enter username and password correctly. I think i have to use bind-style. now I have code like this:
<fieldset class="error-message" {{bind-style visibility="isVisible:visible:hidden"}}>
<span>invalid username/password</span>
</fieldset>
what is the best way to do it ?
Ember Handlebars supports dynamic class binding exceptionally better than it does style binding. To do that you'd bind-attr to the class. http://emberjs.com/guides/templates/binding-element-class-names/
Css
.visible{
visibility:visible;
}
.hidden{
visibility:hidden;
}
Handlebars
<fieldset {{bind-attr class=":error-message isVisible:visible:hidden"}}>
<span>invalid username/password</span>
</fieldset>
Example: http://emberjs.jsbin.com/didax/1/edit
You can bind-attr the style property and create a computed property that returns the raw style text visibility:visible, but that's ugly and not necessary in this situation.
Although class is generally the best way to set these visual changes, consider using classNameBindings instead of bind-attr. That would require you to create a View class.
However, the best way to bind element attributes that don't have a specific binding mechanism, would be via attributeBindings:
(this approach also needs a View class)
App.IndexView = Ember.View.extend({
attributeBindings: ['style'],
style: function() {
return 'color: #F00';
}.property()
});
This is way is a little better because you can watch the style property of your view class and it will automatically bind to your view markup. And since that is a computed property, you can create your own code to determine changes of other attributes in your view that could cause the style attribute to be reconstructed, and again, automatically bound to your view.
You could have a property that the style property watches with property('dependency'), so when it changes, style is once again computed and the view is updated. For example, let's say that you have a view which is a custom input box with built-in validation. You have a property valid which returns boolean, being true for valid and false for invalid values.
App.IndexView = Ember.View.extend({
attributeBindings: ['style'],
valid: function() {
return false;
}.property(),
style: function() {
// these variables and all should ideally be somewhere else,
// as color codes could potentially be global for the app
var _invalidColor = "#F00";
var _validColor= "#000";
if (this.get('valid')) {
return 'color: ' + _validColor + ';';
} else {
return 'color: ' + _invalidColor + ';';
}
}.property('valid')
});
(see jsbin)
Keep in mind this is a crude example to show the functionality/possibilities. Manually change the return value of valid property of the IndexView in JS Bin to see how it affects the view template.

Ember.js: Dynamic width based on model attribute value

First-time Ember user here. In the app, my model objects are each represented by a rectangular-shaped <div> element. The width of each div is determined by its model's size property. The catch is that the possible values for Model.size are 1-10, not simply pixel values. The div's width is then calculated based on the size. For example, a size of 1 might equal a width of 100px, and a size of 2 would equal 200px, and so on. Thus, these CSS width values need to be calculated and bound to the template. Being new to Ember, I don't yet know where this logic should live. Helper? Controller? Because it's really just presentation logic, it doesn't seem right to have it in the model.
<script type="text/x-handlebars" id="things">
{{#each model}}
<div>
{{description}}
</div>
{{/each}}
</script>
Also, will binding it to the template allow the calculated width to be updated automatically in the template whenever the Model.size value is changed (say, from 1 to 3, thus the div would grow wider)?
While it is a good idea to keep presentation and logic separate, sometimes they need to be mixed. I've certainly had use cases. I used this helper when I had a similar issue. Assuming you had this property in your model:
divWidth: function() {
return this.get('size') * 100;
}.property('size')
You could use this template (which is bound to the property value):
<script type="text/x-handlebars" id="things">
{{#each model}}
<div {{bindStyle width="divWidth" width-unit="px"}}>
{{description}}
</div>
{{/each}}
</script>
I don't think this is the right way to do this, but you should be able to do it this way. You can add a function to your controller (or, I believe your model as well) that listens to the size property for changes, and then calls a jQuery function.
i.e.
changeSize: function() {
return $('#things').css( 'width', this.get('size') * 100);
}.property('size')
Also, you could create a computed property in your model the calculates the size for you:
divSize: function() {
return this.get('size') * 100;
}.property('size')
And you'd reference the divSize in changeSize function, which would be useful if your conversion was more complicated than just multiplying by 100. The helper in the other answer looks useful and more Ember-esque, but here's another way it could be done.