Backbone.js - base path not concat with subpaths - backbone.js

In my base collection i have the base path, from the base path i am extending further urls.. but while i console the url in the extended collection i am not getting the full path of the url..
instead just i am getting the url - what the extended collection have.. why i am getting like so, and what should be the proper approach?
here is my try :
BaseCollection = Backbone.Collection.extend({
path: 'http://path/to/api'
});
TeachersCollection = BaseCollection.extend({
url:"xyz/abc",
initialize:function(){
console.log(this.url);//xyz/abc - why i am getting like this instead of full path?
//full url should be 'http://path/to/api/xyz/abc' - how can i get like this..?
}
});
var x = new TeachersCollection;
live demo

path is not a special property on any of Backbone's classes
Models can have urlRoot, but there's no such thing for collections
Here's an approach that should work for you:
TeachersCollection = BaseCollection.extend({
url:function() {
return this.path + "/xyz/abc"
},
initialize:function(){
// this.url = _.result(this, 'url');
console.log(_.result(this, 'url'));
}
});
You may actually want to think about changing the constructor on your base collection, like this if you're going to be extending it a lot:
BaseCollection = Backbone.Collection.extend({
constructor: function() {
this.url = 'http://path/to/api' + this.url;
Backbone.Collection.prototype.constructor.apply(this, arguments);
}
});
TeachersCollection = BaseCollection.extend({
url: "/xyz/abc",
initialize:function(){
console.log(this.url);//xyz/abc
//full url should be 'http://path/to/api/xyz/abc'
}
});
var x = new TeachersCollection;

