From 302c0d5ceeeafc6db75fde2e95bb236c0f2fc05e Mon Sep 17 00:00:00 2001
From: John Wright
Date: Thu, 28 Oct 2010 20:21:59 -0600
Subject: [PATCH 1/2] Added Backbone.emulateJSON to enable the current behavior
of syncing and made sending the body as application/json without a wrapping
model param, the default
---
backbone.js | 43 +++++++++++++++++++++++++------------
index.html | 25 ++++++++++++++++------
test/sync.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++------
3 files changed, 102 insertions(+), 26 deletions(-)
diff --git a/backbone.js b/backbone.js
index 61127598..fe72b4b8 100644
--- a/backbone.js
+++ b/backbone.js
@@ -27,9 +27,14 @@
// For Backbone's purposes, jQuery owns the `$` variable.
var $ = this.jQuery;
- // Turn on `emulateHttp` to fake `"PUT"` and `"DELETE"` requests via
+ // Turn on `emulateHTTP` to fake `"PUT"` and `"DELETE"` requests via
// the `_method` parameter.
- Backbone.emulateHttp = false;
+ Backbone.emulateHTTP = false;
+
+ // Turn on `emulateJSON` to encode the body as
+ // `application/x-www-form-urlencoded` instead of `application/json`
+ // with the model as a JSON string in the param `model`
+ Backbone.emulateJSON = false;
// Backbone.Events
// -----------------
@@ -654,25 +659,37 @@
// * Send up the models as XML instead of JSON.
// * Persist models via WebSockets instead of Ajax.
//
- // Turn on `Backbone.emulateHttp` in order to send `PUT` and `DELETE` requests
+ // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
// as `POST`, with an `_method` parameter containing the true HTTP method.
// Useful when interfacing with server-side languages like **PHP** that make
- // it difficult to read the body of `PUT` requests.
+ // it difficult to read the body of `PUT` requests. Also, turn on Backbone.emulateJSON
+ // to send the body as `application/x-www-form-urlencoded` instead of
+ // `application/json`, with the model in a JSON string param named `model`,
Backbone.sync = function(method, model, success, error) {
var sendModel = method === 'create' || method === 'update';
- var data = sendModel ? {model : JSON.stringify(model)} : {};
+ var data = sendModel ? model : {};
var type = methodMap[method];
- if (Backbone.emulateHttp && (type === 'PUT' || type === 'DELETE')) {
+ if (Backbone.emulateHTTP && (type === 'PUT' || type === 'DELETE')) {
data._method = type;
type = 'POST';
- }
+ }
+
+
+ if (sendModel && Backbone.emulateJSON) data.model = JSON.stringify(model);
+ if (sendModel && !(Backbone.emulateHTTP || Backbone.emulateJSON)) data = JSON.stringify(data);
+
+ processData = Backbone.emulateJSON ? true : false;
+ contentType = Backbone.emulateJSON ? "application/x-www-form-urlencoded" : "application/json";
+
$.ajax({
- url : getUrl(model),
- type : type,
- data : data,
- dataType : 'json',
- success : success,
- error : error
+ url : getUrl(model),
+ type : type,
+ data : data,
+ processData : processData,
+ contentType : contentType,
+ dataType : 'json',
+ success : success,
+ error : error
});
};
diff --git a/index.html b/index.html
index a5a592de..36b2955d 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.
@@ -1255,15 +1255,26 @@ 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:
+ instead, by turning on Backbone.emulateHTTP:
-Backbone.emulateHttp = true;
+Backbone.emulateHTTP = true;
model.save(); // Sends a POST to "/collection/id", with "_method=PUT"
+
+ Also you can choose to send the body with content-type
+ application/x-www-form-urlencoded and the model in a JSON
+ string param called model by turning on Backbone.emulateJSON:
+
+
+
+Backbone.emulateJSON = true;
+
+model.save(); //sends data as application/x-www-form-urlencoded
+
As an example, a Rails handler responding to an "update" call from
Backbone.sync might look like this: (In real code, never use
@@ -1557,7 +1568,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..bf9dc5a5 100644
--- a/test/sync.js
+++ b/test/sync.js
@@ -36,10 +36,23 @@ $(document).ready(function() {
equals(lastRequest.url, '/library');
equals(lastRequest.type, 'POST');
equals(lastRequest.dataType, 'json');
+ var data = JSON.parse(lastRequest.data);
+ equals(data.title, 'The Tempest');
+ equals(data.author, 'Bill Shakespeare');
+ equals(data.length, 123);
+ });
+
+ test("sync: create with emulateJSON", function() {
+ Backbone.emulateJSON = true;
+ library.add(library.create(attrs));
+ equals(lastRequest.url, '/library');
+ equals(lastRequest.type, 'POST');
+ equals(lastRequest.dataType, 'json');
var data = JSON.parse(lastRequest.data.model);
equals(data.title, 'The Tempest');
equals(data.author, 'Bill Shakespeare');
equals(data.length, 123);
+ Backbone.emulateJSON = false;
});
test("sync: update", function() {
@@ -47,25 +60,53 @@ $(document).ready(function() {
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');
equals(data.length, 123);
});
- test("sync: update with emulateHttp", function() {
- Backbone.emulateHttp = true;
+ test("sync: update with emulateHTTP", function() {
+ Backbone.emulateHTTP = true;
+ library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+ equals(lastRequest.url, '/library/2-the-tempest');
+ equals(lastRequest.type, 'POST');
+ equals(lastRequest.dataType, 'json');
+ equals(lastRequest.data._method, 'PUT');
+ Backbone.emulateHTTP = false;
+ });
+
+ test("sync: update with emulateJSON", function() {
+ Backbone.emulateJSON = true;
+ library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+ equals(lastRequest.url, '/library/2-the-tempest');
+ equals(lastRequest.type, 'PUT');
+ var data = JSON.parse(lastRequest.data.model);
+ equals(lastRequest.dataType, 'json');
+ equals(data.id, '2-the-tempest');
+ equals(data.title, 'The Tempest');
+ equals(data.author, 'Tim Shakespeare');
+ equals(data.length, 123);
+ Backbone.emulateJSON = false;
+ });
+
+ test("sync: update with emulateHTTP and emulateJSON", function() {
+ Backbone.emulateHTTP = true;
+ Backbone.emulateJSON = true;
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
equals(lastRequest.url, '/library/2-the-tempest');
equals(lastRequest.type, 'POST');
equals(lastRequest.dataType, 'json');
equals(lastRequest.data._method, 'PUT');
var data = JSON.parse(lastRequest.data.model);
+ equals(lastRequest.dataType, 'json');
equals(data.id, '2-the-tempest');
equals(data.title, 'The Tempest');
equals(data.author, 'Tim Shakespeare');
equals(data.length, 123);
+ Backbone.emulateHTTP = false;
+ Backbone.emulateJSON = false;
});
test("sync: read model", function() {
@@ -76,19 +117,26 @@ $(document).ready(function() {
});
test("sync: destroy", function() {
- Backbone.emulateHttp = false;
library.first().destroy();
equals(lastRequest.url, '/library/2-the-tempest');
equals(lastRequest.type, 'DELETE');
ok(_.isEmpty(lastRequest.data));
});
- test("sync: destroy with emulateHttp", function() {
- Backbone.emulateHttp = true;
+ test("sync: destroy with emulateJSON", function() {
+ library.first().destroy();
+ equals(lastRequest.url, '/library/2-the-tempest');
+ equals(lastRequest.type, 'DELETE');
+ ok(_.isEmpty(lastRequest.data));
+ });
+
+ test("sync: destroy with emulateHTTP", function() {
+ Backbone.emulateHTTP = true;
library.first().destroy();
equals(lastRequest.url, '/library/2-the-tempest');
equals(lastRequest.type, 'POST');
equals(JSON.stringify(lastRequest.data), '{"_method":"DELETE"}');
+ Backbone.emulateHTTP = false;
});
});
From 6c972695db10b1021fd21452fc68884fd8f16eb7 Mon Sep 17 00:00:00 2001
From: John Wright
Date: Fri, 29 Oct 2010 10:47:59 -0600
Subject: [PATCH 2/2] Default behavior for Backbone.sync is now to send
everything as application/json. Emulating http and sending data as form
url-encoded can be turned on with Backbone.emulateHttp
---
backbone.js | 69 ++++++++++++++++++++++++++--------------------------
index.html | 23 ++++++------------
test/sync.js | 61 ++++++----------------------------------------
3 files changed, 49 insertions(+), 104 deletions(-)
diff --git a/backbone.js b/backbone.js
index fe72b4b8..082733d9 100644
--- a/backbone.js
+++ b/backbone.js
@@ -27,14 +27,11 @@
// For Backbone's purposes, jQuery owns the `$` variable.
var $ = this.jQuery;
- // Turn on `emulateHTTP` to fake `"PUT"` and `"DELETE"` requests via
- // the `_method` parameter.
- Backbone.emulateHTTP = false;
-
- // Turn on `emulateJSON` to encode the body as
- // `application/x-www-form-urlencoded` instead of `application/json`
- // with the model as a JSON string in the param `model`
- Backbone.emulateJSON = false;
+ // 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
// -----------------
@@ -659,38 +656,42 @@
// * Send up the models as XML instead of JSON.
// * 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.
+ // Turn on `Backbone.emulateHttp` in order to send `PUT` and `DELETE` requests
+ // 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. Also, turn on Backbone.emulateJSON
- // to send the body as `application/x-www-form-urlencoded` instead of
- // `application/json`, with the model in a JSON string param named `model`,
+ // 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 : {};
var type = methodMap[method];
- if (Backbone.emulateHTTP && (type === 'PUT' || type === 'DELETE')) {
- data._method = type;
- type = 'POST';
- }
+ 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;
- if (sendModel && Backbone.emulateJSON) data.model = JSON.stringify(model);
- if (sendModel && !(Backbone.emulateHTTP || Backbone.emulateJSON)) data = JSON.stringify(data);
-
- processData = Backbone.emulateJSON ? true : false;
- contentType = Backbone.emulateJSON ? "application/x-www-form-urlencoded" : "application/json";
-
- $.ajax({
- url : getUrl(model),
- type : type,
- data : data,
- processData : processData,
- contentType : contentType,
- dataType : 'json',
- success : success,
- error : error
- });
+ $.ajax(ajaxParams);
};
// Helpers
diff --git a/index.html b/index.html
index 36b2955d..18419aac 100644
--- a/index.html
+++ b/index.html
@@ -1252,10 +1252,12 @@ 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.
@@ -1264,20 +1266,9 @@ Backbone.emulateHTTP = true;
model.save(); // Sends a POST to "/collection/id", with "_method=PUT"
-
- Also you can choose to send the body with content-type
- application/x-www-form-urlencoded and the model in a JSON
- string param called model by turning on Backbone.emulateJSON:
-
-
-
-Backbone.emulateJSON = true;
-
-model.save(); //sends data as application/x-www-form-urlencoded
-
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.)
diff --git a/test/sync.js b/test/sync.js
index bf9dc5a5..971c25ed 100644
--- a/test/sync.js
+++ b/test/sync.js
@@ -42,18 +42,6 @@ $(document).ready(function() {
equals(data.length, 123);
});
- test("sync: create with emulateJSON", function() {
- Backbone.emulateJSON = true;
- library.add(library.create(attrs));
- equals(lastRequest.url, '/library');
- equals(lastRequest.type, 'POST');
- equals(lastRequest.dataType, 'json');
- var data = JSON.parse(lastRequest.data.model);
- equals(data.title, 'The Tempest');
- equals(data.author, 'Bill Shakespeare');
- equals(data.length, 123);
- Backbone.emulateJSON = false;
- });
test("sync: update", function() {
library.first().save({id: '1-the-tempest', author: 'William Shakespeare'});
@@ -67,46 +55,18 @@ $(document).ready(function() {
equals(data.length, 123);
});
- test("sync: update with emulateHTTP", function() {
- Backbone.emulateHTTP = true;
+ test("sync: update with emulateHttp", function() {
+ Backbone.emulateHttp = true;
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
equals(lastRequest.url, '/library/2-the-tempest');
equals(lastRequest.type, 'POST');
equals(lastRequest.dataType, 'json');
equals(lastRequest.data._method, 'PUT');
- Backbone.emulateHTTP = false;
- });
-
- test("sync: update with emulateJSON", function() {
- Backbone.emulateJSON = true;
- library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
- equals(lastRequest.url, '/library/2-the-tempest');
- equals(lastRequest.type, 'PUT');
- var data = JSON.parse(lastRequest.data.model);
- equals(lastRequest.dataType, 'json');
+ 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.emulateJSON = false;
- });
-
- test("sync: update with emulateHTTP and emulateJSON", function() {
- Backbone.emulateHTTP = true;
- Backbone.emulateJSON = true;
- library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
- equals(lastRequest.url, '/library/2-the-tempest');
- equals(lastRequest.type, 'POST');
- equals(lastRequest.dataType, 'json');
- equals(lastRequest.data._method, 'PUT');
- var data = JSON.parse(lastRequest.data.model);
- equals(lastRequest.dataType, 'json');
- equals(data.id, '2-the-tempest');
- equals(data.title, 'The Tempest');
- equals(data.author, 'Tim Shakespeare');
- equals(data.length, 123);
- Backbone.emulateHTTP = false;
- Backbone.emulateJSON = false;
+ Backbone.emulateHttp = false;
});
test("sync: read model", function() {
@@ -123,20 +83,13 @@ $(document).ready(function() {
ok(_.isEmpty(lastRequest.data));
});
- test("sync: destroy with emulateJSON", function() {
- library.first().destroy();
- equals(lastRequest.url, '/library/2-the-tempest');
- equals(lastRequest.type, 'DELETE');
- ok(_.isEmpty(lastRequest.data));
- });
-
- test("sync: destroy with emulateHTTP", function() {
- Backbone.emulateHTTP = true;
+ test("sync: destroy with emulateHttp", function() {
+ Backbone.emulateHttp = true;
library.first().destroy();
equals(lastRequest.url, '/library/2-the-tempest');
equals(lastRequest.type, 'POST');
equals(JSON.stringify(lastRequest.data), '{"_method":"DELETE"}');
- Backbone.emulateHTTP = false;
+ Backbone.emulateHttp = false;
});
});