I'm learning Backbone.
I want to create a list that can contain different models, with different attributes.
For example, listing folder contents, which could include models of type file and models of type folder, in any order.
file : {
title : "",
date : "",
type : "",
yaddayadda : ""
}
folder : {
title : "",
date : "",
haminahamina : ""
}
What is the proper way to represent this in Backbone? Is it possible to have a single Collection with multiple models?
Create a base model that your other models inherit from:
var DataModel = Backbone.Model.extend({
// Whatever you want in here
});
var FileModel = DataModel.extend({
// Whatever you want in here
});
var FolderModel = DataModel.extend({
// Whatever you want in here
});
And make the model type of the collection be that same base model:
var DataCollection = Backbone.Collection.extend({
model: DataModel
});
You could also do it the backbone way. Check out the docs backbone collection
Basically you would create different models adding a tie breaker attribute say "type" in this case.
var file = Backbone.Model.extend({
defaults: {
// will need to include a tie breaker attribute in both models
type: 'file'
}
}),
folder = Backbone.Model.extend({
defaults: {
// tie breaker
type: 'folder'
}
});
var fs = Backbone.Collection.extend({
model: function(model, options) {
switch(model.type) {
case 'file':
return new file(model, options);
case 'folder':
return new folder(model, options);
}
}
});
// after that just add models to the collection as always
new fs([
{type: 'file',name: 'file.txt'},
{type: 'folder',name: 'Documents'}
]);
Backbone documention is not complete in this case. It will not work when used with merge:true option and idAttribute. In that case you need to:
var ModelFactory = function (attr, options) {
switch (attr.type) {
case 'file':
return new file(attr, options);
case 'folder':
return new folder(attr, options);
}
};
ModelFactory.prototype.idAttribute = '_id';
var fs = Backbone.Model.extend({
model: ModelFactory
});
var bannedList = app.request('rest:getBan');
var whiteIpList = app.request("rest:getWhite");
var whiteGroupList = app.request("rest:....");
$.when(bannedList, whiteIpList, whiteGroupList).
done(function (bannedList, whiteIpList, whiteGroupList) {
var collection = new Backbone.Collection();
collection.add(bannedList);
collection.add(whiteIpList);
collection.add(whiteGroupList);
});
app.reqres.setHandler("rest:getBannedList", function (data) {
return API.getBannedList(data);
});
getBannedList: function (data) {
var user = new Backbone.Model();
user.url = '/banned';
user.cid = 'bannedList';
var defer = $.Deferred();
user.fetch({
type: 'GET',
data: data,
success: function (data) {
defer.resolve(data);
},
error: function (data) {
defer.reject(data);
}
});
return defer.promise();
},
Related
I'm looking for the correct backbone structure to achieve the following:
Two server APIs:
GET api/event/4 : returns the event object with id 4.
GET api/event/4/registrations : returns the list of registration objects belonging to event with id 4
I want a view displaying the event object and the list of registrations.
This is very straightforward but I cannot figure out how to organize my Event and Registration models.
Should I use backbone-relational?
My Event model is currently like this:
(the collection is expected to contain the next 10 events from now).
How should I define my Registration model and how will I initialize it, knowing that it is always in the context of an Event model?
var app = app || {};
app.EventModel = Backbone.Model.extend({
urlRoot: app.API_server + 'event'
});
app.EventCollection = Backbone.Collection.extend({
model: app.EventModel,
url: app.API_server + 'event',
initialize: function(){
dt = new Date();
start_dt = dt.toISOString();
this.fetch({
data: {limit:10, start_dt:start_dt},
error: function (model, response, options) {
if(response.status == '403') {
app.Session.logout();
}
}
})
}
});
Make a collection for the registration and use the url property as a function. By default, the urlRoot of the models of the RegistrationCollection will be the url of the collection with their id appended.
app.RegistrationCollection = Backbone.Collection.extend({
url: function() {
return app.API_server + 'event/' + this.id + '/registrations';
},
initialize: function(models, options) {
options = options || {};
this.id = options.id;
}
});
Then, on EventModel initializing, add a RegistrationCollection as a property, passing the event id as an option to the collection.
app.EventModel = Backbone.Model.extend({
urlRoot: app.API_server + 'event',
initialize: function() {
this.registrations = new app.RegistrationCollection(null, {
id: this.id
});
}
});
Remove the fetch from the init, you want to make your collection reusable.
app.EventCollection = Backbone.Collection.extend({
model: app.EventModel,
url: app.API_server + 'event',
});
Fetch inside the view or the router, depending on where it makes more sense for your app.
var EventView = Backbone.View.extend({
initialize: function() {
this.collection = new app.EventCollection();
var dt = new Date(),
start_dt = dt.toISOString();
// this should be here, outside of the collection.
this.collection.fetch({
data: { limit: 10, start_dt: start_dt },
error: function(model, response, options) {
if (response.status === 403) {
app.Session.logout();
}
}
});
},
});
In my Marionette app, I have a Collection view, with a childView for it's models.
The collection assigned to the CollectionView is a PageableCollection from Backbone.paginator. The mode is set to infinite.
When requesting the next page like so getNextPage(), the collection is fetching data and assigning the response to the collection, overwriting the old entries, though the full version is store in collection.fullCollection. This is where I can find all entries that the CollectionView needs to render.
Marionette is being smart about collection events and will render a new childView with it's new model when a model is being added to the collection. It will also remove a childView when it's model was removed.
However, that's not quite what I want to do in this case since the collection doesn't represent my desired rendered list, collection.fullCollection is what I want to show on page.
Is there a way for my Marionette view to consider collection.fullCollection instead of collection, or is there a more appropriate pagination framework for Marionette?
Here's a fiddle with the code
For those who don't like fiddle:
App = Mn.Application.extend({});
// APP
App = new App({
start: function() {
App.routr = new App.Routr();
Backbone.history.start();
}
});
// REGION
App.Rm = new Mn.RegionManager({
regions: {
main: 'main',
buttonRegion: '.button-region'
}
});
// MODEL
App.Model = {};
App.Model.GeneralModel = Backbone.Model.extend({});
// COLLECTION
App.Collection = {};
App.Collection.All = Backbone.PageableCollection.extend({
model: App.Model.GeneralModel,
getOpts: function() {
return {
type: 'POST',
contentType: 'appplication/json',
dataType: 'json',
data: {skip: 12},
add: true
}
},
initialize: function() {
this.listenTo(Backbone.Events, 'load', (function() {
console.log('Load more entries');
// {remove: false} doesn't seem to affect the collection with Marionette
this.getNextPage();
})).bind(this)
},
mode: "infinite",
url: "https://api.github.com/repos/jashkenas/backbone/issues?state=closed",
state: {
pageSize: 5,
firstPage: 1
},
queryParams: {
page: null,
per_page: null,
totalPages: null,
totalRecords: null,
sortKey: null,
order: null
},
/*
// Enabling this will mean parseLinks don't run.
sync: function(method, model, options) {
console.log('sync');
options.contentType = 'application/json'
options.dataType = 'json'
Backbone.sync(method, model, options);
}
*/
});
// VIEWS
App.View = {};
App.View.MyItemView = Mn.ItemView.extend({
template: '#item-view'
});
App.View.Button = Mn.ItemView.extend({
template: '#button',
events: {
'click .btn': 'loadMore'
},
loadMore: function() {
Backbone.Events.trigger('load');
}
});
App.View.MyColView = Mn.CollectionView.extend({
initialize: function() {
this.listenTo(this.collection.fullCollection, "add", this.newContent);
this.collection.getFirstPage();
},
newContent: function(model, col, req) {
console.log('FullCollection length:', this.collection.fullCollection.length, 'Collection length', this.collection.length)
},
childView: App.View.MyItemView
});
// CONTROLLER
App.Ctrl = {
index: function() {
var col = new App.Collection.All();
var btn = new App.View.Button();
var colView = new App.View.MyColView({
collection: col
});
App.Rm.get('main').show(colView);
App.Rm.get('buttonRegion').show(btn);
}
};
// ROUTER
App.Routr = Mn.AppRouter.extend({
controller: App.Ctrl,
appRoutes: {
'*path': 'index'
}
});
App.start();
You could base the CollectionView off the full collection, and pass in the paged collection as a separate option:
App.View.MyColView = Mn.CollectionView.extend({
initialize: function(options) {
this.pagedCollection = options.pagedCollection;
this.pagedCollection.getFirstPage();
this.listenTo(this.collection, "add", this.newContent);
},
// ...
}
// Create the view
var colView = new App.View.MyColView({
collection: col.fullCollection,
pagedCollection: col
});
Forked fiddle
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();
});
Here my problem, I want to change my model dynamically (change dynamically a variable in my model when the collection is instantiated).
So here my code :
define(['backbone'], function (backbone) {
var MyModel = Backbone.Model.extend({
initialize: function() {
var that = this;
var likes;
var UrlGetLike = "https://api.facebook.com/method/fql.query?query=select%20like_count%20from%20link_stat%20where%20url=%27https://www.facebook.com/pages/Stackoverflow/1462865420609264%27&format=json";
$.getJSON( UrlGetLike, {
format: "json"
})
.done(function(data) {
likes = data[0].like_count;
that.set({
'likes' : likes
});
});
},
});
return MyModel;
});
But the data are not updated, MyModel is returned before the .done() finished ..
I try this too :
define(['backbone'], function (backbone) {
var MyModel = Backbone.Model.extend({
initialize: function() {
var that = this;
var likes;
var UrlGetLike = "https://api.facebook.com/method/fql.query?query=select%20like_count%20from%20link_stat%20where%20url=%27https://www.facebook.com/pages/Stackoverflow/1462865420609264%27&format=json";
$.getJSON( UrlGetLike, {
format: "json"
})
.done(function(data) {
likes = data[0].like_count;
that.set({
'likes' : likes
});
that.returnn;
});
},
returnn: function(){
return this;
}
});
});
But I got this error Cannot read property 'prototype' of undefined, because I fired
var collection = new Collection({collection : MyModel});
before MyModel is return (I think)
If anyone have a solution or something to help me, it would be appreciate :).
You can fetch the info for each model in the collection after its creation (it's actually a bad thing to fetch data in initialize method as this method was not created for that purpose. It's better to call a fetch method for a model explicitly (in our case let's call it fetchLikes))
var MyModel = Backbone.Model.extend({
fetchLikes: function () {
var UrlGetLike = "https://api.facebook.com/method/fql.query?query=select%20like_count%20from%20link_stat%20where%20url=%27https://www.facebook.com/pages/Stackoverflow/1462865420609264%27&format=json";
$.getJSON(UrlGetLike, {
format: "json"
}, _.bind(function (data) {
likes = data[0].like_count;
that.set({
'likes': likes
});
}, this));
}
});
var Collection = Backbone.Collection.extend({
model: MyModel
})
var collection = new Collection();
//.. insert models in the colleciton ..
collection.forEach(function (model) {
model.fetchLikes();
})
Bare in mind that you are doing ajax requests in for-loop that is considered a bad practice.
Do it only if you have no way to get the whole data in one request.
I have created a very basic backbone app, to understand how it works.
In the router, I just wanna display just 1 model, i.e. a user, not the whole collection, by passing an id in the url, how to do that?
For example, I'd like to do someapp.com/app/#user/2, and this would display just user no2 details.
Please see my work in jsfiddle
// router
var ViewsRouter = Backbone.Router.extend({
routes: {
'': 'viewOne',
'one': 'viewOne',
'two': 'viewTwo',
'user/:id': 'user'
},
viewOne: function() {
var view = new TheViewOne({ model: new TheModel });
},
viewTwo: function() {
var view = new UserView({ model: new TheModel });
},
user: function(id) {
// how to get just 1 user with the corresponding id passed as argument
// and display it???
}
});
Many thanks.
https://github.com/coding-idiot/BackboneCRUD
I've written a complete Backbone CRUD with no backend stuff for beginners. Below is the part where we get the user from the collection and show/render it.
View
var UserEditView = Backbone.View.extend({
render: function(options) {
if (options && options.id) {
var template = _.template($("#user-edit-template").html(), {
user: userCollection.get(options.id)
});
this.$el.html(template);
} else {
var template = _.template($("#user-edit-template").html(), {
user: null
});
// console.log(template);
this.$el.html(template);
}
return this;
},
Router
router.on('route:editUser', function(id) {
console.log("Show edit user view : " + id);
var userEditView = new UserEditView({
el: '.content'
});
userEditView.render({
id: id
});
});
Update
Particularly, sticking to your code, the router will look something like this :
user: function(id) {
var view = new UserView({ model: userCollection.get(id) });
}