Ember: store.update updates all attributes? - ember.js

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?

Related

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-rails: function returning 'undefined' for my computed value

Both functions here return 'undefined'. I can't figure out what's the problem.. It seems so straight-forward??
In the controller I set some properties to present the user with an empty textfield, to ensure they type in their own data.
Amber.ProductController = Ember.ObjectController.extend ({
quantity_property: "",
location_property: "",
employee_name_property: "",
//quantitySubtract: function() {
//return this.get('quantity') -= this.get('quantity_property');
//}.property('quantity', 'quantity_property')
quantitySubtract: Ember.computed('quantity', 'quantity_property', function() {
return this.get('quantity') - this.get('quantity_property');
});
});
Inn the route, both the employeeName and location is being set...
Amber.ProductsUpdateRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('product', params.product_id);
},
//This defines the actions that we want to expose to the template
actions: {
update: function() {
var product = this.get('currentModel');
var self = this; //ensures access to the transitionTo method inside the success (Promises) function
/* The first parameter to 'then' is the success handler where it transitions
to the list of products, and the second parameter is our failure handler:
A function that does nothing. */
product.set('employeeName', this.get('controller.employee_name_property'))
product.set('location', this.get('controller.location_property'))
product.set('quantity', this.get('controller.quantitySubtract()'))
product.save().then(
function() { self.transitionTo('products') },
function() { }
);
}
}
});
Nothing speciel in the handlebar
<h1>Produkt Forbrug</h1>
<form {{action "update" on="submit"}}>
...
<div>
<label>
Antal<br>
{{input type="text" value=quantity_property}}
</label>
{{#each error in errors.quantity}}
<p class="error">{{error.message}}</p>
{{/each}}
</div>
<button type="update">Save</button>
</form>
get rid of the ()
product.set('quantity', this.get('controller.quantitySubtract'))
And this way was fine:
quantitySubtract: function() {
return this.get('quantity') - this.get('quantity_property');
}.property('quantity', 'quantity_property')
Update:
Seeing your route, that controller wouldn't be applied to that route, it is just using a generic Ember.ObjectController.
Amber.ProductController would go to the Amber.ProductRoute
Amber.ProductUpdateController would go to the Amber.ProductUpdateRoute
If you want to reuse the controller for both routes just extend the product controller like so.
Amber.ProductController = Ember.ObjectController.extend ({
quantity_property: "",
location_property: "",
employee_name_property: "",
quantitySubtract: function() {
return this.get('quantity') - this.get('quantity_property');
}.property('quantity', 'quantity_property')
});
Amber.ProductUpdateController = Amber.ProductController.extend();
I ended up skipping the function and instead do this:
product.set('quantity',
this.get('controller.quantity') - this.get('controller.quantity_property'))
I still dont understand why I could not use that function.. I also tried to rename the controller.. but that was not the issue.. as mentioned before the other two values to fetches to the controller...
Anyways, thanks for trying to help me!

Ember.js clear controller on transitionToRoute call

I have a route that displays a list of parcels, and an Ember.Select that allows the user to select which state's parcels to show.
Model
App.Parcel = DS.Model.extend({
addresses: DS.attr('array')
});
Route
App.ParcelsRoute = Ember.Route.extend({
state: null,
renderTemplate: function () {
this.render({ outlet: 'parcels' });
},
model: function (params) {
state = params.state;
App.ParcelAdapter.state = state;
App.ImageAdapter.state = state;
return Ember.RSVP.hash({
props: this.store.findAll('parcel'),
states: this.store.findAll('state'),
});
},
setupController: function (controller, model) {
controller.set('states', model.states);
controller.set('props', model.props);
controller.set('selectedState', state);
}
});
Controller
App.ParcelsController = Ember.ObjectController.extend({
selectedState: null,
props: null,
states: null,
first: true,
modelReloadNeeded: function () {
if (this.get('selectedState') != undefined && !this.get('first')) {
this.transitionToRoute('/parcels/' + this.get('selectedState'));
}else{
this.set('first', false);
}
}.observes('selectedState')
});
Handlebars
<script type="text/x-handlebars" id="parcels">
{{view Ember.Select content=states optionValuePath="content.id" optionLabelPath="content.id" value=selectedState}}
<input class="search" placeholder="Search"/>
<ul class="list nav">
{{#each props}}
<li>{{#link-to 'parcel' this}}<h3 class="name">{{addresses.0.street_address}}</h3>{{/link-to}}</li>
{{/each}}
</ul>
</script>
When the select transitions to the new route, both the old routes data and new routes are in the model, but if I reload the page, only the current routes data is loaded. Is there a way to clear the DS.RecordArray for props in the controller without a location.reload() call?

Redirect if Invalid id is given

I have the following Routes
App.Router.map(function() {
this.resource('gradebooks', function() {
this.resource('gradebook', { path: ':gradebook_id' });
});
});
App.GradebooksRoute = Em.Route.extend({
model: function() {
return this.store.find('gradebook');
}
});
App.GradebookRoute = Em.Route.extend({
model: function(params) {
var id = params.gradebook_id;
var gradebook = this.store.find('gradebook', id);
var self = this;
gradebook.then(null, function(reason) {
self.transitionTo('gradebooks');
});
return gradebook;
}
})
Templates:
<ul>
{{#each}}
<li {{bind-attr class="isActive:active"}}>
{{#link-to "gradebook" this}}
{{title}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
It's works fine and dandy except when an invalid id is given. (ex. #/gradebooks/invalid_id).
Currently, the redirection works great (I do get the error Error while loading route: TypeError: Cannot read property 'id' of undefined, but it still redirects. However, the resulting list of gradebooks has the invalid gradebook in the list.
If I manually navigate to #/gradebooks/invalid_id_1, #/gradebooks/invalid_id_2, #/gradebooks/invalid_id_3, etc., the invalid gradebook gets added to the list of gradebooks every time.
Any ideas why? Or is there a better solution?
instead of manually handling the find model promise its better to let ember do it, it will trigger an error event if the find
promise is rejected
App.GradebookRoute = Em.Route.extend({
model: function(params) {
var id = params.gradebook_id;
return this.store.find('gradebook', id);
},
actions: {
error: function(reason) {
alert(reason); // "FAIL"
// Can transition to another route here, e.g.
// transitionTo('gradebooks');
// Uncomment the line below to bubble this error event:
// return true;
}
})
more info http://emberjs.com/guides/routing/asynchronous-routing/

Ember View is rendering too early

I have a Route for creating new documents, making a copy of an existing document.
App.DocumentsNewRoute = Ember.Route.extend({
model: function (params) {
this.modelParams = params; // save params for reference
return App.Document.createRecord();
},
setupController: function (controller, model) {
// use the params to get our reference document
var documentModel = App.Document.find(this.modelParams.document_id);
documentModel.one('didLoad', function () {
// once loaded, make a serialized copy
var documentObj = documentModel.serialize();
// and set the properties to our empty record
model.setProperties(documentObj);
console.log('didLoad');
});
}
});
I added some logs in my View.
App.DocumentView = Ember.View.extend({
init: function () {
this._super();
// fires before the didLoad in the router
console.log('init view');
},
willInsertElement: function () {
// fires before the didLoad in the router
console.log('insert elem');
}
});
And this is my template
{{#if model.isLoaded }}
{{ view App.DocumentView templateNameBinding="model.slug" class="document portrait" }}
{{ else }}
Loading...
{{/if}}
The problem it seems is that my model isLoaded, but not populated when my template is rendered, so the templateNameBinding doesn't exist at this point, and doesn't seem to be updated when the data gets populated.
Should I use something else than model.isLoaded in my template, or should I force a re-render of my template, and if so, how and where? Thanks!
It seem that you are overcomplicating the things. It should be:
EDIT
I misunderstood your question in first read, so
App.DocumentsNewRoute = Ember.Route.extend({
model: function (params) {
var originalDoc = App.Document.find(params.document_id),
newDoc = App.Document.createRecord();
originalDoc.one('didLoad', function () {
newDoc.setProperties(this.serialize());
});
return newDoc;
},
setupController: function (controller, model) {
controller.set('content', model);
}
});
If everything is right, ember will re-render things automatically..