Backbone - model.destroy() function not defined for model - javascript

For some reason, I am getting a TypeError in my JavaScript regarding a supposed Backbone model object for which I am trying to call "model.destroy()":
Here's my Backbone code:
var Team = Backbone.Model.extend({
idAttribute: "_id",
urlRoot: '/api/teams'
});
var TeamCollection = Backbone.Collection.extend({
model: Team
});
var teamCollection = new TeamCollection([]);
teamCollection.url = '/api/teams';
teamCollection.fetch(
{
success: function () {
console.log('teamCollection length:', teamCollection.length);
}
}
);
var UserHomeMainTableView = Backbone.View.extend({
tagName: "div",
collection: teamCollection,
events: {},
initialize: function () {
this.collection.on("reset", this.render, this);
},
render: function () {
var teams = {
teams:teamCollection.toJSON()
};
var template = Handlebars.compile( $("#user-home-main-table-template").html());
this.$el.html(template(teams));
return this;
},
addTeam: function (teamData) {
console.log('adding team:', team_id);
},
deleteTeam: function (team_id) {
console.log('deleting team:', team_id);
var team = teamCollection.where({_id: team_id}); //team IS defined here but I can't confirm the type even when logging "typeof"
console.log('team to delete', typeof team[0]);
console.log('another team to delete?',typeof team[1]);
team.destroy({ //THIS FUNCTION CALL IS THROWING A TYPEERROR
contentType : 'application/json',
success: function(model, response, options) {
this.collection.reset();
},
error: function(model, response, options) {
this.collection.reset();
}
});
}
});
So I am fetching the data from the node.js server, and the server is returning JSON. The JSON has cid's and all that jazz, so those objects were once Backbone models at some point.
I just don't know why the type of team would not be a Backbone model.
Any ideas?

.where returns an array. You need to use .findWhere instead.
Or call destroy for every model in the resulting array.
.where({...}).forEach(function(model){
model.destroy();
});

Related

Backbone Fetch success callback not executing when fetch response is an empty array

I have Person model and I am retrieving a person info inside a view. The success callback FetchSuccess executes when the response has an object. But when response is empty, the callback is not called. Any Guess?
Models.Basic = Backbone.Model.extend({
parse: function(response) {
return response;
}
});
Models.PersonModel = Backbone.Model.extend({
url: function() {
return '/person/' + this.data.id;
}
});
Backbone.View.extend({
template: Templates['template'],
initialize: function(options) {
this.id = options.id;
_.bindAll(this, 'FetchSuccess');
this.personModel = new Models.PersonModel();
this.model = new Models.Basic();
this.fetchData();
return this;
},
render: function() {
this.$el.append(this.template(this.model.toJSON()));
},
fetchData: function() {
this.personModel.data = {
id: this.id
};
this.personModel.fetch({
context: this,
success: this.FetchSuccess
});
},
FetchSuccess: function() {
this.model.set({
name: this.personModel.get('name');
});
this.render();
}
});
this.personModel = new Models.PersonModel();
This is a Backbone Model, not a collection.
this.personModel.fetch({
reset: true, // this doesn't exist on model
success: this.FetchSuccess
});
You can't fetch a model without an id. Also, the model, when fetching, expects an object to be returned.
If you want to fetch a specific person, give an id to the model, then fetch.
this.personModel = new Models.PersonModel({ id: "id_here" });
// ...
this.personModel.fetch({
context: this,
success: this.FetchSuccess
});
Here's the code with the corrections
// parse isn't needed if you're not going to parse something
Models.Basic = Backbone.Model.extend({});
Models.PersonModel = Backbone.Model.extend({
urlRoot: 'person/', // this handles putting the id automatically
});
Backbone.View.extend({
template: Templates['template'],
initialize: function(options) {
this.id = options.id;
// pass the id here
this.personModel = new Models.PersonModel({ id: this.id });
this.model = new Models.Basic();
this.fetchData();
// makes no sense in the initialize since it's never called
// manually and never used to chain calls.
// return this;
},
render: function() {
// render should be idempotent, so emptying before appending
// is a good pattern.
this.$el.html(this.template(this.model.toJSON()));
return this; // this is where chaining could happen
},
fetchData: function() {
// This makes no sense unless you've stripped the part that uses it.
// this.personModel.data...
this.personModel.fetch({
context: this, // pass the context, avoid `_.bindAll`
success: this.onFetchSuccess,
error: this.onFetchError
});
},
onFetchSuccess: function() {
this.model.set({
name: this.personModel.get('name')
});
this.render();
},
onFetchError: function() { this.render(); }
});
You could catch the error with the error callback, or just do nothing and render by default, and re-render on fetch.
You could also listen to the model events (inside the initialize):
this.listenTo(this.personModel, {
'sync': this.FetchSuccess,
'error': this.onFetchError
});
this.personModel.fetch();

