I'm new to Ember and Handlebars.
I am trying to create a table with a dynamic content.
I add the code into jsfiddle
I have the following code:
<script type="text/x-handlebars">
<table border="1" bordercolor="FFCC00" style="background-color:FFFFCC" width="400" cellpadding="3" cellspacing="3">
{{#each Table.tableController}}
<tbody>
<tr>
<td width ="30%">{{title}}</td>
<td width ="30%">{{artist}}</td>
<td width ="40%">{{genre}}</td>
</tr>
</tbody>
{{/each}}
</table>
A simple table binding a content from my controller.
And here is my controller and application:
Table = Ember.Application.create({});
Table.Cell = Ember.Object.extend({
title: null,
artist: null,
genre: null,
listens: 0
});
Table.tableController = Ember.ArrayProxy.create({
content: [],
init: function() {
var data = Table.Cell.create({
title: 'Ruby',
artist: 'Kaiser Chiefs',
genre: 'Indie Rock',
});
this.pushObject(data);
data = Table.Cell.create({
title: 'Somebody Told Me',
artist: 'Killers',
genre: 'Indie Rock',
});
this.pushObject(data);
},
createSong: function(title, artist, genre) {
var cell = Table.Cell.create({
title: title,
artist: artist,
genre: genre,
});
this.pushObject(cell);
},
});
That's working just fine.
Now I want to add a new song, and I am doing this via Chrome console with the following line:
Table.tableController.createSong('Feiticeira', 'Deftones', 'Alternative');
I can verify that a new Object is created and inserted into my content array.
But there is no changes into my table.
What am I doing wrong?
Why my table is not creating a new content?
I pushed this code into jsfiddle to get help you help me.
Here's the link to the code again.
UPDATED ANALYSIS
You forgot calling this._super(); in the init, which prevented your instance to have its binding management setup. (Fixed revision)
You felt in a typical Ember gotcha: initial values should never be objects, as they would be shared by all class instances... See this article for more informations.
FIX SAMPLE - Still recommended implementation
Updated your JSFiddle # http://jsfiddle.net/MikeAski/AgyAk/12/ with a few improvements (The most notable: make use of a controller rather than directly ArrayProxy. It is more idiomatic and ready to switch to Router infrastructure).
Related
Im trying to implement Vue.js + jQuery's DataTables but there's a weird things happening.
Check this fiddle on firefox (not working on chrome):
http://jsfiddle.net/chrislandeza/xgv8c01y/
when I change the state of DataTable (e.g. sort, search, etc.):
Newly added data on the list disappears
The DOM is not reading the directives or the vue properties
I'm pretty sure anyone who tried to mix vue.js+datatables experienced this problem. what did you do to solve this?
or is there a pure Vue.js script/plugin that has the same (or close) functionality like jquery's DataTable? (pagination, searching, sorting, number of entries to show, etc.).
here's the code from the fiddle above:
HTML:
<div class='container-fluid' id="app">
<div class='row'>
<div class='col-md-9'>
<table class="table table-bordered" id="app-datatable">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-repeat="user: users">
<td>{{ user.name }}</td>
<td>{{ user.age }}</td>
<td>
<button type="button" v-on="click: foo(user)">Action</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class='col-md-3'>
<div class="form-group">
<label>Name</label>
<input type="text"
class="form-control"
v-model="newUser.name"
>
</div>
<div class="form-group">
<label>Age</label>
<input type="name"
class="form-control"
v-model="newUser.age"
>
</div>
<button type="submit" class="btn btn-primary" v-on="click: addUser()">Add</button>
</div>
</div>
</div>
JavaScript:
$(document).ready(function () {
var dT = $('#app-datatable').DataTable();
});
var vm = new Vue({
el: '#app',
data: {
newUser: {},
users: [
{name: 'Chris', age: 1},
{name: 'John', age: 2}
]
},
methods:{
addUser: function(){
this.users.push(this.newUser);
this.newUser = {};
},
foo: function(user){
console.log(user.name);
}
}
});
any suggestions are greatly appreciated.
To get the DataTables plugin integrated correctly with Vue, there are a few things to keep in mind:
Per your example, you can use var dT = $('#app-datatable').DataTable(); to initialize the DataTables if you already have the data ready and rendered to the DOM. If you don't have the DOM <table></table> fully rendered (perhaps due to data populated via a delayed ajax call), you can't initialize the DataTables until the data is ready. As an example, if you have a fetchData method in your component, you can initialize the DataTable once the promise has been fulfilled.
To update the table once initialized, perhaps due to a change in the underlying table data, the best (perhaps the only way), is to first destroy the table, before the new data is received and written to the DOM by Vue:
var dT = $('#app-datatable').DataTable();
dT.destroy();
Then, once the data (in your case, the users array) has been updated,
re-initialize the DataTable as so:
this.$nextTick(function() {
$('#app-datatable').DataTable({
// DataTable options here...
});
})
The $nextTick is necessary to ensure Vue has flushed the new data to the DOM, before re-initializing. If the DOM is updated after the DataTable plugin has been initialized, you'll see the table data, but the usual sorting, paging, etc. won't work.
Another important point, is to have a row id in your dataset, and set the key in the <tr></tr>:
<tr v-repeat="user: users" track-by="id">
Without the track-by, Vue will complain when flushing new data to the DOM after DataTables has been initializing, likely due to not finding DOM elements hi-jacked by DataTables.
maybe you can use lifecycle hooks, infact these weird things are caused by competition of manipulating the DOM. in your vue instance, add a created() hook then initialize the DataTable, just like the following:
var vm = new Vue({
el: '#app',
data: {
newUser: {},
users: [
{name: 'Chris', age: 1},
{name: 'John', age: 2}
]
},
methods:{
addUser: function(){
this.users.push(this.newUser);
this.newUser = {};
},
foo: function(user){
console.log(user.name);
}
},
created(){
this.$nextTick(function() {
$('#app-datatable').DataTable();
})
}
});
I have externally supplied table data (not an Ajax call from DataTable) and destroying / re-creating the table using only these lifecycle hooks is working for me:
beforeUpdate: function() {
if (this.dataTable) {
this.dataTable.destroy()
}
},
updated: function() {
this.dataTable = $(this.$el).DataTable()
}
From https://alligator.io/vuejs/component-lifecycle/
"The beforeUpdate hook runs after data changes on your component and the update cycle begins, right before the DOM is patched and re-rendered. It allows you to get the new state of any reactive data on your component before it actually gets rendered."
"The updated hook runs after data changes on your component and the DOM re-renders. If you need to access the DOM after a property change, here is probably the safest place to do it."
I'm trying to implement an interface for a logger with EmberJS and has been great but I run into an wall.
Currently I have the following code:
logs.hbs
...
{{input action="search" onEvent="keypress" value=searchText class="search" placeholder="Search"}}
...
<table id="log" class="log">
<thead>
<th>Service</th>
<th>Start Time</th>
<th>End Time</th>
<th>URL</th>
<th>Method</th>
<th>User</th>
<th>Response Code</th>
</thead>
<tbody>
{{render "dashboard/logTableLine" model}}
</tbody>
</table>
...
logTableLine.hbs
{{#each model}}
{{#link-to "dashboard.log" _id tagName="tr"}}
<td>{{ServiceID}}</td>
<td>{{DateString StartTime}}</td>
<td>{{DateString EndTime}}</td>
<td>{{URL}}</td>
<td>{{Method}}</td>
<td>{{AuthName Auth}}</td>
<td>{{StatusCode}}</td>
{{/link-to}}
{{/each}}
and my app.js
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true
});
App.Router.map(function(){
this.resource("dashboard", { path: "/" }, function(){
this.route("logs", { path: "/logs" });
this.route("log", { path: "/logs/log/:_id" });
});
});
App.DashboardLogsRoute = Ember.Route.extend({
model: function(){
return Ember.$.getJSON("/logs.json");
},
actions: {
search: function(value){
if(value != '')
this.set("content", Ember.$.getJSON("/logs.json?search=" + value));
}
}
});
App.DashboardLogRoute = Ember.Route.extend({
model: function(params){
return Ember.$.getJSON("/logs/log/" + params._id + ".json");
}
});
My problem is binding that model to the view, so that after the search call to the server the view rerenders. I want to search all data, not only what I have in the interface (last 100 records).
So, first question: why the view isn't updating (binded) to the model?
Second: Is there a better way (best pratice) to do this?
Thanks in advance for any help.
Right now you're implementing search without triggering a re-route. So if the user searched and then wanted to go back to the page before they were at before they searched, they would have no way to do that. What you want to do is re-route the user to the same page, but pass in query parameters. Then in the model function you can see what parameters were passed in and fetch the data accordingly. Ember's latest and greatest way of doing this is the query-params feature: http://emberjs.com/guides/routing/query-params/ It's been in the works for a while and is now in beta. I'd give it a go, because it's a really clean, intuitive way of solving this problem. Good luck!
I have a page where a user edits an uploaded photo and applies a tag for individual photos on the model using Ember-Data. However, after saving on the controller, and transitioning to a page with all of the photos listed, the tag appears on all of the items and replaces any that existed before. If I reopen the page the tag has not saved at all.
I'm not quite sure what is causing this issue. Any help would be appreciated.
//The photo model
App.Photo = DS.Model.extend({
title: attr(),
description: attr(),
image: attr(),
width: attr(),
height: attr(),
important_top: attr(),
important_left: attr(),
important_bottom: attr(),
important_right: attr(),
created: attr('date'),
authors: hasMany('author'),
app_data: {
tags: []
},
imageURL: function() {
return document.location.origin + '/media/' + this.get('image');
}.property('image'),
});
// Photo edit route
App.PhotoseditRoute = Ember.Route.extend({
model: function() {
this.store.find('photo');
// Populate model with photos from the lowest upload ID to higest.
return this.store.filter('photo', function(image){
return image.get('id') >= App.Upload.uploadedImages[0]; // Get data from uploader
});
},
activate: function() {
$('#page-title').text('Edit Photos');
},
});
// Photo Edit Controller
App.PhotoseditController = Ember.ObjectController.extend({
parsedTags: function() {
// Get tags from the view's input field
var tagData = this.get('app_data').tags;
// Convert tags to an array
return tagData.join(',');
}.property('app_data'),
// Watch parsedTags and apply array to model when converted
parsedDataChanged: function() {
Ember.run(this, function() {
this.get('app_data').tags = this.get('parsedTags').split(',');
});
}.observes('parsedTags'),
actions: {
save: function() {
var that = this;
that.get('model').save().then(function(success) {
that.transitionToRoute('photos');
});
}
}
});
// Photo edit template
<h2>Edit Photo Meta Data</h2>
<button {{action 'save'}} style="float:right;">Save All</button>
<table>
<thead>
<tr>
<th></th>
<th>Title</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{{#each object in content itemController='photosedit'}}
<tr>
<td><img {{bind-attr src="imageURL"}} width="200" /></td>
<td>{{input title valueBinding="title"}}</td>
<td>{{input description valueBinding="description"}}</td>
<td>{{input parsedTags valueBinding="parsedTags"}}</td>
</tr>
{{else}}
<tr>
<td colspan="6">No photos yet.</td>
</tr>
{{/each}}
</tbody>
</table>
The problem comes from the way you declare app_data. This variable will be shared across all instances of App.Photo, which explains why you see all photos getting the same tags.
You can solve this by initializing the attribute differently:
App.Photo = DS.Model.extend({
init: function() {
this._super();
this.app_data = { tags: [] };
}
});
instead of
App.Photo = DS.Model.extend({
app_data = { tags: [] }
});
See this JsBin for an example highlighting the problem and the solution http://emberjs.jsbin.com/wawoq/3
You need to check that the store gets called with the correct data when you call save() and trace back from there.
Aside from this, parsedTags and parsedDataChanged seem to be referring to each other.
I'm new to Ember, and I'm following along with their Todo tutorial and making a basic app to create blog posts, adjust their code for my purposes. The app was working fine until I added an itemController to the template and a controller to handle the isCompleted event. Rather than showing the content, as it did before, it shows: <Posts.Post:ember257:1> which appears to be the model name rather than content. The Ember inspector says the model has the right attribute. It just doesn't display properly. Here's some code:
<script type="text/x-handlebars" data-template-name="posts">
<section id="postapp">
<section id="main">
<ul id="post-list">
// new code added
{{#each itemController="post"}}
<li {{bind-attr class="isCompleted:completed"}}>
{{input type="checkbox" checked=isCompleted class="toggle"}}
<label>{{title}}</label>
<p>{{content}}</p>
</li>
{{/each}}
</ul>
</section>
</section>
</script>
And the relevant JavaScript (see the bottom at PostController to see the only change after the code worked):
Posts.Post = DS.Model.extend({
title: DS.attr('string'),
content: DS.attr('string'),
isCompleted: DS.attr('boolean')
});
Posts.Post.FIXTURES = [
{
id: 1,
title: "JavaScript: The Dark Side",
content: "Here is a bunch of information on the dark side of " +
"Javascript. Welcome to hell!"
},
{
id: 2,
title: "The glory of underscore",
content: "Here, we're going to talk about the many uses of the " +
"underscore library. Read on!"
},
{
id: 3,
title: "Objectifying Objects",
content: "Objects are confusing, eh? Let's play around with objects " +
"a bit to see how to really use them."
}
];
// This is the only code that changed before the app was functioning properly
Posts.PostController = Ember.ObjectController.extend({
isCompleted: function(key, value){
var model = this.get('model');
if (value === undefined) {
// property being used as a getter
return model.get('isCompleted');
} else {
// property being used as a setter
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
Any insight as to why the right content isn't displayed would be greatly appreciated.
I just figured out the problem. content is a property all Ember controllers, so my variable name for the post content was creating some confusion when Ember was rendering the page. When I changed the variable name in my model and other places to post_content, content was rendering properly in the page.
// template
{{#each itemController="post"}}
<li {{bind-attr class="isCompleted:completed"}}>
{{input type="checkbox" checked=isCompleted class="toggle"}}
<label>{{title}}</label>
<p>{{post_content}}</p>
</li>
{{/each}}
//model
Posts.Post = DS.Model.extend({
title: DS.attr('string'),
post_content: DS.attr('string'),
isCompleted: DS.attr('boolean')
});
And problem solved.
I'm new with emberjs so it may be possible that I totally missed a few things.
I am trying to render the body of a table with content I retrieve from an Ajax request.
I use an ArrayController and a {{#collection}} in my view that I bind to the content of the controller (as the doc suggests).
It works when I set an initial value to content inside my ArrayController but it doesn't automatically update when I call .set('content', [...]).
Note that it only fails when I set the tagName of my view to 'tobdy', it works if for example I use 'pre' (or pretty much anything else).
JSFiddles that show that issue:
http://jsfiddle.net/midu/DjxG4/10/ (with tbody, should update after 2s, but doesn't)
http://jsfiddle.net/midu/DjxG4/11/ (with pre, updates after 2s)
My questions are:
is this a bug or did I not understand how it should be working?
any advice for rendering the body of a table in a template?
This is what my code looks like:
index.html
<table id="the-table">
<thead>
<tr>
<th>status</th>
<th>title</th>
<th>url</th>
<th># messages</th>
<th>judging status</th>
</tr>
</thead>
</table>
<script type="text/x-handlebars" data-template-name="submissions">
{{#collection contentBinding="App.submissionsController"}}
<tr>
<td>{{content.status}}</td>
<td>{{content.title}}</td>
<td>note</td>
<td>{{content.nbMessages}}</td>
<td>{{content.judgingStatus}}</td>
</tr>
{{/collection}}
</script>
index.js
var App = Em.Application.create({
ready: function () {
App.submissionsController.index();
this._super();
}
});
// model
App.Submission = Em.Object.extend({
title: null,
judgingStatus: null,
status: null,
url: null,
nbMessages: 0
});
// controller... not really.
App.submissionsController = Em.ArrayController.create({
content: [ App.Submission.create({title: 'kevin', status: 'a', judgingStatus: 'b', url: 'http://google.com', nbMessages: 1}) ],
index: function () {
// simulates data that arrives to the page after a few seconds (think ajax request)
window.setTimeout(function () {
App.submissionsController.set('content', [
App.Submission.create({title: 'stefano', status: 'c', judgingStatus: 'd', url: 'http://stackoverflow.com', nbMessages: 2})
]);
}, 2000);
}
});
Em.View.create({
templateName: 'submissions',
tagName: 'tbody'
}).appendTo('#the-table');
Two things I did to fix the code which can be seen on jsfiddle.
I would intermittently get uncaught exception: Error: <Ember.View:ember161> - Unable to find template "submissions" because the handlebar script is not evaluated before the app is inserted into the DOM. See here.
Change the view to {{#each}} instead of the deprecated {{#collection}}. I haven't found a definite source on the deprecation – but it's indicated here and here.