Ember has-block to detect empty block - ember.js

In ember octane, is there a way to detect {{yield}} is not an empty string?
The has-block function will return true if we use an empty component like this
<IntakeFormElement></IntakeFormElement> while false when <IntakeFormElement/>.
How do I get both <IntakeFormElement></IntakeFormElement> and <IntakeFormElement/> return false?
{{#if (has-block)}}
Content: {{yield}}
{{else}}
No block
{{/if}}
Ember Twiddle code:
https://ember-twiddle.com/7bf99058ec9f125b8b88dd73350ad3b4?openFiles=templates.components.intake-form%5C.hbs%2Ctemplates.components.intake-form%5C.hbs

Since empty block is still a block, there is a way to convey to developers that they shouldn't use your particular component with an empty block.
Via template-lint plugin!
Here is an example astexplorer.net that demonstrates the logic that you'd use.
ElementNode(node) {
if (node.tag === 'MyComponent') {
if (node.children.length === 0) {
console.log(`use ember-template-lint's log function to log a lint error`);
}
}
}
Here are the docs for creating a template-lint plugin: https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/plugins.md
And some example template-lint plugins:
https://www.npmjs.com/package/ember-template-lint-plugin-prettier
https://www.npmjs.com/package/ember-template-lint-plugin-css-modules
https://www.npmjs.com/package/ember-template-lint-plugin-tailwindcss

Related

Ember component: computed property observing local storage array

I'm somewhat at a loss with what should be easily achieved with Ember computed properties. I'm using https://github.com/funkensturm/ember-local-storage to manage a local favorites list.
Now I have a component which should display the object's status, along with the infamous "star" button for toggling the favorite state.
My component code goes as follows:
import Ember from 'ember';
import FavoritesLocal from 'my-app/models/favorites-local';
export default Ember.Component.extend({
storedFavorites: FavoritesLocal.create(),
isFavorite: Ember.computed('storedFavorites', function () {
return this.get('storedFavorites').contains(this.model.get('id'));
}),
actions: {
toggleFavorite() {
if(this.get('isFavorite')) {
this.get('storedFavorites').removeObject(this.model.get('id'));
} else {
this.get('storedFavorites').addObject(this.model.get('id'));
}
}
}
});
The template contains a
{{#if isFavorite}}
<a {{action 'toggleFavorite'}} href="#"></a>
{{else}}
<a {{action 'toggleFavorite'}} href="#"></a><
{{/if}}
The model for the local-storage is simply
import StorageArray from 'ember-local-storage/local/array'
export default StorageArray.extend({
storageKey: 'myFavorites'
});
Now, of course I want the component to update if the button is clicked.
My specific question is concerning WHAT exactly the computed property should be observing. A crude attempt for listening for changes in the storedFavorites property (see above) failed.
Clearly, I could send an action up to the controller and let it handle it and update the template, but that seems a little overdo? What am I missing?
Thanks!
I haven't worked with this yet, but my guess is that you want to observe storedFavorites.length.

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

How do I make a conditional helper with ember-cli and handlebars 2.0.0?

I am trying to achieve something similar to the form or function of:
{{#if has-permission "my_permission"}}
// do some stuff here
{{else}}
// fallback
{{/if}}
OR
{{#hasPermission session.user.permissions "my_permission"}}
I cannot figure this out. I've read this but there isn't much explaining on anything other than helpers that render content or alter it e.g. {{render "something"}}
When I try to make it a conditional I get this:
registerBoundHelper-generated helpers do not support use with Handlebars blocks.
Any help greatly appreciated, thanks!
EDIT:
My User object is serialized as such:
{"User": {
"id": 3,
"name": "john doe",
"permissions": [
"view_projects",
"edit_milestones",
"change_widgets"
]}
}
I want to be able to check a certain permission in the template, not bind a specific one to a computed property.
PS Im using ember 1.9.1, latest data and handlebars 2.0
According to the docs, this is not possible with a bound helper
Bound helpers do not support use with Handlebars blocks or the addition of child views of any kind.
What you can do is push that logic to the controller as follows:
App.IndexController = Ember.ArrayController.extend({
permission: "my_permission",
hasPermission: function(){
var permission = this.get('permission');
return permission === 'my_permission';
}.property('permission')
});
Then, in your template you can do:
<script type="text/x-handlebars" data-template-name="index">
{{#if hasPermission }}
<ul>
{{#each item in model}}
<li>{{item}}</li>
{{/each}}
</ul>
{{ else }}
// fallback
{{/if}}
</script>
Working solution here
You can make use of Ember's boundIf/unboundIf default helpers to create a nice and powerful helper to manage client side's user permissions and end up with something like this:
{{#can 'createPost'}}
<button {{action newBlogPost}}>New Post</button>
{{else}}
You don't have permission to post
{{/can}}
{{#each post in controller}}
<a {{action viewPost post href=true}}>{{post.title}}</a>
{{#can 'editPost' post}}
<button {{action editPost post}}>Edit</button>
{{/can}}
{{/each}}
If you take a look at the Ember's source code and see how if works:
Ember.Handlebars.registerHelper('if', function(context, options) {
Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2);
Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop);
return helpers.boundIf.call(options.contexts[0], context, options);
});
You can see that it only does some sanity checking and hands off to boundIf:
Ember.Handlebars.registerHelper('boundIf', function(property, fn) {
var context = (fn.contexts && fn.contexts[0]) || this;
var func = function(result) {
if (Ember.typeOf(result) === 'array') {
return get(result, 'length') !== 0;
} else {
return !!result;
}
};
return bind.call(context, property, fn, true, func, func);
});
This in turn calls bind which handles setting up all the observers and re-rendering when properties change. The result of the func it builds determines whether to display the content or not.
So if you create a helper which calls boundIf with some property to observe on an object, it will take care of the rest for us.
Handlebars.registerHelper('can', function(permissionName, property, options){
// do magic here
Ember.Handlebars.helpers.boundIf.call(someObject, "someProperty", options)
});
Lets fake out the magic and see what happens:
Handlebars.registerHelper('can', function(permissionName, property, options){
var permission = Ember.Object.create({
can: function(){
return true;
}.property()
});
Ember.Handlebars.helpers.boundIf.call(permission, "can", options)
});
Hmm, that leaves the content as hidden. It seems that it’s not calling the can on our permission.
If we look back at boundIf then we can see that it’s looking up the context on the options and only falls back to this if there’s not one set:
var context = (fn.contexts && fn.contexts[0]) || this;
We can get around this by nuking the contexts on the options we pass through to boundIf. (I’m not sure if this will cause issues, but it worked for me… YMMV and all that).
Handlebars.registerHelper('can', function(permissionName, property, options){
var permission = Ember.Object.create({
can: function(){
return true;
}.property()
});
// wipe out contexts so boundIf uses `this` (the permission) as the context
options.contexts = null;
Ember.Handlebars.helpers.boundIf.call(permission, "can", options)
});
If you twiddle the result of can from true to false then we see our content disappear and re-appear, success!
This example goes into more detail in this excellent post by Richard Livsey

Ember.js - Animate Handlebars Change

I am using an {{if}} statement in a handlebars template (in Ember.js) and currently have it setup so if foo is true, a form is displayed.
I have a button which toggles the value of foo and hides/shows the form. This is done with an {{action}} attribute.
What I would like to do is animate this change. If possible, how can I do this using the current setup?
Note: I would be fine with fadeIn/fadeOut (jQuery).
Take a look at Liquid Fire (see: https://github.com/ef4/liquid-fire). I personally didn't work with it yet, but the examples look promising. Especially this form example looks similar to what you want: http://ef4.github.io/ember-animation-demo/#/animated-if-demo
Source is on the next slide: http://ef4.github.io/ember-animation-demo/#/animated-if-source
You can do this way:
in the handlebars file:
<script type="text/x-handlebars" data-template-name="newPost">
<p> Template for New Post </p>
<div class="errorMsg">
<p>Error Message</p>
</div>
</script>
in the view you can set a trigger with an action, and hide the div with the message error
App.NewPostView = Ember.View.extend({
showError: function() {
$('.errorMsg').fadeToggle("slow");
},
didInsertElement : function(){
$('.errorMsg').hide();
this.get('controller').on('showError', this, this.showError);
},
willClearRender: function()
{
this.get('controller').off('showError', this, this.showError);
}
});
And finally in the controller, this must extend to Ember.Evented and when you want to show the message error, you must call a this.trigger error.
App.NewPostController = Ember.ObjectController.extend(Ember.Evented,{
actions:{
// This an example the method that verify and show error after validating
addNewPost: function(post){
if(post.get('name') === 'valid'){
//do your business
}else{
//this will show the message error
this.trigger('showError');
}
}
}
})
when you want to hide the message error, you must call this.trigger('showError') again and this hide the message, you can use this with other effects.

Morph fails to remove in IE 8/9

I'm working on an Ember app (using RC1) and am having a problem with IE (surprise). We have a series of nested views that can show either a trip's summary, details, or edit. The trip view looks (roughly) like this:
{{#if tripController.showSummary}}
{{template "view/TripSummaryView"}}
{{/if}}
{{#if tripController.showDetails}}
{{template "view/TripDetailView"}}
{{/if}}
{{#if tripController.showEdit}}
{{template "view/TripEditView"}}
{{/if}}
On the div surrounding the trip summary view, I have an action:
<div {{bindAttr id="tripController.tripId"}}
class="card single-line"
{{action "toggleDetails" tripController on="click"}}
style="cursor:pointer">
And the controller handles the action:
toggleDetails: function(tripController) {
if (this.get('showDetails')) {
this.set('showDetails', false);
this.set('showSummary', true);
} else {
this.set('showDetails', true);
this.set('showSummary', false);
}
},
All pretty vanilla, and it works perfectly in Chrome, FF, and Safari. But when I run it in IE, it bombs in the metamorph's realNode() function (line 17264 in ember.js):
var realNode = function(start) {
while (start.parentNode.tagName === "") {
start = start.parentNode;
}
return start;
};
with this error:
SCRIPT5007: Unable to get value of the property 'parentNode': object is null or undefined
When I put a breakpoint in the code, it appears that Ember is trying to remove 2 metamorphs, but that the second is inside the first and so start is null the second time around. I put a breakpoint in that function in Chrome, but it doesn't ever appear to get called.
I've tried reducing this down to a JSFiddle, but I can't seem to get it to break - so there must be something in the way we've got our views set up that's causing a problem. Any suggestions on how to go about debugging this would be most appreciated!
Thanks,
Scott
You can achieve the same using ContainerView but I am also facing similar problem where childviews are not receiving model/controller. Here is a [similar post] (An example of ContainerView required where childViews are binded with their respective controllers "containerview's childviews controller binding")