Count generated by model (through pivot table) not shown - ember.js

I am trying to get the count of how many people participated in a scan.
at this moment i need 2 counts:
Total participants count
Total finished count
i made a pivot table in Ember that holds specific data about the participant, like he finished the scan or not.
My model:
import DS from 'ember-data';
var inflector = Ember.Inflector.inflector;
inflector.irregular('scan', 'scan');
var scanModel = DS.Model.extend({
title: DS.attr('string'),
mailInviteSubject: DS.attr('string'),
mailInviteMessage: DS.attr('string'),
mailResponseAddress: DS.attr('string'),
dateDeadline: DS.attr('date'),
scanGroup: DS.belongsTo('scanGroup', {async: true}),
questionGroups: DS.hasMany('question-group', {async: true}),
/**
* Participants
*/
scanParticipants: DS.hasMany('scanParticipants', {async: true}),
participants: Ember.computed('scanParticipants.#each.participants', function () {
return this.get('scanParticipants').then(function(scanParticipants) {
return scanParticipants.mapBy('participant');
})
}),
participantsCount: Ember.computed('scanParticipants', function () {
return this.get('scanParticipants').then(function (participants) {
return participants.get('length');
});
}),
participantsCountFinished: Ember.computed('scanParticipants', function () {
return this.get('scanParticipants').then(function (participants) {
return participants.filterBy('finished', true).get('length');
});
}),
isClosed: Ember.computed('dateDeadline', function () {
let scanDate = moment(this.get('dateDeadline'));
let date = moment();
if(scanDate < date)
return true;
else
return false;
}),
});
export default scanModel;
I have the following template:
<td>{{input type="checkbox" checked=isChecked action="scanChecked"}}</td>
<td>
<h2>{{scan.title}}</h2>
Verloopt op {{format-date scan.dateDeadline format="DD MMMM"}} <small class="_muted">({{#if scan.isClosed}}Afgerond{{else}}Over{{/if}} {{format-date scan.dateDeadline type='remaining'}} geleden)</small>
</td>
<td>
<h2>{{scan.participantsCount}}</h2>
<small class="_muted">uitgenodigd</small>
</td>
<td>
<h2>{{scan.participantsCountFinished}}</h2>
<small class="_muted">voltooid</small>
</td>
<td class="_align-right">
{{#link-to 'scangroup.scan' scan.id class="btn btn-inverted btn-small"}}Bekijk{{/link-to}}
</td>
The problem now is, is that {{scan.participantsCount}} and{{scan.participantsCountFinished}} shows [Object Object] in my template instead of the counts.
But if i log the counts in the promise i get the good count that should be displayed in the template.
How does it come that it show's [Object Object] instead of the count, and how can i make it possible to show the count?
Thanks in advance!
Kindly regards,
Pascal

You are observing the property itself instead of the array. This means that they will only re-compute when scanParticipants change value, like this.set('scanParticipants', someValue) and not when it updates. To fix this you can do Ember.computed('scanParticipants.[]', function() {.
You are seeing [Object Object] because that's the promise that's being returned from the computed properties. When you return a promise from a computed property you need to wrap it in a PromiseObject or PromiseArray, but Ember Data does this for you already for relationships.
There are a couple of approaches possible to fix this problem:
You can use {{scan.scanParticipants.length}} in the template
You can declare participantsCount: Ember.computed.alias('scanParticipants.length')
You can do participantsCount: Ember.computed('scanParticipants.[]', function () { return this.get('scanParticipants.length'); })
Notice that you also have an Ember.computed.filterBy method.

Related

Ember.js update model attribute

I do not have the best understanding of how ember.js works. I am currently working on updating an attribute called features (an array of strings) that each owner has using a multi select checkbox. So far everything seems to be working except for the updated features attribute is not saving. When I click the checkbox it updates the selected computed property in the multi-select-checkboxes component. I thought if I was passing model.owner.features as selected to the component it would directly update the model when the component changes.
(function() {
const { shroud } = Sw.Lib.Decorators;
Sw.FranchisorNewAnalyticsConnectionsUsersRoute = Ember.Route.extend({
currentFranchisorService: Ember.inject.service('currentFranchisor'),
#shroud
model({ account_id }) {
console.log("load model in route")
return Ember.RSVP.hash({
account: this.get('store').find('account', account_id),
owners: Sw.AccountOwner.fetch(account_id),
newAccountOwner: Sw.AccountOwner.NewAccountOwner.create({ accountID: account_id }),
});
},
actions: {
#shroud
accountOwnersChanged() {
this.refresh();
},
close() {
this.transitionTo('franchisor.newAnalytics.connections');
},
},
});
})();
users controller:
(function() {
const { shroud, on, observer } = Sw.Lib.Decorators;
Sw.FranchisorNewAnalyticsConnectionsUsersController = Ember.Controller.extend(Sw.FranchisorControllerMixin, {
isAddingUser: false,
adminOptions: [{
label: 'Employee Advocacy',
value: 'employee advocacy'
}, {
label: 'Second Feature',
value: 'other'
}, {
label: 'Can edit users?',
value: 'edit_users'
}],
});
})();
users.handlebars
{{#each model.owners as |owner|}}
<tr
<td>
{{owner.name}}
</td>
<td>
{{owner.email}}
</td>
<td>{{if owner.confirmedAt 'Yes' 'No'}}</td>
<td>
{{log 'owner.features' owner.features}}
{{multi-select-checkboxes
options=adminOptions
selected=owner.features
owner=owner
model=model
}}
</td>
<td>
<button class="btn light-red-button"
{{action "remove" owner}}>
Remove
</button>
</td>
</tr>
{{/each}}
multi-select-checkboxes.handlebar:
{{#each checkboxes as |checkbox|}}
<p>
<label>
{{input type='checkbox' checked=checkbox.isChecked}}
{{checkbox.label}}
</label>
</p>
{{/each}}
multi_select_checkboxes.jsx:
// Each available option becomes an instance of a "MultiSelectCheckbox" object.
var MultiSelectCheckbox = Ember.Object.extend({
label: 'label',
value: 'value',
isChecked: false,
changeValue: function () { },
onIsCheckedChanged: Ember.observer('isChecked', function () {
var fn = (this.get('isChecked') === true) ? 'pushObject' : 'removeObject';
this.get('changeValue').call(this, fn, this.get('value'));
})
});
Sw.MultiSelectCheckboxesComponent = Ember.Component.extend({
labelProperty: 'label',
valueProperty: 'value',
// The list of available options.
options: [],
// The collection of selected options. This should be a property on
// a model. It should be a simple array of strings.
selected: [],
owner: null,
model: null,
checkboxes: Ember.computed('options', function () {
console.log("CHECKBOXES", this.get('model'))
var _this = this;
var labelProperty = this.get('labelProperty');
var valueProperty = this.get('valueProperty');
var selected = this.get('selected');
var model = this.get('model');
return this.get('options').map(function (opt) {
var label = opt[labelProperty];
var value = opt[valueProperty];
var isChecked = selected.contains(value);
return MultiSelectCheckbox.create({
label: label,
value: value,
isChecked: isChecked,
model: model,
changeValue: function (fn, value, model) {
_this.get('selected')[fn](value);
console.log("model in middle", this.get('model'))
this.get('model').save();
}
});
});
}),
actions: {
amountChanged() {
const model = this.get(this, 'model');
this.sendAction('amountChanged', model);
}
}
});
Seems like you have a pretty decent understanding to me! I think your implementation is just missing a thing here or there and might be a tad more complex than it has to be.
Here's a Twiddle that does what you're asking for. I named the property on the model attrs to avoid possible confusion as attributes comes into play with some model methods such as rollbackAttributes() or changedAttributes().
Some things to note:
You don't need to specify a changeValue function when creating your list of checkbox objects. The onIsCheckedChanged observer should be responsible for updating the model's attribute. Just pass the model or its attribute you want to update (the array of strings) into each checkbox during the mapping of the options in the multi select checkbox:
return Checkbox.create({
label: option.label,
value: option.value,
attributes: this.get('owner.attrs') // this array will be updated by the Checkbox
});
If the model you retrieve doesn't have any data in this array, Ember Data will leave the attribute as undefined so doing an update directly on the array will cause an error (e.g., cannot read property 'pushObject' of undefined) so be sure the property is set to an array before this component lets a user update the value.
The observer will fire synchronously so it might be worthwhile to wrap it in a Ember.run.once() (not sure what else you will be doing with this component / model so I note this for completeness).
If you want to save the changes on the model automatically you'll need to call .save() on the model after making the update. In this case I would pass the model in to each checkbox and call save after making the change.

ember - hide created.uncommitted records

I need a little help about ember-data record creation.
My app is a basic one, with books and reviews, based on Code School Tutorial
The app is using the RESTadapter
There is a form on the book page to write and send a review
<div class="row">
<div class="col-sm-09">
<h4>Reviews</h4>
{{#each review in reviews}}
<div class="col-sm-3">
<p>{{review.text}}</p>
<p class="text-info">{{review.reviewedDate}}</p>
</div>
{{else}}
<p class="text-muted">No Reviews Yet. Be the first to write one!</p>
{{/each}}
</div>
<div class="col-sm-3">
<h4>Review</h4>
{{textarea valueBinding='review.text'}}
<br>
<button {{action 'createReview'}} class='btn-primary'>Review</button>
{{#if review.text}}
<br><h4>Preview</h4>
<p class="text-muted">{{review.text}}</p>
{{/if}}
</div>
My Controller
App.BookController = Ember.ObjectController.extend({
logoAvailable: 'images/instock.jpg',
logoUnavailable: 'images/unavailable.jpg',
logoAvailability: function () {
if (this.get('isAvailable'))
return this.logoAvailable;
else
return this.logoUnavailable;
}.property('isAvailable'),
review: function () {
return this.store.createRecord('review', {
book: this.get('model')
});
}.property('model'),
actions: {
createReview: function () {
var controller = this;
this.get('review').save().then(function (review) {
controller.set('text', '');
controller.get('model.reviews').addObject(review);
}, function (error) {
console.error(error);
controller.set('text', '');
review.unloadRecord();
});
}
}
});
My Models:
App.Book = DS.Model.extend({
title: DS.attr('string'),
isbn: DS.attr('string'),
summary: DS.attr('string'),
isAvailable: DS.attr('boolean'),
featured: DS.attr('boolean'),
author: DS.belongsTo('author', {async: true}),
reviews: DS.hasMany('review', {async: true}),
// computed properties
image: function () {
return 'images/books/' + this.get('id') + '.jpg';
}.property('id'),
isNotAvailable: function () {
return !this.get('isAvailable');
}.property('isAvailable')
});
App.Review = DS.Model.extend({
text: DS.attr('string'),
reviewedAt: DS.attr('date'),
book: DS.belongsTo('book'),
//Computed Properties
reviewedDate: function () {
return moment(this.get('reviewedAt')).format('LLL');
}.property('reviewedAt')
});
The controller creates a new "review" object for each opened book, and will eventualy save it when the button is pressed.
This is working, but
My uncommited record is shown in the Book reviews list, even before submiting it, (as if it was a live preview), and before the call of controller.get('model.reviews').addObject(review);
What's wrong with my code, and how can I only display commited records (sucessful save() call).
You can filter the reviews by the isNew flag.
in your BookController you could do something like this:
committedReviews: function () {
return this.get('model.reviews').filter(function(review) {
return !review.get('isNew');
});
}.property('model.reviews')

Ember js filter model hasMany content via checkbox

I am building a small educational app where the structure is as follows -
Exam hasMany Subjects and Subjects hasMany courses.
My model relationships -
App.Exam = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
subjects : DS.hasMany('subject',{ async: true }),
});
App.Subject = DS.Model.extend({
name: DS.attr('string'),
description:DS.attr('string'),
exam: DS.belongsTo('exam', { async: true })
});
Initially I display all the exams and on exam/1 I display all the subjects belonging to that exam.
I am having trouble in filtering the subjects via checkbox
Here is the Demo
Not able to figure out how to do it. Can someone suggest me how to approach this ?
Basically on click of physics checkbox only physics subject should be displayed in the view.
I am basically using the MultipleSelectionFilterComponent from my blog post mentioned in the comments. This component will take care of managing the selection of the different checkboxes and send a filter function to the controller. There you can use the function to filter the data. You can refer to my post for more details.
Here is the working demo.
The code looks like
App.ExamsExamRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('exam', params.exam_id).then(function (exam) {
console.log("found", exam);
return exam;
});
},
setupController: function(controller, model) {
this._super(controller, model);
controller.set('filteredContent', model.get('data.subjects'));
}
});
App.ExamsExamController = Em.ObjectController.extend({
filteredContent: [],
actions: {
filterBySubject: function(filterFn) {
this.set('filteredContent',
this.get('model.data.subjects').filter(filterFn));
}
}
});
<ul class="list-group">
{{#multiple-selection-filter filter-key-path="name"
action="filterBySubject"}}
{{#each subject in model.subjects}}
<li class="">
<label>
<input type="checkbox" class="item-checkbox" {{bind-attr
value=subject.name}}/>
{{subject.name}}
</label>
</li>
{{/each}}
{{/multiple-selection-filter}}
</ul>
<h3>Subjects Details - </h3>
{{#each subject in filteredContent}}
<div class="col-md-3 well">
{{subject.name}}
<br>{{subject.description}}
</div>
{{/each}}
Here is the code to the MultipleSelectionFilterComponent.
App.MultipleSelectionFilterComponent = Em.Component.extend({
selectedItems: [],
click: function(event) {
var el = Em.$(event.target);
var filterFn;
if(el.is('input[type=checkbox]')) {
if(el.is(':checked')) {
this.get('selectedItems').pushObject(el.val());
} else {
this.get('selectedItems').removeObject(el.val());
}
}
if(this.get('selectedItems.length')) {
filterFn = function(item) {
return this.get('selectedItems')
.contains(Em.get(item, this.get('filter-key-path')));
}.bind(this);
} else {
filterFn = function() {return true;};
}
this.sendAction('action', filterFn);
}
});
that is SIMPLY not possible in ember.js
yeahhh it sux

Ember: store.update updates all attributes?

I'm creating a real time multiplayer textual game in Ember.js.
So far very exciting, but I'm running a little problem.
I have a game model which looks a bit like this:
App.Game = DS.Model.extend({
numbers: DS.attr(),
drawnNumbers: DS.attr(), // array
gameStatus: DS.attr(),
table: DS.belongsTo('table'),
bingoCards: DS.hasMany('bingoCard', { async: true })
});
My controller looks like this (leaving out unnecessary information):
App.GameController = Ember.ObjectController.extend({
gameBingoCards: function () {
var gameId;
gameId = this.get('id');
console.log("inside gameBingoCards");
return this.get('store').filter('bingoCard', function (bingoCard) {
return (bingoCard.get('game.id') === gameId);
});
}.property('model.bingoCards'),
ownBingoCards: function () {
var gameId, userId;
gameId = this.get('id');
userId = this.get('session.content.id');
console.log("inside ownBingoCards");
return this.get('store').filter('bingoCard', function (bingoCard) {
return (bingoCard.get('game.id') === gameId && bingoCard.get('user.id') === userId);
});
}.property('gameBingoCards.[]'),
gameMessages: function () {
var gameId;
gameId = this.get('id');
console.log("gameMessages");
return this.get('store').filter('message', function (message) {
return (message.get('game.id') === gameId);
});
}.property('model.messages'),
});
In the view I render the cards:
{{#each bingoCard in ownBingoCards}}
<div class="col-sm-4">
<div class="table-responsive">
<span class="label label-primary">Card {{bingoCard.id}}</span>
<table class="table table-bordered table-card">
<tbody>
{{#each row in bingoCard.squares}}
<!-- displaying the numbers here -->
{{/each}}
</tbody>
</table>
</div>
</div>
{{/each}}
Whenever the game updates I update the store like this:
record = serializer.extractSingle(store, type, data);
// record looks like this:
// {id: "538c56843800226245c3621a", gameStatus: "idle"}
store.update("game", record);
If I open the console I get the following:
inside ownBingoCards GameController.js:102
inside gameBingoCards GameController.js:32
inside ownBingoCards GameController.js:102
Note: the game receives many updates during the game, so every time all the cards get rerendered. How can I prevent this?
edit:
After I reload the page on that specific game route it only goes inside ownBingoCards and gameBingoCards once and it doesn't re-render everytime after an update.
edit2:
The gameMessages attribute also only gets called once, why does the gameBingoCards keep getting called?
Alright, I've fixed it after countless hours.
My route looked like this:
model: function (params) {
return this.store.find('game', params.game_id);
},
setupController: function (controller, model) {
model.reload();
controller.set('model', model);
},
And I've changed it to this:
model: function (params) {
return this.store.find('game', params.game_id);
},
setupController: function (controller, model) {
model.reload();
controller.set('model', model);
controller.set('modelBingoCards', model.get('bingoCards'));
controller.set('modelMessages', model.get('messages'));
},
Plus I also changed the property listeners to .property('modelMessages') and .property('modelBingoCards').
Could any please tell me why this worked?

Sorting an array does not update the Dom

I can not make the following code work in my test app:
this.propertyWillChange('tableContent');
this.get('tableContent').sort(function (a, b) {
var nameA = a.artikel_name,
nameB = b.artikel_name;
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0 //default return value (no sorting)
});
this.propertyDidChange('tableContent');
The data gets sorted, but the dom is not updated.
The template looks like this:
<tbody>
{{#each NewApp.router.gridController.tableContent}}
{{#view NewApp.TableRow rowBinding="this"}}
<td style="width: 100px">{{view.row.product_no}}</td>
<td align="right" style="width: 100px">{{view.row.price}}</td>
<td>{{view.row.artikel_name}}</td>
{{/view}}
{{/each}}
</tbody>
I tried to reproduce this problem with a short jsfiddle snippet. But there it works. The only difference is, that I fetch the data using an ajax call (and some additional router setup).
selectionChanged: function () {
var that = this;
if (this.selection) {
$.getJSON("api/v1/lists/" + this.selection.id + "/prices", function (content) {
that.set('tableContent', content);
});
}
}.observes('selection')
The same code works if i copy the array and reassign the copied array.
Did you try to use the built-in SortableMixin ? If not, is this good for you ?
JavaScript:
App = Ember.Application.create();
App.activities = Ember.ArrayController.create({
content: [{name: 'sleeping'}, {name: 'eating pizza'},
{name: 'programming'}, {name: 'looking at lolcats'}],
sortProperties: ['name']
});
App.ActivityView = Ember.View.extend({
tagName: "li",
template: Ember.Handlebars.compile("{{content}}")
});
App.SortButton = Ember.View.extend({
tagName: "button",
template: Ember.Handlebars.compile("Sort"),
click: function() {
App.activities.toggleProperty('sortAscending');
}
});
jsfiddle: http://jsfiddle.net/Sly7/cd24n/#base