Wrong backbone collection length. Can't each this collection

Sorry for my bad English. Tell me why the following happens:
I have some backbone collection:
var Background = window.Models.Background = Backbone.Model.extend({});
var Backgrounds = window.Models.Backgrounds = Backbone.Collection.extend({
model: window.Models.Background,
url: '/backgrounds/',
initialize: function() {
this.fetch({
success: this.fetchSuccess(this),
error: this.fetchError
});
},
fetchSuccess: function( collect_model ) {
new BackgroundsView ({ collection : collect_model });
},
fetchError: function() {
throw new Error("Error fetching backgrounds");
}
});
And some view:
var BackgroundsView = window.Views.BackgroundsView = Backbone.View.extend({
tagName: 'div',
className: 'hor_slider',
initialize: function() {
this.render();
},
render: function() {
console.log(this.collection);
this.collection.each( function (background) {
console.log(background);
//var backgroundView = new BackgroundView ({ model: background });
//this.$el.append(backgroundView.render().el);
});
}
});
now i creating collection
var backgrounds = new Models.Backgrounds();
but when I must render this view, in the process of sorting the collection its length is 0, but should be two. This log I see at console. How is this possible? What am I doing wrong??
You are creating the view before the collection fetch is successfull. Your code should be:
initialize: function() {
this.fetch({
success: this.fetchSuccess,
//------------------------^ do not invoke manually
error: this.fetchError
});
},
fetchSuccess: function(collection, response) {
new BackgroundsView ({ collection : collection});
},
You should let backbone call fetchSuccess when the fetch succeeds. Right now you're invoking the funcion immediately and passing the return value undefined as success callback.
This looks like a wrong pattern. Your data models shouldn't be aware of/controlling the presentation logic.
You have a view floating around without any reference to it. You should be creating a view instance with reference(for example from a router, or whatever is kick starting your application) and passing the collection to it. Then fetch the collection from it's initialize method and render after the fetch succeeds. Collection can be referenced via this.collection inside view.
Alternatively you can fetch the collection from router itself and then create view instance. Either way collection/model shouldn't be controlling views.
If the code is structured in the following way, the problem is solved. It was necessary to add a parameter reset to fetch.
var Background = window.Models.Background = Backbone.Model.extend({});
var Backgrounds = window.Models.Backgrounds = Backbone.Collection.extend({
model: window.Models.Background,
url: '/backgrounds/',
initialize: function() {
this.fetch({
reset : true,
});
}
});
var BackgroundsView = window.Views.BackgroundsView = Backbone.View.extend({
tagName: 'div',
className: 'hor_slider',
initialize: function() {
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.collection.each( function (background) {
var backgroundView = new BackgroundView ({ model: background });
this.$el.append(backgroundView.render().el);
}, this);
$('#view_list').empty();
$('#view_list').append(this.$el);
return this;
}
});

Backbone.js setting model with data from url

