Input helper ember reverse disabled state - ember.js

I want to disable an inputfield if a property is false or does not exist. To do this, you should need a reverse binding for the input helper, something like (pseudo code):
{{ input ... disabled=!isNew}}
After reading the docs, I could find nothing about reverse boolean structure.
Should I solve this by using a computed property*, or is there a better way?
*
Something like:
loginFieldDisabled: function() {
return ! this.get('isNew');
}.property('isNew')

For those who came to question by googling, currently you could use the Ember Truth helpers to achieve a solution for this problem:
{{input ... disabled=(not isNew)}}

I can think of two ways that I would do it. First, use a conditional block:
{{#if isNew}}
{{input}}
{{else}}
{{input disabled}}
{{/if}}
But that comes with its own set of issues, since it adds and removes the text field from the DOM. The other way would be to modify Ember.TextField. Here's something that would work (tested in a JSBin):
Ember.TextField.reopen({
notDisabled: function(key, value) {
if (arguments.length > 1) {
this.set('disabled', !value);
}
return !this.get('disabled');
}.property('disabled')
});
Then, in your template:
{{input notDisabled=isNew}}
The second one is probably what you want. You could also extend Ember.TextField instead of modifying it.

Related

Helper broken in Ember 1.10

I was using custom Handlebars helper extending the functionality of 'if' block.
In Ember 1.10 this doesnt work anymore as there is no Ember.Handlebars.bind property which allowed binding to the property....
Ember.Handlebars.registerHelper('ifCond', function (a, b, options) {
return Ember.Handlebars.bind.call(options, contexts[0], a, options, true, function(result) {
return result === b
});
});
The usage would be:
{{#ifCond property "value"}}
{{some-other-component}}
{{else}}
something other...
{{/ifCond}}
but this returns an error "Cannot read property 'call' of undefined"
Is there any way that I can bind to passed properties in helper? I cannot use registerBoundHelper because it doesn't support having child blocks...
I wanted to use Component instead of helper but then I cannot use the {{else}} block...
This solution for the helper was previously taken from Logical operator in a handlebars.js {{#if}} conditional
I actually managed to find a way to do it, so I'll write the answer for my own question...
Instead of using undocumented hacks, I used the proposed change: https://github.com/emberjs/ember.js/issues/10295
So my helper looks like this now:
Ember.Handlebars.registerBoundHelper('isSame', function(value1, value2) {
return value1 === value2
});
and the usage:
{{#if (isSame property "value")}}
{{some-other-component}}
{{else if (isSame property "otherValue"}}
something other...
{{else}}
something other...
{{/if}}

EmberJS Template concatenate

How can I can concatenate strings( or how to add classes ) on templates on EmberJs?
ex.
<script type="text/x-handlebars">
// This div I want to add a class go, Is this the right way to do it?
<div class="fly {{isGo}}">Fly now</div>
// Or it's something like this?
<div class="fly "{{isGo}} >Fly now</div>
</script>
bind-attr used to be a good way of working around a limitation within Ember's rendering. Now with HTMLbars Ember has recommend that we move away from bind-attr as we have more powerful methods.
Ember 1.13 deprecated bind-attr in favor of the new syntax.
http://emberjs.com/deprecations/v1.x/#toc_bind-attr
Working example of the two proposed methods can be seen in action on ember twiddle ,here:
https://ember-twiddle.com/38f69f01d2fd994af3b0965f10882005?openFiles=templates.application.hbs%2C
Method 1
If you want to do the combination inside your handlebars template you could do something like:
<div class={{concat "fly " isGo}}>Fly now</div>
Method 2
otherwise use a computed property like:
flyingClass: Ember.computed('isGo', function() {
// return a string with 'fly' and the value of
// isGo. Will be updated if isGo changes.
// Array values are created with space delimited by
// ['className', 'anotherClassName', 'lastClastName'].join(' ');
// => "className anotherClassName lastClassName"
let going = this.get('isGo') ? 'going' : ''
return ['fly', going].join(' ');
})
and then in your handlebars template:
<div class={{flyingClass}}>Fly now</div>
The main difference between the two methods depends on how you want your separation of concerns. Right now it might be easier to just do Method 1, but as conditions get more complicated you could hide more of the work in the computed property.
There is a complete discussion of this in the Ember guide: http://emberjs.com/guides/templates/binding-element-class-names/
But you'd do it like this:
<div {{bind-attr class="isGo"}}>Fly now</div>
And in your controller:
App.MyController = Ember.ObjectController.extend({
flightIsAGo: true,
isGo: function() {
return "fly"+this.get('flightIsAGo') ? ' isGo' : '';
}.property('flightIsAGo')
}

How to pass variables to handlebars helpers

I'm create handlebars helper:
export default Ember.Handlebars.registerHelper('if-eq', function(v1, v2, options) {
if (this.get(v1) === this.get(v2)) {
return options.fn(this);
}
return options.inverse(this);
});
And execute like this:
{{#each item in model.cons }}
{{#if-eq model.currentUser.id item.record_poster_id}}
<div class="td author current">{{model.currentUser.name}}</div>
{{else}}
<div class="td author remote">{{model.remoteUser.name}}</div>
{{/if-eq}}
{{/each}}
I recieved variables like text "model.currentUser.id", using this.get(v1) I can get value.
But this.get working only for model.currentUser.id and don't work for item.record_poster_id (maybe because it's variable from the loop)
QUESION: how i can pass variable by value?
ANSWER: How to compare values in each?
You are passing the variable's value, you're just accessing them incorrectly. Why are you using this.get(v1) to access the argument? Just use v1. It's already the value you passed in, you don't have to do anything special to it.
Also, I'm not sure if that Handlebars syntax will work. Ember might require you to do something like this.

Check for equality in Spacebars?

I am trying to do what I think should be a very simple task, but have been failing to do so in the past hour. I want to select a select option by default if the user property matches the value.
<select name="myName">
{{#each addKeys myTable}} <!-- addKeys creates variables for keys and values -->
<option value="{{key}}" {{#if currentUser.property === key}}selected="selected"{{/if}}>{{value}}</option>
{{/each}}
</select>
Now I thought this was straightforward enough to be implemented. But it turns out that Spacebars do not allow conditional operators other than the negation exclamation mark, so equal signs are out of question. I then tried something horrible for the sake of trying:
In template myTemplate:
<select name="myName">
{{#each addKeys myTable}}
<option value="{{key}}" {{isSelected currentUser.property key}}>{{value}}</option>
{{/each}}
</select>
In mytemplate.js :
Template.myTemplate.helpers({
isSelected: function(v1, v2) {
if (v1 === v2)
return "selected=\"selected\"";
return '';
}
});
Not only is this code terrible, terrible to look at, it does not work:
Exception in Meteor UI: String contains an invalid character
I don't understand why something that simple seems so impossible to achieve. Am I missing something there?
Here's an overview of {{#if}} statements in Spacebars
Property
Of course the simplest possible implementation is when the scoped object has a property that evaluates to a boolean
For example if you had:
var item = {
text: 'hello',
checked: false
};
Then you could evaluate an if block like this:
class="{{#if checked}}checked{{/if}}"
Function
We could also evaluate a function here as well. Rather than add a function to the item, we can add a function to the helper and it will inherit the datacontext of the item being passed to it. So if we had the following helper:
Template.item.helpers({
saysHi: function() {
return this.text === "hi";
}
});
Then we could run the following code:
<template name="item">
{{text}}
{{#if saysHi}} - Hi Back {{/if}}
</template>
Note: The helper's implementation can access the current data context as this.
Function with Parameters
You can also pass any number of parameters to helper functions like this:
Template: {{frob a b c verily=true}}
Helper Call: frob(a, b, c, Spacebars.kw({verily: true}))
When applied to our if block, we can do the following:
{{#if equals owner currentUser._id}}
<button class="delete">×</button>
{{/if}}
Then add equals to our template helper like this:
Template.item.helpers({
equals: function(v1, v2) {
return (v1 === v2);
}
});
Universal Helpers
Since this is a generic method that could be useful anywhere, we should add it to every template instead of recreating it.
Note: To create a helper that can be used in any template, use Template.registerHelper.
Template.registerHelper('equals',
function(v1, v2) {
return (v1 === v2);
}
);
Here's a working Demo in MeteorPad that includes one of each of the types of IF's constructed here
Try this:
In your template:
<option value={{key}} selected={{isSelected currentUser.property key}}>
Then have your helper return a boolean.
More on the topic here: https://github.com/meteor/meteor/wiki/Using-Blaze#conditional-attributes-with-no-value-eg-checked-selected
Although spacebars does not support equality, this method works to check for equality
you can use something like this
{{#if chkeq variable value}}
example
{{#if chkeq applicationStatus "Draft"}}
at least it worked for me
where chkeq is a global helper,
which goes something like this
chkeq:function(a,b){
if(a==b){
return true;}
else return false;
}

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.