TodoMVC with ember, id does not increment - ember.js

I am following the getting started guide from emberjs, and am at the point where I can add todos. My problem is though that when I add a todo it has an id value of null - is there a practical way to auto increment this?
var TodosController = Ember.ArrayController.extend({
actions: {
createTodo: function() {
var title = this.get('newTitle');
if (!title.trim()) {
return;
}
var todo = this.store.createRecord('todo', {
title: title,
isCompleted: false
});
this.set('newTitle', '');
todo.save();
}
}
});

When you call this.store.createRecord() you have an "option" to have an id autogenerated (see here) Ultimately though, that responsibility is delegated to an adapter. If your adapter has generateIdForRecord() method - this will be used to create an id. So, for example, FixtureAdapter implements this method as follows (see here):
generateIdForRecord: function(store) {
return "fixture-" + counter++;
}
ember-data uses RestAdapter by default (see here), so you would need to add the method for the id to be generated on the client...

Related

Ember return length of a model created today

I am trying to do this: I have a model called 'trip', and inside trip, an attribute called 'createdToday', which returns the date when a trip is created. What I want is to return a list of trips that were made today.
Here is my trip model:
import DS from 'ember-data';
export default DS.Model.extend({
driver: DS.belongsTo('driver', {
async: true,
inverse: 'trip'
}),
..... etc .......
createdAt: DS.attr('string', {
defaultValue() {
return new Date();
}
}),
isBookedToday: function(trip) {
var today = new Date().toDateString();
return (today === trip.get('createdAt').toDateString);
},
getTripsToday: Ember.computed('trip.#each.createdAt', function() {
var tripsToday = this.get('trip');
return tripsToday.filterBy('isBookedToday', true).get('length');
})
});
In my isBookedToday, I'm trying to see if an individual trip's created time is the same as todays time, and in getTripsToday, I am trying to loop through all the trips and filtering by isBookedToday.
And in my .hbs file, I'm saying: {{trips.getTripsToday}}, which won't render anything, so something's wrong.
I guess I am most confused at Ember's #each and exactly how it works.
Thanks for any feedback.
First you have to understand that your Trip Model instances represents a single Trip! Its absolutely not the right place to put a function that gives you a filtered list of trips!
Next isBookedToday is a normal function not a Computed Property. So you can't filterBy on it.
You may want to implement a isBookedToday on your trip, but you definitely have to filter your trips on the same place where you fetch them! Probably in a model() hook or a Computed Property on a component or a controller.
So you could do but don't need to do in your models/trip.js:
...
isBookedToday: Ember.computed('createdAt', {
get() {
let now = new Date();
let created = get(this, 'createdAt');
return now.getFullYear() === created.getFullYear() &&
now.getMonth() === created.getMonth() &&
now.getDate() === created.getDate();
}
})
...
And then in your model hook:
model() {
return this.store.findAll('trip').then(trips => trips.filterBy('isBookedToday'));
}
Or in a Computed Property in a controller or a component:
tripsToday: Ember.computed('trips.#each.isBookedToday', {
return get(this, 'trips').filterBy('isBookedToday');
})
Be careful. This will result in confusing things if you leave the page open overnight! when your date changes the Computed Properties will not recompute automatically!

What's the proper way to change views in Ember, based on an action from the controller?

