I'm currently learning EmberJS as I feel single page apps using a JS framework and consuming a backend API are the way things are going to be moving. I've recently learned Laravel without any particular problem, but I'm finding Ember remarkably difficult.
Most of my issues I've worked through, but I'm having one that I don't even know how to debug.
Essentially, my interface displays a list of clients. Clicking on a name goes to that client. All of this is connected to a PHP/Laravel based API, which is working entirely fine and in the right format. Data is not the problem here.
That said, I should point out that my data does not have an "id" strictly, but connects using something more like a guid: 912ec803b2 instead of 231. The API is built to handle that, and it's a requirement. It also means there is no "id" in the data, but there is a "hash" which may potentially be confusing ember in some way?
This basic functionality just doesn't seem to work correctly. When a user clicks the Client link, it does navigate to the client. But it's adding a blank row to the clients list page, then going to the client page. Returning to the clients page shows the extra blank row, and the same thing will recur if clicked again.
Checking the Ember inspector in Chrome shows the data collection increasing, with blank data being added. I'm not sure why.
I've posted the code here. This should be all that's necessary. Is this behaviour a common trap for young players, of have I done something unusual?
Apologies for the length, just didn't want to omit something relevant.
// app.js
App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.Router.map(function() {
this.resource('clients', function() {
this.route('new');
});
this.resource('client', { path: 'clients/:client_hash' }, function(){
});
});
App.Store = DS.Store.extend({
url: 'http://myclientinfo'
});
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'api'
});
App.Client = DS.Model.extend({
name: DS.attr('string'),
hash: DS.attr('string')
});
App.ClientsRoute = Ember.Route.extend({
model: function(){
return this.store.find('client');
}
});
App.ClientIndexRoute = Ember.Route.extend({
model: function(params){
return this.store.find('client', params.client_hash);
}
});
// templates
<script type="text/x-handlebars">
<div class="container">
<ul class="nav nav-pills">
<li>{{#link-to 'clients'}}Clients{{/link-to}}</li>
</ul>
{{outlet }}
</div>
</script>
<script type="text/x-handlebars" id="client">
client outer
{{outlet}}
</script>
<script type="text/x-handlebars" id="clients">
client outer
{{outlet}}
</script>
<script type="text/x-handlebars" id="client/index">
client index
<h1>{{name}}</h1>
</script>
<script type="text/x-handlebars" id="clients/index">
<div class="row">
<div class="col-md-12 col-lg-12">
<h1>All Clients</h1>
</div>
</div>
<div class="row"><div class="col-md-12 col-lg-12">
{{#link-to 'clients.new' class="btn btn-info add-btn"}}
<i class="fa fa-plus"> Add new client</i>
{{/link-to}}
</div></div>
<div class="row">
<div class="col-md-12 col-lg-12">
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{{#each item in model}}
<tr>
<td>{{#link-to 'client' item.hash}}{{item.name}}{{/link-to}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
</script>
Ok, just following this up since I've sorta kinda fixed it.
this.resource('client', { path: 'clients/:client_hash' }, function(){
This line did more than I thought. I thought it was more of a defining placeholder, like in Laravel.
Route::get('user/{id}', function($id)
The name used, whether it's {id} or {hash} or {poopypants} doesn't matter because it's being defined and used at that time.
I changed it to this:
this.resource('client', { path: 'clients/:client_id' }, function(){
That lets the default link-to helpers work, rather than simply getting "undefined" like they were for everything else I tried. The name of that segment is actually meaningful, it is used to derive the actual values for the segment.
With this done I was able to refactor the loop code slightly, fixing the helper.
{{#each item in model}}
<tr>
<td>{{#link-to 'client' item}}{{item.name}}{{/link-to}}</td>
This could also be refactored down more.
{{#each}}
<tr>
<td>{{#link-to 'client' this}}{{name}}{{/link-to}}</td>
Note that {{#link-to 'client' this}} works, while {{#link-to 'client' this.id}} mostly works, but gets the bug stated in the OP. A bug that I believe has taken me three DAYS to fix.
Related
i am new to emberjs and i am trying to iterate through arr and render a table with the content of elements of the array the each loop renders <!----> instead of rows in the table
what am i doing wrong?
here is my code:
app.js
arr=[{id:1,name:foo,completed:'yes'},{id:2,name:'bar', completed:'no'}]
App= Ember.Application.create();
App.Router.map(function(){
})
App.IndexRoute=Ember.Route.extend({
model: function(){
return arr;
}
})
App.SingleTaskComponent = Ember.Component.extend({
templateName: "components/single-task",
tagName: ""
});
App.TasksCollectionComponent = Ember.Component.extend({
templateName: "components/tasks-collection",
tagName: "tbody",
actions: {
newTask: function(){
console.log("here");
}
}
});
and here is the hbs code:
<script type="text/x-handlebars" id="components/single-task">
<tr {{bind-attr id=id}}>
<td>
<input type="text" name="task" {{bind-attr value=name}} >
</td>
<td>
<input type="checkbox" name="completed">
</td>
</tr>
</script>
<script type="text/x-handlebars" id="components/tasks-collection">
<tr>
<td><button>+</button></td>
<td>Tasks</td>
</tr>
{{#each model as |task|}}
{{single-task id=task.id name=task.name completed=task.completed}}
{{/each}}
</script>
<script type="text/x-handlebars" id="index">
<table>
{{tasks-collection}}
</table>
</script>
Your coding looks like you are not using ember-cli. please use it. In this link you can go through enormous projects which are using ember, you can learn from them how are they using it. You can even play with ember-twiddle for easy learning. I will encourage you to follow ember guides tutorial.
Regarding your question,
You need to pass the required data to components,
{{tasks-collection model=model}}
In single-task component, you can avoid bind-attr instead you can use it like
<tr id={{id}}>
Refer deprecation guide bind-attr for more details
I've got a list of messages that are provided by a Rails backend. What I need is when the "toggle_visibility" action button is pressed, it would toggle the "publicly_viewable" property. This means, making a corresponding REST call (to effect the database) and changing the state of the corresponding cached message. Here is where I'm at so far.
Here's what I've got so far, that manages to end up on the debug console:
# app.js
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.extend({
url: 'http://localhost:3000'
})
});
App.Message = DS.Model.extend({
body: DS.attr('string'),
mobile_number: DS.attr('string'),
publicly_viewable: DS.attr('boolean'),
created_at: DS.attr('date')
});
App.Router.map(function() {
this.resource('messages');
});
App.MessagesRoute = Ember.Route.extend({
model: function() { return App.Message.find() }
});
App.MessagesController = Ember.ArrayController.extend({
toggle_visibility: function(){
debugger;
}
});
# index.html
{{#each model}}
<button class="close" {{action toggle_visibility this}}><i class="icon-eye-close"></i></button>
<p class="message_body lead">{{body}}</p>
<small class="source_number">from {{mobile_number}}, received {{date created_at}}</small>
{{/each}}
I've been spending the past few hours reading through the Ember Guides and while I've gotten an idea on what the different classes there are, I still can't visualize clearly how to go about it. Particularly, I'm not sure if this should be a route concern or a controller, and I know that if ever it was a controller responsibility, I know that it should be on an ObjectController but I've been having trouble making it work.
You can use ArrayController#itemController and define a controller for the individual record in your ModelArray. Then you have to specify in the Array Controller the Object Controller responsible for a single object, which you have to reference as well in Handlebars. You can do something like this:
JS:
App.MessageController = Ember.ObjectController.extend({
visibilityClass: function() {
var visibility = this.get('model.publiclyViewable');
return 'toggle-visibility mdi-action-visibility%#'.fmt(
visibility ? '':'-off'
);
}.property('model.publiclyViewable'),
actions: {
toggleVisibility: function() {
var model = this.get('model');
model.toggleProperty('publiclyViewable');
model.save();
}
}
});
Handlebars:
<script type="text/x-handlebars" data-template-name="messages">
<!--
At this point the {{each}} helper will know how to lookup for
the controller simply by it's name
-->
{{#each model itemController="message"}}
<div class="panel panel-primary">
<div class="panel-heading">
<div class="pull-left">
<h3 class="panel-title">{{title}}</h3>
</div>
<div class="pull-right">
<a {{action 'toggleVisibility'}}>
<i class={{visibilityClass}} style="color: #FFF"></i>
</a>
</div>
</div>
<div class="panel-body">
{{body}}
</div>
<div class="panel-footer">
<i class="mdi-communication-quick-contacts-dialer"></i> {{mobileNumber}}
<i class="mdi-notification-event-note"></i> {{createdAt}}
</div>
</div>
{{/each}}
</script>
(see fiddle)
Note: Updated to Ember 1.11.x-beta and changed the code a little bit
First many thanks to Mike Grassotti, for helping in the IRC. He helped resolved the bugs with the save method.
My problem is that in the console I see that records are created but they are not displayed.
I am using ember-data to create new record. The addComment function creates the record in a transaction, while the save function, only calls this.transaction.commit.
In the console after I click save(), the record seems created but handlebars doesn't display the newly created record. This is an excerpt of what I see in the console when I dig into the results of console.log
>committed: Object
firstRecordKind: "belongsTo"
firstRecordName: "post"
>firstRecordReference: Object
clientId: 4
id: "ember411"
>type: EmBlog.Comment
ClassMixin: Ember.Mixin
>FIXTURES: Array[1]
0: Object
body: "ty"
id: "ember411"
post: "1"
To create a new record, click on post -> then 'post title' -> at the bottom addComment- > then save and you will see that the record was not created.
The relevant bit of code from the jsfiddle. This controller will not have a route as it will be sideloaded
EmBlog.CommentNewController = Em.ObjectController.extend({
needs: ['postsShow'],
isAddingNew: false,
addComment: function(body){
console.log("this: ", this.toString());
var post = this.get('controllers.postsShow.content');
console.log(post);
transaction = this.get('store').transaction();
console.log(transaction);
console.log(this.get('content').toString());
this.set('content', transaction.createRecord(EmBlog.Comment, ({post: post })));
this.transaction = transaction;
console.log(this.get('content').toString());
this.set('isAddingNew', true);
},
save: function(){
var comment = this.get('content');
comment.one('didCreate', this, function() {
this.set('isAddingNew', false);
});
this.transaction.commit();
}
});
The relevant bit from the handlebars template
<script type="text/x-handlebars" data-template-name="posts/show">
<h1>Post</h1>
<h3> {{title}} </h3>
<h3> {{body}} </h3>
<br/>
<p> {{#linkTo 'posts.index'}} back {{/linkTo}}</p>
<p> {{#linkTo 'posts.edit' content}} Edit the post {{/linkTo}}</p>
<br/>
<b> Comments</b>
{{render 'comment/new' comments}}
</script>
<script type='text/x-handlebars' data-template-name='comment/new'>
{{#if controller.isAddingNew}}
<form {{action save on='submit'}}>
{{view Ember.TextArea valueBinding="body" placeholder="body"}}
<button type="submit"> save comment </button>
</form>
{{/if}}
<br/>
<div>
<button {{action addComment}} {{bindAttr disabled="isAddingNew"}}>Add Comment</button>
</div>
</script>
Thanks
hmm, maybe i'm blind, but i can't see any code for displaying comments in your templates.
something like
<ul>
{{#each comment in comments}}
<li>{{comment.body}}</li>
{{/each}}
</ul>
should probably do the trick.
This is pretty basic, but I've spent hours trying to figure out why I can't seem to use Handlebars's built-in #if helper inside the #each loop of my template. The second I insert a reference to {{#if}}, Chrome (or Safari) crash and the console reports:
Uncaught RangeError: Maximum call stack size exceeded
meta
targetSetFor
sendEvent
Ember.Evented.Ember.Mixin.create.trigger
Ember.CoreView.Ember.Object.extend.trigger
newFunc
(anonymous function)
Ember.View.Ember.CoreView.extend.invokeRecursively
newFunc
(repeats many times)
Why is this causing me a recursion fault in Ember?
<div id="gridChannelListDiv" tabindex="0">
{{#each item in content}}
{{#if item.hilight}}
<div class="gridCellHilight">
...
</div>
{{else}}
<div class="gridCell">
...
</div>
{{/if}}
{{/each}}
</div>
This happens even if the {{#if}} statement does nothing.
<div id="gridChannelListDiv" tabindex="0">
{{#each item in content}}
{{#if}}{{/if}} // this line will cause instant "oh snap" crash
<div class="gridCell">
{{item.name}}
</div>
{{/each}}
</div>
The associated ArrayController contains a simple list of 5 Ember objects in "content" and the templates work fine until I insert an #if.
Baffled.
There does not seem to be anything wrong with your code. Could be a bug in your version of ember or perhaps an incompatible version of a supporting library (handlebars/jQuery). Either that or something crazy going on in some other aspect of your application.
I created a simple app/controller and used it to get your template code up and running here: http://jsbin.com/ekemak/2/edit - Tried in both chrome and safari, in both cases app works with no js errors.
//app.js
App = Ember.Application.create({});
App.IndexRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', [
Em.Object.create({name: 'aaa', hilight: false}),
Em.Object.create({name: 'BBB', hilight: true}),
Em.Object.create({name: 'ccc', hilight: false})
]);
}
});
//index.hbs
<ul>
{{#each item in content}}
{{#if item.hilight}}
<div class="gridCellHilight">
<B>{{item.name}}</B>
</div>
{{else}}
<div class="gridCell">
{{item.name}}
</div>
{{/if}}
{{/each}}
</ul>
I have the following code in a ember.js template. userController is an ArrayController, with multipe "users" within.
{{#each CollaborativeEditor.userController}}
{{#view CollaborativeEditor.OnlineUserView userBinding="this"}}
<div class="avatar">
<div class="avatar_name">{{name}}</div>
<div class="avatar_status">{{status}}</div>
</div>
<div id="dropdown-1">
<ul>
<li><a href="#" {{action startChat target="onlineUser"}}>Talk to </a></li>
</ul>
</div>
{{/view}}
{{/each}}
This is the code of the respective view:
CollaborativeEditor.OnlineUserView = Ember.View.extend({
tagName: 'li',
startChat : function() {
console.log(this.get('user'));
}
});
Although, the name and status is set correctly for each user, the startChat action attached to the link always prints the first user of the array to the console.
What is wrong with the binding ?
Thanks a lot for your request, to put it in a jsfiddle !
While I was trying to reproduce the error there, I realized the problem, and it has nothing to do with ember.
The div with id="dropdown-1" was called from another link and it was always the same id, therefore always the same action with its user-binding.
Now I've bound the Id to the user-object and it works perfectly.