How to deal with a collection nested in a RelationalModel? - javascript

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.

Related

Knockout ko.mappings.fromJS not working

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/

Backbone.js Uncaught TypeError this.model.each is not a function

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();

Backbone.js Error when binding a model to a view

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({})
})()

Model destroy calling incorrect REST url

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>

Simplest way to represent an arbitrarily deep nested list in Backbone

I'm new to Backbone. I have an arbitrarily deep nested list where each node in the list could potentially have a child collection. Eg:
item 1
item a
item i
item ii
item b
item c
item 2
item 3
etc
I am wondering what is the simplest way to represent this data structure using Backbone.
If it matters, I do not have a preference between loading the entire structure initially, or loading each level as it is needed. Whatever is easiest.
I would prefer not to go the route of Backbone-Relational, as I have a feeling that is overkill for something like this.
Thanks (in advance) for your help.
Assuming your data structure looks something like this
[
{
title: "item 1",
nodes: [
{title: "item a",
nodes: [
{title: "item i"},
{title: "item ii"}
]
},
{title: "item b"
}
]
},
{
title: "item 2"
}
]
you could set up your hierarchy by overriding the parse method of your models:
var Node = Backbone.Model.extend({
parse: function(data) {
this.nodes = new Nodes(data.nodes, {parse: true});
return _.omit(data, 'nodes');
}
});
var Nodes = Backbone.Collection.extend({
model: Node
});
var c = new Nodes(data_structure, {parse: true});
// parse: true is only needed if you pass the data as an argument
Node.parse extracts the nodes property from the data hash to build a custom attribute on the object and then returns the rest to let Backbone handle the other attributes. You then access the collection with model.nodes. And a Fiddle to play with http://jsfiddle.net/C8HGY/
I would make a simple model, maybe with default attribute children set to new Collection (if needed). The main part is loading from JSON data to models.
var MyModel = Backbone.Model.extend({
});
var MyCollection = Backbone.Collection.extend({
model: MyModel,
load: function(data) {
for(var i in data) {
if (_.isString(data[i])) {
this.add(new MyModel({
title: data[i]
}));
} else {
var collection = new MyCollection();
collection.load(data[i]);
this.add(new MyModel({
title: i,
children: collection
}));
}
}
}
});
var collection = new MyCollection();
collection.load({
'item1': {
'item a': ['item i', 'item ii'],
0: 'item b',
1: 'item c'
},
0: 'item2',
1: 'item3'
});
console.log(collection);
Only that in this example items with "0" and "1" keys are going to the beggining of the collection, but you can see the idea.

Categories

Resources