How to use function with arguments in IF statement in the template - ember.js

Currently Im trying to use a function that observes a field of a controller/component in ember template (handlebars).
index.hbs
<div>
{{ input value=model.field1 }}
{{if hasFieldError('field1')}}
<span>This field is required</span>
{{/if}}
</div>
<div>
{{ input value=model.field2 }}
{{if hasFieldError('field2')}}
<span>This field is required</span>
{{/if}}
</div>
index.js
hasFieldError: function(val) {
return true if val is found in an array
}.observes('field1', 'field2'),
But this of course returns a build error:
{#if hasFieldError('compa ----------------------^ Expecting
'CLOSE_RAW_BLOCK', 'CLOSE', 'CLOSE_UNESCAPED', 'OPEN_SEXPR',
'CLOSE_SEXPR', 'ID', 'OPEN_BLOCK_PARAMS', 'STRING', 'NUMBER',
'BOOLEAN', 'UNDEFINED', 'NULL', 'DATA', 'SEP', got 'INVALID'
Any idea how to achieve this?

You can't call a function from a template, except using an action. You can only reference properties, such as fields and computed properties.
Observers are generally not a good idea
Are you really trying to determine if the value of field1 is in an array? Let's assume the array is found at array1. Then you could write a helper named contains:
{{#if (contains array1 field1)}}
But someone has already written this. Welcome to the wonderful Ember community of addons! See https://github.com/DockYard/ember-composable-helpers#contains

Just replace your observer function with a computed property:
hasFieldError: Ember.computed('field1', 'field2', function(val) {
return true if val is found in an array
}),

Related

Ember.js: Is it possible to have a component for each field in a model?

So I thought I had this solved already as it worked with my prototype using just arrays, but now that I'm actually dealing with the model, I'm not sure if it will work.
If I create a property on my controller like so:
myAttributes: [{
name: 'attr1',
label: 'Attribute 1',
value: null
}, {
name: 'attr2',
label: 'Attribute 2',
value: 1
}]
then I can loop through myAttributes with {{#each}}, so while each object is essentially a field, that's different than the fields on a single model, which only have a value, so I can't do {{#each model as |rec|}} when there's only one record.
In a nutshell, I want to have a button group to set the value for each field in my model, like so:
I have around 60 of these fields so that's why I wanted to use {{#each}} and my component for each field, but of course each goes over records in the model, not fields in a record.
Is this impossible to do? Do I just have to bite the bullet and write out the markup for each field like I would do if I had only a few fields?
Update: Even if I could loop through the fields on a single record (maybe with {{#each model.#each as |field|}}?), for this case what I also need to do is break out the fields into sections in the UI, so for example loop through fields 1-10 in the first section, and 11-20 in the next section, and there doesn't seem to be a good way to do that.
In the end, I think I'm better off just using a component on each field, like so:
{{attribute-component value=model.attr1}}
{{attribute-component value=model.attr2}}
.
.
.
There's a neat helper called each-in that iterates over key/attributes of an object.
https://guides.emberjs.com/v2.6.0/templates/displaying-the-keys-in-an-object/
There is a way to do that in your case, but I think that actually a simpler way to get the same effect is this:
template.hbs
{{#each myArrayOfPeople as | person | }}
{{#each attribs as | anAttrib | }}
{{get person (mut anAttrib)}},
{{/each}}
<br>
{{/each}}
component.js
attribKeys:['name', 'phoneNumber', 'otherAttrib'];
This will output something like:
joe, 123, something,
sam, 456, anotherthing,
sarah, 944, foo
you can use that same (mut anAttrib) helper to bind an attribute to an input or whatever your need is.
{{attribute-component value=(mut anAttrib)}}
I have now forgone trying to iterate over each field because I actually need to break out the fields into accordion containers based on other criteria, although #averydev's answer about nested {{#each}} was a very cool tip and useful.
I also updated to Ember 1.13 in order to use the mut helper (although I had previously used an action on the component, that passes the new value to an action in the controller, that sets the value to the model property. Since that was convoluted, this new method using mut is much more understandable.
Just to help anyone else out in the future, here's my code (simplified for SO, the real code has nothing to do with rooms and furniture). I have a custom component that uses a radio button group component to actually set the value (this is from ember-radio-button):
template.hbs
{{room-style title='Living Room' field=(mut model.livingRoomStyle)}}
{{room-style title='Master Bedroom' field=(mut model.masterStyle)}}
templates/components/room-style.hbs
<div class="btn-group" data-toggle="buttons">
{{#radio-button value=1 groupValue=field classNames="btn btn-default" changed="changed"}}
Modern
{{/radio-button}}
{{#radio-button value=2 groupValue=field classNames="btn btn-default" changed="changed"}}
French
{{/radio-button}}
{{#radio-button value=3 groupValue=field classNames="btn btn-default" changed="changed"}}
Rustic
{{/radio-button}}
</div>
components/room-style.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
changed: function() {
//update() is a function you get with attrs.[prop] when using the mut helper
this.attrs.field.update(this.get('field'));
}
}
});

have Ember.TextField observe a change in a property inside an array

I have an array of Objects in the view that supposed to represent a dynamic number of fields in the .hbs
So the array is:
export default Ember.View.extend({
metadata_queries: [{name: '', type: 'Exists',
disableValue: true, queryValue:''}
/*, {...}, {...} */],
});
The rest of the array elements will be added dynamically
I have a Ember.TextField in the .hbs that needs to be disabled (or hidden - whichever is easier) according to disableValue (that changes by observing the type that is bound to an Ember.Select.
The code:
{{#each view.metadata_queries}}
<div class="form-group">
<div class="col-xs-2 col-xl-2">
{{view Ember.Select content=view.metaTypes selection=type}}
</div>
<div class="col-xs-2 col-xl-2">
{{view Ember.TextField classBinding=":tests-query" value=queryValue disabled=disableValue}}
</div>
</div>
{{/each}}
The thing is that disableValue is not a property - so the view doesn't get updated (I checked - the boolean itself does change)
How can I do that?
Made a JSFiddle to examplify:
http://jsfiddle.net/6Evrq/400/
Well... aparrently I didn't update disableValue properly:
The proper way to do it is:
this.get('metadata_queries').forEach(function(item, index, metaQueries) {
Ember.set(item, "disableValue", item.type === "Exists");
});
to make disableValue a property, it has to be wrapped in an Ember Object, like so
metadata_queries: [Ember.Object.create({name: '', type: 'Exists',
disableValue: true, queryValue:''})
/*, {...}, {...} */],

How do you add dynamic partial by concatenating strings in ember js?

We can add partials onto templates in ember js.
{{#each subdetail in leftSubDetails}}
{{partial 'lists/details/link-'+subdetail}}
{{/each}}
Gives following error
Error: Parse error on line 22:
...lists/details/link-'+subdetail}}
-----------------------^
Expecting 'CLOSE', 'CLOSE_UNESCAPED', 'STRING', 'INTEGER', 'BOOLEAN', 'OPEN_SEXPR', 'CLOSE_SEXPR', 'ID', 'DATA', got 'INVALID'
Compute the name of the partial as in the view or controller:
partialName: function() {
return 'lists/details/link/ + this.get('subdetail');
}.property('subdetail')
Then call it like
{{partial partialName}}
Not tested.
Try passing a parameter instead, like this:
{{partial 'lists/details/link' subdetail=blah}}

Ember.js active link class with query params

In an indexview I have links that set the sorting:
# Template
{{#link-to 'products' (query-params sortBy="title")}}Title{{/link-to}}
{{#link-to 'products' (query-params sortBy="price")}}Price{{/link-to}}
# Controller
queryParams: ['sortBy']
sortBy: 'id'
sortProperties: ( ->
[#get("sortBy")]
).property("sortBy")
That generates links that always have the class of 'active', but I want to highlight the currently active sort filter. What is the best way to do it?
I tried binding to a computed property like this:
{{#link-to 'products' (query-params sortBy="price") classNameBindings='sortByPrice'}}Price{{/link-to}}
sortByPrice: -> (
#get('sortBy') == 'price'
).property('sortBy')
That didn't quite work, but even if it did, that's not DRY at all – and eventually I would like to add a lot of different attributes on which to sort.
As I understand, the problem is that ember adds the 'active' class when it's in the context of that controller, which it always is with different query-params.
(Running the latest canary build of Ember as of 14th June)
This has been fixed in Ember Canary, as of https://github.com/emberjs/ember.js/pull/5109
QueryParams should add the "active" class based on whether the declared parameter in the {{#link-to}} helper has the same value as the attribute at that moment, as I can demonstrate in this jsbin.
That said, I'm having the same problem, so I believe there's some fringe case where this doesn't work right, and I'd be happy if you could modify this example to reflect that.
I'm facing the same problem now and I have temporary solution.
<!-- Posts Template -->
<!-- Categories -->
<div class="block step visible-desktop visible-tablet">
<div class="header">
<h3>Category</h3>
</div>
<div class="area categories">
<ul>
{{#each staticCategory in controller.staticCategories}}
{{post-category currentCategory=currentCategory staticCategory=staticCategory}}
{{/each}}
</ul>
</div>
</div>
<!-- Categories end -->
//Posts Controller
staticCategories: ['Front-End', 'JavaScript', 'jQuery', 'null'],
currentCategory: function () {
return this.get('category');
}.property('category'),
queryParams: ['category'],
category: null,
filteredContent: function () {
var category = this.get('category');
var posts = this.get('model');
return category ? posts.filterBy('category', category) : posts;
}.property('category', 'model')
//Post-Category Component template
{{#link-to 'posts' (query-params category=staticCategory)}}
{{staticCategory}}
{{/link-to}}
//Post-Category Component js
Blog.PostCategoryComponent = Ember.Component.extend({
tagName: 'li',
isActive: function () {
return this.get('staticCategory') === this.get('currentCategory')
}.property('currentCategory', 'staticCategory'),
classNameBindings: ['isActive:active']
});
I've found a solution for this. Ember (currently) seems to make a distinction between linking to a resource and linking to a sub-route, e.g doing {{link-to "resource"}} will always set the active class, but doing {{link-to "resource.index"}} will toggle the active state according to their query params.
Here's a jsbin showcasing the difference: http://emberjs.jsbin.com/zawukucisoni/3/edit
I've opened an issue that can be found here: https://github.com/emberjs/ember.js/issues/5359

Emberjs conditional output in a template with handlebars

I got following models:
A Community with a name, members and moderators(both are Users). Users, who have an id and a name.
In the CommunityMembers template i want to show all the users, and if that user is a moderator, i want to add some extra saying that he's a moderator
<script type="text/x-handlebars" data-template-name="communityMembers">
//model contains an array of users in a community
{{#each user in model}}
<li>{{user.name}}</li>
{{#each moderator in controllers.community.moderators}}
//here is the problem-->
{{#if moderator.id == user.id}}
<b>this is a moderator</b>
{{/if}}
{{/each}}
{{/each}}
</script>
i know that in handlebars you can't use moderator.id==user.id but it's an easy way to say what i want to do.
i tried to write a handlebars helper but when i checked in the helper what my argument was i got a string saying: "moderator.id" or "user.id" so that didn't work.
i also tried to do it with a method in my community-object:
App.Community = Ember.Object.extend({
isModerator: function(community, user_id){
return community.moderators.indexOf({"id":user_id})!=-1;
}
});
in the template:
{{#if isModerator(controllers.community,user.id)}}
<h>this is a moderator</h>
{{/if}}
but that gave me errors in the template like:
. Compiler said: Error: Parse error on line 12: .../if}}
{{#if isModerator(controll
----------------------^ Expecting 'CLOSE', 'CLOSE_UNESCAPED', 'STRING', 'INTEGER', 'BOOLEAN', 'ID', 'DATA', 'SEP', got 'INVALID'
Is there anyone who knows how to deal with this?
You can't do this in Handlebars (as you said) and you shouldn't try do mimic this behavior with a helper. This limitation is intentionally designed into the templating, because it is considered a bad practice to have too much logic in the template. Instead your goal should be to write your template like this:
<script type="text/x-handlebars" data-template-name="communityMembers">
//model contains an array of users in a community
{{#each user in controller.usersWithModeratorFlag}}
<li>{{user.name}}</li>
{{#if user.isModerator}}
<b>this is a moderator</b>
{{/if}}
{{/each}}
</script>
Now you are probably asking yourself how to implement this attribute. You could try something like this (if you can't embed this attribute into your user objects):
App.CommunityMembersController = Ember.ArrayController.extend({
needs : ["community"],
usersWithModeratorFlag : function(){
var moderators = this.get("controllers.community.moderators");
return this.get("model").map(function(user){
if(moderators.contains(user)){
user.set("isModerator", true);
}
}
}.property("model.#each", "controllers.community.moderators.#each")
});
As you can see, it is quite easy to move this logic out of the template and into the controller, where it belongs.
You can use ember-truth-helpers
{{#if (eq moderator.id user.id)}}
<b>this is a moderator</b>
{{/if}}