I have the need to load random items in my Ember application. To do this, I do the following:
Test.ApplicationRoute = Ember.Route.extend({
events: {
randomItem: function() {
var route = this;
$.getJSON('item/random.json', function(data) {
Test.Item.find(data).then(function(item) {
route.transitionTo('items.show', item);
});
});
}, // ..... etc
This works fine, except for one thing: nested-sideloaded data is not shown. When items.show is visited via a {{#linkTo 'items.show' item}}, the item's child data is also loaded and visible. However, when this randomItem event is fired, only the direct children are shown. The children of the children are not.
Why is this and/or how do I fix this?
It turns out, the reason the artifacts are not being shown is that I try to listen to controller.content.isLoaded to adapt the view to show a loading image if the content is still loading.
For some reason, the following shows the loading... text and icon forever on subsequent usages of the route:
{{#if controller.model.isLoaded}}
<div class="row">
</div>
{{#each artifact in controller}}
{{render "artifacts.show" artifact}}
{{else}}
<span class="muted">There are no artifacts.</span>
{{/each}}
{{else}}
<i class="icon-spin icon-spinner icon-large"></i> Loading...
{{/if}}
If anyone knows why, please let me know, so I can solve this problem.
Related
I'm new to ember. I have a demo app working, and I'm moving towards making it look nice.
One issue I'm starting to grapple with is how to manipulate DOM elements. Coming from a server-side world, it's been pretty easy to just throw some jquery at stuff like this. Doesn't appear to be as straightforward in ember. But I'm probably missing something or approaching it wrong.
The immediate problem is: I have a list of 40-some <li> elements and I want to create a toggle to show/hide the list after the first 10 items.
I got something to work in my component like this:
import Ember from 'ember';
let $ = Ember.$;
export default Ember.Component.extend({
didInsertElement() {
this._super(...arguments);
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent() {
let listTotal = $("#myList li").length;
$("#myList li").slice(10, listTotal).hide();
}
});
The problem is that when actions trigger and the view is re-rendered, afterRenderEvent() doesn't get called again, and the list shows in its entirety.
The above component corresponds to this template:
<ul id="myList">
{{#each aggs.categories as |category|}}
<li><a href="#" {{action (action add "filter_breadcrumb" category.key)}}>{{category.key}} ({{category.doc_count}})</a></li>
{{/each}}
</ul>
Is there a way to get around this? OR, is there a more "ember" way to approach this problem (and DOM manipulation in general)?
Introduce showCount property in controller and have action to set showCount to total list count.
import Ember from 'ember';
export default Ember.Component.extend({
showCount: 10,
actions: {
setShowCount(count) {
//you can set total count
this.set('showCount', count);
}
}
});
Install ember truth helpers addon or write computed property to check.
<ul id="myList">
{{#each aggs.categories as |category index|}}
<li style="{{if (gt index showCount) 'display:none'}}"><a href="#" {{action (action add "filter_breadcrumb" category.key)}}>{{category.key}} ({{category.doc_count}})</a></li>
{{/each}}
</ul>
If you don't want to display then you can just iterate just showCount aggs.categories alone by writing computed property or using ember-composable-helpers junk method
<ul id="myList">
{{#each (chunk showCount aggs.categories) as |category index|}}
<li><a href="#" {{action (action add "filter_breadcrumb" category.key)}}>{{category.key}} ({{category.doc_count}})</a></li>
{{/each}}
</ul>
Template:
{{#each document in documents}}
<div class="col-md-6" {{action "selectDocument" document}}>{{view Ember.Checkbox checked=document.isSelected}} {{document.name}}</div>
{{/each}}
Controller:
App.IndexDocumentsController = Ember.ArrayController.extend({
actions: {
selectDocument: function(document){
document.set('isSelected', !document.get('isSelected'));
}
}
});
When I click on the div, the checkbox toggles 'checked' property. But when I click on the ckeckbox - nothing happens. What can be the reason?
UPDATED
Link to jsbin: http://emberjs.jsbin.com/nuvocumuteto/1/edit?html,css,js,output
The issue is that when you click on the checkbox 2 things happen.
the checkbox toggles the isActive property, then
the selectRow action is ran which again toggles the isActive property
So the isActive property ends up staying in the same state that it was.
In your case I would get rid of the action, wrap the checkbox in a <label> and set the label to display: block.
The template would look like
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model}}
<li {{bind-attr class="item.isActive:active"}}><label>{{input type="checkbox" checked=item.isActive}}{{item.name}}</label></li>
{{/each}}
</ul>
</script>
and the css would look like
label {
display: block;
}
you would then be able to get rid of the selectRow action completely because clicking on the label will trigger the checkbox.
You can see a working bin here: http://emberjs.jsbin.com/nuvocumuteto/3/edit
I would argue that you are not following "The Ember Way" in two different ways here.
First, Ember.Checkbox is an internal Ember class (http://emberjs.com/api/classes/Ember.Checkbox.html). The recommended way to render a checkbox is to use the Handlebars input helpers (http://emberjs.com/guides/templates/input-helpers/#toc_checkboxes). This is just wrapping Ember.Checkbox anyway.
Second, if you want to update the value of isSelected, the "Ember Way" is to use two-way data bindings. Your code uses one-way data-binding when it reads document.isSelected and then tries to manually re-create the the data-binding in the other direction when the user clicks by manually writing a selectDocument action and calling it from an {{action}}.
Instead, simply bind the Ember Handlebars Input Helper directly to your value like this:
{{#each document in documents}}
<div class="col-md-6">{{input type="checkbox" checked=document.isSelected}} {{document.name}}</div>
{{/each}}
Here are my controllers:
App.DesignPhotosController = Ember.ArrayController.extend({
itemController: 'designPhoto'
});
App.DesignPhotoController = Ember.ObjectController.extend({
needs: ['designPhotos'],
toDelete: false,
actions: {
toggleDelete: function() {
this.set('toDelete', !this.get('toDelete'));
}
}
});
And my template:
{{#each}}
<ul>
<li>
{{title}}
{{#if toDelete}}
<button class="restore" {{action "toggleDelete"}}>Restore</button>
{{else}}
<button class="delete" {{action "toggleDelete"}}>Delete</button>
{{/if}}
</li>
</ul>
{{/each}}
However, when I click on the "Delete" button, I get a message logged:
Error: Nothing handled the action 'toggleDelete'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.
As far as I can tell I am doing this correctly, bit I tried forcing it by various combinations of:
Adding target=this to the action
Adding {{#each item in controller}} and target=item
Changing the action to {{action toggleDelete this}}, with and without quotes
Nothing works.
Can anyone point out what I am doing wrong?
One possible reason of this behaviour is a bug present in 1.13 (up until the very last 1.x release, 1.13.13), which breaks actions that should be processed by item controllers.
In case you can't revert to release 1.12, the second workaround from the question
changing {{#each}} to {{#each item in controller}} and adding target=item to the action
is the officially recommended one and has worked for my code, although the first one
adding target=this to the action (with simple {{#each}})
should probably work too.
(I suspect that OP left either of these in the code and didn't register that it fixed the issue, because both came up as false negatives earlier due to Firefox's very persistent caching.)
I have a toolbar component which has a list of items. When I click on an element I need to add background color to the clicked element. Also need to deselect the previously selected item.
I tried using classNameBinding but it applies to all the elements in the list.
How can I apply Background Color to the elements which are clicked inside the Component
In Template:
<script type="text/x-handlebars" data-template-name="components/test-toolbar">
<ul>
<li {{bindAttr class="bgColor"}} {{action 'redClick'}}>
Red
</li>
<li {{bindAttr class="bgColor"}}>
Yellow
</li>
<li {{bindAttr class="bgColor"}}>
Blue
</li>
</ul>
</script>
<script type="text/x-handlebars" data-template-name="index">
<div>
{{test-toolbar}}
</div>
</script>
In app.js:
App.TestToolbarComponent = Ember.Component.extend({
classNameBindings: ['bgColor'],
bgColor: false,
actions: {
redClick: function () {
this.set('bgColor', true);
}
}
});
Here is the working DEMO: JSBIN LINK
Using actions you can't get the target element to manipulate it. Also using 3 different actions for same is something i don't suggest. You can use click hook to handle this in simple jquery way. here is code and jsbin link
App.TestToolbarComponent = Ember.Component.extend({
click: function(event){
var elem = Ember.$(event.target);
if(elem.is('li')) {
this.$('li').removeClass('active');
elem.addClass('active');
}
}
});
http://emberjs.jsbin.com/qiriwi/2/edit
The answers given will definitely work. But I think there is a very simple way to get this done. You will have a .css (most probably style.css) file associated with your ember app. In that file add the following snippet:
.active{
/*your code goes here
eg. background-color: 'red';*/
}
This will simply allow you to style the component whichever is active at that moment of time.
As you can see in this jsbin, I am trying to do the following:
Load some data. Data is loaded from a FIXTURE, slowly, because we have a high latency in the FIXTURE.
While the data is being loaded, I want to show some simple text: Loading ...
Once the data is received, show an outlet with the real data.
I have tried with controllers.nodesIndex.isUpdating, isLoading, isUpdated .isLoaded, and several other variations, but this is not working. How can I react to a "data is ready" event?
EDIT
I have also tried with a property binding dataLoaded; in the route, when the data is received, I manually update the property. But this is also not working. I am getting several error messages:
Uncaught Error: Something you did caused a view to re-render after it rendered but before it was inserted into the DOM.
Assertion failed: Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications
Uncaught Error: You cannot modify child views while in the inBuffer state
This is the template:
<div id="legend" class="">
<legend class="">Nodes <span class="badge">{{controllers.nodesIndex.length}} records</span></legend>
</div>
<!-- Just to get visual feedback when the data gets loaded -->
{{outlet}}
{{#if controllers.nodesIndex.dataLoaded}}
<!-- This should only be shown when the data is ready -->
{{outlet}}
{{else}}
Loading ...
{{/if}}
</fieldset>
</form>
</article>
This is the controller:
App.NodesIndexController = Ember.ArrayController.extend({
dataLoadedBinding: 'App.nodesLoaded'
});
And this is the route:
App.NodesIndexRoute = Ember.Route.extend({
model: function() {
return App.Node.find().then(function(data) {
console.log('Data received > data=%o', data);
App.set('nodesLoaded', true);
return data;
});
}
});
The canonical way to provide alternate markup in handlebars for content that is loading appears to be isUpdating property of a controller's content.
{{#if content.isUpdating}}
{{!-- content markup here --}}
{{else}}
{{!-- Loading message here --}}
{{/if}}