Skip to content

Instantly share code, notes, and snippets.

@therabidbanana
Created August 1, 2012 15:19
Show Gist options
  • Save therabidbanana/3227739 to your computer and use it in GitHub Desktop.
Save therabidbanana/3227739 to your computer and use it in GitHub Desktop.
Working with dtime api with Javascript and no jquery dependency

Code overview

To interaction with the dtime API via Javascript, you need to be able to chain together API calls in a straightforward manner.

Hypermedia APIs rely on state machine logic - given a current state, there are a number of transitions you can make (Valid HTTP verbs on the available rels)

In the code, transition(current_state, rel, opts) is our way to transition.

where opts has some properties:

  • opts.uriVariables: optional variables to go into a uri template.
  • opts.data: optional data to send with request
  • opts.method: which http method to use (maybe default get)
  • opts.success: callback to attach on success, takes in the json response
  • opts.error: callback to attach on error, takes in the json response

transition will internally use ajax_call(url, method) to make the initial ajax call. The below method will work with both jquery and zepto. For other options, you'd need to change $.ajax to whatever format your XHR requests usually take.

transition internally relies on the href_for function, you pass the current_state
variable in and find the link from that state object.

href_for(current_state, rel, uri_variables)

Href for loops through current_state._links and finds the link with a name == "rel" It uses uri template syntax to turn /questions/{question_id} into /questions/foo, if uri_variables.question_id = "foo".

Deferred objects

In 1.5, jQuery introduced deferred objects - for chaining together multiple api calls, it's a really handy pattern. Read more about them here - http://api.jquery.com/category/deferred-object/

The above code uses a simple library to duplicate jquery deferred functionality: https://github.com/sudhirj/simply-deferred

It's an optional way to help clean up the way callbacks are chained together:

transition a, "b", success: (b)->
  transition b, "c", success: (c)-> 
    console.log(c)

vs

transition(a, "b").done (b)->
  transition(b, "c").done (c)->
    console.log(c)

The added benefit is that multiple done functions may be chained onto a deferred object, and that deferred object can even have done functions called on it after it's succeeded - those done callbacks will just immediately fire.

You can see this pattern used in the load_root() function, and the load_sitemap() function. These functions allow you to make one AJAX request for the sitemap to service any number of requests, by just chaining more done functions onto load_sitemap().done()

Deferreds also allow pipe:

transition(a, "b")
  .pipe((b)-> transition(b, "c"))
  .done((c)-> console.log(c))

Which enforces the order of multiple ajax calls in the order they are piped.

# http://jsfiddle.net/KJp8w/
# Jumpstart data - enough to get
# to the first page without a using a different method.
initial_state = _links:
"dtime:root":
href: "https://dev-api.dtime.com/"
ajax_call = (url, method)->
# Maybe mix in authentication here, if you were authenticated
# console.log "#{method}: ", url
# Obviously this would need to be platform specific if you didn't
# have jQuery
new_def = new Deferred()
# Resolve deferred with success and error
$.ajax
url: url
type: method
dataType: 'json'
success: new_def.resolve
error: new_def.reject
new_def
transition = (current_state, rel, opts = {})->
# Get the link given the rel. Turn it into an href
# if it is a
href = href_for(current_state, rel, opts.uriVariables)
# Default request method is GET
method = opts.method ? "GET"
# Dummy console log for success/error callbacks
opts.success ?= (data)->console.log(data)
opts.error ?= (data)->console.warn(data)
xhr = ajax_call(href, method)
xhr.done(opts.success)
xhr.fail(opts.error)
xhr
href_for = (state, rel, vars = {})->
for link_rel, link of state._links
if link_rel == rel
use_link = link
if use_link
template = uritemplate(use_link.href)
template.expand(vars)
else
# errror!
sitemap_state = new Deferred()
root_state = new Deferred()
transition(initial_state, "dtime:root", success: (data)->
root_state.resolve(data)
transition(data, "dtime:sitemap", success: (data)-> sitemap_state.resolve(data))
)
# Use previously fetched and cached sitemap to immediately do another ajax request,
# without having to go dtime:root -> dtime:sitemap -> dtime:activity:all
sitemap_state.done((sitemap)->transition(sitemap, "dtime:activity:all"))
document.write('see console...')​​
(function() {
var ajax_call, href_for, initial_state, root_state, sitemap_state, transition;
initial_state = {
_links: {
"dtime:root": {
href: "https://dev-api.dtime.com/"
}
}
};
ajax_call = function(url, method) {
var new_def;
new_def = new Deferred();
$.ajax({
url: url,
type: method,
dataType: 'json',
success: new_def.resolve,
error: new_def.reject
});
return new_def;
};
transition = function(current_state, rel, opts) {
var href, method, xhr, _ref, _ref2, _ref3;
if (opts == null) {
opts = {};
}
href = href_for(current_state, rel, opts.uriVariables);
method = (_ref = opts.method) != null ? _ref : "GET";
if ((_ref2 = opts.success) == null) {
opts.success = function(data) {
return console.log(data);
};
}
if ((_ref3 = opts.error) == null) {
opts.error = function(data) {
return console.warn(data);
};
}
xhr = ajax_call(href, method);
xhr.done(opts.success);
xhr.fail(opts.error);
return xhr;
};
href_for = function(state, rel, vars) {
var link, link_rel, template, use_link, _ref;
if (vars == null) {
vars = {};
}
_ref = state._links;
for (link_rel in _ref) {
link = _ref[link_rel];
if (link_rel === rel) {
use_link = link;
}
}
if (use_link) {
template = uritemplate(use_link.href);
return template.expand(vars);
} else {
}
};
sitemap_state = new Deferred();
root_state = new Deferred();
transition(initial_state, "dtime:root", {
success: function(data) {
root_state.resolve(data);
return transition(data, "dtime:sitemap", {
success: function(data) {
return sitemap_state.resolve(data);
}
});
}
});
sitemap_state.done(function(sitemap) {
return transition(sitemap, "dtime:activity:all");
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment