Manage your Ajax server requests with Sycamore

Sycamore is a JavaScript library for jQuery $.ajax request management. It is a wrapper around jQuery's $.ajax function that provides some simplicity and consistency in defining and executing server requests from client applications. It is implemented as a mixin that can be used to extend your own objects with this functionality. The README contains an example that will help illustrate this (and there's one below as well).

Consistency and manageability

There are many reasons why Sycamore was created, not the least of which is the simple elimination of boilerplate code for defining $.ajax calls throughout an application (keeping things DRY). Another reason is that I found surprisingly few options out there that provide the functionality that Sycamore does. appendTo provides the excellent AmplifyJS which has a module for request definition and management, which is very similar to what you'll find in Sycamore, but it does not (out of the box) support jQuery's $.Deferred based callback chaining that is recommended in more recent versions of jQuery. It was primarily these reasons that motivated me to write and evolve Sycamore.

A comparative example

Often its easier to recognize a need for something by seeing an example for comparison. This example illustrates what Sycamore can do for your code. Consider the following examples...which code would you rather maintain?

A module without Sycamore:

(function ($, _) {
    var ModuleOne = function () {
        this.doSomething();
    };

    _.extend(ModuleOne.prototype, {
        doSomething: function () {
            $.ajax({
                url: "http://example.com/foo",
                type: "get",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                data: {
                    orderBy: "someField",
                    sort: "asc"
                },
                success: function (data) {
                    // anonymous function to handle the results...
                },
                error: function (err) {
                    // woops, something bad happened, deal with it
                }
            });
        },

        doSomethingElse: function (newObject) {
            $.ajax({
                url: "http://example.com/foo",
                type: "post",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                data: newObject
            })
            .done(function (data) {
                // do something with the results here too
            });

            // woops...didn't even handle failed requests here...did we...
        },

        doSomeOtherThing: function (someOtherId) {
            $.ajax({
                url: "http://example.com/foo/" + someOtherId + "/eewwww",
                type: "get",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function (data) {
                    // do something with the results/data...
                }
            });
        },

        doWhatever: function (updatedData) {
            $.ajax({
                url: "http://example.com/bar/" + updatedData.Id,
                type: "put",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                data: updatedData
            })
            .then(function () {
                // do this one thing first...
            })
            .then(function () {
                // then do this other thing...
            });
        }
    });

    return ModuleOne;
}(jQuery, underscore));

A module with Sycamore

(function ($, _, requester) { // <- requester is the Sycamore module
    var ModuleTwo = function () {
        this.doSomething();
    };

    _.extend(ModuleTwo.prototype, requester, {
        requests: {
            one: {
                url: "http://example.com/foo",
                done: "onDoSomethingDone",
                fail: "onRequestFail"
            },
            two: {
                url: "http://example.com/foo",
                type: "post"
            },
            three: {
                url: "http://example.com/foo/{id}/aahhh"
            },
            four: {
                url: "http://example.com/bar/{id}",
                type: "put",
                done: "onDoWhateverDone",
                fail: "onRequestFail"
            }
        },

        doSomething: function () {
            this.execute(this.requests.one);
        },

        onDoSomethingDone: function (data) {
            // do something with the results
        },

        doSomethingElse: function () {
            this.execute(this.requests.two)
                .done(this.onDoSomethingElseDone)
                .fail(this.onRequestFail);
        },

        onDoSomethingElseDone: function (data) {
            // handle the response
        },

        doSomeOtherThing: function () {
            this.execute(this.requests.three)
                .done(this.onDoSomeOtherThingDone)
                .fail(this.onRequestFail);
        },

        onDoSomeOtherThingDone: function (data) {
            // you get the idea...
        },

        doWhatever: function () {
            this.execute(this.requests.four)
                .then(this.afterDoWhatever)
                .then(this.afterAfterDoWhatever);
        },

        onDoWhateverDone: function (data) {
            // update something on the UI or something
        },

        afterDoWhatever: function () {
            // something that needs to happen after the request in doWhatever has completed
        },

        afterAfterDoWhatever: function () {
            // chaining methods in order is easy with promises
        },

        onRequestFail: function (err) {
            // something bad happened...let the user know about it, log it, whatever
        }
    });

    return ModuleTwo;
}(jQuery, underscore, Requester));
// Requester is the name of the module that Sycamore exports

One thing you may notice is that Sycamore won't necessarily help you to write less code. What it will do though is facilitate and encourage you to write cleaner, more modular, readable and maintainable code. You can imagine how the requests in ModuleTwo might be removed from it altogether, and maintained and imported separately...it makes your code more flexible as well.

In the wild

Sycamore was inspired by a couple of different client projects that I worked on in the past year. The result of the original inspiration was a couple of throw-away prototypes. More recently though, on another client project, we had need of a more robust solution, so I revisited the idea, and with much help from Ryan Niemeyer, the current version has been solidified and polished and is now in production use with great success. I'm very pleased with the current implementation and am excited for some ideas we have for the future.

Whats with the name?

I dunno...naming stuff is hard. At some point I got tired of letting the fact that I couldn't come up with decent names for stuff keep me from getting anything done with them, so I decided to come up with my own naming scheme for projects, and I chose to go with names of trees. A quick glance of the projects in my GitHub account will confirm this. So thats pretty much it really...Sycamore was the first tree that came to mind and sounded good when I needed a name. I'm open to suggestion if anyone has any more appropriate ideas.

Talk to me

So I've been very pleased with how Sycamore has worked out for me and the projects that I'm using it in, but I want it to be useful for others as well. I would love your feedback on this project. Please keep in mind that its purpose, ultimately, is for $.ajax request management, so please keep things within that scope. That being said, it is just a mixin that could easily be imported into other projects with a broader scope. Let me know what you think about Sycamore.


CREDITS: Thanks again to Ryan Niemeyer for his contributions to this project, and to Jordan Kasper for his review and feedback on this post.