I currently have an action set up in a template for which the purpose is tracking a user's selection, and then changing pages based on that selection.
This is the applicable portion of my router:
this.resource('simpleSearch', function() {
this.resource('simpleSearchOption', {path: ':simpleSearchOption_id'});
Here's the action:
<div {{action "select" this}} class="questiontile">
And here's the controller:
App.SimpleSearchOptionController = Ember.ObjectController.extend({
needs: ["simpleSearch"],
simpleSearch: Ember.computed.alias("controllers.simpleSearch"),
actions: {
select: function(optionId) {
var nextOptionId = parseInt(this.get("id")) + 1;
var numOfOptions = this.get('simpleSearch').get('model').length;
if(nextOptionId < numOfOptions) {
console.log('going to next option');
/** What do I do here?
* This is my current implementation,
* and it works, but is it proper?
*/
this.transitionToRoute('/simpleSearch/' + nextOptionId);
}
}
}
});
The next page is basically the next index up an array of objects which is the model for the parent route/controller/view.
How I'm doing it at the moment is working - but is that proper? Is it 'Ember idiomatic'?
Sorry about previous post, accidentally deleted it!
The transitionToRoute takes two arguments, first is the resource/route name and the second is the model. So this should work
actions: {
select: function(optionId) {
var nextOptionId = parseInt(this.get("id")) + 1;
this.store.find('simpleSearch', nextOptionId).then(function(model){
this.transitionToRoute('simpleSearchOption', model);
});
//OR MAYBE YOU COULD GET IT FROM THE PARENT CONTROLLER??
/*
MAYBE
this.get('simpleSearch.content').forEach(function(model){
if(model.get('id') === nextOptionId){ do transition}
else{ alert some msg!! }
})
*/
}
}
More info here : http://emberjs.com/api/classes/Ember.Controller.html#method_transitionToRoute

Correct way of deleting a model record with ember-data

I have a controller that lists all the units-of-measure in the system. When a user chooses a specific record in the Uom model I want to be able to delete it. I'm using Ember-Data beta-2. Here's what I have so far:
App.UomsController = Ember.ArrayController.extend({
actions: {
deleteRecord: function(id) {
console.log("deleting: " + id);
var promisedDelete = this.store.find('uom',id).then(function(uom){
uom.deleteRecord();
});
}
}
});
The action deleteRecord is called passing in a valid ID and a promise is returned. Using the then() functionality of the promise I then call Ember's deleteRecord() when the promise has been fulfilled and it appears to work locally. I say that because this record immediately disappears from the screen and the Ember Debugger. Unfortunately the delete has not been persisted to the backend and a reload of hte page immediately brings back the locally "deleted" record.
My questions are:
Is this a reasonable way to execute a local delete?
How do I persist a delete to the backend?
You will have to call uom.save() to persist the change to the backend after calling uom.deleteRecord().
What you are doing could work, but seems a bit complicated (for example this.store.find('uom',id) will result into an unnecessary request to the backend). Try this:
App.UomsItemController = Ember.ObjectController.extend({
actions: {
deleteRecord: function() {
this.get('model').destroyRecord();
// .destroyRecord() only exists in recent versions of ED
// for previous versions use .deleteRecord() followed by .save()
// (though you should really consider upgrading :))
}
}
);
App.UomsController = Ember.ArrayController.extend({
itemController: 'uoms_item'
});
and in your template you will have something like this:
{{#each content}}
{{name}} <a href="#" {{action "deleteRecord" this}}>Delete</a>
{{/each}}
EDIT to answer comment below: If this.get('model') is returning a promise, the following should work.
deleteRecord: function() {
this.get('model').then(function(item) {
item.destroyRecord();
})
}
In ember-data v1.0.0-beta.4 they added a destroyRecord method which does the delete and save in one call. Which you can use like this:
this.get('model').destroyRecord().then(function() {
router.transitionTo('users');
});
The deleteRecord method can be called on any instance of DS.Model class. It removes the record form the Store But it will not persist in the backend.
App.UomsController = Ember.ArrayController.extend({
actions: {
deleteRecord: function(id) {
console.log("deleting: " + id);
var promisedDelete = this.store.find('uom',id).then(function(uom){
uom.deleteRecord();
});
}
}
});
For the deletion to persist in the backend, we have to call save method on that record as( same as createRecord() followed by save() to save the record in backend) :
App.UomsController = Ember.ArrayController.extend({
actions: {
deleteRecord: function(id) {
console.log("deleting: " + id);
var promisedDelete = this.store.find('uom',id).then(function(uom){
uom.deleteRecord();
uom.save(); //The deletion will persist now
});
}
}
});
Alternatively, you can also use destroyRecord() method of DS.Model class which persists deletion.
App.UomsController = Ember.ArrayController.extend({
actions: {
deleteRecord: function(id) {
console.log("deleting: " + id);
var promisedDelete = this.store.find('uom',id).then(function(uom){
uom.destroyRecord();
});
}
}
});

How to make a computed property that depends on a global class attribute?

I wanna create a property that depends on a global attribute:
App.Test= Em.Object.extend();
App.Test.reopenClass({ all: Em.A() });
App.Other = Em.object.extend({
stuff: function() {
return "calculated stuff from this.get('foo') and App.Test.all";
}.property('foo', 'App.Test.all.#each.bar')
});
As a workarround I could create a observer and always set a dummy property with a new random value to trigger the property change, but is there a better way to do this?
I need this for some caching. I've a really crazy, and single threaded backend. So I write my own Model classes. So I try to reimplement a bit of the logic in the client for a better caching.
Ive an Item class (App.Item) and another class where each instance has a calculated reduced list of Items.
App.Model = Em.Object.extend({
});
App.Model.reopenClass({
all: Em.A(),
load: function(hash) {
return this.get('all').pushObject(this.create(hash));
}
});
App.Item = App.Model.extend({
});
App.List = App.Model.extend({
loadedInitItems: false,
items: function() {
if(!this.get('loadedInitItems')) { this.set('loadedInitItems', true); Backend.call('thelist', function(item) { App.Item.load(this); }); }
return App.Item.all.filter(function(item) {
// heavy filter stuff, depends on a lot of propertys on the current list instance
});
}.property('someprops', 'App.Item.all.#each.foo')
});
Backend.call represents some AJAX stuff
the point is, that now any item could change so that the filter will return something diffrent. And there are other places om the application, where the user can add Items. I dont want to call the backend again, because its very slow! And I know that the backend will not modify the list! So I wanna cache it.
This is just a reduced example of my use case, but I think've described the point. In reallity I have this dozend of times, with over 25000 objects.
have you tried adding 'Binding' to your property and then the value you want to bind to ?, something like this:
App.PostsController = Em.ArrayController.extend({
nameOfYourVariableBinding: "App.SomeObject.propertyYouWantToBindTo"
})
It looks like the problem is the double uppercase letter. So App.test ist working, but not App.Foo.test.
But I was able to find a Solution with the ArrayProxy.
Its about this:
App.Model = Em.Object.extend({
});
App.Model.reopenClass({
all: Em.A(),
load: function(hash) {
return this.get('all').pushObject(this.create(hash));
}
});
App.Item = App.Model.extend({
});
App.List = App.Model.extend({
loadedInitItems: false,
items: function() {
var self = this;
if(!this.get('loadedInitItems')) {
this.set('loadedInitItems', true);
Backend.call('thelist', function(item) {
App.Item.load(this);
});
}
return Em.ArrayProxy.extend({
content: App.Item.all,
arrangedContent: function() {
return this.get('content').filter(function(item) {
// heavy filter stuff, depends on a lot of propertys on the current list instance
// use self.get('someprops')
})
}.property('content.#each.foo')
});
}.property('someprops')
items: function() {
if(!this.get('loadedInitItems')) { this.set('loadedInitItems', true); Backend.call('thelist', function(item) { App.Item.load(this); }); }
return App.Item.all.filter(function(item) {
// heavy filter stuff, depends on a lot of propertys on the current list instance
});
}.property('someprops', 'App.Item.all.#each.foo')
});

Delete associated model with ember-data

I have two models:
App.User = DS.Model.create({
comments: DS.hasMany('App.Comment')
});
App.Comment = DS.Model.create({
user: DS.belongsTo('App.User')
});
When a user is deleted, it also will delete all its comments on the backend, so I should delete them from the client-side identity map.
I'm listing all the comments on the system from another place, so after deleting a user it would just crash.
Is there any way to specify this kind of dependency on the association? Thanks!
I use a mixin when I want to implement this behaviour. My models are defined as follows:
App.Post = DS.Model.extend(App.DeletesDependentRelationships, {
dependentRelationships: ['comments'],
comments: DS.hasMany('App.Comment'),
author: DS.belongsTo('App.User')
});
App.User = DS.Model.extend();
App.Comment = DS.Model.extend({
post: DS.belongsTo('App.Post')
});
The mixin itself:
App.DeletesDependentRelationships = Ember.Mixin.create({
// an array of relationship names to delete
dependentRelationships: null,
// set to 'delete' or 'unload' depending on whether or not you want
// to actually send the deletions to the server
deleteMethod: 'unload',
deleteRecord: function() {
var transaction = this.get('store').transaction();
transaction.add(this);
this.deleteDependentRelationships(transaction);
this._super();
},
deleteDependentRelationships: function(transaction) {
var self = this;
var klass = Ember.get(this.constructor.toString());
var fields = Ember.get(klass, 'fields');
this.get('dependentRelationships').forEach(function(name) {
var relationshipType = fields.get(name);
switch(relationshipType) {
case 'belongsTo': return self.deleteBelongsToRelationship(name, transaction);
case 'hasMany': return self.deleteHasManyRelationship(name, transaction);
}
});
},
deleteBelongsToRelationship: function(name, transaction) {
var record = this.get(name);
if (record) this.deleteOrUnloadRecord(record, transaction);
},
deleteHasManyRelationship: function(key, transaction) {
var self = this;
// deleting from a RecordArray doesn't play well with forEach,
// so convert to a normal array first
this.get(key).toArray().forEach(function(record) {
self.deleteOrUnloadRecord(record, transaction);
});
},
deleteOrUnloadRecord: function(record, transaction) {
var deleteMethod = this.get('deleteMethod');
if (deleteMethod === 'delete') {
transaction.add(record);
record.deleteRecord();
}
else if (deleteMethod === 'unload') {
var store = this.get('store');
store.unloadRecord(record);
}
}
});
Note that you can specify via deleteMethod whether or not you want to send the DELETE requests to your API. If your back-end is configured to delete dependent records automatically, then you will want to use the default.
Here's a jsfiddle that shows it in action.
A quick-and-dirty way would be to add the following to your user model
destroyRecord: ->
#get('comments').invoke('unloadRecord')
#_super()
I adapted the answer of #ahmacleod to work with ember-cli 2.13.1 and ember-data 2.13.0. I had an issue with nested relationships and the fact that after deleting an entity from the database its id was reused. This lead to conflicts with remnants in the ember-data model.
import Ember from 'ember';
export default Ember.Mixin.create({
dependentRelationships: null,
destroyRecord: function() {
this.deleteDependentRelationships();
return this._super()
.then(function (model) {
model.unloadRecord();
return model;
});
},
unloadRecord: function() {
this.deleteDependentRelationships();
this._super();
},
deleteDependentRelationships: function() {
var self = this;
var fields = Ember.get(this.constructor, 'fields');
this.get('dependentRelationships').forEach(function(name) {
self.deleteRelationship(name);
});
},
deleteRelationship (name) {
var self = this;
self.get(name).then(function (records) {
if (!records) {
return;
}
var reset = [];
if (!Ember.isArray(records)) {
records = [records];
reset = null;
}
records.forEach(function(record) {
if (record) {
record.unloadRecord();
}
});
self.set(name, reset);
});
},
});
Eventually, I had to set the relationship to [] (hasMany) or null (belongsTo). Else I would have run into the following error message:
Assertion Failed: You cannot update the id index of an InternalModel once set. Attempted to update <id>.
Maybe this is helpful for somebody else.