Backbone -- Can a model and a collection have their own separate urls? - backbone.js

I plan on doing separate fetches for an individual model and its collection, but would like the collection to follow the same structure of the model.
Currently, I have separate urls for each, but its crashing on a jQuery error Uncaught TypeError: Cannot read property 'length' of undefined
Should I be doing this a different way? Code is below:
ArticleModel.js
define([
'underscore',
'backbone',
], function(_, Backbone) {
var ArticleModel = Backbone.Model.extend({
defaults: {},
url : function() {
return baseAPIUrl + '/CoreService/GetArticle';
},
parse : function(data) {
console.log(data);
var articleArray = [];
$.each(data.Articles, function(i, item) {
if (isNotEmpty(item.ScaledImages)) {
var Image = item.ScaledImages.ImageUrls[4].Value;
}
articleArray = {
Id : item.Id,
Title : item.Title,
FeedTitle : item.FeedTitle,
Summary : item.Summary,
ImageUrl : Image,
Link: item.Link
};
});
return articleArray;
}
});
return ArticleModel;
});
ArticlesCollection.js
define([
'underscore',
'backbone',
'models/article/ArticleModel'
], function(_, Backbone, ArticleModel){
var ArticlesCollection = Backbone.Collection.extend({
model: ArticleModel,
initialize : function(articles, options) {
},
url : function() {
return baseAPIUrl + '/CoreService/GetFollowedMembersArticles';
},
parse : function(data) {
var articleArray = [];
$.each(data.Articles, function(i, item) {
if (item.ScaledImages != null) {
var image = item.ScaledImages.ImageUrls[4].Value;
}
articleArray.push({
Id : item.Id,
Title : item.Title,
FeedTitle : item.FeedTitle,
Summary : item.Summary,
ImageUrl : image,
Link: item.Link
});
});
return articleArray;
}
});
return ArticlesCollection;
});

Yes, for your model you would use:
var Model = Backbone.Model.extend({
idAttribute: "Id",
urlRoot: function() {
return baseAPIUrl + '/CoreService/GetArticle';
}
});
You would then instantiate your model like follows:
var model = new Model({Id: 2});
model.fetch();
Your api will call the following url then 'host/CoreService/GetArticle/2'

Yes. You can have different urls for models & collections. Only thing to worry would be the "url" and "urlRoot".
For a model it would be :
var User = Backbone.Model.extend({
urlRoot:'/saveuser.php'
});
For a collection it would be :
var Users = Backbone.Collection.extend({
url:'/saveuser.php'
});
Cheers

Related

Collection of unrelated Models in Backbone?

I am creating a single page app that lets users filter for data upon two criteria (Skills and Location). This data is to be populated from two separate web services.
There is a model for each service to consume the data using REST style requests.
I want to use both bits of data in this one view. From my understanding a collection can hold multiple instances of one type of Model e.g. "Movie"
var Movies = Backbone.Collection.extend({
model: Movie,
initialize: function() {
console.log("");
console.log("Movie Collection initialize");
console.log(this);
console.log(this.length);
console.log(this.models);
}
});
var movie1 = new Movie({
"title": "Bag It",
"averageUserRating": 4.6,
"yearReleased": 2010,
"mpaaRating": "R"
});
var movie2 = new Movie({
"title": "Lost Boy: The Next Chapter",
"averageUserRating": 4.6,
"yearReleased": 2009,
"mpaaRating": "PG-13"
});
However I am trying to implement the pattern below, where the collection has two Models. Is this an anti pattern for Backbone. How should this be tackled?
define([
'underscore',
'backbone',
'models/locationsModel',
'models/skillsModel'
], function (_, Backbone, Location, Skills)
{
'use strict';
var FiltersCollection = Backbone.Collection.extend({
// The filters collection requires these two models that will provide data to the filters view
location: new Location(),
skills: new Skills(),
initialize: function() {
//Do stuff
}
});
return new FiltersCollection();
});
I can't advise on what is best for you because I can't visualise your data properly based on the info provided. But if you observe the collection constructor in the Backbone source:
if (options.model) this.model = options.model;
Then in _prepareModel:
var model = new this.model(attrs, options);
And we knew that "model" is a function anyway, and a function can return what you want. So providing your two different data sources have some attribute that can identify them you can do something like this:
var SkillModel = Backbone.Model.extend({
sayMyName: function() {
return 'I am a skill model and I am skilled at ' + this.get('name');
}
});
var LocationModel = Backbone.Model.extend({
sayMyName: function() {
return 'I am a location model and I am relaxing in ' + this.get('name');
}
});
function FilterModel(attrs, options) {
if (attrs.type === 'skill') {
return new SkillModel(attrs, options);
} else if (attrs.type === 'location') {
return new LocationModel(attrs, options);
}
}
var FilterCollection = Backbone.Collection.extend({
model: FilterModel
});
var filteredCollection = new FilterCollection([{
type: 'skill',
name: 'carpentry'
}, {
type: 'location',
name: 'India'
}, {
type: 'skill',
name: 'plumbing'
}]);
var outputEl = document.querySelector('#output');
filteredCollection.each(function(model) {
outputEl.innerHTML += '<p>' + model.sayMyName() + '<p>';
});
<script src="http://underscorejs.org/underscore.js"></script>
<script src="http://backbonejs.org/backbone.js"></script>
<div id="output"></div>

