I´m new to Backbone.js, but after some research I still couldn´t find the source of my problem.
I am developing an App and I have a collection of models, which I eventually want to display in a view (just one model at a time).
Here´s my code so far:
var App = {
Item: Backbone.Model.extend({
defaults: function() {
return {
id: -1,
name: '',
imageReference: '',
itemTags: []
};
},
sync: function() { return null; },
fetch: function() { return null; },
save: function() { return null; }
}),
Items: Backbone.Collection.extend({
model: this.Item,
}),
ItemView: Backbone.View.extend({
el: "#itemdiv",
tagName: "<div>",
className: "item",
template: _.template($("#template-item-view").html()),
initialize: function(model) {
this.model = model;
this.listenTo(this.model, "change", this.render);
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
})
};
var items = new App.Items();
items.add(new App.Item({name: "iPhone", id: 1, imageReference: "iPhone.jpg", ["mobile", "smartphone"]}));
items.add(new App.Item({name: "MacBook", id: 2, imageReference: "MacBook.jpg", ["laptop", "computer"]}));
All of the above works. When I inspect items it has the two models including the parameters. But when I try to add a new item directly to the Collection with Collection.create() like:
items.create({name: "Apple Watch", id: 3, imageReference: "AppleWatch.jpg", ["watch", "redundant"]});
it throws an error:
TypeError: undefined is not a constructor (evaluating 'new this.model(attrs, options)')
In case it helps, this error appears in Backbone.js in line 915 (dev version), the wrapping function is
/* from Backbone.js, Dev-Version, Line 911-919 */
_prepareModel: function(attrs, options) {
if (attrs instanceof Model) return attrs;
options = options ? _.clone(options) : {};
options.collection = this;
var model = new this.model(attrs, options);
if (!model.validationError) return model;
this.trigger('invalid', this, model.validationError, options);
return false;
}
I can´t figure out if that is just a small bug or if something with my architecture is wrong. Am very thankful for help and also on comments towards best practices, etc.
Thanks in advance!
You have an error in your add lines:
items.add(new App.Item({name: "iPhone", id: 1, imageReference: "iPhone.jpg", ["mobile", "smartphone"]}));
items.add(new App.Item({name: "MacBook", id: 2, imageReference: "MacBook.jpg", ["laptop", "computer"]}));
Should be:
items.add(new App.Item({name: "iPhone", id: 1, imageReference: "iPhone.jpg", itemTags: ["mobile", "smartphone"]}));
items.add(new App.Item({name: "MacBook", id: 2, imageReference: "MacBook.jpg", itemTags: ["laptop", "computer"]}));
The field itemTags was missing. Does that fix it?
The point at which the following is run:
Items: Backbone.Collection.extend({
model: this.Item,
}),
this isn't known.
So if you're namespacing to encapsulate your code do one of either:
var App = {}
App.item = Backbone.Model.extend({...})
App.items = Backbone.Collection.extend({...})
App.itemView = Backbone.View.extend({...})
App.items.add({})
App.items.add({})
Or:
(function() {
var item = Backbone.Model.extend({...})
var items = Backbone.Collection.extend({...})
var itemView = Backbone.View.extend({...})
var items.add({})
var items.add({})
})()
Related
I am trying to use knockout mapping, but it isn't working as I expected.
Here I created simpliest fiddle i can and it's not working.
Am I missing something?
https://jsfiddle.net/p48d11j5/1/
function Model(){
var self = this;
self.Id = ko.observable(0);
self.Name = ko.observable("Default");
self.Visible = ko.observable(false);
self.Items = ko.observableArray([]);
}
function ModelItem(){
var self = this;
self.Id = ko.observable(0);
self.Name = ko.observable("Default item name")
}
var m = new Model();
ko.mapping.fromJS({
Id:1,
Name: "Test",
Visible: true,
Items: [
{
Id:1,
Name:"First"
},
{
Id:2,
Name:"Second"
}
]
}, m);
ko.applyBindings(m);
edit: I am working with nested arrays, so I added array
edit2: I want have models "typed" to use functions or ko.computed properties of them
If you call ko.mapping.fromJS with two arguments : ko.mapping.fromJS(data, mappedObject) the second argument is a mappedObject which is already created.Then the second argument will be taken as a viewModel not options.
All you have to do is: ko.mapping.fromJS(data, {}, viewModel) - this one puts your data in your model.
ko.mapping.fromJS({
Id:1,
Name: "Test",
Visible: true,
Items: [{Id: 1, Name: "First"}, {Id: 2, Name: "Second"}]
}, {} ,m); // pass the second argument as an empty object.
Try this
var m = ko.mapping.fromJS({
Id:1,
Name: "Test",
Visible: true,
Items: [
{
Id:1,
Name:"First"
},
{
Id:2,
Name:"Second"
}
]
}, new Model());
ko.applyBindings(m);
Working Example: https://jsfiddle.net/p48d11j5/2/
You can give something like this a try, using the mapping plugin to set up your default state as well as to apply updates:
// Set up the initial model.
var model = ko.mapping.fromJS({
Id: 0,
Name: "Default",
Visible: false,
Items: []
});
ko.applyBindings(model);
// Map new data from the "server"...
var model = ko.mapping.fromJS({
Id:1,
Name: "Test",
Visible: true,
Items: [
{
Id:1,
Name:"First"
},
{
Id:2,
Name:"Second"
}
]
}, model);
// ...or directly manipulate the model.
model.Id(2);
model.Items.push({Id: 3, Name: "Third"});
https://jsfiddle.net/3evtx022/
Ok, so I'm currently going through a course over Backbone.js where I have to create a View with a list of objects, and a delete button, but I keep getting the error (Uncaught TypeError: this.model.each is not a function) where I have indicated below (//THIS IS WHERE THE ERROR IS BEING THROWN). Any help with this is appreciated!
//BACKBONE MODEL//
var Vehicle = Backbone.Model.extend( {
idAttribute: "registrationNumber",
urlRoot: "/api/vehicles",
start: function() {
console.log("Vehicle started.");
}
});
//BACKBONE MODEL//
var Car = Vehicle.extend( {
start: function() {
console.log("Car with registration number XLI887 started.");
},
validate: function(attrs) {
if (!attrs.registrationNumber)
return "Registration number is required.";
}
});
var car = new Car({ registrationNumber: "XLI887", color: "Blue" });
car.start();
//BACKBONE COLLECTION//
var Vehicles = Backbone.Collection.extend({
model: Vehicle,
});
var vehicles = new Vehicles([
new Vehicle({ car: "Car 1", registrationNumber: "XLI887", color: "Blue"}),
new Vehicle({ car: "Car 2", registrationNumber: "ZNP123", color: "Blue"}),
new Vehicle({ car: "Car 3", registrationNumber: "XUV456", color: "Gray"})
]);
var blueCars = vehicles.where({ color: "Blue" });
console.log("Blue Cars", blueCars);
var carRegNumb = vehicles.findWhere({ registrationNumber: "XLI887" });
console.log("Car Reg Numb", carRegNumb);
vehicles.remove(carRegNumb);
vehicles.each(function(vehicle){
console.log(vehicle);
});
//BACKBONE VIEW//
var VehicleView = Backbone.View.extend({
tagName: "li",
className: "vehicle",
id: "registrationNumber",
attributes: {
"data-color": "Blue"
},
render: function(){
this.$el.html(this.model.get("car"));
return this;
},
events: {
"click": "onClick",
"click .delete": "onClickDelete",
},
onClick: function(){
console.log("Delete Clicked");
},
onClickDelete: function(e){
console.log("Delete Clicked");
},
render: function(){
this.$el.html(this.model.get("car") + " <button>Delete</button>");
return this;
}
});
//BACKBONE VIEW//
var VehiclesView = Backbone.View.extend({
tagName: "ul",
className: "vehicle",
id: "registrationNumber",
attributes: {
"data-color": "Blue"
},
render: function(){
var self = this;
this.model.each(function(vehicle){ //THIS IS WHERE THE ERROR IS BEING THROWN
var vehiclesview = new VehiclesView({ model: car });
self.$el.append(vehiclesview.render().$el);
});
return this;
}
});
var vehicles = new Vehicles([
new Vehicle({ car: "Car 1", registrationNumber: "XLI887" }),
new Vehicle({ car: "Car 2", registrationNumber: "ZNP123" }),
new Vehicle({ car: "Car 3", registrationNumber: "XUV456" })
]);
var vehicleView = new VehicleView({ el: "#vehicles", model: car });
vehicleView.render();
var vehiclesView = new VehiclesView({ el: "#vehicles", model: car});
vehiclesView.render();
It seems to me that you get the error because you're passing in a model instance instead of a collection instance to your vehiclesView. Because the car model instance is not an array, you can't iterate over it using each().
Also I believe you're mixing up your model- and collection views. Your vehiclesView is essentially a collection view and should receive a collection of models. It's job is then to instantiate, render and append a vehicleView (model view) for each Vehicle model in your Vehicles collection.
// Vehicles Collection View.
var VehiclesView = Backbone.View.extend({
tagName: 'ul',
className: 'vehicle',
id: 'registrationNumber',
attributes: {
data-color: 'Blue'
},
render: function () {
// This is a collection view, therefore access your data by
// referencing `this.collection` and NOT `this.model`.
this.collection.each(function (vehicleModel) {
// Instantiate a model view for each model in your collection.
var vehicleView = new VehicleView({
model: vehicleModel
});
// Render and append the `vehicleView`
this.$el.append(vehicleView.render().$el);
}, this);
return this;
}
});
Then make sure to instantiate a collection using your models and instantiate your collection view by passing said collection into the collection view.
// Instantiate a collection.
var vehicles = new Vehicles([
new Vehicle({ car: 'Car 1', registrationNumber: 'XLI887' }),
new Vehicle({ car: 'Car 2', registrationNumber: 'ZNP123' }),
new Vehicle({ car: 'Car 3', registrationNumber: 'XUV456' })
]);
// Pass the collection to the vehicles collection view.
var vehiclesView = new VehiclesView({
collection: vehicles // Passing in a collection and NOT a model.
});
// Render the vehicles collection view.
vehiclesView.render();
I use Backbone-Relational to create a complex hierarchy of models, the code looks like this:
var ModelA = Backbone.Model.extend({
initialize: function () {
console.log('model a initialized!')
}
});
var CollectionA = Backbone.Collection.extend({
model: ModelA
});
var ModelB = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
key: 'Collection',
collectionType: CollectionA
}]
});
var data = new ModelB({
name: 'ModelB',
Collection: [{
name: 'ModelA-1'
}, {
name: 'ModelA-2'
}]
});
console.log(data.get('Collection').at(0) instanceof ModelA); // false
console.log(data.get('Collection').at(0) instanceof ModelB); // true
console.log(JSON.stringify(data.toJSON()));
// {"name":"ModelB","Collection":[{"name":"ModelA-1","Collection":[]},{"name":"ModelA-2","Collection":[]}]}
As you see, even though ModelB is successfully created, initialize() of ModelA isn't called. Checking the types proves that the objects inside the collection are not ModelA. How can I fix this?
Two changes necessary: make ModelA a RelationalModel (and not just a standard one), and tell ModelB that ModelA is its relatedModel, like this:
var ModelA = Backbone.RelationalModel.extend({
initialize: function () {
console.log('model a initialized!')
}
});
...
var ModelB = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
relatedModel: 'ModelA',
key: 'Collection',
collectionType: CollectionA
}]
});
Demo.
My model:
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
idAttribute: '_id',
defaults: {
id: 0,
description: '',
amount: 0,
dateEntry: new Date(),
positive: false
},
url: '/api/transactions'
});
My collection:
var Backbone = require('backbone');
var Transaction = require('../models/Transaction')
var TransactionCollection = Backbone.Collection.extend({
model: Transaction,
url: '/api/transactions',
initialize: function() {
this.hasNextPage = false;
this.filter = 'all';
},
parse: function(response) {
this.hasNextPage = response.hasNextPage;
this.balance = response.balance;
return response.transactions || [];
}
});
module.exports = new TransactionCollection();
I have a number of item views, each displaying its model data (transaction).
In each item view there's a "delete" button which triggers:
delete: function() {
console.log("Remove:: ");
console.log(this.model.toJSON());
this.model.destroy();
}
Looking at the console, I see the REST DELETE call to /api/transactions/
Shouldn't it be /api/transaction/model_id ? Actually, I see empty req.body and req.query on the server side (NodeJS).
The browser console logs:
[Log] Object (app.js, line 20298)
__v: 0
_id: "5421da84c6fd7c91060ba405"
amount: 1200
category: Object
dateAdded: "2014-09-23T20:39:32.905Z"
dateEntry: "2014-06-17T22:00:00.000Z"
description: "Example!"
id: 0
positive: true
user: "53fbb34fb91a922f03be61f8"
__proto__: Object
Transaction data is coming from MongoDB, thus having the _id property.
I suspect that is the reason of my problem. How can I have my Backbone app calling the right DELETE url?
In your model you're overriding the url property when you should be overriding the urlRoot property
//intercept the request for testing.
$.mockjax({
// matches /api/transactions/abCD1234
url: /^\/api\/transactions\/([0-9a-zA-A]+)$/,
urlParams: ['id'],
response: function (settings) {
var id = settings.urlParams.id;
$('body').append(id);
}
});
var yourModel = Backbone.Model.extend({
idAttribute: '_id',
defaults: {
id: 0,
description: '',
amount: 0,
dateEntry: new Date(),
positive: false
},
//This is the property that sets the "base" of all your REST urls
urlRoot: '/api/transactions'
});
var yourInstance = new yourModel({
_id:'5421da84c6fd7c91060ba405',
amount: 1200,
dateAdded: "2014-09-23T20:39:32.905Z",
dateEntry: "2014-06-17T22:00:00.000Z",
description: "Example!",
id: 0,
positive: true,
user: "53fbb34fb91a922f03be61f8"
});
yourInstance.destroy();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-mockjax/1.5.3/jquery.mockjax.min.js"></script>
First time of trying to post data from backbone to REST using model collection of backbone.
define(["underscore" , "backbone"],function(_ , Backbone){
var CustomerModel = Backbone.Model.extend({
urlRoot: 'http://myresturl/api/CusWeb',
initialize: function(){
},
defaults : {
UID: "00000000-0000-0000-0000-000000000000",
Sex: 0,
Name: "",
Company: "",
Address: "",
Email: ""
}
});
return CustomerModel;
});
define(["underscore","backbone","models/CustomerModel"] , function(_ ,Backbone,CustomerModel){
var CustomerCollection = Backbone.Collection.extend({
url: 'http://myresturl/api/CusWeb',
model:CustomerModel
});
return CustomerCollection;
});
I'm trying to post data from backbone to REST URL by clicking on a button in backbone view.
define(["jquery" ,
"underscore" ,
"backbone" ,
"text!templates/Customer/registerCustomerTemplate.html",
"models/CustomerModel",
"user",
],function($ , _ , Backbone, RegisterDescriptionTem, CustomerModel, Customer){
var userObj = new Customer();
var ContainerRegisterView = Backbone.View.extend({
el : $("#webbodycontainer"),
initialize : function(){
},
events : {
'click #register' : 'registerUser'
},
registerUser : function(){
this.model.set({
UID: "00000000-0000-0000-0000-000000000000",
Sex: 1,
Name: "",
Company: "",
Address: "",
Email: ""
});
this.model.save();
console.log(this.model.get('Sex'));
},
render : function(){
var _registerDes = _.template(RegisterDescriptionTem);
this.model = new CustomerModel();
this.$el.append(_registerDes((this.model.toJSON())));
}
});
return ContainerRegisterView;
});
console.log(this.model.get('Sex')); is shown in browser's console (1) but data didn't save to database.
Here is the router :
define([
.....
'views/Customer/ContainerRegister',
], function(ContainerRegister){
var AppRouter = Backbone.Router.extend({
routes: {
......
}
var app_router = new AppRouter;
app_router.on('route:registerAction', function(register){
if(window.currentSection){
window.currentSection.remove();
}
window.currentSection = new ContainerRegister({});
$('#webbodycontainer').html(window.currentSection.$el);
window.currentSection.render(register);
});
});
I appreciate any other information that you can provide. Thanks.
Check initializing your model. and also while saving try to give first parameter as null.
define(["jquery" ,
"underscore" ,
"backbone" ,
"text!templates/Customer/registerCustomerTemplate.html",
"models/CustomerModel",
"user",
],function($ , _ , Backbone, RegisterDescriptionTem, CustomerModel, Customer){
var ContainerRegisterView = Backbone.View.extend({
el : $("#webbodycontainer"),
//check initializing your model here.
initialize : function(){
_.bindAll(this);
this.model = new Customer();
},
events : {
'click #register' : 'registerUser'
},
registerUser : function(){
this.model.set({
UID: "00000000-0000-0000-0000-000000000000",
Sex: 1,
Name: "",
Company: "",
Address: "",
Email: ""
});
this.model.save(null,{
success:function(){
//code after success
},error:function(){
//code after error
},
});
console.log(this.model.get('Sex'));
},
render : function(){
var _registerDes = _.template(RegisterDescriptionTem);
this.model = new CustomerModel();
this.$el.append(_registerDes((this.model.toJSON())));
}
});
return ContainerRegisterView;
});
I think your issue might be lack of a model id. For backbone<->server interactions, you might find that the id property is required. Try modifying your code as follows:
define(["underscore" , "backbone"],function(_ , Backbone){
var CustomerModel = Backbone.Model.extend({
urlRoot: 'http://myresturl/api/CusWeb',
initialize: function(){
},
defaults : {
UID: "00000000-0000-0000-0000-000000000000",
Sex: 0,
Name: "",
Company: "",
Address: "",
Email: ""
},
idAttribute: "UID" // set "UID" as the alias for "id"
});
return CustomerModel;
});
Here's a blog post on a similar issue that goes into a little more detail http://blog.markstarkman.com/blog/2012/02/13/getting-backbone-dot-js-to-sync-with-rails-and-mongoid/
First of all, you need to set idAttribute on model as #SteamDev said.
var CustomerModel = Backbone.Model.extend({
urlRoot: 'http://myresturl/api/CusWeb',
initialize: function(){
},
defaults : {
//UID: "00000000-0000-0000-0000-000000000000",
Sex: 0,
Name: "",
Company: "",
Address: "",
Email: ""
},
idAttribute: "UID" // set "UID" as the alias for "id"
});
Secondly, you don't need to set UID value (neither on defaults of model, nor on model instance).