diff --git a/ampersand-sync-browser.js b/ampersand-sync-browser.js new file mode 100644 index 0000000..4c69bd7 --- /dev/null +++ b/ampersand-sync-browser.js @@ -0,0 +1,2 @@ +var xhr = require('xhr'); +module.exports = require('./core')(xhr); diff --git a/ampersand-sync.js b/ampersand-sync.js index 4bb0f7c..fb314df 100644 --- a/ampersand-sync.js +++ b/ampersand-sync.js @@ -1,130 +1,2 @@ -/*$AMPERSAND_VERSION*/ -var result = require('lodash.result'); -var defaults = require('lodash.defaults'); -var includes = require('lodash.includes'); -var assign = require('lodash.assign'); -var xhr = require('xhr'); -var qs = require('qs'); - - -// Throw an error when a URL is needed, and none is supplied. -var urlError = function () { - throw new Error('A "url" property or function must be specified'); -}; - -// Map from CRUD to HTTP for our default `Backbone.sync` implementation. -var methodMap = { - 'create': 'POST', - 'update': 'PUT', - 'patch': 'PATCH', - 'delete': 'DELETE', - 'read': 'GET' -}; - -module.exports = function (method, model, options) { - var type = methodMap[method]; - var headers = {}; - - // Default options, unless specified. - defaults(options || (options = {}), { - emulateHTTP: false, - emulateJSON: false, - // overrideable primarily to enable testing - xhrImplementation: xhr - }); - - // Default request options. - var params = {type: type}; - - // Ensure that we have a URL. - if (!options.url) { - options.url = result(model, 'url') || urlError(); - } - - // Ensure that we have the appropriate request data. - if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { - params.json = options.attrs || model.toJSON(options); - } - - // If passed a data param, we add it to the URL or body depending on request type - if (options.data && type === 'GET') { - // make sure we've got a '?' - options.url += includes(options.url, '?') ? '&' : '?'; - options.url += qs.stringify(options.data); - } - - // For older servers, emulate JSON by encoding the request into an HTML-form. - if (options.emulateJSON) { - headers['Content-Type'] = 'application/x-www-form-urlencoded'; - params.body = params.json ? {model: params.json} : {}; - delete params.json; - } - - // For older servers, emulate HTTP by mimicking the HTTP method with `_method` - // And an `X-HTTP-Method-Override` header. - if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { - params.type = 'POST'; - if (options.emulateJSON) params.body._method = type; - headers['X-HTTP-Method-Override'] = type; - } - - // When emulating JSON, we turn the body into a querystring. - // We do this later to let the emulateHTTP run its course. - if (options.emulateJSON) { - params.body = qs.stringify(params.body); - } - - // Start setting ajaxConfig options (headers, xhrFields). - var ajaxConfig = (result(model, 'ajaxConfig') || {}); - - // Combine generated headers with user's headers. - if (ajaxConfig.headers) { - assign(headers, ajaxConfig.headers); - } - params.headers = headers; - - //Set XDR for cross domain in IE8/9 - if (ajaxConfig.useXDR) { - params.useXDR = true; - } - - // Set raw xhr options. - if (ajaxConfig.xhrFields) { - var beforeSend = ajaxConfig.beforeSend; - params.beforeSend = function (req) { - for (var key in ajaxConfig.xhrFields) { - req[key] = ajaxConfig.xhrFields[key]; - } - if (beforeSend) return beforeSend.apply(this, arguments); - }; - params.xhrFields = ajaxConfig.xhrFields; - } else { - params.beforeSend = ajaxConfig.beforeSend; - } - - // Turn a jQuery.ajax formatted request into xhr compatible - params.method = params.type; - - var ajaxSettings = assign(params, options); - - // Make the request. The callback executes functions that are compatible - // With jQuery.ajax's syntax. - var request = options.xhr = options.xhrImplementation(ajaxSettings, function (err, resp, body) { - if (err || resp.statusCode >= 400) { - if (options.error) return options.error(resp, 'error', err.message || body); - } else { - // Parse body as JSON if a string. - if (body && typeof body === 'string') { - try { - body = JSON.parse(body); - } catch (err) { - if (options.error) return options.error(resp, 'error', err.message); - } - } - if (options.success) return options.success(body, 'success', resp); - } - }); - model.trigger('request', model, request, options, ajaxSettings); - request.ajaxSettings = ajaxSettings; - return request; -}; +var xhr = require('request'); +module.exports = require('./core')(xhr); diff --git a/core.js b/core.js new file mode 100644 index 0000000..6b63070 --- /dev/null +++ b/core.js @@ -0,0 +1,131 @@ +/*$AMPERSAND_VERSION*/ +module.exports = function (xhr) { + var result = require('lodash.result'); + var defaults = require('lodash.defaults'); + var includes = require('lodash.includes'); + var assign = require('lodash.assign'); + var qs = require('qs'); + + + // Throw an error when a URL is needed, and none is supplied. + var urlError = function () { + throw new Error('A "url" property or function must be specified'); + }; + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'patch': 'PATCH', + 'delete': 'DELETE', + 'read': 'GET' + }; + + return function (method, model, options) { + var type = methodMap[method]; + var headers = {}; + + // Default options, unless specified. + defaults(options || (options = {}), { + emulateHTTP: false, + emulateJSON: false, + // overrideable primarily to enable testing + xhrImplementation: xhr + }); + + // Default request options. + var params = {type: type}; + + // Ensure that we have a URL. + if (!options.url) { + options.url = result(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { + params.json = options.attrs || model.toJSON(options); + } + + // If passed a data param, we add it to the URL or body depending on request type + if (options.data && type === 'GET') { + // make sure we've got a '?' + options.url += includes(options.url, '?') ? '&' : '?'; + options.url += qs.stringify(options.data); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (options.emulateJSON) { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + params.body = params.json ? {model: params.json} : {}; + delete params.json; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { + params.type = 'POST'; + if (options.emulateJSON) params.body._method = type; + headers['X-HTTP-Method-Override'] = type; + } + + // When emulating JSON, we turn the body into a querystring. + // We do this later to let the emulateHTTP run its course. + if (options.emulateJSON) { + params.body = qs.stringify(params.body); + } + + // Start setting ajaxConfig options (headers, xhrFields). + var ajaxConfig = (result(model, 'ajaxConfig') || {}); + + // Combine generated headers with user's headers. + if (ajaxConfig.headers) { + assign(headers, ajaxConfig.headers); + } + params.headers = headers; + + //Set XDR for cross domain in IE8/9 + if (ajaxConfig.useXDR) { + params.useXDR = true; + } + + // Set raw xhr options. + if (ajaxConfig.xhrFields) { + var beforeSend = ajaxConfig.beforeSend; + params.beforeSend = function (req) { + for (var key in ajaxConfig.xhrFields) { + req[key] = ajaxConfig.xhrFields[key]; + } + if (beforeSend) return beforeSend.apply(this, arguments); + }; + params.xhrFields = ajaxConfig.xhrFields; + } else { + params.beforeSend = ajaxConfig.beforeSend; + } + + // Turn a jQuery.ajax formatted request into xhr compatible + params.method = params.type; + + var ajaxSettings = assign(params, options); + + // Make the request. The callback executes functions that are compatible + // With jQuery.ajax's syntax. + var request = options.xhr = options.xhrImplementation(ajaxSettings, function (err, resp, body) { + if (err || resp.statusCode >= 400) { + if (options.error) return options.error(resp, 'error', err.message || body); + } else { + // Parse body as JSON if a string. + if (body && typeof body === 'string') { + try { + body = JSON.parse(body); + } catch (err) { + if (options.error) return options.error(resp, 'error', err.message); + } + } + if (options.success) return options.success(body, 'success', resp); + } + }); + model.trigger('request', model, request, options, ajaxSettings); + request.ajaxSettings = ajaxSettings; + return request; + }; +}; diff --git a/package.json b/package.json index 8608f15..5d1c89f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "lodash.includes": "^3.1.0", "lodash.result": "^3.0.0", "qs": "^2.2.4", + "request": "^2.55.0", "xhr": "^2.0.1" }, "devDependencies": { @@ -44,6 +45,7 @@ ], "license": "MIT", "main": "ampersand-sync.js", + "browser": "ampersand-sync-browser.js", "repository": { "type": "git", "url": "https://github.com/ampersandjs/ampersand-sync.git" diff --git a/test/integration.js b/test/integration.js index 0070943..6c130e5 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,5 +1,5 @@ var test = require('tape'); -var sync = require('../ampersand-sync'); +var sync = require('../'); var Model = require('ampersand-model'); diff --git a/test/unit.js b/test/unit.js index 3177395..68a4fe2 100644 --- a/test/unit.js +++ b/test/unit.js @@ -1,5 +1,5 @@ var test = require('tape'); -var sync = require('../ampersand-sync'); +var sync = require('../'); var Model = require('ampersand-model');