BackboneJS + RequireJS: Undefined is not a function

I'm trying to implement RequireJS in my project and am running into some problems. I'm getting an error is my Item model. In the model, I'm trying to set an attribute to a new collection. In that line I get the following error: "Uncaught TypeError: undefined is not a function."
My router looks something like this:
define(['backbone', 'collections/items', 'views/itemsView'], function (Backbone, Items, ItemsView) {
var Workspace = Backbone.Router.extend({
routes: {
'*path': 'nearby'
},
nearby: function() {
var items = new Items();
items.fetch();
var itemsView = new ItemsView({
collection: items
});
});
return Workspace;
});
Collection:
define(['backbone', 'models/item', 'tastypie'], function(Backbone, Item) {
var Items = Backbone.Collection.extend({
url: function() {
var url;
url = '/api/v1/nearby/?lat=' + lat1 + '&lng=' + lon1;
return url;
},
model: Item,
});
return Items;
});
Model:
define(['backbone', 'collections/locations'], function (Backbone, Locations) {
var Item = Backbone.Model.extend({
urlRoot: '/api/v1/item',
url: function () {
// stuff here
},
set: function (attrs) {
// Making a new Locations collection ordered by distance
if (attrs.items && attrs.items.length > 0) {
// Sets the collection as an attrubute
// The following line is where I get the error
attrs.locations = new Locations(attrs.items);
Backbone.Model.prototype.set.apply(this, [attrs]);
// Sets the closest attribute
attrs.closest = this.get('locations').getClosestItem().get('distance');
Backbone.Model.prototype.set.apply(this, [attrs]);
attrs.numItems = this.get('items').length;
Backbone.Model.prototype.set.apply(this, [attrs]);
}
return Backbone.Model.prototype.set.apply(this, [attrs]);
},
});
return Item;
});
Anyone know why I'm getting the error? I made sure to define to Location collection in my model.
edit: adding in the locations.js file:
define(['backbone', 'models/location'], function (Backbone, LocationModel) {
var Locations = Backbone.Collection.extend({
model: LocationModel,
comparator: function(item) {
return item.get('distance') + ' ' + item.get('name');
},
getClosestItem: function() {
return this.at(0);
}
});
return Locations;
});
Thanks for the help.

uncaught typeerror: object function has no method 'tojson'

I have created a model like this
define(['backbone', 'text_dictionary'], function(Backbone, Text_Dict) {
var IndexPageModel = Backbone.Model.extend({
defaults:{
val_btn_gotohomepage : Text_Dict.val_btn_gotohomepage,
val_btn_gotologinpage : Text_Dict.val_btn_gotologinpage,
val_btn_gotolistpage : Text_Dict.val_btn_gotolistpage
}
});
return IndexPageModel;
});
and instantiated this model with 'new' in my page code like this
define([ 'page_layout',
'panel_itemview',
'header_itemview',
'content_itemview',
'footer_itemview',
'templates',
'text_dictionary',
'indexpage_model',
'indexpage_logic'],
function( Page,
Panel,
Header,
Content,
Footer,
Templates,
Text_Dict,
IndexPageModel,
IndexPage_BusnLogic) {
console.log("Success..Inside Index Page.");
var Page_Index = {};
Page_Index.page = (function(){
var _pageName = Text_Dict.indexpage_name;
var _pageModel = new IndexPageModel();
return _pageLayout = Page.pageLayout({
name:_pageName,
panelView: Panel.panelView({name:_pageName, pagetemplate: Templates.simple_panel}),
headerView: Header.headerView({name:_pageName, title: Text_Dict.indexpage_header, pagetemplate: Templates.header_with_buttons}),
contentView: Content.contentView({name:_pageName, page_model:_pageModel, pagetemplate:Templates.content_index, busn_logic:IndexPage_BusnLogic.HandleEvents}),
footerView: Footer.footerView({name:_pageName, title: Text_Dict.indexpage_footer, pagetemplate: Templates.simple_footer})
});
})();
return Page_Index;
});
my page gets created using the page layout
define([ 'underscore', 'marionette' ], function( _, Marionette ) {
console.log("Success..Inside Index View.");
var Page = {};
var _ReplaceWithRegion = Marionette.Region.extend({
open: function(view){
//Need this to keep Panel/Header/Content/Footer at the same level for panel to work properly
this.$el.replaceWith(view.el);
}
});
Page.pageLayout = function (opts) {
var _opts = _.extend ({ name: 'noname',
panelView: null,
headerView: null,
contentView: null,
footerView: null,
}, opts);
return new ( Marionette.Layout.extend({
tagName: 'section',
attributes: function() {
return {
'id': 'page_' + _opts.name,
'data-url': 'page_' + _opts.name,
'data-role': 'page',
'data-theme': 'a'
};
},
template: function () {
return "<div region_id='panel'/><div region_id='header'/><div region_id='content'/><div region_id='footer'/>";
},
regions: {
panel: {selector: "[region_id=panel]", regionType: _ReplaceWithRegion},
header: {selector: "[region_id=header]", regionType: _ReplaceWithRegion},
content: {selector: "[region_id=content]", regionType: _ReplaceWithRegion},
footer: {selector: "[region_id=footer]", regionType: _ReplaceWithRegion},
},
initialize: function(){
$('body').append(this.$el);
this.render();
},
onRender: function() {
if (this.options.panelView) {
this.panel.show (this.options.panelView);
};
if (this.options.headerView) {
this.header.show (this.options.headerView);
};
if (this.options.contentView) {
this.content.show(this.options.contentView);
};
if (this.options.footerView) {
this.footer.show (this.options.footerView);
};
},
}))(_opts);
};
return Page;
});
but in my itemview when i am passing model reference like this
define([ 'underscore', 'marionette', 'event_dictionary', 'app' ], function(_,
Marionette, Event_Dict, App) {
console.log("Success..Inside Content Index View.");
var Content = {};
Content.contentView = function(opts) {
return new (Marionette.ItemView.extend({
tagName : 'div',
attributes : function() {
console.log('options name==' + opts.name);
console.log("page model=="+opts.page_model);
return {
'region_id' : 'content',
'id' : 'content_' + opts.name,
'data-role' : 'content'
};
},
initialize : function() {
_.bindAll(this, "template");
},
template : function() {
return opts.pagetemplate;
},
model : function() {
return opts.page_model;
}
}))(opts);
};
return Content;
});
It's giving me error
Uncaught TypeError: Object function () {
return opts.page_model;
} has no method 'toJSON'
The model property of a view cannot be a function. Backbone allows this for some things like url (by way of the _.result helper function), but not in this case. Change your view code to not have a model function and just do this in initialize:
initialize: function (options) {
this.model = this.page_model = options.page_model;
}
UPDATE since you won't just take my word for it, here is the Marionette source that is almost certainly the top of your exception stack trace. Once again: view.model has to be a model object not a function. Fix that and the error will go away.
The accepted answer is correct, but it took a bit of messing about to find out why I had that error coming up, so I'm offering what the solution for my personal use-case was in case it helps anyone else stumbling upon this page in the future.
I had this:
app.module 'Widget.Meta', (Meta, app, Backbone, Marionette, $, _) ->
Meta.metaView = Backbone.Marionette.ItemView.extend
model: app.Entities.Models.meta
template: '#meta-template'
... when I should have had this:
app.module 'Widget.Meta', (Meta, app, Backbone, Marionette, $, _) ->
Meta.metaView = Backbone.Marionette.ItemView.extend
model: new app.Entities.Models.meta()
template: '#meta-template'
It's just a matter of instantiating the function definition.

Backbone JS Collection not being populated

Just started using backbone and all was going good until I tried load in some Json data.
// Category Model
var Category = Backbone.Model.extend({
defaults: {
title: "Not specfied",
paramName: "not_specified",
filter: false
},
initialize: function(){
console.log("Cateogory Model Initialized");
}
});
// Category Collection
var CategoryList = Backbone.Collection.extend({
url: '/assets/js/libs/items.json',
parse: function(resp, xhr) {
return resp.facets;
},
model: Category,
initialize : function() {
console.log('This Collection has been called');
}
});
var categories = new CategoryList();
categories.fetch();
If i try to look at any of the items in the collection it is empty !
My json file looks like the following
{
"facets": [
{
"title": "Military Service & Conflict",
"searchParam": "Military"
},
{
"title":"Census, land & substitutes",
"searchParam":"Census"
},
{
"title":"Education & work",
"searchParam":"Education"
}
]
}
If I can get this working the I just need to sort my view out
Rob

Handeling response from backbone.js collection using fetch

am pretty new to backbone.js and managed recently to finish my first application. I made a collection that is responsible for fetching data through a API but am not able to loop through the result and use it.
Here is my model file
define([
'jquery',
'underscore',
'backbone'
], function($, _, Backbone){
var VehicleLookupModel = Backbone.Model.extend({
//data will contain one of the items returned from the collection's 'parse' function.
parse: function(data){
return data;
}
})
return VehicleLookupModel;
});
collection file
define([
'jquery',
'underscore',
'backbone',
'l/models/VehicleLookupModel'
], function($, _, Backbone, VehicleLookupModel){
var VehicleLookupModelSet = Backbone.Collection.extend({
model : VehicleLookupModel,
url : function() {
return '/en/car/api/model-lookup-model.json/'+this.make+'/';
},
parse : function(response) {
return response;
},
initialize: function(options) {
options || (options = {});
this.make = options.make;
}
})
return VehicleLookupModelSet;
});
and finally the view file
define([
'jquery',
'underscore',
'backbone',
'l/collections/VehicleLookupMakeSet',
'l/collections/VehicleLookupModelSet',
'l/collections/VehicleLookupTrimSet'
], function($, _, Backbone, VehicleLookupMakeSet, VehicleLookupModelSet, VehicleLookupTrimSet){
var BrowseVehicleView = Backbone.View.extend({
el: $('#vehicle-browse-form'),
initialize: function(){
// Extend JQuery example
// This would extend JQuery function for resetting elements on the form
$.fn.extend({
resetElement: function(){
$(this).attr('disabled', 'disabled');
$(this).html('');
return $(this);
}
});
// define array of elements to be used in DOM manipulations
this.elements = {
"make" : $('#id_make', this.el),
"model" : $('#id_model', this.el),
"trim" : $('#id_trim', this.el),
"year" : $('#id_year', this.el)
}
},
events: {
"change #id_make" : "onMakeChange",
"change #id_model" : "onModelChange",
"change #id_trim" : "onTrimChange"
},
render: function(){
// Using Underscore we can compile our template with data
},
onMakeChange: function(event) {
this.elements.model.resetElement();
this.elements.trim.resetElement();
this.collection = new VehicleLookupModelSet({make: this.elements.make.val()})
this.collection.fetch();
console.log(this.collection);
},
onModelChange: function(event) {
var VehicleLookupTrimInstance = new VehicleLookupTrimSet({make: this.elements.make.val(), model: this.elements.model.val()})
VehicleLookupTrimInstance.fetch();
},
onTrimChange: function(event) {
},
renderItem: function(object, item) {
console.log(item);
}
});
// Our module now returns our view
return new BrowseVehicleView;
});
The above is console.log(this.collection) is returning an object with many property which am not sure how to use. But, I noticed that there is a method "models" and inside models there is many number of objects, each represent the value of the json.
Any ideas how i can loop through the object?
this.collection.fetch({
success: function(collection, response) {
_.each(collection.models, function(model) {
console.log(model.toJSON());
})
}
});

Resources