Whenever I do a model.destroy() with Backbone, it tries to do a DELETE request on the following URL:
http://localhost:8000/api/v1/item/?format=json
Since I am doing a destroy on a model, I would expect that the ID of the model should be passed, so the URL to do a DELETE request on would be this:
http://localhost:8000/api/v1/item/8/?format=json
where the ID is specified. Because of the lack of ID, and my use of tastypie, all items get deleted. How do I make it so that the URL contains the ID of the item that I wish to delete?
I'm guessing that your model looks something like this:
var M = Backbone.Model.extend({
url: '/api/v1/item/?format=json',
// ...
});
The url for a model is supposed to be a function but, since it ends up going through the internal getValue function, a string will also "work"; if you check the source I linked to, you'll see why a string for url would give you the results you're seeing.
The solution is to use a function for url as you're supposed to:
url model.url()
Returns the relative URL where the model's resource would be located on the server. If your models are located somewhere else, override this method with the correct logic. Generates URLs of the form: "/[collection.url]/[id]", falling back to "/[urlRoot]/id" if the model is not part of a collection.
You'd probably want something like this:
url: function() {
if(this.isNew())
return '/api/v1/item/?format=json';
return '/api/v1/item/' + encodeURIComponent(this.id) + '/?format=json';
}
or this:
url: function() {
if(this.isNew())
return '/api/v1/item/?format=json';
return '/api/v1/item/' + encodeURIComponent(this.get('id')) + '/?format=json';
}
Related
I need to make a POST to a server-side API. I must send an id key into the request body to the server.
I use a Backbone model. But when I do:
myModel.set("id", somevalue)
myModel.save()
The network request that is fired is : URL/someValue [PUT]
Backbones doesn't do a POST but a PUT and appends the id to the url.
So I just want to pass an id key to the server without Backbone noticing.
From Backbone's doc:
Backbone is pre-configured to sync with a RESTful API.
[...]
The default sync handler maps CRUD to REST like so:
create → POST /collection
read → GET /collection[/id]
update → PUT /collection/id
patch → PATCH /collection/id
delete → DELETE /collection/id
A new entry doesn't have an ID, so if you give an ID to the model before saving it, Backbone defaults to a PUT request because it thinks you want to save an existing entry.
How to make a POST request with an id?
Choose one of the following solutions.
Stick to a RESTful API
This one is the obvious one. If you can, stick to the standard.
Change the API to handle PUT/PATCH requests and only use POST on creation. Make the API endpoint take the ID from the URL.
RESTful API best practices
Pass the type option1
Simple and works really well for a one-off situation.
Every options passed to save (or fetch) overrides the options the sync function defines by default and passes to jQuery.ajax function.
Backbone sync source
// Make the request, allowing the user to override any Ajax options.
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
var url = model.url(); // get the url before setting the `id`
model.save({
id: somevalue
}, {
url: url, // fix the url
type: 'POST' // choose the HTTP verb
});
Fixing the url that the model uses is simple, you have also some choices:
pass the url option (like above)
override the url function of the model
Overriding the url function (source) works well for situation where every call should use a specific url, without the default id appended to it.
var MyModel = Backbone.Model.extend({
url: function() {
return _.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
}
});
Set the idAttribute on the model
This depends on what the id you're trying to pass means in the data.
Backbone Model uses "id" has the default id attribute name. You can specify a different name by overriding the idAttribute property of the model. Whatever the name, it is always automatically made available through the model.id property.
Now, assuming the id attribute isn't related to this model and this model's real id attribute name is something like UID, you could change the idAttribute of the model to reflect the real name of the attribute (or it could even be a string that's never going to be an attribute).
var MyModel = Backbone.Model.extend({
idAttribute: 'UID',
});
Now, the id attribute is not considered an id for the current model, and model.isNew() will return true, sending a POST request to create it on save.
Change the sync/save function behavior
If the API you're using is not RESTful, you can adjust the behaviors by overriding the sync function. This can be done on the model or collection, or on the Backbone.sync function which is used by default by the collections and models.
For example, if you wanted to make every request use POST by default for MyModel class:
var MyModel = Backbone.Model.extend({
sync: function(method, model, options) {
return Backbone.sync.call(this, method, model,
_.extend({ type: 'POST' }, options));
}
});
You could do something similar with only the save function to let the fetch do its GET request as usual.
Use the emulateHTTP setting2
If you want to work with a legacy web server that doesn't support
Backbone's default REST/HTTP approach, you may choose to turn on
Backbone.emulateHTTP. Setting this option will fake PUT, PATCH and
DELETE requests with a HTTP POST, setting the X-HTTP-Method-Override
header with the true method.
[...]
Backbone.emulateHTTP = true;
model.save(); // POST to "/collection/id", with "_method=PUT" + header.
Do not override isNew
Has this model been saved to the server yet? If the model does not yet
have an id, it is considered to be new.
Some other answers on this site suggest overriding the isNew function. Don't. The function has its purpose and overriding it to force a POST request is a poor hack, not a solution.
isNew is used internally but can also be used by your code or other libraries and Backbone plugins.
1 While I did not take this from stack overflow, it was already an answer by Andrés Torres Marroquín on a similar question.
2 Taken from Maanas Royy's answer.
REST:
My REST API is on the following URL:
http://localhost/Project/index.php/rest/resource/car
This runs a standard get request that returns all the data in the table as a JSON.
However, my REST API also has the following:
http://localhost/Project/index.php/rest/resource/car/carId/2.
Which when used will return only the data for the Car, with carId = 2.
Backbone:
this.mycar.fetch({
data: { 'carId': '1' },
success: function () { ...
Running the above code in backbone will create and run a request that looks like this: http://localhost:7070/Project/index.php/rest/resource/car?carId=1, which is not compatible with my REST api, so how can I change it?
Edit:
Model and Collection:
var Car = Backbone.Model.extend({
urlRoot: ROOT + 'car',
idAttribute: 'carId'
});
var Cars = Backbone.Collection.extend({
model: Car,
url: ROOT + 'car',
})
The data property is the jQuery $.ajax's data property. For GET requests it's used for setting query parameters.
You should update the .url method/property of the model instead.
Setting it directly as a parameter:
this.mycar.url = 'api_entry_point/car/' + car_id;
edit: I just have noticed that you have carId as a resource in your url. This is a rather unconventional url. It seems this is why you have idAttribute: 'carId' in your model constructor. The carId doesn't have to be in the url. It could be:
http://localhost/Project/index.php/rest/resource/car/2
In the above url, the 2 is the model id. There is absolutely no need to have carId in the url. Also as resource contains more than one item, using cars instead of the car segment makes more sense.
http://localhost/Project/index.php/rest/resource/cars/2
Of course it's just a personal preference.
In your backbone model file you can pass like this
url : return "http://localhost/Project/index.php/rest/resource/car/carId/=" + this.id;
in your backbone view you can pass like
this.model.id = this.id;
this.mycar.fetch({
success: function () {
Ok, I have got this working with answers posted on another question here, but I am trying to do something a little more. I have a Backbone set up as follows,
var MyModel= Backbone.Model.extend();
var MyCol = Backbone.Collection.extend({
model : MyModel,
url: '/GetData/2',
parse: function(response) {
return response;
}
});
var stuff = new MyCol;
stuff.fetch({
success: function (collection, response) {
console.log(response);
}
})
Now this code fully works. Now to explain, the URL is set with PHP Slim, which returns JSON encoded data, where will be four key/pair groupings, for example:
<code> { id; XX, data: XX, another:XX, last:YY } </code>
This data is being received form a database, now how should I do this dynamically? By that I mean that on the URL' line in my collection, it passes 2 as the current ID. How can I get backbone to call each ID I need, there are currently 6 listing in the database. So I need it display/console log (currently for testing) all 6 inputs, so how would I change that ID input to 1-6? Should I just use a for loo? I really want something that will not need to change if say lists are removed form the database or more are added?
The end aim is for this JSON data is for it to load into my Backbone view, which is a template for a form.
All help most welcome,
Thanks
Glenn
P.S Sorry if my spelling is off I am dyslexic, also may not have explained things right, so let me know and I will improve my wording, thanks.
EDITS
This is what I am working with now ::
var AdminModel = Backbone.Model.extend({
defaults: {},
urlRoot: '/GetData'
});
var AdminColModel = Backbone.Collection.extend({
model : AdminModel,
url: '/',
parse: function(response) {
//console.log(response);
return response;
}
});
var stuff = new AdminColModel;
stuff.fetch({
success: function (collection, response) {
console.log(collection);
}
})
This does not console log anything at all? But the PHP is set up and working fine, so when you go to /getData you get a whole list if all the rows in the database. When you view /getData/X <- X is the id number, that will return just that rows ID data.
Glenn
You should not make an individual request for each model in your collection at the collection level. The url you have set for your collection should request all of the models by default. So instead of it being:
'/GetData/2'
It should instead be:
'/GetData'
Then on the server side you should ensure that the GetData action returns all MyModels.
You can then write overrides for GetData that take parameters to either return an individual model, or filter the results. You will be particularly interested in the individual model retrieval, which should be the URL used at the model level. in fact, you already have this action, as you are using it (incorrectly) to retrieve the collection. This is the action associated with the /GetData/2 URL. This action should be the one used when you call fetch inside the model, not the collection.
Hopefully this helps.
Post Edit
Adding some code to assist.
var AdminModel = Backbone.Model.extend({
urlRoot: '/GetData'
});
var AdminCollection = Backbone.Collection.extend({
model : AdminModel,
url: '/GetData'
});
var wholeCollection= new AdminCollection();
// the fetch method on the collection uses the AdminCollection.url property
wholeCollection.fetch({
success: function (collection) {
console.log(collection); // will output collection object to console
}
});
// this model does not exist in any collection
var modelOutsideCollection = new AdminModel({}, { id: "2" });
// because the model is not in a collection, fetch uses the AdminModel.urlRoot proeprty
modelOutsideCollection.fetch({
success: function (model) {
console.log(model); // outputs a single model with id of 2 to collection
}
});
So... The way you seem to be doing things is going to make it really hard (if even possible) to work with Backbone.
What you should have is an API endpoint (e.g.) "getData" that will return all models:
[
{id: 1, name: "asdflj"},
{id: 2, name: "lsdfkjg"},
// etc.
]
Then, you define "getData" as the collection's url. For each model in the collection, Backbone will automatically compute the model instance's url as (e.g.) "getData/2" (if the model's id is 2).
In other words, you'd need to change your API to have:
getData -> return all models
getData/2 -> return model with id 2
I have an index that creates randomly generated dynamic content.
So everytime you load the index, it'll create a series of view that are dependent on what my Rails model has produced and sent to Backbone.
From backbone, I am curious what I could do to "refresh" the page without doing something like this :
window.location = '/'
I'd like to do it within Backbone.. something like this :
Backbone.history.navigate('/', {trigger: true, replace: true});
But this doesn't necessarily send a new request to the url.
All I would need to do to accomplish my goals is send a GET request to /, which should return a JSON object I can pipe through the rest of my Backbone app.
Is there a way to send this request within Backbone? Or should I just go a traditional jQuery route, and just make a $.get request?
Since your REST api returns a JSON object, simply use a Backbone.Model to structure this data. You can then bind events to do whatever you like in your application.
var RandomData = Backbone.Model.extend({ url: '/' });
var randomData = new RandomData();
// Here, `Backbone` can be substituted by any `View`, `Collection`, `Model...
Backbone.listenTo( randomData, 'change', function() {
//Do something everytime this changes.
});
// When you need to issue a GET '/' request. The following will put the
// JSON response inside of `randomData.attributes`
randomData.fetch();
I am trying to implement a search function for my website. When the user types a search term foobar into a input box and submits it, he is redirected to http://mydomain.com/search?query=foobar.
Problem:: How should I grab the GET parameters query from the URL, and send it to the backend and get a array of results back as a JSON response? Should I even do it this way?
My current attempt below does not even cause the search function to be triggered.
Router
var AppRouter = Backbone.Router.extend({
routes: {
'search?query=:query': 'search'
// ... and some other routes
},
search: function(query) {
this.photoList = new SearchCollection();
var self = this;
this.photoList.fetch({
data: {query: query},
success: function() {
self.photoListView = new PhotoListView({ collection: self.photoList });
self.photoListView.render();
}
});
}
});
var app = new AppRouter();
Backbone.history.start({
pushState: true,
root: '/'
});
There have been several issues filed against Backbone for this very issue. There is an existing plugin that works well for this:
https://github.com/jhudson8/backbone-query-parameters
Alternatively, I'm currently using query string parameters in a mock API that matches Backbone's route matching. Looks something like this
Route
"/api/v2/application/:query"
Query
application: function(query) {
var params = $.deparam(query.slice(1));
// params.something...
}
As to your actual issue at hand how are you redirecting to index.html to support pushState?
I hit this same issue and contemplated using backbone-query-parameters, but that should be considered generally an incorrect approach.
The url query string is not meant for the front end. They get sent to the server and force a refresh when navigating from page.html to page.html?something=something.
You should be using hash fragments instead. i.e. http://www.example.com/ajax.html#key1=value1&key2=value2 then just get those values the normal backbone way and build your request params from that.
See https://github.com/jashkenas/backbone/issues/891, https://developers.google.com/webmasters/ajax-crawling/docs/specification, https://www.rfc-editor.org/rfc/rfc3986#section-3.5
You can always read the URL via jQuery URL plugin. It works well.
https://github.com/allmarkedup/jQuery-URL-Parser
There are very few cases when you need to read the URL and extract the GET params. I think that you are doing things wrong and here are my options:
1) if you are having just one page in your app (single app page) you can display results as they type in your input field or after they hit submit
2) if you are redirecting the user to a different page that means you can bootstrap data so that after the page is loaded backbone will just have to render your results and only make other requests if you change your search word
3) you can have a javascript variable which is initialized on page load directly from the server where working with GET params is probably easier