sycamore
v0.5.0
Published
A mixin with functionality to wrap jQuery $.ajax calls, and simplify the definition and consumption of $.ajax request options
Downloads
108
Readme
Sycamore
In its simplest form, Sycamore is a wrapper around jQuery's $.ajax
that provides a simple and consistent means of defining and executing those calls in your application. Sycamore is a mixin that you can use to extend your own objects with this functionality. This is probably more effectively described with some examples.
Your module without sycamore
(function ($, _) {
var WithoutSycamore = function () {};
_.extend(WithoutSycamore.prototype, {
goGetSomethingFromTheServer: function () {
$.ajax({
url: "http://example.com/you/need/sycamore",
type: "get",
data: {
orderBy: "someField",
sort: "asc"
},
dataType: "json",
contentType: "application/json; charset=utf-8",
context: this
}).done(function (data) {
// do something with the data that was returned...
}).fail(function () {
// OOPS! something went wrong...might wanna let the user know
});
},
sendSomethingToTheServer: function () {
$.ajax({
url: "http://example.com/eewww/look/at/all/this/boilerplate/code",
type: "post",
data: {
name: "Foo",
description: "Bar",
notes: "We're creating a new object on the server"
},
dataType: "json",
contentType: "application/json; charset=utf-8",
context: this
}).done(function (data) {
// add the new object to some collection on the client...
}).fail(function () {
// OOPS! something went wrong...might wanna let the user know
});
},
updateSomethingOnTheServer: function () {
$.ajax({
url: "http://example.com/something/should/be/done/about/this",
type: "get",
data: {
id: 123456789,
name: "Foo (updated)",
description: "Bar (updated)"
},
dataType: "json",
contentType: "application/json; charset=utf-8",
context: this
}).done(function (data) {
// update the client with the updated object...
}).fail(function () {
// OOPS! something went wrong...might wanna let the user know
});
},
deleteSomethingFromTheServer: function () {
$.ajax({
url: "http://example.com/we/can/do/better/than/this",
type: "delete",
data: {
id: 123456789
},
dataType: "json",
contentType: "application/json; charset=utf-8",
context: this
}).done(function (data) {
// remove the item from the client as well...
}).fail(function () {
// OOPS! something went wrong...might wanna let the user know
});
}
});
return WithoutSycamore;
}(jquery, underscore));
Lots of messy, repetitive boilerplate there, huh? What happens when you need or want to start sending some custom headers with those requests? What about caching the results? Those are the kinds of things that sycamore can help clean up for you. Lets take a look at what a module written using sycamore looks like, with the same functionality as above...
Your module with sycamore
(function ($, _, Requester) {
var WithSycamore = function () {};
_.extend(WithSycamore.prototype, Requester, {
requests: {
getSomethingFromTheServer: {
url: "http://example.com/much/better/with/sycamore",
data: {
orderBy: "someField",
sort: "asc"
},
done: "onGetSomethingFromTheServerDone",
fail: "serverRequestFailed"
},
sendSomethingToTheServer: {
url: "http://example.com/aahhh/no/more/repetitive/boilerplate/code",
type: "post",
data: {
name: "Foo",
description: "Bar",
notes: "We're creating a new object on the server"
},
done: "onSendSomethingToTheServerDone",
fail: "serverRequestFailed"
},
updateSomethingOnTheServer: {
url: "http://example.com/this/is/much/nicer",
type: "put"
},
deleteSomethingFromTheServer: {
url: "http://example.com/i/knew/we/could/do/better",
type: "delete"
}
},
getSomethingFromTheServer: function () {
this.execute(this.requests.getSomethingFromTheServer);
// alternately, if you prefer the syntax, you could also use the `fetch` function
// this.fetch(this.requests.getSomethingFromTheServer);
},
onGetSomethingFromTheServerDone: function (data) {
// do something with the data that was returned...
},
sendSomethingToTheServer: function () {
this.execute(this.requests.sendSomethingToTheServer);
},
onSendSomethingToTheServerDone: function (data) {
// add the new object to some collection on the client...
},
updateSomethingOnTheserver: function () {
this.execute(this.requests.updateSomethingOnTheServer, {
id: 123456789,
name: "Foo (updated)",
description: "Bar (updated)"
})
.done(this.onUpdateSomethingOnTheServer)
.fail(this.serverRequestFailed);
},
onUpdateSomethingOnTheServerDone: function (data) {
// update the client with the updated object...
},
deleteSomethingFromTheServer: function (itemId) {
this.execute(this.requests.deleteSomethingFromTheServer, { id: itemId })
.done(this.onDeleteSomethingFromTheServer)
.fail(this.serverRequestFailed);
},
onDeleteSomethingFromTheServer: function () {
// remove the item from the client as well...
},
serverRequestFailed: function () {
// OOPS! something went wrong...lets let the user know about it...
}
});
return WithSycamore;
}(jquery, underscore, Requester));
Hopefully those samples help draw a clear enough picture of how sycamore was designed to work.
Documentation
Get it
Using Bower:
bower install sycamore [--save]
Or just download it: Minified | Uncompressed | Full package archive
Usage
As mentioned previously, sycamore is a mixin. To use it, you simply need to import/reference/load it:
// AMD/RequireJS
// main.js
require.config({
paths: {
"requester": "/path/to/requester"
}
});
// your-module.js
define(["requester"], function (Requester) {
// extend your module with Requester...see below...
});
// CommonJS/node
var Requester = require("/path/to/requester");
<!-- Browser script reference -->
<script src="/path/to/requester.js"></script>
and then extend your module or object with it:
// constructor function
var YourModule = function () {
// initialize your module here...
};
// extend it with underscore.js...
_.extend(YourModule.prototype, Requester, {
// your module implementation
});
// extend it with jQuery if you prefer...
$.extend({}, YourModule.prototype, Requester, {
// your module implementation
});
return YourModule;
Now go forth and request...
Requests
There's no right or wrong way to define your requests, really. Ultimately, the way sycamore works is that it adds an execute
function to your object, that takes two arguments: a request definition object, and an optional data payload to use with the request.
The following sample code will be used for reference throughout the documentation below:
// sample-module-requests.js
(function () {
var sampleModuleRequests = {
sampleGet: {
url: "http://example.com/foo/{id}",
done: "onSampleGetDone",
fail: "onRequestFail",
cache: {
key: "some-unique-cache-key",
expiresAfter: 5,
source: "local"
}
},
samplePost: {
url: "http://example.com/foo",
data: "createNewObject",
done: "onSamplePostDone",
fail: "onRequestFail"
},
samplePut: {
url: "http://example.com/foo/{id}"
},
sampleDelete: {
url: "http://example.com/foo/{id}"
}
};
return sampleModuleRequests;
}());
// sample-module.js
(function ($, _, requester, sampleModuleRequests) {
var SampleModule = function () {
this.requests = sampleModuleRequests;
};
_.extend(SampleModule.prototype, requester, {
onSampleGetDone: function (data) {
// do something with the data returned from the server
},
createNewObject: function () {
return {
name: "Foo",
description: "just some new object",
notes: []
};
},
onSamplePostDone: function (data) {
// do something with the new object that was created...
},
updateSomething: function (objId) {
this.execute(this.requests.samplePut, { id: objId })
.done(this.onSamplePutDone)
.fail(this.onRequestFail);
},
onSamplePutDone: function (data) {
// do something with the data returned from the update request
},
onRequestFail: function (data) {
// some request failed...let the user know about it
}
});
return SampleModule;
}(jquery, underscore, requester, sampleModuleRequests));
Defining Requests
The request parameter that the execute
function accepts is a simple object literal with any of the standard settings that the jQuery $.ajax
function can take. The most commonly expected settings on the object are listed below, with their default values, along with some examples.
<setting name>
: <default value>url
: REQUIRED. No default value. This is the only setting thats required.type
: GETheaders
: {}data
: {}dataType
: jsoncontentType
: "application/json; charset=utf-8"context
: this
You can pass any object with the appropriate settings on it that you want, but, as in the sample above, I prefer to define my request definition objects off of a parent object, so they can be referenced by name when passed to the execute
function, as in:
this.execute(this.requests.sampleGet);
Just a little cleaner and easier to read in my opinion.
Executing Requests and passing data (request payloads)
As mentioned previously, sycamore adds an execute
function to your module. Making a request to the server is as simple as calling this function and passing it a request definition and an optional data payload. Take, for example, the sampleGet
request definition from the example requests above:
this.execute(this.requests.sampleGet);
Optionally, you can pass a payload as a second argument to the execute
function that will be used as the data
parameter with the request sent to the server. Consider the samplePut
request above:
this.execute(this.requests.samplePut, {
id: 12345,
name: "Bar",
description: "Foo has been updated and is now Bar"
});
This is a unique example because it demonstrates how sycamore will handle payloads for requests that have tokenized URLs. Notice that the URL parameter for this request has an {id}
token embedded in the URL. Also note that the sample payload submitted as the second argument to the call to execute
above includes values for id
, name
and description
.
For requests such as this, internally, sycamore will evaluate the payload submitted with the request and compare it against any tokens in the URL. For any tokens that are found, the token in the URL will be replaced with the value from the payload, and that value will then be stripped from the payload and not sent to the server as part of the data
sent with the request. So, in this case, a PUT
request would be submitted to http://example.com/foo/12345
with a data
payload of { name: "Bar", description: "Foo has been updated and is now Bar" }
.
Handling callbacks/responses
There are a couple of different ways that callbacks can be handled in sycamore. As of jQuery version 1.5, the jqXHR
object returned by the $.ajax
function implements the Promise interface (see information on the $.Deferred
object for more details about this). This is the object that is returned by sycamore's execute
function. This fact allows you to be able to chain callback methods to the result of the execute
function. Sycamore allows you to do this one of two primary ways:
Your first option is to set the name of the function to handle your callback on the request definition that gets passed to the call to execute
. You can see an example of this on the sampleGet
and samplePost
requests in the example above. Notice how samplePost
has "done" and "fail" options set on it for onSamplePostDone
and onRequestFail
respectively? This tells the execute function to bind those functions to the done
and fail
callbacks of the jqXHR
objects that is returned from the execution of the $.ajax
call, and will execute them at the appropriate time, with this
as the default context for execution.
The other option, is to chain your callbacks directly to the execute
function itself. As mentioned previously, the execute
function returns the jqXHR
object that is returned from the $.ajax
call, so it can have callbacks chained directly onto it. In the sample above, note that neither the samplePut
or the sampleDelete
requests have any callbacks defined on them at all. Handling callbacks on those requests would look like this:
this.execute(this.requests.samplePut)
.done(this.onSamplePutDone)
.fail(this.onRequestFail);
this.execute(this.requests.sampleDelete)
.done(this.onSampleDeleteDone) // or whatever function you define for this callback...its not defined above
.fail(this.onRequestFail);
Caching results
Sycamore can also handle caching of request results for you as well. Caching is handled by defining a cache option on your request definition. The cache
object on the request definition expects three options:
key
: cached results are stored in a hash, and this option defines the key that will be used in the hash to store and retrieve the cached dataexpiresAfter
: the amount of time (in minutes) to cache the results forsource
(optional): this option should only be used if you wish to override the default cache source, which is in memory, and use localStorage instead. If you wish to use localStorage as your cache store, use this option and set its value tolocal
. Any other value on this setting and sycamore will assume and use an in memory cache store.
See the sampleGet
request definition above for an example of setting request cache options.
NOTE: There are a couple of minor known issues with the current implementation of caching that are on the current roadmap to fix in the near future. Please note that when these issues are fixed, the option name will likely change from cache
to something different that the already-defined $.ajax
option. The other known issue is with the uniqueness of the cache key for multiple GET
requests with unique URL values.