I get data back from a url: /app/api/assetDetail/{id} where the id is a parameter passed to a presenter which sets up the new assetModel and assetView.
I'm struggling with where to build and call the above url with id and then set the model.
asset presenter
define([
'text!html/regions/tplAssetPage.html',
'views/assetView',
'collections/assets',
'models/asset'
],
function (template, AssetView, Assets, Asset) {
return {
load: function (params) {
$(mv.sections.mainContainer).html(template);
var view1 = 'assetView',
id = params || '';
this.model = new Asset({
wid: params, //sets id on model
url: function(){
var url = 'api/assetDetail/' + params;
return url;
}
});
mv.i.views[view1] = new AssetView({
'el': '#asset-container',
model: asset
});
mv.i.views[view1].setup();
},
};
});
asset model
define([], function () {
return Backbone.Model.extend({
defaults: {
id:''
},
initialize: function () {}
});
});
asset view
define([
'text!html/tplAsset.html',
'models/asset'
], function (template, Asset) {
return Backbone.View.extend({
el: '',
template: _.template(template),
initialize: function () {},
render: function () {
//var data = this.model.toJSON();
this.$el.html(this.template(data));
},
setup: function () {
var self = this;
$.when(self.model.fetch())
.done(function () {
//console.log(self.model.toJSON());
self.render();
})
.fail(function () {
console.log('request for data has failed');
});
},
events: {},
});
});
now getting these errors:
ERR: Routing error Error: A "url" property or function must be specified
at Backbone.View.extend.setup (/js/views/assetView.js:36:22)
$.when(self.model.fetch())
at Object.load (/js/presenters/asset.js:34:23)
mv.i.views[view1].setup();
To set the model url dynamically in your model instance:
var asset = new Asset({
wid: params, //sets id on model
url: function(){
var url = '/app/api/assetDetail/' + this.id;
return url;
}
});
Then, after you have set the url, do asset.fetch()
Note that this will now be the URL for any communication with the server (save and fetch) for that model instance. If you need greater flexibiliy, you'll need to do adjust the Bacbkone sync method for your model.
UPDATE:
Once you've fetched the data you want for the model, you then can call the render function:
render: function () {
this.$el.html( this.template(this.model.toJSON() ) );
return this;
}
This will then render model data in your templates. If you're using the underscore templates it will look like:
<p>some html<span> <%= data %> </span><p>
If you want to check what you have fetched, don't forget that fetch accepts success and error callbacks: http://backbonejs.org/#Model-fetch

Backbone.js Collection's fetch call is erroring out

Still wrapping my head around Backbone, and running into an issue where my Collection won't populate when I'm passing it JSON from a file. The fetch() call is erroring out, but I'm not sure how to debug it exactly, as my console.log()'s aren't telling me anything useful (as far as I know how to dig through them).
Here's the code:
ItemGroup = Backbone.Collection.extend({
model: Item, // left out definition since it's very simple
url: 'js/items.json',
initialize: function(models, options) {
this._meta = {};
},
parse: function(response) { // parse is not being called, not sure why?
this.set(response.name, 'name');
return response.items;
},
set: function(val, prop) {
this._meta[prop] = val;
},
get: function(prop) {
return this._meta[prop];
}
});
ItemGroupView = Backbone.View.extend({
el: '#item-container',
initialize: function(options) {
this.collection.bind('reset', this.render, this);
this.collection.fetch({
success : function() {
alert('successful');
},
error : function(collection, xhr, options) { // This is being triggered, not sure why?
console.log(collection);
console.log(xhr);
console.log(options);
}
});
},
processItem: function(item) {
var itemView = new ItemView({ model: item });
this.$el.append(itemView.render().el);
},
render: function() {
console.log(this.collection); // this shows an empty collection
var itemGroupHtml = this.template({ name: this.collection.get('name') });
$(this.main).html(itemGroupHtml);
_.each(this.collection.models, this.processItem, this);
return this;
}
});
var toiletryItems = new ItemGroup();
var toiletryGroupView = new ItemGroupView({ collection: toiletryItems });
And here's my json (I can see that's it's successfully finding the file, since I see it as a 200 in the network requests using the Chome inspector):
[
{
'name' : 'toiletries',
'items' : [
{ 'name': 'toothbrush' },
{ 'name': 'deodorant' },
{ 'name': 'toothpaste' },
]
}
]
Thanks in advance!
UPDATE:
Fixed the invalid json, but still seeing an empty toiletryItems collection. Any ideas?
Discovered the problem was with my parse function. The response param is an array, and so that function should be:
parse: function(response) { // parse is not being called, not sure why?
this.set(response[0].name, 'name');
return response[0].items;
},

Backbone - Validation not working on create, only update/edit?

