Is it possible to use function in Handlebars #if? - ember.js

I have a controller object that's like:
MyApp.objController = Ember.ArrayController.create({
init: function(data) {
data.isValid = function() {
return (data.validity === "valid");
}
this.pushObject(MyApp.MyObj.create(data));
}
});
My view is like:
{{#each MyApp.objController}}
{{#if isValid}}
<some markup>
{{else}}
<some other markup>
{{/if}}
{{/each}}
I was assuming that the if conditional in Handlebars accepts both values and functions, but this doesn't seem to be the case. Is it actually possible, and I'm just doing it wrong?

Handlebars if statements only compares if a value exists, to if it is a falsy value (ie non-existant, 0, an empty string etc.). You have to write a custom helper function.
You could do it like this
Handlebars.registerHelper('isValid', function (value, options) {
if (value == "valid") {
return options.fn(this);
}
return options.inverse(this);
});
This registers a block helper. If the value you pass in evaluates to "valid" it returns the template following the the helper with the current data. If it does not evaluate to valid it returns the template following the else statement with the current data.
Then in your template you could use it like this
{{#each MyApp.objController}}
{{#isValid validity}}
<some markup>
{{else}}
<some other markup>
{{/isValid}}
{{/each}}
Otherwise, if you wanted to abide by the spirit of Handlebars and do a 'logic-less' template, set a flag before you render the template indicating whether or not that data is valid, then use the handlebars if helper with the flag.
You could also possible set up a generic function to handle this and other cases. See my answer in Logical operator in a handlebars.js {{#if}} conditional for an example for a generic if (similar to the above answer)

If you define your isValid as a property, you can use it in your if statement without creating a custom Handlebars helper, see http://jsfiddle.net/pangratz666/Dq6ZY/:
Handlebars:
<script type="text/x-handlebars" data-template-name="obj-template" >
{{view Ember.TextField valueBinding="age" }}
{{#if isValid}}
Si Si.
{{else}}
Nope!
{{/if}}
</script>​
JavaScript:
App.MyObj = Ember.Object.extend({
isValid: function() {
return this.get('age') >= 18;
}.property('age')
});
Ember.View.create({
templateName: 'obj-template',
controller: App.MyObj.create({
age: 21
})
}).append();
​

You could create a custom Handlebars helper to do this.

Try this:
<ul>
{{#each subsites}}
{{#if this}}
<li>{{{WhatIsTheSiteFor this}}}</li>
{{else}}
<li>no found</li>
{{/if}}
{{/each}}
</ul>
The helper function WhatIsTheSiteFor:
Handlebars.registerHelper('WhatIsTheSiteFor', function(siteName) {
var subSiteName = '',
siteNameStr = '';
$.each(siteName, function(i, item) {
siteNameStr += item;
return siteNameStr;
});
if(siteNameStr === 's.gbin1.com') {
subSiteName = 'GB搜索引擎';
}
else if (siteNameStr === 'm.gbin1.com') {
subSiteName = 'GB手机阅读';
}
else if (siteNameStr === 'rss.gbin1.com') {
subSiteName = 'RSS消息订阅';
}
return subSiteName;
});
A demo can be found here: http://www.gbin1.com/gb/networks/uploads/71bb1c1e-0cd3-4990-a177-35ce2612ce81/demo6.html

Related

Meteor spacebars nested {{each}} helper issue

I have 2 collections: TestPlans and Users
TestPlans has an array called "assigned" which contains user _ids.
Users has an avatarUrl string
I need to iterate over each testPlan and furthermore each assignedUser and grab an avatarUrl for that user:
Something like {{#each testPlan}} {{#each assignedUser}} {{avatarUrl}}
the helper for testPlans I have:
testPlan: function() {
var testPlans = TestPlans.find({}, { sort: {createdAt: -1}}).fetch();
return testPlans;
}
the helper for assignedUser I have:
assignedUser: function(){
var assignedUsers = TestPlans.findOne({_id: this._id}).assigned;
return assignedUsers;
}
I'm not sure how call the avatarUrl for each assignedUser though.
The query would be
Users.findOne({_id: thisUserId}).profile.avatarUrl;
Just not sure how to pass the userid exactly.
I would change your assignedUser template helper to accept a user ID parameter and then return the Avatar URL
assignedUser: function(userId) {
return Users.findOne({_id: userId}).profile.avatarUrl
};
Then your template code would look something like this.
{{#each tp in testPlan}}
{{#each au in tp.assignedUser}}
{{avatarUrl au._id}}
{{/each}}
{{/each}}
Also, I like to use each...in instead of just each because it makes it more explicit what is actually happening
if assignedUsers is an array of userIds, which might be the best implementation in this case..
//html
{{#each testPlan}}
{{#each assignedUser}}
{{profile.avatarUrl}}
//you can do this because {{this}} is the user object
//{{profile.avatarUrl}} is the same as {{this.profile.avatarUrl}}
{{/each}}
{{/each}}
//js
assignedUser: function(){
var assignedUsers = TestPlans.findOne({_id: this._id}).assigned;
var usersCursor = Users.find({_id:{$in:assignedUsers}}});
return usersCursor;
}

Update UI for a particular model value in Ember

How to iterate over each model value and based on the value update the handlebar UI.
I am using ArrayController. Basically for a particular value in the model I want to change how I display it.
I am not sure what is wrong in the above code. But it does not function as required.
App.SomeStat = Ember.Object.extend({
target: null,
starts: null
}
{{#each stat in controller}}
{{#if isRestricted}} Do something..
{{/if}}
{{/each}}
App.SomestatController = Ember.ArrayController.extend({
isRestricted: function () {
this.forEach(function(target) {
var t= target.get('target');
return t >= MAGIC_NUMBER;
});
}.property('model.#each.target'),
});
You should setup the ArrayController itemController property to an ObjectController which extends the content for each array content.
App.ExtendIndexController = Ember.ObjectController.extend({
isRestricted: Em.computed(function () {
return this.get('name') === 'red';
}).property('name')
});
App.IndexController = Ember.ArrayController.extend({
itemController: 'extendIndex'
});
Then, you could access the added properties in your template when iterating the controller:
{{#each controller}}
<li>{{name}} ({{isRestricted}})</li>
{{/each}}
http://emberjs.jsbin.com/gexos/1/edit
This case is documented in the Ember guide but I think, this specific case should documented as well.
Try this:
App.CensusStat = Ember.Object.extend({
targetPc: null,
starts: null,
isRestricted: function () {
var offTarget = this.get('targetPc');
return (offTarget &&
(Math.abs(offTarget) >=
Ember.I18n.t('ps.label.census.offtarget.restricted.percentage')));
}.property('targetPc')
});

How can I pass the index from a template loop into my helper, and have it return a Handlebars expression

The problem stems from the inability to do comparisons in the template:
{{#if model.input.type == 'select'}}
The option around this, that I'm aware of so far, is to use a custom Handlebars helper. This would allow me to loop through the model and build out the HTML, but this doesn't allow me to bind attributes, plus just it seems like too much work.
1. Can't bind attributes, and I don't want to build out HTML with a JS string.
Handlebars.registerHelper('formBuilder', function()
{
var html_string = '';
for(var q=0; q<this.content.questions.length; q++)
{
if(this.content.questions[q].type.match(/select/i))
{ /* build out html with model propeties */ }
if(this.content.questions[q].type.match(/checkbox|radio/i))
{ /* build out html with model propeties */ }
}
return new HandleBars.SafeString(html_string);
}
A better option would be if I could track the index in my helper, and return a Handlebars expression, all without breaking the template's loop:
2. Can't pass in loop index or return expressions.
{{#each question in model.questions}}
{{#formBuilder parent.index this}}
/* uses #index to iterate through its children and return a Handlebars
expressions for either select or checkbox html */
{{/formBuilder}}
{{/each}}
...and the formBuilder helper would look something like this:
Handlebars.registerHelper('formBuilder', function(q_index, model)
{
if(model[q_index].type == 'select')
{
return Handlebars.compile({{select action 'myAction' type='model[q_index].type'}});
}
if(model[q_index].type.match(/checkbox|radio/i))
{
return Handlebars.compile({{input action 'myAction' type='model[q_index].type'}});
}
});
wich would return controll to the outer loop.
How is this problem solved in Ember?
Looking at what you are trying to achieve more closely, I would suggest you use a view instead of a handlebars helper to create either a checkbox or a select field. The way you would do that would be
{{#each question in questions}}
{{view QuestionView contentBinding=this}}
{{/each}}
and in your questionView,
App.QuestionView = Em.View.extend({
defaultTemplate: (function() {
if(this.get('type') === 'select') {
return Em.Handlebars.compile(//select box string goes here);
}
else {
return Em.Handlebars.compile(//checkbox html);
}
})()
});

differentiate polymorphic association type in views [duplicate]

I have a list of items I'm trying to display with Ember. For each of these items, I'd like to be able to dynamically select the view type to use to display it based on a "message_type" field in each model.
I currently have something like this, which totally sucks and is not scalable:
{{#each message in controller}}
{{#if message.isImage}}
{{view App.ImageMessageView}}
{{/if}}
....
{{#if message.isVideo}}
{{view App.VideoMessageView}}
{{/if}}
{{/each}}
How can you dynamically select a view based on a model's field in Ember?
Here is a similar question that showed 2 ways to do this: Collection of objects of multiple models as the iterable content in a template in Ember.js
rendering items based on a property or their type
I know of two ways to do this:
add a boolean property to each object and use a handlebars {{#if}} to check that property and render the correct view
extend Ember.View and use a computed property to switch which template is rendered based on which type of object is being rendered (based on Select view template by model type/object value using Ember.js)
Method 1
JS:
App.Post = Ember.Object.extend({
isPost: true
});
App.Bookmark = Ember.Object.extend({
isBookmark: true
});
App.Photo = Ember.Object.extend({
isPhoto: true
});
template:
<ul>
{{#each item in controller.stream}}
{{#if item.isPost}}
<li>post: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isBookmark}}
<li>bookmark: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isPhoto}}
<li>photo: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{/each}}
</ul>
Method 2
JS:
App.StreamItemView = Ember.View.extend({
tagName: "li",
templateName: function() {
var content = this.get('content');
if (content instanceof App.Post) {
return "StreamItemPost";
} else if (content instanceof App.Bookmark) {
return "StreamItemBookmark";
} else if (content instanceof App.Photo) {
return "StreamItemPhoto";
}
}.property(),
_templateChanged: function() {
this.rerender();
}.observes('templateName')
})
template:
<ul>
{{#each item in controller.streamSorted}}
{{view App.StreamItemView contentBinding=item}}
{{/each}}
</ul>
JSBin example - the unsorted list is rendered with method 1, and the sorted list is rendered with method 2
It would probably need some more thought, but here is what I have come up with quickly:
var get = Ember.get,
isGlobalPath = Ember.isGlobalPath,
normalizePath = Ember.Handlebars.normalizePath;
var getProp = function (context, property, options) {
if (isGlobalPath(property)) {
return get(property);
} else {
var path = normalizePath(context, property, options.data);
return get(path.root, path.path);
}
};
Ember.Handlebars.registerHelper('detect', function (definition, instance, options) {
Ember.assert("You must pass exactly two argument to the detect helper", arguments.length === 3);
Ember.assert("You must pass a block to the detect helper", options.fn && options.fn !== Handlebars.VM.noop);
var path = '_detect_' + definition.replace('.', '_').toLowerCase();
context = (options.contexts && options.contexts[0]) || this;
definition = getProp(context, definition, options);
instance = getProp(context, instance, options);
context.set(path, definition.detectInstance(instance));
return Ember.Handlebars.helpers.boundIf.call(options.contexts[0], path, options);
});
Then you can use a helper like this:
{{#detect App.Definition instance}}
DETECTED
{{else}}
NOT DETECTED
{{/detect}}

Selecting view type based on model

I have a list of items I'm trying to display with Ember. For each of these items, I'd like to be able to dynamically select the view type to use to display it based on a "message_type" field in each model.
I currently have something like this, which totally sucks and is not scalable:
{{#each message in controller}}
{{#if message.isImage}}
{{view App.ImageMessageView}}
{{/if}}
....
{{#if message.isVideo}}
{{view App.VideoMessageView}}
{{/if}}
{{/each}}
How can you dynamically select a view based on a model's field in Ember?
Here is a similar question that showed 2 ways to do this: Collection of objects of multiple models as the iterable content in a template in Ember.js
rendering items based on a property or their type
I know of two ways to do this:
add a boolean property to each object and use a handlebars {{#if}} to check that property and render the correct view
extend Ember.View and use a computed property to switch which template is rendered based on which type of object is being rendered (based on Select view template by model type/object value using Ember.js)
Method 1
JS:
App.Post = Ember.Object.extend({
isPost: true
});
App.Bookmark = Ember.Object.extend({
isBookmark: true
});
App.Photo = Ember.Object.extend({
isPhoto: true
});
template:
<ul>
{{#each item in controller.stream}}
{{#if item.isPost}}
<li>post: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isBookmark}}
<li>bookmark: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isPhoto}}
<li>photo: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{/each}}
</ul>
Method 2
JS:
App.StreamItemView = Ember.View.extend({
tagName: "li",
templateName: function() {
var content = this.get('content');
if (content instanceof App.Post) {
return "StreamItemPost";
} else if (content instanceof App.Bookmark) {
return "StreamItemBookmark";
} else if (content instanceof App.Photo) {
return "StreamItemPhoto";
}
}.property(),
_templateChanged: function() {
this.rerender();
}.observes('templateName')
})
template:
<ul>
{{#each item in controller.streamSorted}}
{{view App.StreamItemView contentBinding=item}}
{{/each}}
</ul>
JSBin example - the unsorted list is rendered with method 1, and the sorted list is rendered with method 2
It would probably need some more thought, but here is what I have come up with quickly:
var get = Ember.get,
isGlobalPath = Ember.isGlobalPath,
normalizePath = Ember.Handlebars.normalizePath;
var getProp = function (context, property, options) {
if (isGlobalPath(property)) {
return get(property);
} else {
var path = normalizePath(context, property, options.data);
return get(path.root, path.path);
}
};
Ember.Handlebars.registerHelper('detect', function (definition, instance, options) {
Ember.assert("You must pass exactly two argument to the detect helper", arguments.length === 3);
Ember.assert("You must pass a block to the detect helper", options.fn && options.fn !== Handlebars.VM.noop);
var path = '_detect_' + definition.replace('.', '_').toLowerCase();
context = (options.contexts && options.contexts[0]) || this;
definition = getProp(context, definition, options);
instance = getProp(context, instance, options);
context.set(path, definition.detectInstance(instance));
return Ember.Handlebars.helpers.boundIf.call(options.contexts[0], path, options);
});
Then you can use a helper like this:
{{#detect App.Definition instance}}
DETECTED
{{else}}
NOT DETECTED
{{/detect}}