Empty vs. not-loaded Ember.js controller - ember.js

The handlebars each helper is used to iterate over a list of items in a backing ArrayController.
We can use the following construct to do something with a list of items AND show alternative details when that list is empty:
{{#each item in controller}}
<!-- stuff goes here -->
{{else}}
<!-- other stuff goes here -->
{{/each}}
This is great, but what if we want to differentiate between empty and not loaded? I feel like this must be a fairly common use case, but I can't figure out how to approach it - I don't see anything in the guides. Any help?

For this use case, i just wrap the "each" with a "if", that test an additional argument 'loaded' on the model.
{{#if content.loaded}}
{{#each item in controller}}
<!-- stuff goes here -->
{{else}}
<!-- other stuff goes here -->
{{/each}}
{{/if}}
The 'loaded' is toggled to true when the ajax promise (or whatever you do) completes.
Hope it helps!

For me with Ember 1.8.1, Ember Data 1.0.0-beta11 and iterating over hasMany collection on a model loaded wasn't working, as well as isLoaded. The thing that worked was isFulfilled.

Related

Ember - if lastItem in each

I am trying to show a piece of markup under the very last item in an each in ember -- I tried something like #last - but it came back with an error
<ul>
{{#each people as |person index|}}
<li>Hello, {{person.name}}! You're number {{index}} in line</li>
{{/each}}
</ul>
Ember's {{each}} template helper does not provide an easy way to do so.
As you might figured out, you could use {{unless index}} to conditionally render stuff on the first object. This is working since index starts at zero and 0 is considered to be falsely.
You could combine ember-truth-helpers and ember-math-helpers to achieve something comparable for last element. Both are well maintained ember addons that provide a full set of template helpers.
Ember-truth-helpers provides a eq helper that adds support for equal comparison in templates. Ember-math-helpers provides a sub helper to subtract a value from another in a template. Combining both of them we could build a condition that will be true only for the last element:
{{#each items as |item index|}}
<li>
{{item}}
{{#unless index}}(first){{/unless}}
{{#if (eq index (sub items.length 1))}}(last){{/if}}
</li>
{{/each}}
You find an ember twiddle demonstrating the approach here: https://ember-twiddle.com/6ea05a2e9c884bd772eefabc91173d08?openFiles=templates.application.hbs%2C
It's even simpler if the array is an Ember.NativeArray. NativeArray of ember provides a lastObject property, which points to the last item in the array. You could use that one for equal comparison directly as pointed out by #Gaurav in a comment: {{#if (eq item items.lastObject)}}(last){{/if}} But be aware that this may give wrong results if the array has duplicated elements.

Displaying a tag only for one element in a list. ember.js.(cloaked-collection, discourse)

I have a source code(discourse) than I need to work with with ember.js. I am trying handle only one "post" (the first) in a list using cloaked-collection.
//topics.hbs
{{#unless model.postStream.loadingFilter}}
{{cloaked-collection itemViewClass="post"
defaultHeight="200"
content=postsToRender
slackRatio="15"
loadingHTML=""
preservesContext="true"
uncloakDefault="true"
offsetFixedTop="header"
offsetFixedBottom="#reply-control"}}
{{/unless}}
//post.hbs
//some code here.
//Then I want to insert <div class="uniw"></div> only on the first post
The question is: for the list of itemViewClass="post", how can I check if I am in the first "post"? so that i can insert a piece if code.
You should be able to use firstObject to get first object of a collection. Something like this:
{{items.firstObject}}
Inside each block you can check index:
{{#each people as |person index|}}
{{#if (is-equal index 0)}}
{{person}}
{{/if}}
{{/each}}
Note that is-equal is non-standard helper. You can get it from ember-truth-helpers addon or write it yourself

EmberJS loop through array show key and value

Ok so I have a pretty simple array of objects that I retrieved with emberjs, now all I need it to do is have something along the lines of
{{#each array}}
{{value}}
{{/each}}
and have it so that the array is looped by each item, and each item then is looped by each value and I display all the values of that object, I am not sure how to get it to work.
I have used this helper here https://gist.github.com/strathmeyer/1371586 but it's no use, I have no idea where to start, it just seems like such a simple thing I don't understand why handlebars/ember doesn't support it, also does #key work at all with ember?
Thanks
Ember Handlebars doesn't support looping over objects.
To loop in an array you simply use
{{#each item in collection}}
{{item}}
{{/each}}
or
{{#each collection}}
{{this}}
{{/each}}

Combining view class with layout and {{#each}} multiple times gives incorrect behavior

I'm seeing some very strange behavior in this jsfiddle.
I am building an accordion control (hopefully eventually a good contribution to ember-bootstrap), and so I built a view class that uses layout to wrap the contents of the view:
Bootstrap.Accordion = Ember.View.extend({
tagName: 'div',
classNames: 'accordion',
layout: Ember.Handlebars.compile('{{yield}}')
});
Then I use it like so, with the {{#view}} helper, and include an {{#each}} block which will eventually include other views to set up the inside of the accordion control. And in one case so far, I do this twice in the same template, to display different information in two different accordion controls, sort of like this:
{{#view Bootstrap.Accordion}}
{{#each content}}
<div><strong>Field 1:</strong> {{field1}}</div>
{{/each}}
{{/view}}
{{#view Bootstrap.Accordion}}
{{#each content}}
<div><strong>Field 2:</strong> {{field2}}</div>
{{/each}}
{{/view}}
But, as you can see in the fiddle, this produces a very unexpected result. Basically, the second instance of the view is an exact copy of the first. Even the static content inside the {{#each}} block is not right:
Field 1: Instance 1 Field 1
Field 1: Instance 2 Field 1
Field 1: Instance 1 Field 1
Field 1: Instance 2 Field 1
However, if I put something between the {{#view...}} and {{#each}} helpers, it behaves as expected:
{{#view Bootstrap.Accordion}}
Fourth try...
{{#each content}}
<div><strong>Field 4:</strong> {{field4}}</div>
{{/each}}
{{/view}}
So, it looks like something about the similarity of the content directly within the {{#view}} helper causes the result to be cached by Handlebars...or something. That's just a wild hypothesis. Can anyone see what's going wrong here?
(Note that the Bootstrap library is not included in the fiddle, so it can't be that Bootstrap is goofing something up.)
This looks like a bug.
This github issue was created today, only with the {{#linkTo}} helper.
Looks like this is occurring with all block helpers.

In templates in Ember.js, how do you refer to a value in the parent context when you are inside an #each block?

I have a situation in a template where I want to use an if block on a value in the parent context while inside an each block.
The code:
App = Ember.Application.create({});
App.view = Ember.View.extend({
foo: [1, 2, 3],
bar: true
});
The template:
<script type="text/x-handlebars">
{{#view App.view}}
{{#each foo}}
{{#if bar}}
{{this}}
{{/if}}
{{/each}}
{{/view}}
</script>
This does not work because names referenced inside an each loop are scoped to the element of iteration. How do you refer to things in the parent context?
Demo: http://jsfiddle.net/hekevintran/sMeyC/1/
I found a better solution.
From the Ember.js View Layer guide (http://emberjs.com/guides/understanding-ember/the-view-layer/):
Handlebars helpers in Ember may also specify variables. For example, the {{#with controller.person as tom}} form specifies a tom variable that descendent scopes can access. Even if a child context has a tom property, the tom variable will supersede it.
This form has one major benefit: it allows you to shorten long paths without losing access to the parent scope.
It is especially important in the {{#each}} helper, which provides a {{#each person in people}} form. In this form, descendent context have access to the person variable, but remain in the same scope as where the template invoked the each.
The template:
<script type="text/x-handlebars" >
{{#view App.view}}
{{#each number in view.foo}}
{{#if view.bar}}
{{number}}
{{/if}}
{{/each}}
{{/view}}
</script>​
Demo: http://jsfiddle.net/hekevintran/hpcJv/1/
What hekevintran's answer means is that you can rename any variable using #with. We have a similar problem in JavaScript with this. In JavaScript, sometimes you'll see code like this to work around it.
var self = this;
doSomething(function() {
// Here, `this` has changed.
if (self.bar) {
console.log(this);
}
});
In Ember flavored Handlebars, something similar is happening with view. Say you have App.MyOuterView and another view inside it. You can work around it like this.
{{#with view as myOuterView}}
{{#each foo}}
{{#if myOuterView.bar}}
{{this}}
{{/if}}
{{/each}}
{{/with}}
Similar to the JavaScript, you can essentially rename view to something else so it doesn't get shadowed by the inner view. {{#each person in people}} is just a special case of that. But renaming using {{#with view as myView}} is the more general solution/workaround to this problem that also works with nested calls to the view helper.
I was also stumped on this. This thread and this other thread (Using a container view in ember.js - how to access parent variables from child view) helped me with the solution. I used Jonathan's suggestion to do {#with} and also figured out that I should access my variable by calling the controller. Mine worked like this:
// I use the #which command to preserve access to the outer context once inside the #each
{{#with view as myOuterView}}
{{#each myInnerArray}}
//here, i get my 'name' property from the *controller* of myOuterView
{{myOuterView.controller.name}}
// stuff i did in inner array
{{/each}
{{/with}
No need to place the if inside each in the first place:
<script type="text/x-handlebars">
{{#view App.view}}
{{#if view.bar}}
{{#each view.foo}}
{{this}}
{{/each}}
{{/if}}
{{/view}}
</script>
Demo: http://jsfiddle.net/ppanagi/NQKvy/35/