EmberJS Template concatenate - templates

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')
}

Related

Ember 2.8: Should I randomize an array as a handlebars helper or write a function in the server side?

I have a function that takes in an array and randomizes the values inside of it.
shuffle([1,2,3,4]) => [2,4,1,3];
Now, I have succesfully used this function in my server side code for my ember app like so:
mirage config.js file
this.get('/games/:id', function (db, request) {
var game = games.find((game) => request.params.id === game.id);
var cardsShuffled = shuffle(game.attributes.cards);
game.attributes.cards = cardsShuffled;
return {data: game};
});
And then rendering my handlebars view like so:
play.hbs
<div>
{{#each model.cards as |instance|}}
{{game-card symbol=instance.url}}
{{/each}}
</div>
But I was wondering if it is better to create some sort of handlebars helper? I'm new to ember, but it doesn't look too clean to me to have the backend do that kind of logic. So here's what I was thinking:
shuffle-array.js helper
import Ember from 'ember';
export function shuffleArray(array) {
var m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
};
export default Ember.Helper.helper(shuffleArray);
And then somehow use it like so (tried this. failed):
play.hbs (revised)
<div>
{{shuffle-array model.cards}}
{{#each model.cards as |instance|}}
{{game-card symbol=instance.url}}
{{/each}}
</div>
So I guess my question is two-fold: Is it acceptable/unnaceptable to have this kind of logic coming from the mirage backend? Also, what is the best way to have a handlebars helper implement this?
Is it acceptable/unnaceptable to have this kind of logic coming from the mirage backend?
If the logic isn't meant to be persistent, I'd say no.
Also, what is the best way to have a handlebars helper implement this?
I'd prefer a computed property on the relevant controller. Using this shuffle function, it'd be:
import Ember from 'ember';
export default Ember.Controller.extend({
shuffledCars: Ember.computed('model.cars.[]', function(){
return shuffle(this.get('model.cars'));
})
});
Acceptable this or not depends on your needs. As I can see from code, you are shuffling some cards for some game. It possible that you need to do such kind of things on backend because of some security concerns.
But doing it with helper is also good and acceptable (but user with abilities to code can read/manipulate data). A few notes about helpers:
Helper function have two parameters: array of unnamed parameters and a hash of named parameters. So in your code you will access first parameter as array[0]
You should use helpers like this:
{{#each (shuffle-array model.cards) as |instance|}}
{{game-card symbol=instance.url}}
{{/each}}
More about helpers
Computed properties, mentioned in other answer, also are good for such kind of things. Maybe even better, because helper will shuffle array on each rendering (i.e. when user navigates back and forth), while computed prop will do that only when original array changes.

How can I add a class in ember js

<script type="text/x-handlebars">
<div class="wrapper">
<div class="sideMenu">
{{#link-to 'home'}}Home{{/link-to}}
{{#link-to 'posts'}}Posts{{/link-to}}
</div>
<div class="content">
{{outlet}}
</div>
</div>
</script>
I am new to ember js. How can I add a class on 'content' class each time when view changes.
We do something like this:
Ember.Route.reopen({
activate: function() {
var cssClass = this.toCssClass();
// you probably don't need the application class
// to be added to the body
if (cssClass !== 'application') {
Ember.$('body').addClass(cssClass);
}
},
deactivate: function() {
Ember.$('body').removeClass(this.toCssClass());
},
toCssClass: function() {
return this.routeName.replace(/\./g, '-').dasherize();
}
});
It would add a class to the body (in your case just use content), that is the same as the current route.
#torazaburo had some excellent points about #Asgaroth (accepted) answer, but I liked the idea of not having to write this same functionality over and over again. So, what I am providing below is a hybrid of the two solutions plus my own two cents and I believe it addresses #torazaburo concerns regarding the accepted answer.
Let's start with the 2nd point:
I also don't like the idea of polluting Ember.Route
Can you pollute Ember.Route without polluting Ember.Route? (Huh?) Absolutely! :) Instead of overwriting activate, we can write our own function and tell it to run .on(activate) This way, our logic is run, but we are not messing with the built-in/inherited activate hook.
The accepted answer is very procedural, imperative, jQuery-ish, and un-Ember-like.
I have to agree with this as well. In the accepted answer, we are abandoning Ember's data binding approach and instead fall back on the jQuery. Not only that, we then have to have more code in the deactivate to "clean up the mess".
So, here is my approach:
Ember.Route.reopen({
setContentClass: function(){
this.controllerFor('application').set("path", this.routeName.dasherize());
}.on('activate')
});
We add our own method to the Ember.Route class without overwriting activate hook. All the method is doing is setting a path property on the application controller.
Then, inside application template, we can bind to that property:
<div {{bind-attr class=":content path"}}>
{{outlet}}
</div>
Working solution here
Just bind the currentPath property on the application controller to the class of the element in the template:
<div {{bind-attr class=":content currentPath"}}>
{{outlet}}
</div>
In case you're not familiar with the {{bind-attr class= syntax in Ember/Handlebars:
the class name preceded with a colon (:content) is always added to the element
properties such as currentPath result in the current value of that property being inserted as a class, and are kept dynamically updated
To be able to access currentPath in a template being driven by a controller other than the application controller, first add
needs: ['application']
to the controller, which makes the application controller available under the name controllers.application, for use in the bind-attr as follows:
<div {{bind-attr class=":content controllers.application.currentPath"}}>
You may use currentRouteName instead of or in addition to currentPath if that works better for you.
The class name added will be dotted, such as uploads.index. You can refer to that in your CSS by escaping the dot, as in
.uploads\.index { }
Or, if you would prefer dasherized, add a property to give the dasherized path, such as
dasherizedCurrentPath: function() {
return this.('currentPath').replace(/\./g, '-');
}.property('currentPath')
<div {{bind-attr class=":content dasherizedCurrentPath"}}>
This has been tested in recent versions of ember-cli.

Input helper ember reverse disabled state

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.

Calling an action within controller in Ember

I am getting some data from server and in my controller I am trying to display that in list format. What I want to do is to allow the user to click on any item of that list and call an action behind it. Here is my js code.
for(var index in posts) {
if (posts.hasOwnProperty(index)) {
console.log(1);
var attr = posts[index];
//console.log(attr);
/*$("ul#previous_recipients").append('<li class="list-group-item"><label>'+attr.name+'' +
'</label><a href="javascript:void(0)" class="close g-green" {{action "aa"}}>Add</a></li>');*/
$("ul#previous_recipients").append(Ember.Handlebars.compile('<li class="list-group-item"><label>{{attr.name}} </label>Add</li>'));
}
}
How to call action on it? {{action "test"}} is not being called.
You have to compile the handlebars template and define the test action inside actions hash in the same controller.
$("ul#previous_recipients").append(Ember.Handlebars.compile('
<li class="list-group-item"><label>{{attr.name}} </label>
Add</li>
'));
You have to move this html code from controller to a handlebars file,that will be good.
What you need to do is create an ember view and give it a
templateName
property which will contain your html and handlebars expression. Alternately, you could, inside the view say
defaultTemplate: Em.Handlebars.compile(/*your handlebars and html markup here*/)
using Jquery to do things like this is not the ember way of doing things.

How can I use holder.js inside an #each loop in Handlebars (Ember.js) template?

I'm looping through an array of objects in an Ember.js Handlebars template and trying to display a placeholder image for each using holder.js.
Outside of the #each loop context, holder.js renders the placeholder image as expected:
<img data-src="holder.js/100x100" src='' alt='person image' />
{{#each person in controller}}
person.name
{{/each}}
When inside the #each loop, no placeholder images appear:
{{#each person in controller}}
<img data-src="holder.js/100x100" src='' alt='person image' />
person.name
{{/each}}
Perhaps there's something I'm not understanding about how #each loops work, but is there a trick to getting placeholder images to appear here?
The problem is Holder.run() is called before Ember loops through all your data, so you have to call it yourself again at a later point.
You could try adding
Em.run.schedule('afterRender', null, function () { Holder.run(); })
or
Em.run.next(function () { Holder.run(); })
to the renderTemplate hook of your route. (Even better would be to add it after you know your controller has been loaded with all your data.)
I just want to be a bit more explicit in case anyone like me runs into similar problems or gets themselves confused with how it works.
First, add a View for your template if you don't have one. If you're working with the MushroomsRoute, create a MushroomsView. Inside your view, do something like this:
App.MushroomsView = Ember.View.extend({
didInsertElement: function() {
Ember.run.next(function() {
Holder.run();
})
}
});
And that should work -- or at least it does with Ember 1.0.0.