So, I am able to validate just fine when I am editing an existing item. However, if I want to create, validation for some reason is not getting kicked off. Instead, I am seeing the errors below:
//this is if the field I want to validate is empty
Uncaught TypeError: Object #<Object> has no method 'get'
//this is if everything in the form is filled out
Uncaught TypeError: Cannot call method 'trigger' of undefined
Here is(what I think is) the relative portion of my js. Sorry if its an overload, I wanted to add as much as I can to be as specific as possible:
Comic = Backbone.Model.extend({
initialize: function () {
this.bind("error", this.notifyCollectionError);
this.bind("change", this.notifyCollectionChange);
},
idAttribute: "ComicID",
url: function () {
return this.isNew() ? "/comics/create" : "/comics/edit/" + this.get("ComicID");
},
validate: function (atts) {
if ("Name" in atts & !atts.Name) {
return "Name is required";
}
if ("Publisher" in atts & !atts.Publisher) {
return "Publisher is required";
}
},
notifyCollectionError: function (model, error) {
this.collection.trigger("itemError", error);
},
notifyCollectionChange: function () {
this.collection.trigger("itemChanged", this);
}
});
Comics = Backbone.Collection.extend({
model: Comic,
url: "/comics/comics"
});
comics = new Comics();
FormView = Backbone.View.extend({
initialize: function () {
_.bindAll(this, "render");
this.template = $("#comicsFormTemplate");
},
events: {
"change input": "updateModel",
"submit #comicsForm": "save"
},
save: function () {
this.model.save(
this.model.attributes,
{
success: function (model, response) {
model.collection.trigger("itemSaved", model);
},
error: function (model, response) {
model.trigger("itemError", "There was a problem saving " + model.get("Name"));
}
}
);
return false;
},
updateModel: function (evt) {
var field = $(evt.currentTarget);
var data = {};
var key = field.attr('ID');
var val = field.val();
data[key] = val;
if (!this.model.set(data)) {
//reset the form field
field.val(this.model.get(key));
}
},
render: function () {
var html = this.template.tmpl(this.model.toJSON());
$(this.el).html(html);
$(".datepicker").datepicker();
return this;
}
});
NotifierView = Backbone.View.extend({
initialize: function () {
this.template = $("#notifierTemplate");
this.className = "success";
this.message = "Success";
_.bindAll(this, "render", "notifySave", "notifyError");
comics.bind("itemSaved", this.notifySave);
comics.bind("itemError", this.notifyError);
},
events: {
"click": "goAway"
},
goAway: function () {
$(this.el).delay(0).fadeOut();
},
notifySave: function (model) {
this.message = model.get("Name") + " saved";
this.render();
},
notifyError: function (message) {
this.message = message;
this.className = "error";
this.render();
},
render: function () {
var html = this.template.tmpl({ message: this.message, className: this.className });
$(this.el).html(html);
return this;
}
});
var ComicsAdmin = Backbone.Router.extend({
initialize: function () {
listView = new ListView({ collection: comics, el: "#comic-list" });
formView = new FormView({ el: "#comic-form" });
notifierView = new NotifierView({el: "#notifications" });
},
routes: {
"": "index",
"edit/:id": "edit",
"create": "create"
},
index: function () {
listView.render();
},
edit: function (id) {
listView.render();
$(notifierView.el).empty();
$(formView.el).empty();
var model = comics.get(id);
formView.model = model;
formView.render();
},
create: function () {
var model = new Comic();
listView.render();
$(notifierView.el).empty();
$(formView.el).empty();
formView.model = model;
formView.render();
}
});
jQuery(function () {
comics.fetch({
success: function () {
window.app = new ComicsAdmin();
Backbone.history.start();
},
error: function () {
}
});
})
So, shouldnt my create be getting validated too? Why isnt it?
When creating a new instance of a model, the validate method isn't called. According to the backbone documentation the validation is only called before set or save.
I am also struggling with this problem and found solutions in related questions:
You could make a new model and then set its attributes (see question 9709968)
A more elegant way is calling the validate method when initializing the model (see question 7923074)
I'm not completely satisfied with these solutions because creating a new instance of the model like described in the backbone documentation shouldn't happen when an error is triggered. Unfortunately, in both solutions you're still stuck with a new instance of the model.
edit: Being stuck with a new instance of the model is actually quite nice. This way you can give the user feedback about why it didn't pass the validator and give the opportunity to correct his/her input.
OK. So, I'm having some mild success here.
First, I wrote my own validation framework, Backbone.Validator since I didn't like any of the ones out there that I found.
Second, I am able to get the validation framework to set off the validation routine by setting silent: false with in the object provided during the new Model creation.
Along with using the use_defaults parameter from my validation framework I am able to override bad data during setup in initial testing. I'm still working on doing some more tests on this, but it seems to be going OK from from the Chrome browser console.

Categories

Resources