Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ember Data Adapter Mixin to use fetch #31

Merged
merged 6 commits into from
Jun 15, 2017
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,24 @@ export default Ember.Route.extend({
});
```

### Use with Ember Data
To have Ember Data utilize `fetch` instead of jQuery.ajax to make calls to your backend, extend your project's `application` adapter with the `adapter-fetch` mixin.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets give a quick code example.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!


```js
// app/adapters/application.js
import DS from 'ember-data';
import AdapterFetch from 'ember-fetch/mixins/adapter-fetch';

export default RESTAdapter.extend(AdapterFetch, {
...
});
```

further docs: https://github.com/github/fetch

### Browser Support

* evergreen / IE9+ / Safari 6.1+ https://github.com/github/fetch#browser-support
* evergreen / IE10+ / Safari 6.1+ https://github.com/github/fetch#browser-support
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah is the polyfil IE10+ only now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, according to their readme: https://github.com/github/fetch#browser-support

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks



### does this replace ic-ajax?
Expand Down
202 changes: 202 additions & 0 deletions addon/mixins/adapter-fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import Ember from 'ember';
import fetch from 'fetch';

const {
assign,
merge,
RSVP
} = Ember;

const RBRACKET = /\[\]$/;

/**
* Helper function that turns the data/body of a request into a query param string.
* This is directly copied from jQuery.param.
* @param {Object} queryParamsObject
* @returns {String}
*/
export function serialiazeQueryParams(queryParamsObject) {
var s = [];

function buildParams(prefix, obj) {
var i, len, key;

if (prefix) {
if (Array.isArray(obj)) {
for (i = 0, len = obj.length; i < len; i++) {
if (RBRACKET.test(prefix)) {
add(s, prefix, obj[i]);
} else {
buildParams(prefix + '[' + (typeof obj[i] === 'object' ? i : '') + ']', obj[i]);
}
}
} else if (obj && String(obj) === '[object Object]') {
for (key in obj) {
buildParams(prefix + '[' + key + ']', obj[key]);
}
} else {
add(s, prefix, obj);
}
} else if (Array.isArray(obj)) {
for (i = 0, len = obj.length; i < len; i++) {
add(s, obj[i].name, obj[i].value);
}
} else {
for (key in obj) {
buildParams(key, obj[key]);
}
}
return s;
}

return buildParams('', queryParamsObject).join('&').replace(/%20/g, '+');
}

/**
* Part of the `serialiazeQueryParams` helper function.
* @param {Array} s
* @param {String} k
* @param {String} v
*/
function add(s, k, v) {
v = typeof v === 'function' ? v() : v === null ? '' : v === undefined ? '' : v;
s[s.length] = `${encodeURIComponent(k)}=${encodeURIComponent(v)}`;
}

/**
* Helper function to create a plain object from the response's Headers.
* Consumed by the adapter's `handleResponse`.
* @param {Headers} headers
* @returns {Object}
*/
export function headersToObject(headers) {
let headersObject = {};
headers.forEach((value, key) => {
headersObject[key] = value;
});
return headersObject;
}
/**
* Helper function that translates the options passed to `jQuery.ajax` into a format that `fetch` expects.
* @param {Object} options
*/
export function mungOptionsForFetch(_options, adapter) {
const options = (assign || merge)({
credentials: 'same-origin',
}, _options);

let adapterHeaders = adapter.get('headers');
if (adapterHeaders) {
options.headers = assign({}, options.headers || {}, adapterHeaders);
}
options.method = options.type;

// Mimics the default behavior in Ember Data's `ajaxOptions`
if (options.headers === undefined || !(options.headers['Content-Type'] || options.headers['content-type'])) {
options.headers = options.headers || {};
options.headers['Content-Type'] = 'application/json; charset=utf-8';
}

// GET and HEAD requests can't have a `body`
if (options.data && Object.keys(options.data).length) {
if (options.method === 'GET' || options.method === 'HEAD') {
options.url += `?${serialiazeQueryParams(options.data)}`;
} else {
options.body = options.data;
}
}

return options;
}

export default Ember.Mixin.create({
/**
* @param {String} url
* @param {String} type
* @param {Object} options
* @override
*/
ajax(url, type, options) {
const requestData = {
url,
method: type,
};

const hash = this.ajaxOptions(url, type, options);

return this._ajaxRequest(hash)
.catch((error, response, requestData) => {
throw this.ajaxError(error, response, requestData);
})
.then((response) => {
if (response.ok) {
// We want to check that there is a body in the response, otherwise JSON.parse will throw an error.
// Instead, we'll let Ember Data handle an empty response.
const bodyPromise = response.headers.get('content-length') ? response.json() : {};
return this.ajaxSuccess(response, bodyPromise, requestData);
}
throw this.ajaxError(null, response, requestData);
});
},
/**
* Overrides the `_ajaxRequest` method to use `fetch` instead of jQuery.ajax
* @param {Object} options
* @override
*/
_ajaxRequest(options) {
const _options = mungOptionsForFetch(options, this);
return fetch(_options.url, _options);
},

/**
* @param {Object} response
* @param {Promise} bodyPromise
* @param {Object} requestData
* @override
*/
ajaxSuccess(response, bodyPromise, requestData) {
const headersObject = headersToObject(response.headers);

return bodyPromise.then((body) => {
const returnResponse = this.handleResponse(
response.status,
headersObject,
body,
requestData
);

if (returnResponse && returnResponse.isAdapterError) {
return RSVP.Promise.reject(returnResponse);
} else {
return returnResponse;
}
});
},

/**
* @param {Error} error
* @param {Object} response
* @param {Object} requestData
*/
ajaxError(error, response, requestData) {
let returnedError;

if (error instanceof Error) {
returnedError = error;
} else {
try {
const headersObject = headersToObject(response.headers);
returnedError = this.handleResponse(
response.status,
headersObject,
this.parseErrorResponse(response.statusText) || error,
requestData
);
} catch (e) {
throw e;
}
}

return returnedError;
}
});
1 change: 1 addition & 0 deletions app/mixins/adapter-fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-fetch/mixins/adapter-fetch';
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"ember-cli-release": "^0.2.9",
"ember-cli-shims": "^1.1.0",
"ember-cli-uglify": "^1.2.0",
"ember-data": "^2.13.1",
"ember-load-initializers": "^1.0.0",
"ember-resolver": "^4.0.0",
"ember-source": "~2.13.0",
Expand Down
Loading