The easiest solution would be to use a function for url, then you could do things like this:
TeachersCollection = BaseCollection.extend({
url: function() {
return this.path + '/xyz/abc';
},
//...
The problem with trying to use just a string for url in such cases is getting the right this so that you can look up the path. You could go through BaseCollection.prototype:
TeachersCollection = BaseCollection.extend({
url: BaseCollection.prototype.path + '/xyz/abc',
//...
but that's pretty cumbersome and noisy, I don't think saving the overhead of a function call is worth the hassle.

Maybe you can write this code:
console.log(this.path+this.url)

Related

How to fetch the model in a view with a dynamic url defined in a view

I am using backbone.js in my app. My model named MyModel is
Backbone.Model.extend({
urlRoot: 'home'
});
Need to fetch model with url "home/abc/xyz" where "abc" and "xyz" are dynamic in my view. I did the following
var model = new MyModel({id:'abc/xyz'});
model.fetch();
but its not working.
It goes to url "home/abc?xyz".
How should i solve this issue?
Here is the url function of Backbone.Model which is responsible for such kind of behavior in Backbone:
url: function() {
var base =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
}
As you can see encodeURIComponent(this.id) will encode your id, so you can't pass and '/' -> '%2F'.
You always can rewrite this function, but I guess it's not the best idea, cause it can break another things.
I can suggest you another approach:
Just define urlRoot of your model as function and there do your job:
var yourModel = Backbone.Model.extend({
defaultUrl: 'home',
urlRoot: function () {
return defaultUrl + this.get('yourUrlAttribute');
}
});
Just pass the yourUrlAttribute as model attribute when creating it and fetch the model.
Having in mind this approach and that Backbone.Model will append encoded 'id' as the last part of URL (when you fetching model) you can get what you want.

Customizing backbone models to work with urls like /documents/6/editor

Is it possible to override the url method of a backbone model to insert the id in the middle of the url string.
I.e. I dont want to fetch from this
documents/6
but this
documents/6/editor
and similiary update to
documents/6/editor
Currently backbone insists on always appending the id to the end of the url string.
I tried
urlRoot: function(){
return "/documents" + this.id + "/editor";
}
Whilst this works for fetching a model from the server it fails on updates. It seems to be trying the url
/documents/6/editor/6
and not
/documents/6/editor
Overriding Model.urlRoot alters the prefix of your url and is later used in Model.url
Try
var M = Backbone.Model.extend({
urlRoot: '/documents',
url: function() {
var base = Backbone.Model.prototype.url.call(this);
if (this.isNew()) return base;
return base+'/editor';
}
});
And a demo http://jsfiddle.net/nikoshr/pjr81pLd/

How to pass a URL to a collection in Backbone

I need to build several collections in Backbone that only differ by their URL. Here is my model:
App.Models.Main = Backbone.Model.extend({});
Here is my collection:
App.Collections.Mains = Backbone.Collection.extend({
model: App.Models.Main,
initialize: function() {
this.fetch({
success: function(data) {
//console.log(data.models);
}
});
},
url: this.url
});
In my router, I tried:
mc = new App.Collections.Mains({ url: 'main-contact'});
mains = new App.Views.Mains({ collection: mc});
$('#web-leads').append(mains.el);
But I get an this error: Error: A "url" property or function must be specified
How do I pass the URL into the collection?
The Backbone.Collection constructor looks like this:
Backbone.Collection = function(models, options) { ...
It expects the options as a second argument. The first argument is a list of models with which to initialize the collection.
You need to initialize the collection with:
mc = new App.Collections.Mains(null, { url: 'main-contact'})
Also, when you're defining your collection you're not setting the url property correctly. The expression this.url is evaluated when your model is defined, not when it is initialized. The context of this does not point to any instance of the collection, but the scope at the time of defining the model, probably window. Javascript allows you to try to set it to window.url, but because window has no such property, it's set to undefined.
Instead of:
App.Collections.Mains = Backbone.Collection.extend({
url: this.url
});
You need to set the url in the initialize method:
App.Collections.Mains = Backbone.Collection.extend({
initialize: function(models, options) {
if(options && options.url) {
this.url = options.url;
}
}
});
This should work, it's a bit ugly though:
mc = new (App.Collections.Mains.extend({ url: 'main-contact'}));
mains = new App.Views.Mains({ collection: mc});
You basically extend your base collection and call a new on it. Unwrapping it would be something like:
MainContantCollection = App.Collections.Mains.extend({ url: 'main-contact'});
mc = new MainContactCollection();
mains = new App.Views.Mains({ collection: mc});

Failing to pass models correctly from collection view?

I've been staring at this for a while and trying various tweaks, to no avail.
Why am I getting a "this.model is undefined" error at
$(function(){
window.Sentence = Backbone.Model.extend({
initialize: function() {
console.log(this.toJSON())
}
});
window.Text = Backbone.Collection.extend({
model : Sentence,
initialize: function(models, options){
this.url = options.url;
}
});
window.SentenceView = Backbone.View.extend({
initialize : function(){
_.bindAll(this, 'render');
this.template = _.template($('#sentence_template').html());
},
render : function(){
var rendered = this.template(this.model.toJSON());
$(this.el).html(rendered);
return this;
}
})
window.TextView = Backbone.View.extend({
el : $('#notebook') ,
initialize : function(){
_.bindAll(this, 'render');
},
render : function(){
this.collection.each(function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
return this;
}
});
function Notebook(params){
this.text = new Text(
// models
{},
// params
{
url: params.url
}
);
this.start = function(){
this.text.fetch();
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
};
}
window.notebook = new Notebook(
{ 'url': 'js/mandarin.js' }
);
window.notebook.start();
})
There's an online version wher eyou can see the error in a console at:
http://lotsofwords.org/languages/chinese/notebook/
The whole repo is at:
https://github.com/amundo/notebook/
The offending line appears to be at:
https://github.com/amundo/notebook/blob/master/js/notebook.js#L31
I find this perplexing because as far as I can tell the iteration in TextView.render has the right _.each syntax, I just can't figure out why the Sentence models aren't showing up as they should.
var view = new SentenceView({model: sentence});
I'm pretty sure when you pass data to a backbone view constructor, the data is added to the Backbone.View.options property.
Change this line
var rendered = this.template(this.model.toJSON());
to this
var rendered = this.template(this.options.model.toJSON());
and see if it works
UPDATE:
From the doco:
When creating a new View, the options you pass are attached to the view as this.options, for future reference. There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, and tagName
So, disregard the above advice - the model should by default be attached directly to the object
Things to check next when debugging:
confirm from within the render() method that this is actually the SentenceView object
confirm that you are not passing in an undefined sentence here:
var view = new SentenceView({model: sentence});
UPDATE2:
It looks like the collection is borked then:
this.textView = new TextView({
collection: this.text
});
To debug it further you'll need to examine it and work out what's going on. When I looked in firebug, the collection property didn't look right to me.
You could have a timing issue too. I thought the fetch was asynchronous, so you probably don't want to assign the collection to the TextView until you are sure it has completed.
Backbone surfaces underscore.js collection methods for you so you can do this. See if this works for you:
this.collection.each(function(sentence) {
// YOUR CODE HERE
});
I think the problem is on line 48 of notebook.js shown below:
render : function(){
_(this.collection).each(function(sentence){
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
The problem is you are wrapping the collection and you don't have to. Change it to
this.collection.each(function(sentence){ ...
hope that fixes it
EDIT:
OK i'm going to take another crack at it now that you mentioned timing in one of your comments
take a look at where you are fetching and change it to this:
this.start = function(){
this.text.fetch({
success: _.bind( function() {
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
}, this)
);
};
I typed this manually so there may be mismatching parentheses. The key is that fetch is async.
Hope this fixes it
try using _.each
_.each(this.collection, function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
},this);

How to set a Collection's url

let's say I have :
var Book = Backbone.Model.extend();
var Collection = Backbone.Collection.extend({
model: Book,
url: '/books',
initialize: function(){
this.fetch();
})
})
How can I change the Collection's url when instantiating a new collection ?
var AdventureBooks = new Books({ url: '/books/adventure' }) does not work
var AdventureBooks = new Books({ category: 'adventure' })
and in the Collection definition:
url : '/books/' + this.category does not work either.
Thanks.
The following should work:
var AdventureBooks = new Books();
AdventureBooks.url = '/books/adventure';
var Book = Backbone.Model.extend({
"url": function() {
return '/books/' + this.get("category");
}
});
For some reason the parameters passed to Collection constructor (for example "url") are not set to the object. The collection uses only few of those (model and comparator).
If you want to pass the url via constructor you need to create initialize method that copies the necessary parameters to the object:
var Book = Backbone.Model.extend({
initialize: function(props) {
this.url = props.url;
}
}
var book = new Book({url: "/books/all"});
Like Daniel Patz pointed out , the problem lies in how you're instantiating the collection. I just struggled with this for a bit right now, so I thought I'd update this, even though the question is somewhat old.
The first argument is expected to be an array of models, with the options coming after. This should work:
var AdventureBooks = new Books([], { url: '/books/adventure' })
If you want a dynamic URL, then Raynos' answer might be the way to go.
If you want to have dynamic urls for your collection, try this (tested with backbone 1.1.2):
Create an instance of your backbone collection and pass the dynamic url parameter as an option (the options object needs to be the the second argument as the first one is an optional array of models):
var tweetsCollection = new TweetsCollection(null, { userId: 'u123' });
Then inside of your collection, create a dynamic url function that uses the value from the options object:
var TweetsCollection = Backbone.Collection.extend({
url: function() {
return '/api/tweets/' + this.options.userId;
},
model: TweetModel
});
The best solution for me is the initialize method, look at this example:
Entities.MyCollection = Backbone.Collection.extend({
model: Entities.MyModel,
initialize: function(models,options) {
this.url = (options||{}).url || "defaultURL";
},
}
use it as follows:
var items = new Entities.MyCollection(); //default URL
var items = new Entities.MyCollection([],{url:'newURL'}); //changed URL
I know that this a late reply, but I had a similar although slightly more complicated situation, and the selected answer didn't really help me.
I have a Conditions collection, and each Experiment model has multiple conditions, and I needed my url to be /api/experiments/:experimentId/conditions, but I didn't know how to access the experimentId from the empty Conditions collection.
In my Conditions collection url function, I did a console.log(this.toJSON()) and discovered
that Backbone inserts a single dummy model in the empty collection with whatever attributes you passed in at it's creation time.
so:
var Conditions = new ConditionsCollection({
experimentId: 1
});
I somehow doubt that this would be considered a best practice, hopefully someone else will respond with a better solution, but here's how I defined my Collection:
var ConditionsCollection = Backbone.Collection.extend({
model: Condition,
url: function(){
var experimentId = this.at(0).get("experimentId");
return "/api/experiments/" + experimentId + "/conditions";
}
});
This work for me (tested with backbone 1.2.1):
var serverData = Backbone.Collection.extend({
url: function() {
return '//localhost/rest/' + this.dbname;
},
constructor: function(a) {
if(a.dbname){
this.dbname = a.dbname;
}
Backbone.Collection.apply(this, arguments);
}
});
use it as follows:
var users = new serverData({dbname : 'users'});

Resources