How to build comment system in ember.js - javascript

I am fairly a newbie to ember.js. I currently working on a school project which is essentially a message board (forum) application that allows users to add posts with comments.
My application contains three models: courses, messages, comments
course->hasMany->messages->hasMany->comments
So far I have been able to view all the courses and related messages using filter query to my server. Adding new messages works fine also except that it is not updating the new message added to the screen.
Problem: It is only when I refresh the page the new message I added is displayed.
App.Router.map(function() {
this.resource('home', { path : '/'}, function() {
this.resource('mycourse', { path : ':course_id' } );
});
});
App.MycourseRoute = Ember.Route.extend({
model: function(params) {
// the model for this route is a new empty Ember.Object
var string = '{"filters":[{"name":"courseid","op":"eq","val":'+params.course_id+'}]}'
return this.store.find('message', { q: string });
}
});
App.HomeRoute = Ember.Route.extend(
{
model: function() {
return this.store.find('course');
}
});
Here is my message controller:
App.MycourseController = Ember.ArrayController.extend({
actions: {
addMessage: function(messageText) {
var message = messageText;
var messageAdd =this.store.createRecord('message', {
message: message,
posttime: "12:00pm",
courseid: 4,
userid: 1
});
messageAdd.save();
}
}
});
My html part:
<script type="text/x-handlebars" id="home">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
{{#each}}
<li>{{#link-to 'mycourse' this.id}}{{name}}{{/link-to}}</li>
{{/each}}
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">Subscribed Courses:</h1>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" id="mycourse">
<button class="btn btn-default" type="button" id="create-message" {{action "addMessage" messageTxt}}>
Post!
</button>
{{input type="text" value=messageTxt}}
<br><br>
{{#each}}
<b>{{message}}</b>
<li>{{posttime}}</li>
<li>User name: {{user.username}}</li>
<li>Course name: {{course.alt_name}}</li>
<h4>Comments:</h4>
{{#each comments}}
<li>{{comment}}</li>
{{/each}}
<br>
{{/each}}
</script>

Turns out when you use findQuery (which I believe is the same as using find with query parameters), Ember does not return a live updating array, whereas it does for a straight up find/findAll. See this question on that exact issue that I asked a while back.
The solution here (adapted from kingpin2k's answer to said question) is to use filter to trick Ember into auto-updating:
App.MycourseRoute = Ember.Route.extend({
model: function(params) {
// the model for this route is a new empty Ember.Object
var string = '{"filters":[{"name":"courseid","op":"eq","val":'+params.course_id+'}]}'
return this.store.find('message', { q: string });
},
setupController:function(controller, model){
var filter = this.store.filter('color', function(color){
return model.contains(color);
});
this._super(controller, filter);
}
});

Related

Ember.js Not Updating DOM with Observed Value

I've got an Ember.js app with this javascript:
var App = Ember.Application.create({
LOG_TRANSITIONS: false,
LOG_TRANSITIONS_INTERNAL: false,
LOG_VIEW_LOOKUPS: false,
});
App.Router.map(function() {
this.route('messageviewer', { path: '/message_viewer' });
});
App.Message = Ember.Object.extend({
message: {},
topic: '',
});
App.Message.reopenClass({
messages: {},
topics: [],
getAllMessagesList: function(){
var ret = [];
for(var i = 0; i < this.topics.length; i++){
ret = ret.concat(this.messages[this.topics[i]]);
}
return ret;
}.observes('App.Message.messages');
addMessage: function(message){
console.log(message);
if(!(message.topic in this.topics)){
this.topics.push(message.topic);
this.messages[message.topic] = [];
}
this.messages[message.topic].push(App.Message.create({
message: message,
topic: message.topic,
}));
},
});
App.MessageviewerRoute = Ember.Route.extend({
model: function(){
return App.Message.getAllMessagesList();
},
});
App.MessageviewerView = Ember.View.extend({
templateName: 'messageViewer',
didInsertElement: function(){
Ember.run.scheduleOnce('afterRender', this, fayeStartListening);
},
willDestroyElement: function(){
fayeStopListening();
},
});
Here's the template that goes with the view:
<div class="row">
<div id="filter-panel" class="col-md-3">
<h2>Topics</h2>
<div class="btn-group-vertical full-width" data-toggle="buttons">
<label class="btn btn-primary">
<input type="radio" name="topics">Test 1</input>
</label>
<label class="btn btn-primary">
<input type="radio" name="topics">Test 2</input>
</label>
</div>
{{#with content}}
{{#each}}
{{!-- Make a topic list for testing --}}
<h4>{{topic}}</h4>
{{/each}}
{{/with}}
</div>
</div>
There's also a Faye client running, and it's subscribed to a channel that gets quite a few messages/sec. In its subscription, it calls App.Message.addMessage(message); and I know this is working based on the log output in addMessage. Also, I've confirmed in the Ember Inspector that the model is being updated. However, the DOM is not changing. If I switch to a different route and come back, the list shows all the items gathered while the Faye client was listening. I'm not quite sure what I should do to get the list to update dynamically.
I did notice that if I do {{#each in content}} or {{#each in model.content}} instead of the {{#with}} block then I get an error "Cannot set property 'dataSourceBinding' of undefined". Also, I've seen people suggest using this.set(..., ...) but that won't work in my case because I'd have to call it from a static context so it's undefined.
Does anyone know how to convince Ember to update the DOM in real time?
Use pushObject or addObject instead of push, it's analogous to the setter for arrays.
Since you're trying to watch the messages object, you are watching whether the reference changes, not whether or not anything under it changed.
http://emberjs.com/api/classes/Ember.Observable.html#method_notifyPropertyChange

How can you filter a data-list to render into multiple outlets in emberjs

How can you filter a data-list to render into multiple outlets in emberjs.
What I have now in not really working, but may help you understand what I want to achieve.
I can solve this by making multiple file-list.hbs template-files ( where I change file in the each to fileList1 or fileList2, ...), but that doesn't seem right.
What I want to achieve
I have a documents page where I want to list all of the document in the file list (see fixtures file). But instead of printing out one files-list, I want to split the lists so I have multiple lists according to the filter.
Please look at the code to understand it better ^^
Can anyone help? :)
File.FIXTURES
App.File.FIXTURES = [
{
id: 1,
showHomepage: false,
filter: 'filter1',
url: '/file1.pdf',
description: 'file1'
},
{
id: 2,
showHomepage: false,
filter: 'filter2',
url: '/file2.pdf',
description: 'file2'
},
{
id: 3,
showHomepage: true,
filter: 'filter2',
url: '/file3.pdf',
description: 'file3'
},
{
id: 4,
showHomepage: true,
filter: 'filter3',
url: '/file4.pdf',
description: 'file4'
}
];
Route
App.InfoDocumentenRoute = Ember.Route.extend({
model: function() {
var store = this.store;
return Ember.RSVP.hash({
fileList1: store.find('file' , { filter: "filter1" }),
fileList2: store.find('file' , { filter: "filter2" }),
fileList3: store.find('file' , { filter: "filter3" })
});
},
renderTemplate: function() {
this.render('file-list', { // the template to render
into:'info.documenten', // the route to render into
outlet: 'file-list-filter1', // the name of the outlet in the route's template
controller: 'file' // the controller to use for the template
});
this.render('file-list', { // the template to render
into:'info.documenten', // the route to render into
outlet: 'file-list-filter2', // the name of the outlet in the route's template
controller: 'file' // the controller to use for the template
});
this.render('file-list', { // the template to render
into:'info.documenten', // the route to render into
outlet: 'file-list-filter3', // the name of the outlet in the route's template
controller: 'file' // the controller to use for the template
});
}
});
info/documents.hbs
{{ outlet file-list-filter1 }}
{{ outlet file-list-filter2 }}
{{ outlet file-list-filter3 }}
file-list.hbs
<ul class="download-list">
{{#each file in file}}
<li class="download-list__item">
<a {{bind-attr href=file.url}} target="_blank" class="download-list__link">
<i class="icon-download download-list__link__icon"></i>
{{file.description}}
</a>
</li>
{{else}}
<li>
Geen documenten beschikbaar.
</li>
{{/each}}
I think the best way to go about this would be to declare your file-list.hbs as a partial and include it within your other templates where needed as: {{partial "file-list"}}. In your showHomepage where you only want to use it a single time, merely include the {{partial "file-list"}} within your showHomepage.hbs.
Then, for your InfoDocumentRoute, put the following to declare your model as an array of filelists:
App.InfoDocumentenRoute = Ember.Route.extend({
model: function() {
var store = this.store;
return [
store.find('file' , { filter: "filter1" }),
store.find('file' , { filter: "filter2" }),
store.find('file' , { filter: "filter3" })
];
}
});
And your InfoDocument.hbs as:
{{#each file in model}}
{{partial "file-list"}}
{{/each}}
Which will then render the file-list template for each item in the model array.
More info about partials
So from what i gather about your question you want to filter your model on your filter property on the model. I am sure there are a few ways to accomplish this but here is another possible solution that could spark another solution.
So in the route I returned the models. Then in the controller I created properties that are filtering the array of models from the route. Then in the template I loop over the array that filter property gives me in the controller and output in the template.
Heres JSBin. http://emberjs.jsbin.com/vunugida/5/edit
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('File');
}
});
App.IndexController = Ember.ArrayController.extend({
filter1: function() {
return this.filter(function(item) {
return item.get('filter') === "filter1";
});
}.property(),
filter2: function() {
return this.filter(function(item) {
return item.get('filter') === "filter2";
});
}.property(),
filter3: function() {
return this.filter(function(item){
return item.get('filter') === "filter3";
});
}.property()
});
TEMPLATE:
<script type="text/x-handlebars" data-template-name="index">
<h1>Index Template</h1>
<ul>
{{#each}}
<li>{{url}}</li>
{{/each}}
</ul>
<p>Filter 1</p>
{{#each filter1}}
<li>{{url}}</li>
{{/each}}
<p>Filter 2</p>
{{#each filter2}}
<li>{{url}}</li>
{{/each}}
<p>Filter 3</p>
{{#each filter3}}
<li>{{url}}</li>
{{/each}}
</script>

Ember multiple Json request Error while loading route Object has no method 'addArrayObserver'

im having a problem with my ember app. Im new to it, and trying to do something fun. So the idea of this app is to go and fetch a list of artists from a server via an ajax call, and then if you click the artist it will go to the server again and fetch the albums via another ajax call.
So the first part is working, it is actually fetching the artists through the ajax call when i click on "music library", but then when clicking on the artist it throws the following error:
Assertion failed: Error while loading route: TypeError: Object [object Object] has no method 'addArrayObserver'
I've read so many different options, and i think im on the right track because by printing on the console i can see that it is actually going to the server and fetching the right artist's albums, but the error is throw at the last moment, so it is not displaying the albums. I was also able to show the albums when reloading or typing the url (not now, since i changed the code to implement the afterModel)
So, here is my code:
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true
});
App.Library = Ember.Object.extend({
name: null,
artist: []
});
App.Library.reopenClass({
loadArtist: function() {
var artistList = Em.A();
$.getJSON('url').then(function(data){
//json parsing, creating a library object and putting it into the array
});
return artistList;
}
});
App.Artist = Ember.Object.extend({
id: null,
name: null,
coverArt: null,
albumCount: null
});
App.Albums = Ember.Object.extend({
albums: []
});
App.Artist.reopenClass({
loadAlbums: function(params) {
var albumsJson = 'url' + params.artist_id +'';
var albumList = Em.A();
$.getJSON(albumsJson).then(function(data){
//parsing json, creating artist objects and pushing them into the array
});
return albumList;
//});
}
});
/*****************************ROUTER**************************************************************************************/
App.Router.map(function() {
// put your routes here
this.resource('library', function() {
this.resource('artist', { path: '/:artist_id'});
});
});
App.IndexRoute = Ember.Route.extend({
model: function() {
var hi = ['Welcome'];
return hi;
}
});
App.LibraryRoute = Ember.Route.extend({
model: function() {
return App.Library.loadArtist();
}
});
App.ArtistRoute = Ember.Route.extend({
model: function(params) {
this.transitionTo('artist', params);
},
afterModel: function(params, transition){
var artist = Em.A();
if(params.artist_id==null){
artist.push(App.Artist.create({artist_id: params.id}));
} else {
artist.push(App.Artist.create({artist_id: params.artist_id}));
}
return App.Artist.loadAlbums(artist[0]);
}
});
/**************************************CONTROLLERS***********************************************************************************/
App.ArtistController = Ember.ArrayController.extend({
needs: "library"
});
App.LibraryController = Ember.ArrayController.extend({});
I would really appreciate some help!
Also, the HTML is as follows:
<script type="text/x-handlebars">
<div class="navbar navbar-default">
<div class="navbar-inner">
<a class="navbar-brand" href="#">My Library</a>
<ul class="nav navbar-nav">
<li>{{#linkTo 'index'}}Home{{/linkTo}}</li>
<li>{{#linkTo 'library'}}Music Library{{/linkTo}}</li>
</ul>
</div>
</div>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<div class="container">
{{#each item in model}}
<h1>{{item}}</h1>
{{/each}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="library">
<div class="container">
<div class="row">
<div class="col-md-4">
<table class="table">
{{#each model}}
<tr><td>
{{name}}
</td></tr>
{{#each artist}}
<tr><td>
{{#linkTo 'artist' this}}
{{name}}
{{/linkTo}}
<!--<a {{action 'selectArtist' this}}> {{name}} </a>-->
</td></tr>
{{/each}}
{{/each}}
</table>
</div>
<div class="col-md-8">
<p>Albumes</p>
{{outlet}}
</div>
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="artist">
<div class="container">
<div class="col-md-4">
<table class="table">
<tr><td><p>{{controllers.library.artist.name}}</p></td></tr>
{{#each itemController='album'}}
<tr><td>
{{{name}}}
</td></tr>
{{/each}}
</table>
</div>
</div>
</script>
Thanks a lot!!
To get rid of the error you need to modify the model function of the App.ArtistRoute, to return an array as App.ArtistController is an Ember.ArrayController.
For example,
App.ArtistRoute = Ember.Route.extend({
model: function(params) {
//this.transitionTo('artist', params);
return [];
},
....
Or even place the code of afterModel function in model function to retrieve the albums of this artist.
Although i'm not certain if you really want the model of your artist context to be the albums, it does not look correct to me. I would suggest to make the App.ArtistController aν Ember.ObjectController, assign the model to an App.Artist object and store the albums related to this artist in a property of the App.Artist class. In that case you will need to add a property in App.Artist and create a class of App.Album.
With this in mind have a look at the following example which is a very rough modification of your code (caution the App.ArtistController has not been switched instead its model is an array of albums),
http://emberjs.jsbin.com/AdOfiyiN/2#/library/2
OK, i solved it using this question:
Why isn't my ember.js route model being called?
Instead of putting the logic in the model or afterModel, i just needed to set the controller.
Hope it helps to someone.
Best!

Accessing computed properties outside of their model in ember.js

I have an ember application with a model called users.js with associated controllers and routing. In my usersController.js, I have a function which counts the number of users in the system. I can then display this figure in my users template. However, I want to display that figure in my index template instead, is this possible? How would I go about it- right now the figure doesn't seem to be available for use outside of my users model.
Here's my usersController-
App.UsersController = Ember.ArrayController.extend({
sortProperties: ['name'],
sortAscending: true,
numUsers: function() {
return this.get('model.length');
}.property('model.[]')
});
And my html-
<script type = "text/x-handlebars" id = "index">
<h2>Homepage</h2>
//This is where I would like the figure to be
<h3>There are {{numUsers}} users </h3>
</script>
<script type = "text/x-handlebars" id = "users">
<div class="col-md-2">
{{#link-to "users.create"}}<button type="button" class="btn btn-default btn-lg"><span class="glyphicon glyphicon-plus"></button> {{/link-to}}
//This works fine
<div>Users: {{numUsers}}</div>
</div>
<div class="col-md-10">
<ul class="list-group">
{{#each user in controller}}
<li class="list-group-item">
{{#link-to "user" user}}
{{user.name}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
</div>
</script>
You can just load all users in the IndexRoute, something like this:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('user');
}
});
And extract the shared logic, in that case user count, to a mixin, and use where needed:
App.UsersCountMixin = Ember.Mixin.create({
numUsers: function() {
return this.get('model.length');
}.property('model.[]')
});
App.IndexController = Ember.ArrayController.extend(App.UsersCountMixin, {
});
App.UsersController = Ember.ArrayController.extend(App.UsersCountMixin, {
sortProperties: ['name'],
sortAscending: true
});
So {{numUsers}} will be avaliable in your index template.
To share logic with more than one model, you will need to create some alias for model property to avoid ambiguity:
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
users: this.store.find('user'),
subjects: this.store.find('subject'),
})
}
});
App.UsersCountMixin = Ember.Mixin.create({
users: Ember.required(),
numUsers: function() {
return this.get('users.length');
}.property('users.[]')
});
App.SubjectsCountMixin = Ember.Mixin.create({
subjects: Ember.required(),
numSubjects: function() {
return this.get('subjects.length');
}.property('subjects.[]')
});
App.UsersController = Ember.ArrayController.extend(App.UsersCountMixin, {
users: Ember.computed.alias('model'),
sortProperties: ['name'],
sortAscending: true
});
App.SubjectsController = Ember.ArrayController.extend(App.SubjectsCountMixin, {
subjects: Ember.computed.alias('model'),
sortProperties: ['name'],
sortAscending: true
});
App.IndexController = Ember.ArrayController.extend(App.UsersCountMixin, App.SubjectsCountMixin, {});
Of course this is a lot of code to just show the data length, since you can just use:
<h3>There are {{users.length}} users </h3>
<h3>There are {{subjects.length}} subjecst </h3>
But I think you will have more complex computed properties to share. In that cases, mixins is a good way to achieve it.

EmberJS nesting

Given the following code, I thought the person.index and nested person.finish routes would use the PersonController content/model property since theirs was empty/undefined? What am I doing wrong? http://jsfiddle.net/EasyCo/MMfSf/5/
To be more concise: When you click on the id, the {{id}} and {{name}} are blank? How do I fix that?
Functionality
// Create Ember App
App = Ember.Application.create();
// Create Ember Data Store
App.Store = DS.Store.extend({
revision: 11,
adapter: 'DS.FixtureAdapter'
});
// Create parent model with hasMany relationship
App.Person = DS.Model.extend({
name: DS.attr( 'string' ),
belts: DS.hasMany( 'App.Belt' )
});
// Create child model with belongsTo relationship
App.Belt = DS.Model.extend({
type: DS.attr( 'string' ),
parent: DS.belongsTo( 'App.Person' )
});
// Add Person fixtures
App.Person.FIXTURES = [{
"id" : 1,
"name" : "Trevor",
"belts" : [1, 2, 3]
}];
// Add Belt fixtures
App.Belt.FIXTURES = [{
"id" : 1,
"type" : "leather"
}, {
"id" : 2,
"type" : "rock"
}, {
"id" : 3,
"type" : "party-time"
}];
App.Router.map( function() {
this.resource( 'person', { path: '/:person_id' }, function() {
this.route( 'finish' );
});
});
// Set route behaviour
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Person.find();
},
renderTemplate: function() {
this.render('people');
}
});
Templates
<script type="text/x-handlebars">
<h1>Application</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" id="people">
<h2>People</h2>
<ul>
{{#each controller}}
<li>
<div class="debug">
Is the person record dirty: {{this.isDirty}}
</div>
</li>
<li>Id: {{#linkTo person this}}{{id}}{{/linkTo}}</li>
<li>Name: {{name}}</li>
<li>Belt types:
<ul>
{{#each belts}}
<li>{{type}}</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" id="person">
<h2>Person</h2>
Id from within person template: {{id}}<br><br>
{{outlet}}
</script>
<script type="text/x-handlebars" id="person/index">
Id: {{id}}<br>
Name: <a href="#" {{action "changeName"}}>{{name}}</a><br><br>
{{#linkTo index}}Go back{{/linkTo}}<br>
{{#linkTo person.finish}}Go to finish{{/linkTo}}
</script>
<script type="text/x-handlebars" id="person/finish">
<h2>Finish</h2>
{{id}}
</script>
You can use this in your router:
model: function() {
return this.modelFor("person");
}
Instead of your's:
controller.set('content', this.controllerFor('person'));
Your views were served through different controllers, either Ember's generated one or the one you defined PersonIndexController and that contributed to the issue you were facing. Instead of patching your original example to make it work, i instead reworked it to show you how you should structure your views/routes to leverage Emberjs capabilities.
You should design your application/example as a series of states working and communicating with each other and captured in a Router map. In your example, you should have a people, person resource and a finish route with corresponding views and controllers, either you explicitly create them or let Ember do that for you, providing you're following its convention.
Here's a working exemple and below I highlighted some of the most important parts of the example
<script type="text/x-handlebars" data-template-name="people">
<h2>People</h2>
<ul>
{{#each person in controller}}
<li>
<div class="debug">
Is the person record dirty: {{this.isDirty}}
</div>
</li>
<li>Id: {{#linkTo 'person' person}}{{person.id}}{{/linkTo}}</li>
<li>Name: {{person.name}}</li>
<li>Belt types:
<ul>
{{#each person.belts}}
<li>{{type}}</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="person">
<h2>Person</h2>
Id from within person template: {{id}}<br><br>
Id: {{id}}<br>
Name: <a href="#" {{action "changeName"}}>{{name}}</a><br><br>
{{#linkTo index}}Go back{{/linkTo}}<br>
{{#linkTo person.finish}}Go to finish{{/linkTo}}
{{outlet}}
</script>
Models, Views, Controllers and Route definitions
DS.RESTAdapter.configure("plurals", { person: "people" });
App.Router.map( function() {
this.resource('people',function() {
this.resource('person', { path: ':person_id' }, function() {
this.route( 'finish');
});
})
});
App.PeopleController = Ember.ArrayController.extend();
App.PeopleRoute = Ember.Route.extend({
model: function() {
return App.Person.find();
}
})
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('people');
}
});
App.PersonRoute = Ember.Route.extend({
model: function(params) {
debugger;
return App.Person.find(params.client_id);
},
renderTemplate: function() {
this.render('person',{
into:'application'
})
}
})
App.PersonFinishRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('finish',{
into:'application'
})
}
})

Categories