Ember.js Handlebars block helper - ember.js

Apparently this does not work: http://jsbin.com/efapob/3/edit
Ember.Handlebars.registerHelper('foo', function(options) {
var result = 'BEFORE '
+ options.fn(this)
+ ' AFTER';
return new Handlebars.SafeString(result);
});
And I assume it's because the fn() writes directly to the output buffer.
However, I need a way to directly work with the output of the block's content.
I tried overwriting a view's render function, but that also didn't lead me anywhere.
(Background: I'm trying to write an {{#ifchanged}} helper block that only renders if the contents have changed in comparison to the last call. The use case is a loop that should display something every time one property of the model is different to the last one. If you have other ideas how to achieve this, comments very appreciated!)

If anyone is interested, in this specific use-case I worked around the issue of not being able to use the return of fn() like so:
var ifchanged_last;
Ember.Handlebars.registerHelper('ifchanged', function(property, options) {
var value = Ember.Handlebars.get(this, property);
if (value !== ifchanged_last) {
options.fn(this, options);
}
ifchanged_last = value;
return;
});
Template:
{{#each content}}
{{#ifchanged some_key}}
The value changed
{{/ifchanged}}
{{/each}}
Large room for improvement, but a usable starting point.

You can use isDirty property from DS.Model to know when the data changes.
In some template:
{{#if isDirty}}
You changed the model<br/>
{{/if}}
And a jsfiddle with the demo.

Related

Handlebars looping over an Ember component simple property

In an ember component how can I generate 6 things in the template, given a component property called num with value 6?
Do I have to create an array just for the purposes of this in the component? If so can someone advise the most reusable way to do this?
I think I would need to do this via a helper:
Ember.Handlebars.helper('highlight', function(value, options) {
var escaped = Handlebars.Utils.escapeExpression(value);
return new Ember.Handlebars.SafeString('<span class="highlight">' + escaped + '</span>');
});
So in my experience, this works well:
{{#each row in rows}}
//do something
{{/each}}
However, as you say, you'll probably need an array populated. I've used this with an array of Ember models to populate a table, but I'm sure you'll be able bend it to your purpose!

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.

Handling MouseUp on Slider in Ember.js

I've been working on incorporating a slider into a page in an app I'm working on. My first attempt was to use the following in my template:
<span class="value">{{scaledDistThreshold}}%</span>
{{input type="range" min="0" max="100" step="1" value=scaledDistThreshold}}
<button class="set" {{action "setDistThreshold"}}>Set</button>
This works; that is, I can use the slider and see the bound value change, and pressing the button will persist whatever value to the server.
What I'd like to do now is to remove the 'Set' button and persist on mouseup from the slider itself, and I'd prefer to do it with Ember as opposed to, say, a direct jQuery hook. So far, my searches for sliders using Ember has turned up basically nothing.
Any ideas how I might accomplish this? Thanks in advance!
FOLLOW-UP EDIT
Per Justin's answer, I was able to do the following:
App.InputRangeComponent = Em.TextField.extend({
type: 'range',
action: 'mouseUp',
mouseUp: function () {
var value = this.get('value');
this.sendAction('action', value);
}
});
I was then able to use this component in my markup as follows:
{{input-range min="0" max="100" step="1" value=scaledDistThreshold action="setDistThreshold"}}
The action I named gets called on mouse-up and passes along the current value of the slider, all as intended. Thanks again to Justin for the answer!
My first thought, and this isn't, strictly-speaking, an answer to your question, but another option might be just saving the bound value on change, and throttling it to only do so, say, once every quarter-second or so.
Now, as for answering your actual question:
(Please note: I'm still fairly new to Ember, so if what I say doesn't make sense, it's probably me, not you)
I didn't know that type="range" was an option for the {{input}} helper. Learn something new every day. :)
Take a look at the Ember docs on the {{input}} helper. When you use type="text", Ember is creating an instance of Ember.TextField. You have a couple of options:
You can reopen Ember.TextField and add a mouseup event to it.
You can create your own, new helper, that uses a new view (that you create) that extends Ember.TextField.
I'd recommend #2 for fairly obvious reasons (you probably don't want every textfield you make doing the same thing on mouseup).
SO, option 2.
I think this is how it would go down. If not, hopefully, it will at least point you in the right direction, or someone will correct me.
First, you'll want to create a new view that extends Ember.TextEdit. Let's call it "rangeSlider".
var rangeSlider = Ember.TextField.extend({
});
Here is where you should add your mouseup event:
App.RangeSlider = Ember.TextField.extend({
mouseUp: function(){
//Do stuff here
}
});
Now, if you don't want to use {{view "App.RangeSlider"}}, you can create a new helper:
Ember.Handlebars.helper('rangeSlider', App.RangeSlider);
Then you just have to use {{rangeSlider}} to insert that view.
I'm not 100% sure how adding attributes via your new helper works, though.
Looking at the ember.js code, the {{input}} helper had a bit more going for it at declaration:
Ember.Handlebars.registerHelper('input', function(options) {
Ember.assert('You can only pass attributes to the `input` helper, not arguments', arguments.length < 2);
var hash = options.hash,
types = options.hashTypes,
inputType = hash.type,
onEvent = hash.on;
delete hash.type;
delete hash.on;
if (inputType === 'checkbox') {
Ember.assert("{{input type='checkbox'}} does not support setting `value=someBooleanValue`; you must use `checked=someBooleanValue` instead.", options.hashTypes.value !== 'ID');
return Ember.Handlebars.helpers.view.call(this, Ember.Checkbox, options);
} else {
if (inputType) { hash.type = inputType; }
hash.onEvent = onEvent || 'enter';
return Ember.Handlebars.helpers.view.call(this, Ember.TextField, options);
}
});
Now that I think about it, I wonder if there's a way to just extend an existing helper... Not sure. I'm not seeing anything in the API.
Anyway, hopefully, this will point you in the right direction. There may be a totally easier way, but this was the first thing that popped into my head when I read your question. :)

Programmatically setting computed property of an itemController

I have a template with the following code:
{{#each types itemController='type'}}
<div class='col checkbox'>
<label>
{{input type='checkbox' checked=isSelected disabled=notAllowed}}
<span {{bind-attr class='isSelected'}}>{{name}}</span>
</label>
</div>
{{/each}}
types is set in setupController:
this.store.find('type').then(function(types){
controller.set('types', types);
});`
//Having 2 other models here that I am setting and having an itemController for, exactly in the same fashion as types.
for the ArrayController which has the itemController.
NOTE: To clarify, I am using and setting 3 different models, which work pretty much in the same way as type, that makes this a bit more complicated.
Then the itemController itself:
App.TagController = Ember.ObjectController.extend({
isSelected: function(key, value){
//bunch of code that does some stuff and returns true or false depending on value
}.property()
});
App.TypeController = App.TagController.extend();
Now the problem: I have a resetbutton that should deselect all checkboxes and remove the span classes.
I would have thought about using an action (in the ArrayController) that sets all the isSelected properties to false, but I don't seem to be able to find a way to access and manually set that itemController computed property.
One thing I tried in the ArrayController is the following:
actions: {
resetFilters: function(){
this.get('types').forEach(function(type) {
console.log(type.get('isSelected'));
//type.set('isSelected', false);
});
}
}
But unfortunately this returns undefined. And using jQuery manually to remove the class and uncheck the checkbox seems to work the first instance, but the problem is, the computed property doesn't get updated and that messes things up.
Any idea how I can achieve what I want?
If anything is unclear let me know and I will do my best to clarify.
Thank you.
You are setting controller.types, this will not work with itemController. You should always be setting an array controller's content property.
The following should work:
controller.set('content', this.store.find('type'));
Then to set the isSelected:
controller.setEach('isSelected', false);
This assumes that controller is an instance of an ArrayController that has an itemController set in it's definition, e.g.
App.TypesController = Em.ArrayController.extend({itemController: 'type'});
store.find returns a PromiseArray, so it should be resolved first. You can set the types as follows in setupController:
this.store.find('type').then(function(types){
controller.set('types', types);
});
Or you can resolve types in the reset:
this.get('types').then(function(types) {
types.forEach(function(type) {
console.log(type.get('isSelected'));
});
});
I would recommend the first one though.

selection attribute in Ember.select not working within a loop

I have select view within a loop, and I wanted to set the default selection on the drop down. I tried setting "value" attribute as well as "selection" attribute but nothing worked for me. I was trying to create jsbin to demonstrate the issue, but then it is giving me completely different issue which I don't see in my dev code though.
My controller is defined like :
App.AnswerController = Ember.ObjectController.extend({
answerLayouts: function () {
return this.get('store').findAll('layout');
}.property(),
selectedAnswerLayout: null,
initialize: function () {
this.set('selectedAnswerLayout', this.get('store').find('layout', this.get('id')));
}.on('init')
});
and in the template I am doing:
<table>
{{#each answers itemController="answer"}}
<tr>
<td>{{name}}</td>
<td>{{view Ember.Select
content=answerLayouts
optionValuePath="content.name"
optionLabelPath="content.displayName"
class="form-control"
prompt="Answer Layout"
selection=selectedAnswerLayout}}
</td>
</tr>
{{/each}}
</table>
and does't see answerLayouts as an array, but when I check {{answerLayouts.length}} it returns 3!
Here is a jsbin link that demonstrates the issue: http://jsbin.com/AcUPIpEl/1/
It's an issue with the old version of Ember, it was fixed somewhere between 1.0+ and 1.2
http://emberjs.jsbin.com/ODaKIjIw/1/edit
Ok had to do things bit differently to get things working. Rather than defining "answerLayouts" as a property, I defined it as an array and when the controller gets initialized, I populated the array and set the selectedAnswerlayout there like this:
App.AnswerController = Ember.ObjectController.extend({
answerLayouts: Ember.A(),
selectedAnswerLayout: null,
initialize: function () {
var self = this,
selectedlayoutId = this.get('id');
this.get('store').find('layout').then(function(data){
data.forEach(function(layout){
self.get('answerLayouts').pushObject(layout);
if(layout.get('id') === selectedlayoutId){
self.set('selectedAnswerLayout',layout);
}
});
});
}.on('init')
});
Here is the working jsbin link : emberjs.jsbin.com/ODaKIjIw/3.
I have realized that when we can define things in init, it's best to do so there. I have had issues with bindings when relying on computed property before. Lesson learned :)