diff --git a/backbone.js b/backbone.js index e393b7d2..45eef10e 100644 --- a/backbone.js +++ b/backbone.js @@ -27,8 +27,10 @@ // For Backbone's purposes, jQuery owns the `$` variable. var $ = this.jQuery; - // Turn on `emulateHttp` to fake `"PUT"` and `"DELETE"` requests via - // the `_method` parameter. + // Turn on `emulateHttp` to use support legacy HTTP servers. Setting this option will + // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a + // `X-Http-Method-Override` header, will encode the body as`application/x-www-form-urlencoded` + // instead of `application/json` and will send the model in a param named `model`. Backbone.emulateHttp = false; // Backbone.Events @@ -688,25 +690,41 @@ // * Persist models via WebSockets instead of Ajax. // // Turn on `Backbone.emulateHttp` in order to send `PUT` and `DELETE` requests - // as `POST`, with an `_method` parameter containing the true HTTP method. + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` instead of + // `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. Backbone.sync = function(method, model, success, error) { var sendModel = method === 'create' || method === 'update'; - var data = sendModel ? {model : JSON.stringify(model)} : {}; var type = methodMap[method]; - if (Backbone.emulateHttp && (type === 'PUT' || type === 'DELETE')) { - data._method = type; - type = 'POST'; - } - $.ajax({ - url : getUrl(model), - type : type, - data : data, - dataType : 'json', - success : success, - error : error - }); + var ajaxParams = {}; + + if (Backbone.emulateHttp) { + ajaxParams.contentType = "application/x-www-form-urlencoded"; + ajaxParams.processData = true; + ajaxParams.data = {}; + if (sendModel) ajaxParams.data.model = model.toJSON(); + if (type === 'PUT' || type === 'DELETE') { + ajaxParams.data._method = type; + type = 'POST'; + ajaxParams.beforeSend = function(xhr) { + xhr.setRequestHeader("X-Http-Method-Override", "PUT"); + } + } + } else { + ajaxParams.contentType = "application/json"; + ajaxParams.processData = false; + ajaxParams.data = sendModel ? JSON.stringify(model.toJSON()) : {}; + } + + ajaxParams.url = getUrl(model); + ajaxParams.type = type; + ajaxParams.dataType = "json"; + ajaxParams.success = success; + ajaxParams.error = error; + + $.ajax(ajaxParams); }; // Helpers diff --git a/index.html b/index.html index a5a592de..18419aac 100644 --- a/index.html +++ b/index.html @@ -1232,10 +1232,10 @@ var othello = NYPL.create({

With the default implementation, when Backbone.sync sends up a request to save - a model, its attributes will be passed, serialized as JSON, under the model - parameter. When returning a JSON response, send down the attributes of the - model that have been changed by the server, and need to be updated on the - client. When responding to a "read" request from a collection + a model, its attributes will be passed, serialized as JSON, and sent in the HTTP body + with content-type application/json. When returning a JSON response, + send down the attributes of the model that have been changed by the server, and need + to be updated on the client. When responding to a "read" request from a collection (Collection#fetch), send down an array of model attribute objects.

@@ -1252,21 +1252,23 @@ var othello = NYPL.create({

- If your web server makes it difficult to work with real PUT and - DELETE requests, you may choose to emulate them instead, using - HTTP POST, and passing them under the _method parameter - instead, by turning on Backbone.emulateHttp: + If you want to work with a legacy web server that doesn't support Backbones's + default JSON centric approach, you may choose to turn on Backbone.emulateHttp. + Setting this option will emulate PUT and DELETE requests with + a HTTP POST, and passing them under the _method parameter. Setting this option + will also send the model under a model param and use application/x-www-form-urlencoded + instead of the default application/json as the content-type for data sent.

-Backbone.emulateHttp = true;
+Backbone.emulateHTTP = true;
 
 model.save();  // Sends a POST to "/collection/id", with "_method=PUT"
 

As an example, a Rails handler responding to an "update" call from - Backbone.sync might look like this: (In real code, never use + Backbone might look like this: (In real code, never use update_attributes blindly, and always whitelist the attributes you allow to be changed.)

@@ -1557,7 +1559,7 @@ var DocumentView = Backbone.View.extend({ as an option, which will be invoked if validation fails, overriding the "error" event. You can now tell backbone to use the _method hack instead of HTTP - methods by setting Backbone.emulateHttp = true. + methods by setting Backbone.emulateHTTP = true. Existing Model and Collection data is no longer sent up unnecessarily with GET and DELETE requests. Added a rake lint task. Backbone is now published as an NPM module. diff --git a/test/sync.js b/test/sync.js index 36888b19..971c25ed 100644 --- a/test/sync.js +++ b/test/sync.js @@ -36,18 +36,19 @@ $(document).ready(function() { equals(lastRequest.url, '/library'); equals(lastRequest.type, 'POST'); equals(lastRequest.dataType, 'json'); - var data = JSON.parse(lastRequest.data.model); + var data = JSON.parse(lastRequest.data); equals(data.title, 'The Tempest'); equals(data.author, 'Bill Shakespeare'); equals(data.length, 123); }); + test("sync: update", function() { library.first().save({id: '1-the-tempest', author: 'William Shakespeare'}); equals(lastRequest.url, '/library/1-the-tempest'); equals(lastRequest.type, 'PUT'); equals(lastRequest.dataType, 'json'); - var data = JSON.parse(lastRequest.data.model); + var data = JSON.parse(lastRequest.data); equals(data.id, '1-the-tempest'); equals(data.title, 'The Tempest'); equals(data.author, 'William Shakespeare'); @@ -61,11 +62,11 @@ $(document).ready(function() { equals(lastRequest.type, 'POST'); equals(lastRequest.dataType, 'json'); equals(lastRequest.data._method, 'PUT'); - var data = JSON.parse(lastRequest.data.model); + var data = lastRequest.data.model; equals(data.id, '2-the-tempest'); - equals(data.title, 'The Tempest'); equals(data.author, 'Tim Shakespeare'); equals(data.length, 123); + Backbone.emulateHttp = false; }); test("sync: read model", function() { @@ -76,7 +77,6 @@ $(document).ready(function() { }); test("sync: destroy", function() { - Backbone.emulateHttp = false; library.first().destroy(); equals(lastRequest.url, '/library/2-the-tempest'); equals(lastRequest.type, 'DELETE'); @@ -89,6 +89,7 @@ $(document).ready(function() { equals(lastRequest.url, '/library/2-the-tempest'); equals(lastRequest.type, 'POST'); equals(JSON.stringify(lastRequest.data), '{"_method":"DELETE"}'); + Backbone.emulateHttp = false; }); });