Last active
December 21, 2015 07:48
-
-
Save jbmartin/6273497 to your computer and use it in GitHub Desktop.
This gist provides an API that wraps and extends d3's svg/xml import methods. This API aims to avoid callback nesting by wrapping D3's async xml calls in promises (see jQuery 1.8+ docs). This approach allows users to ignore common async issues because promises are resolved internally. In sum, this is really just syntactic sugar for d3's [xhr obj…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Requires d3 3.2+ and jQuery 1.8+ | |
// CONTRACT | |
// void -> SVG | |
// | |
// PURPOSE | |
// This API both abstracts and extends d3's svg/xml import methods. The goal is to avoid | |
// callback nesting by wrapping d3's async xml calls in a promise, and | |
// then resolving them internally (see jQuery 1.8+ AJAX specs). All | |
// methods return `this` to allow for method cascading. | |
// | |
// EXAMPLES | |
// myStimulus = SVG().load("static/svg/stimulus.svg").appendTo("#stimulus"); | |
// myStimulus.on('mouseover', function(){ console.log('mouseover!'); }) | |
// myStimulus.layer('#layer1').css('fill', 'red').css('width', '100px'); | |
// | |
// TODO | |
// Inheret either jQuery or d3's prototype(s), making all select/manipulate | |
// methods available. Note, inherited methods would need to be wrapped in a | |
// $.done/$.when call to not break current async flow. Alternatively, make SVG a | |
// d3 plugin (same async issues apply). See | |
// http://stackoverflow.com/questions/13983864/how-to-make-a-d3-plugin | |
var SVG = function() { | |
// CONTRACT | |
// attachAsyncJQueryMethod :: SVG, String -> SVG | |
// | |
// PURPOSE | |
// TO extend SVG objects with async jQuery methods. | |
// | |
// EXAMPLES: attachAsyncJQueryMethod(css) | |
function attachAsyncJQueryMethod(SVG, method) { | |
args = getArgs() | |
return function() { // Anon f'n returned on invocation | |
$.when(that.loadingRootSVGNode, that.loadingLayer).done(function() { | |
attachMethod(method, args); | |
}, null); | |
return that | |
} | |
} | |
// CONTRACT | |
// getArgs :: Arguments -> [a] | |
// | |
// PURPOSE | |
// TO convert arguments to an array. According to dev.mozilla: The | |
// arguments object is not an Array. It is similar to an Array, but does | |
// not have any Array properties except length. For example, it does not | |
// have the pop method. However it can be converted to a real Array. | |
// | |
// EXAMPLES | |
// | |
function getArgs() { | |
return Array.prototype.slice.apply(arguments); | |
} | |
// CONTRACT | |
// attachMethodWhenReady :: String, [a] -> void | |
// | |
// PURPOSE | |
// | |
// EXAMPLES | |
// | |
function attachMethodWhenReady(method, args) { | |
ifSVGLoaded(attachMethod, method, args); | |
} | |
// CONTRACT | |
// attachMethod :: String, [a] -> void | |
// | |
// PURPOSE | |
// | |
// EXAMPLES | |
// | |
function attachMethod(method, args) { | |
$(that.el)[method](args[0], args[1]); // TODO(): Make n-ary. | |
} | |
// CONTRACT | |
// ifSVGLoadedCall :: (a -> b) -> c | |
// | |
// PURPOSE | |
// TO run callbacks on SVG object once it's loaded. | |
// | |
// EXAMPLES | |
// function sum() { | |
// var total = 0, | |
// args = getArgs(); | |
// $.each(args, function(){ total += this; }); | |
// } | |
// | |
// // Loaded | |
// ifSVGLoadedCall(sum, 2, 2) | |
// >> 4 | |
// | |
// //Not loaded | |
// ifSVGLoadedCall(sum, 2, 2) | |
// >> Exception Missing SVG element. Please load before appending. | |
function ifSVGLoadedCall(fn) { | |
isNodeImported = that.importedNode; | |
if (isNodeImported) { | |
throw new Error("Missing SVG element. Please load before appending."); | |
} else { | |
fn(getArgs()) | |
} | |
} | |
return { // This is the object that will be returned when SVG is invoked. | |
el: null, // DOM element associated with SVG. | |
importedNode: null, // String containing SVG script. | |
loadingRootSVGNode: null, // Promise (thunk) container for async loading SVG root node. | |
loadingSVGLayer: null, // Promise (thunk) container for async loading SVG layer node. | |
// CONTRACT | |
// String -> SVG | |
// | |
// PURPOSE | |
// TO asynchronously pull SVG file from URL, place it in importedNode | |
// member, and then resolve promise. | |
// | |
// EXAMPLES | |
load: function(url) { | |
var that = this; // Work around for Javascript's function scoping. | |
that.loadingRootSVGNode = new $.Deferred(); // Create a new promise | |
d3.xml(url, "image/svg+xml", function(xml) { | |
that.importedNode = document.importNode(xml.documentElement, true); | |
that.loadingRootSVGNode.resolve(); | |
}); | |
return that | |
}, | |
setElement: function(el) { | |
this.el = el; | |
}, | |
// CONTRACT | |
// appendTo :: String -> SVG | |
// | |
// PURPOSE | |
// Check if SVG has been loaded, if so append code to `el`, | |
// else wait. El is a standard jQuery selector string ('#svg', | |
// '.svg', etc.). | |
// | |
// EXAMPLES | |
// | |
appendTo: function(el) { | |
var that = this; // Work around for Javascript's function scoping. | |
setElement(el); | |
function appendLayer(node) { | |
d3.select(el).node().appendLayer(node); | |
} | |
ifSVGLoadedCall(appendLayer, that.importedNode) | |
return that | |
}, | |
// CONTRACT | |
// layer :: String -> SVG | |
// | |
// PURPOSE | |
// Selector for SVG layers (nodes). Useful for binding events and other | |
// jQuery/d3 events. `selector` must be a proper jQuery selector | |
// ('#item', '.items', etc.). The example assumes a SVG element has | |
// already been loaded. | |
// | |
// EXAMPLES | |
// myStimulus.layer('#layer').css('fill', 'red'); | |
layer: function(selector) { | |
var that = this; // Work around for Javascript's function scoping. | |
that.loadingSVGLayer = new $.Deferred(); | |
that.loadingRootSVGNode.done(function() { | |
ifSVGLoadedCall() | |
that.el = selector | |
that.loadingSVGLayer.resolve(); | |
}); | |
return that | |
}, | |
// Attach async jQuery methods. | |
// TODO(): Add more jQuery and d3 methods. | |
// TODO(): Cascading method calls (e.g., .css('...').on('...'), etc.) | |
// are blocking. Fix. | |
on: attachAsyncJQueryMethod('on'), | |
css: attachAsyncJQueryMethod('css'), | |
attr: attachAsyncJQueryMethod('attr') | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment