Reacting to a controller/view initialization event - ember.js

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

Related

Ember: Nothing Handled the Action Error Occured

So I'm following a tutorial to build an app on CodeSchool and I was trying to figure out how to write in a toggle when I noticed an error in the console basically saying that nothing is handling the action block I wrote in the template.
"Nothing handled the action 'toggleOption model option'. 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."
The code below is the part of the template I'm having trouble with.
<ul class='list list--answer'>
{{#each model.poll.option as |option|}}
<li class='list-item'>
<button class='list-item-checkbox {{if (eq model.option option) "is-selected"}}' {{action "toggleOption model option"}}>
<b class='srt'>Select</b>
</button>
<span>{{option.label}}</span>
</li>
{{/each}}
</ul>
This is the route associated with the template.
import Ember from 'ember';
export default Ember.Route.extend({
store: Ember.inject.service(),
model(){
const poll = this.modelFor('polls.poll');
return this.get('store').createVote(poll);
},
actions: {
toggleOption(vote,option){
vote.toggleOption(option);
}
}
});
Anyways, is there something I'm missing? I've been staring at this for awhile and I couldn't figure this out. The tutorial video I've been following and their completed code doesn't seem to run into this issue either.
There is syntax issue {{action "toggleOption model option"}} it should be {{action "toggleOption" model option}}.

Road to Ember 2.0 - High level Ember app structure feedback?

I find I'm trying to pick up learning Ember at a time of particular fluctuation. The recent "Road to 2.0" blog post has helped me clarify which direction to head, but I'm struggling to validate my approach to Ember at a high level.
I want to be sensitive to people's time. My full code is here for anyone interested in providing more specific feedback (would love), but I'm mostly interested in this high level feedback on my app's structuring and my utilization of Ember's capabilities.
App Background:
I'm working on a user-to-many chat to text SMS app. Visually, each user has multiple chat windows (a Conversation) open w/ messages (Message) specific to a Profile message history. The rails backed sends messages to the target Profile. This project is very much in development.
Key Questions:
What is the best way to associate a model with a component? I'm passing each conversation model to a conversation component. As my component logic becomes so tightly integrated to the view, tt seems like a component class is taking on too much heft outside of UI. I'm starting to add of logic around how UI bubbles up to the model, but wonder if there are better approaches.
Since I'm breaking away from the proxying behavior of Array controller, I find myself referencing my model collection via this.get('content') - is there a better way to deal with the collection of conversations?
Finally, to invoke actions in a component, I've read of using Ember.Evented mixin to trigger and observe events. I.e. when a user tries to open a chat window for a profile when that chat is already open, I'd want to flash the target chat window. Is this a good way to manage these interaction in context of "Road to 2.0"?
What about passing events from the controller to the Message subcomponents? Message subcomponents would be bound to each messages' statuses (success, fail, etc). I imagine i'd just bind some message display to a record's state and status attribute. Any way I could do it better?
I'm super open to feedback. Be harsh! :)
High level code:
(full code)
ChatApp.Router.map(function () {
this.resource('conversations', { path: '/' });
});
ChatApp.ConversationsRoute = Ember.Route.extend({
model: function () { //this is a collection of active conversations
},
activate: function() { //listens to event stream
}
});
ChatApp.ConversationsController = Ember.Controller.extend({
actions: {
openChat: function(user_id, profile_id){ //open chat if one isn't open.
}
},
removeExcessChats: function(){ // removes chats that don't fit in window
},
});
ChatApp.ConversationHolderComponent = Ember.Component.extend({
actions: {
markInactive: function(){
// referencing a passed in conversation is the only way I know to reference the model.
this.get('conversation').markInactive();
},
createMessage: function(){
}
},
isFlashed: false
});
Component templates:
<script type="text/x-handlebars" data-template-name="components/conversation-holder">
<button {{action "markInactive"}}>close</button>
<h3>Profile: {{conversation.profile}} Conversation: {{conversation.id}}</h3>
<ul class="list-unstyled">
{{#each message in conversation.messages}}
<li><strong>{{message.type}}</strong> {{message.body}}</li>
{{/each}}
<li>
<form class="form-inline" {{action "createMessage" on="submit"}}>
{{input class="message_body" placeholder="Start typing a message..." value=conversation.new_message_body type="text"}}
{{input class="btn" type="submit" value="Send"}}
</form>
</li>
</ul>
</script>
<script type="text/x-handlebars" data-template-name="conversations">
<section id="todoapp">
<header id="header">
<h1>Chat Messaging</h1>
</header>
</section>
<section id="main">
<p>Open a new chat with profile id #1 <a href="#" {{action "openChat" 1 1}} >Open w/ profile 1</a> | <a href="#" {{action "openChat" 1 6}} >open profile already in convo</a></p>
<ul id="chat-app" class="list-unstyled clearfix">
{{#each conversation in model}}
<li>{{chat-holder conversation=conversation}}</li>
{{/each}}
</ul>
</section>
</script>
I didn't go through your app design, but I'm answering based on the more general Ember concepts that you mentioned.
1.
There isn't really a model object in Ember. You have a route with a model hook that returns whatever you want as your model. It can be a string, array or just a number.
When you use Ember Data, what will happen is that the model hook returns Ember Data objects.
A component can receive any object as its model/content. So, there isn't a best or worst way of associating a model and component, you just pass it what it needs.
2.
If your component is starting to get too big, probably you should split it in two or more components. Nothing wrong with having a component's template render other components.
Also, if you have logic that is shared among many components, you can refactor that into a mixin and include it in each component.
3.
Your idea for message passing between the controller and the components is *probably* right. The usual flow in Ember apps is events up & data down. Since the controller is at a higher level than a component, you can't send event in that direction, but by updating bound values you can pass new info to the components.

Ember.checkbox nested in action doesn't change value

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

Ember.js: ArrayController undefined in template

Problem:
I am kind of struggling with the organization of my first ember app. The current issue is that the my Items ArrayController is not defined in my dashboard template:
<script type="text/x-handlebars" data-template-name="dashboard">
{{#if controllers.items}}
<p class="alert alert-error">Dashboard can access item's info - Nice!</p>
{{else}}
<p class="alert alert-error">Dashboard cannot access items... :-/</p>
{{/if}}
</script>
Likely cause: *
**EDIT: after talking with #conrad below, I'm kind of questioning this:*
I had a similar issue in an earlier post and kingpin2k suggested the cause was that I:
"never created anything that uses the options controller".
This is probably the case here as well. This quick screencast shows that a breakpoint on my ArrayController is not hit on page load - but it is hit when I inspect the Items controller in the Ember inspector tool (eg, Ember creates the ArrayController object right then for the first time).
Apparent non-solutions:
My Dashboard controller says it needs the Items controller. I guess that isn't enough to instantiate the ArrayController?
App.ItemsController = Ember.ArrayController.extend({
len: function(){
return this.get('length');
}.property('length'),
totalCost: function() {
return this.reduce( function(prevCost, item){
return parseInt(item.get('values').findBy('type', 'cost').price, 10) + prevCost;
}, 0);
}.property('#each.values')
[more computed properties...]
});
App.DashboardController = Em.Controller.extend({
needs: ['items'],
itemsLength: Ember.computed.alias('controllers.items.len'),
itemsTotalCost: Ember.computed.alias('controllers.items.totalCost'),
[more computed properties...]
});
Furthermore, each item in Items is being rendered in my items template. I guess that does not create the missing controllers.items either...
<script type="text/x-handlebars" data-template-name="items">
{{#each}}
[these render fine]
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="display">
<!-- DISPLAY TEMPLATE -->
{{!- DASHBOARD -}}
{{render dashboard}}
{{!- ITEMS -}}
{{render 'items' items}}
</script>
So then.. what?
I can imagine many possible avenues, but haven't gotten any of them to work yet:
Specify the Items ArrayController in {{render dashboard}}?
Some configuration in a Route?
Maybe my templates/routes are not correctly arranged?
You could make sure that the ItemController is instantiated in the dashboard template by calling it in the DashboardController's init function:
App.DashboardController = Em.Controller.extend({
needs: ['items'],
init: function() {
this._super();
this.get('controllers.items.length');
}
});
/edit:
removed the part that was not helpful

Ember.js not sideloading

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.