I have the following Backbone code, that's supposed to create a collection with a model, and create a new model instance within it, and save it on the server.
var Project = Backbone.Model.extend({});
var Projects = Backbone.Collection.extend({
model: Project,
url: "/api/projects"
});
var projects = new Projects();
projects.add({
"title": "My Project"
}).sync();
However, I get the following error when running this;
A "url" property or function must be specified
I thought the model would inherit the url property from the collection as per the documentation. Why isn't it? What's wrong?
JSFiddle: http://jsfiddle.net/6L8v4dj8/
according to what I see in documentation you should call
projects.sync('create', projects.models[0]) http://backbonejs.org/#Sync
In this case you could use method create, for example:
var Project = Backbone.Model.extend({});
var Projects = Backbone.Collection.extend({
model: Project,
url: "/api/projects"
});
var projects = new Projects();
projects.create({
title: "My Project"
});
Creating a model will cause an immediate "add" event to be triggered on the collection, a "request" event as the new model is sent to the server, as well as a "sync" event, once the server has responded with the successful creation of the model.
documentation
Related
I am creating simple book library using backbone.js. Functionalities are very simple.
1.) Get All Books
2.) Get Particular Book (from server)
3.) Display Books
from the collection i can get all the books. But how can I get single book from server using Models ? I need to make ajax call to server to get the book details. since the details may update pretty soon (I don't want to get it from all books collection)
So far I created Views, Model, Collections as follows
var Book: Backbone.Model.extend({
defaults:{
name: "",
id: ""
}
});
var Library: Backbone.Collection.extend({
model: Book,
url: function(){
return "/books";
},
fetchBooks: function(){
fetch({
success: success_callback,
error: error_callback
});
},
fetchBook: function(bookId){
//how to get single book ?
}
});
How do I get single item from the server using my existing models and collections ?
If you need to use your Model outside the scope of a collection, you need to specify the urlRoot property:
var Book: Backbone.Model.extend({
defaults: {
name: ""
// ** See Note on Defaults below ...
},
urlRoot: "/books"
});
After that you can initialize the Model with a given ID and fetch it:
fetchBook: function(bookId){
var book = new Book({ id: bookId });
book.fetch({
success: function() {
console.log('Received book: ' + book.toJSON());
}
});
}
This will result in a GET request to `/books/{id}'. Assuming the server returns JSON, all your results will be populated as attributes on the book instance.
** NOTE on Defaults: You definitely shouldn't have an id default to an empty string, as Backbone uses the presence or absence of an id to determine if a model has been saved on the server or not. For instance if you ever use model.save(...), Backbone will issue a POST if the id does not exist, or a PUT if the id does exist.
I am building an app which will load a JSON file and use that to populate all models. I have to keep a list of changes and then post this back to the server after a 'publish' button is clicked.
I think a combination of using Backbone.LocalStorage and using model change events to then track which models have changes sounds right but it'd help to hear from someone who's gone down this route or solved similar!
Does this approach makes sense? Is there a better one?
If you are trying to track individual changes and not just the final state before saving, then it is probably a good idea to create an Audit model or something similar. You can hook into the change events as you suggested. Saving those Audit models to the server can be done using the standard version (or some batched modification) of Backbone.sync whenever you want. That model might look something like this:
var Audit = Backbone.Model.extend({
defaults : {
auditableType: "", auditableId: null, auditedChanges : ""
},
paramRoot : "audit"
});
var Audits = Backbone.Collection.extend({
model : Audit
});
Then create a Model prototype that all audited models can extend from:
var audits = new Audits();
var AuditedModel = Backbone.Model.extend({
initialize : function (options) {
this.listenTo(this, "change", function (model, options) {
audits.add({
auditableType : this.paramRoot,
auditableId : this.id,
auditedChanges : this.changed
});
});
}
});
Now just extend from your AuditedModel for any models you want to track changes on.
var User = AuditedModel.extend({
paramRoot : "user",
// Do whatever
});
HI my basic model which fetches data from server is working perfect. I want to implement a search feature. When user enters any data the request goes to browser and desired model is returned.
var Book = Backbone.Model.extend({
urlRoot: '/books'
});
render: function(options) {
books = new Book({id:options.name});
books.fetch();
}
where
name = "search/"+dynamic_data;
Request URL that is being formed when i pass --> 'life' in variable dynamic_data
http://host/path/search%2Flife
Request URL that i want
http://host/path/search/life
How can I encode/escape my string to achieve the desired result. I have tried escape(), encodeURI(), encodeURIComponents
A workaround to solve this is create one more model with urlRoot as /books/search and pass just name . I don't think this is correct. Should I use this ?
According to your additionnal precisions stating that life is actually a book name...
It looks like Backbone is better integrated with RESTful API's. In REST, your urls should not contain verbs and to search books, you would do a GET /books/?name=life.
In that case, you would only have to define a Backbone.Collection like:
var BooksCollection = Backbone.Collection.extend({
model: Book,
url: '/books'
});
The to fetch books:
var books = new BooksCollection();
books.fetch({data : {name: 'life'}}); //GET /books/?name=life
If you really want your search operation to target /search/:name, you will have to check the Backbone.Collection api, but I think you will want to look at http://backbonejs.org/#Sync
You could override your collection's sync method to something like:
Backbone.Collection.extend({
...
sync: function (method, model, options) {
//for read operations, call the search url
if (method === 'read') {
options.url = '/search/' + options.data.name;
delete options.data.name;
}
//call the default sync implementation
return Backbone.Collection.prototype.sync.apply(this, arguments);
}
});
In this cased calling books.fetch({data : {name: 'life'}}); will result in GET /books/life.
Here's a fiddle that shows the example.
this would work:
books = new Book({id:options.name}, {url: options.name));
decodeURIComponent() will decode http://host/path/search%2Flife to http://host/path/search/life.
I have the following Model:
window.MyModel = Backbone.Model.extend({
initialize: function(props){
this.url = props.url;
}
parse: function(){
// #override- parsing data fetched from URL
}
});
// instantiate
var mod = new MyModel({url: 'some/url/here'});
I use this global variable 'mod' to fetch some data into this model from backend.
// fetch
mod.fetch({
success: function(){ ...},
error: ...
});
All above works well....
My Issue: I want to reuse this model by changing resetting the url and call fetch but it does not update the url somehow. I have tried the following:
mod.fetch({
data: {url:'/some/other/url'},
postData: true,
success: function(){ //process data},
error: ...
});
mod.set({url: '/some/other/url'});
// called fetch() without data: and postData: attributes as mentioned in previous
How do I set the url for my model so that I could call fetch() and it fetches data from updated url? Am I missing something. Thanks for any pointers..
UPDATE 1: Basically, I am unable to get updated values if I did
model.set({url: 'new value'});
followed by
model.fetch();
'model' is a global variable. Creating a fresh instance of 'model' works:
model = new Model({url:'/some/other/url'});
model.fetch();
however, works as required. Does this mean that a model instance is permanently attached to a url and it cannot be reset?
ANSWER TO MY QUESTION in UPDATE 1 Model instance is not permanently attached to a url. It can be reset dynamically. Please read through #tkone's thorough explanation and then #fguillens' solution for a better understanding.
After have understood the #tkone 's explanation...
If you still want to have a dynamic Model.url you always can delay its construction to run time, try this:
window.MyModel = Backbone.Model.extend({
url: function(){
return this.instanceUrl;
},
initialize: function(props){
this.instanceUrl = props.url;
}
}
Well the answer here is that you want to do:
mod.url = '/some/other/url'
The URL isn't part of the instance of the model itself, but rather an attribute of the MyModel object that you're creating your model instance from. Therefore, you'd just set it like it was an normal JavaScript object property. set is used only when the data you're setting (or conversely getting with get) is actually an attribute of the data you want to send/receive from the server.
But why you're changing the URL is the question we should be asking. The idea behind Backbone's model/collection system is that you speak to a REST endpoint and each model has a corresponding endpoint.
Like you've got a blog and that blog has an "entry" object which is available at:
/rest/entry/
And you've got a Backbone model for Entry:
Entry = Backbone.Model.extend({urlBase: '/rest/entry'});
Now when you save or fetch Backbone knows how this works.
So like you're making a new model:
e = new Entry();
e.set({title: "my blog rulez", body: "this is the best blog evar!!!!1!!"});
e.save();
This would then make Backbone do an HTTP POST request to /rest/entry with the body:
{
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!"
}
(When you do your mod.set({url: '/some/other/url'}); you're actually adding a field called url to the dataset, so the server would send "url": "/some/other/url" as part of that JSON POST body above:
{
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!",
"url": "/some/other/url"
}
The server would then respond with an HTTP 200 (or 201) response with the same model, only with, like, say, and ID attached:
{
"id": 1,
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!"
}
And that's not what you're looking for, right?)
Now you've got this model and it's got an ID. This means if you change it:
e.set('title', 'my blog is actually just ok');
e.save()
Backbone now makes an HTTP PUT request on /rest/entry/1 to update the resource on the server.
The server sees that you're talking about ID 1 on the /rest/entry/ endpoint, so knows to update an existing record (and send back an HTTP 200).
TL;DR
Don't change the URL, Backbone will. Make a new model for a new piece of data.
model.urlRoot = "/your/url"
OR
model.urlRoot = function(){ return "/your/url"; }
OR
model.url = "/your/url"
OR
model.url = function(){ return "/your/url"; }
Default 'url' property of a Backbone.Model object is as below. Backbone.js doc says:
Default URL for the model's representation on the server -- if you're using Backbone's restful methods, override this to change the endpoint that will be called.
url: function() {
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
Clearly, It first gets the value of urlRoot, if not available, it will look at the url of the collection to which model belongs. By defining urlRoot instead of url has an advantage of falling back to collection url in case urlRoot is null.
You can set url as option in fetch function, like this:
var mod = new MyModel();
mod.fetch({
url: '/some/other/url',
data: {}
});
I have an API that filters a set of objects based on the ID present in the URL, like so:
http://localhost:8000/api/v1/goal_update/?goal__id=12&format=json
I have a collection that does a GET request on the above URL. Here is the code for the collection:
var GoalUpdateList = Backbone.Collection.extend({
// Reference the Goal Update model
model: GoalUpdate,
// Do HTTP requests on this endpoint
url: function() {
return "http://localhost:8000/api/v1/goal_update/?goal__id=" + this.goal_id + "&format=json";
},
// Set the goal ID that the goal update list corresponds to
initialize: function(goal_id) {
this.goal_id = goal_id;
},
});
I want to pass in the ID when I create a new instance of the collection, so I created a view that has this code:
this.collection = new GoalUpdateList(this.model.get("id"));
It thinks that I am passing in model parameters, though. I am trying to pass in information that tells the collection what URL to use. So it runs the ID through a validate function and messes up the rest of the backbone code.
Backbone's collections expect model data as the first parameter, as you've already noted. But you can specify a second parameter as an object literal, like other Backbone objects. Just pass null or undefined as the first parameter, and then in your collection initialize method, get the options as the second parameter.
GoalUpdateList = Backbone.Collection.extend({
initialize: function(data, options){
this.goal_id = options.goal_id;
}
});
new GoalUpdateList(null, model.get("goal_id"));