Accessing computed properties outside of their model in ember.js - javascript

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.

Related

How to build comment system in ember.js

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

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.js hasMany as list of checkboxes

I have the following two models:
App.Child = DS.Model.extend({
name: DS.attr('string')
});
And:
App.Activity = DS.Model.extend({
children: DS.hasMany('child',{async:true}),
name: DS.attr('string')
});
I want to use checkboxes to choose between the existing children, for the hasMany relation.
For example, I have these three children:
App.Child.FIXTURES = [
{ id: 1, name: 'Brian' },
{ id: 2, name: 'Michael' },
{ id: 3, name: 'James' }
];
The user should be able to use checkboxes, while creating or editing an activity, for choosing which children, to add to the hasMany relation.
I've created a JSFiddle to illustrate my question: http://jsfiddle.net/Dd6Wh/. Click 'Create a new activity' to see what I'm trying to do.
Basically it's the same as Ember.Select [ ... ] multiple="true", but for checkboxes.
What's the correct approach for something like this with Ember.js?
You can use an itemController in your each view helper to manage the selection. In the code below I created one called ChildController:
App.ChildController = Ember.ObjectController.extend({
selected: function() {
var activity = this.get('content');
var children = this.get('parentController.children');
return children.contains(activity);
}.property(),
selectedChanged: function() {
var activity = this.get('content');
var children = this.get('parentController.children');
if (this.get('selected')) {
children.pushObject(activity);
} else {
children.removeObject(activity);
}
}.observes('selected')
});
With a itemController you can expose some properties and logics, without add it directlly to your models. In that case the selected computed property and the selectedChanged observer.
In your template, you can bind the selection using checkedBinding="selected". Because the itemController proxy each model, the selected property of the itemcontroller will be used, and the {{name}} binding, will lookup the name property of the model:
<script type="text/x-handlebars" data-template-name="activities/new">
<h1>Create a new activity</h1>
{{#each childList itemController="child"}}
<label>
{{view Ember.Checkbox checkedBinding="selected"}}
{{name}}
</label><br />
{{/each}}
{{view Ember.TextField valueBinding="name"}}
<button {{action create}}>Create</button>
</script>
The same aproach in edit template:
<script type="text/x-handlebars" data-template-name="activities/edit">
<h1>Edit an activity</h1>
{{#each childList itemController="child"}}
<label>
{{view Ember.Checkbox checkedBinding="selected"}}
{{name}}
</label><br />
{{/each}}
{{view Ember.TextField valueBinding="name"}}
<button {{action update}}>Update</button>
</script>
This is a fiddle with this working http://jsfiddle.net/marciojunior/8EjRk/
Component version
Template
<script type="text/x-handlebars" data-template-name="components/checkbox-select">
{{#each elements itemController="checkboxItem"}}
<label>
{{view Ember.Checkbox checkedBinding="selected"}}
{{label}}
</label><br />
{{/each}}
</script>
Javascript
App.CheckboxSelectComponent = Ember.Component.extend({
/* The property to be used as label */
labelPath: null,
/* The model */
model: null,
/* The has many property from the model */
propertyPath: null,
/* All possible elements, to be selected */
elements: null,
elementsOfProperty: function() {
return this.get('model.' + this.get('propertyPath'));
}.property()
});
App.CheckboxItemController = Ember.ObjectController.extend({
selected: function() {
var activity = this.get('content');
var children = this.get('parentController.elementsOfProperty');
return children.contains(activity);
}.property(),
label: function() {
return this.get('model.' + this.get('parentController.labelPath'));
}.property(),
selectedChanged: function() {
var activity = this.get('content');
var children = this.get('parentController.elementsOfProperty');
if (this.get('selected')) {
children.pushObject(activity);
} else {
children.removeObject(activity);
}
}.observes('selected')
});
Updated fiddle http://jsfiddle.net/mgLr8/14/
I hope it helps

Link tag dosen't work in Emberjs

This is model
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.FixtureAdapter
});
App.Markets = DS.Model.extend({
ids: DS.attr("string"),
name: DS.attr("string"),
created: DS.attr("string")
});
App.Markets.FIXTURES = [
{ids:"312", name:"joy", created:"2012/1/1"},
{ids:"412", name:"adel", created:"2012/1/2"},
{ids:"512", name:"john", created:"2012/1/3"}
];
App.Sources = DS.Model.extend({
source_channel: DS.attr("string"),
handle: DS.attr("handle")
});
App.Sources.FIXTURES = [
{source_channel:"sc1", handle: "hn1"},
{source_channel:"sc2", handle: "hn2"}
];
This is route.
var App = Ember.Application.create();
App.Router.map(function() {
this.resource('markets', {path: '/markets'}, function() {
this.resource("sources", { path: "/:market_id" });
});
});
App.MarketsRoute = Ember.Route.extend({
model: function () {
return App.Markets.find();
}
});
App.SourcesRoute = Ember.Route.extend({
model: function(){
return App.Sources.find();
}
});
This is template
<script type="text/x-handlebars" id="_sources">
{{#each sources in content}}
<span>{{sources.handle}}</span>
<span>{{sources.sources_channel}}</span>
{{/each}}
</script>
<script type="text/x-handlebars" id="markets">
{{#each markets in content }}
{{#linkTo 'sources' markets.ids class="test" }}<span>Source</span>{{/linkTo}}
<span>{{markets.name}}</span>
<span>{{markets.created}}</span>
{{/each}}
<div class="sources">
{{partial "sources"}}
</div>
</script>
When I go to /#/markets, I can see the markets lists. This is correct.
Focus in {{#linkTo 'sources' markets.ids class="test" }}Source{{/linkTo}} of markets template.
In here, markets.ids doesn't work.
I'd like to go to /#/markets/markets_id when I click the link.
Along with passing the object to the sources route in linkTo,
define serialize method in your App.SourcesRoute.
The template:
{{#linkTo 'sources' markets class="test" }}<span>Source</span>{{/linkTo}}
The Route:
App.SourcesRoute = Ember.Route.extend({
model: function(){
return App.Sources.find();
},
serialize: function(model) {
return { market_id: model.ids };
}
});
You want to use linkTo 'sources' markets instead. ie:- pass the model in the each loop to the `linkTo.
Note: Regarding your naming conventions. Ember likes models to be singular, and Routes/Controllers plural or singular depending on whether the route points to one or more models.
Edit: Clarification.
Change the linkTo to this,
{{#linkTo 'sources' markets class="test" }}<span>Source</span>{{/linkTo}}

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