/*
I find myself writing the same helper functions in situations where I don't want to pull in a library.
This is a different kind of toolkit ... there's no file to download or include.
Instead, there's a collection of functions along with their descriptions and implementation for you to copy and paste into your code.
Note 1: The one-line version is intentionally not "minified" or obfuscated ... it's intended to be a readable variation of the multi-line that fits in one line.
Note 2: This README is valid javascript. Really, if you want to you can just include it as a script ... that's why the "stray" comments are peppered around. In fact, the test suite includes this readme file.
Every helper is self-contained. No function depends on any other function in the 'library.'
This is something I call "low-touch coding" which borrows from the low-touch sales technique. In low-touch sales you generally leave the customer alone and allow them to make their own decision. The theory of low-touch is that it leads to fewer issues because you don't
- cajole customers into an entitlement trap that you have to make good on
- need to understand what they are doing or their motivations
- get distracted by things not relating to the sale
Analagously, in low-touch coding, their aren't
- empty promises of a better 'design'
- assumptions of how the code is (or even worse, should be) written
- impositions on any business logic or grand architecture
Low-touch code should be able to drop into the most unmaintainable ill-founded poorly written garbage code and get its job done quickly and effectively without breaking things. Need to get a new feature done quickly on legacy code written by a bunch of drunken monkeys without breaking things? No problem. That's low touch.
Let's get started
- addTrigger - a module that can add ".ready" to an object
- when - runs a block of code when a global is defined
- req - loads a script and then runs a when
- multi - runs multiple functions through a single assignment
- chain - cascades the return values of multiple functions with a single assignment
- once - makes sure that a function is run only once
- wrapfn - wraps a function in a middleware
This is similar to a $(document).ready
in jQuery. This author has seen many DIY implementations with subtle timing bugs.
For instance, here is one recently (Feb 2016) found in Volusion:
function volusionDocReady(callback) {
volusionDocReadyMethods.push(callback);
}
$(function() {
$.each(volusionDocReadyMethods, function (index, volusionDocReadyMethod) {
volusionDocReadyMethod();
});
});
return {
ready: volusionDocReady
};
This code has a crucial error that leads to things sometimes not working.
To understand the bug, let's look at a usage of this library where this invocation exists (called CB henceforth):
$(document).ready(function(){
volusion.ready(function () { <<-- CB
...
});
});
Let's see what this code does:
- A callback, CB is always added to
volusionDocReadyMethods
. - The members of
volusionDocReadyMethods
always get called. - BUT CB is NOT always added BEFORE the methods get called.
Therefore, CB can register AFTER the methods are called, and thus may never run.
This is where honest and well-intended implementations of .ready()
can break.
Wouldn't it be better if the .ready()
implementation was robust enough to not take issue with this? Fragile foundations are hardly worth using at all. Let's do better.
addTrigger
can be placed upon existing singletons (or global objects - however you want to call them):
addTrigger('ready', object);
This can be composed pretty easily to our .ready()
implementation above.
function addReady(object) { return addTrigger('ready', object); }
The genericity of this, allows multiple triggers to exist. So you could have .onLogin
and .ready
or something else.
When you want to execute the code, call the named function without a callback function argument.
For instance, in our above example we could have turned the first block of code into this:
addTrigger('ready', volusion);
$(volusion.ready);
Then
volusion.ready( ... );
While avoiding the timing bugs.
Here it is, with no dependencies or demands, frameworks or funk in a single line of code.
Multi-line */
function addTrigger(name, object) {
var stack = [];
object[name] = function(arg) {
if(arg && arg.call && arg.apply) {
return stack === null ? arg() : stack.push(arg);
}
while(stack.length) {
stack.shift()(arg);
}
stack = null;
}
}
/* Single-line
function addTrigger(name, object){ var stack=[]; object[name]=function(arg){ if(arg && arg.call && arg.apply){ return stack===null ? arg():stack.push(arg); } while(stack.length){ stack.shift()(arg); } stack=null; } }
Similar to an AMD Require except designed to delay execution until a library that is included through say an HTML <script>
tag is loaded.
This will wait until JQuery is loaded ( presuming it's being included somewhere else on the page ):
Sometimes, given a not-so-hotly designed system, this can lead to a race-condition.
<script>
$(function() { ... });
</script>
You could either be an "architect" and spend endless hours trying to fix it, or use when:
<script>
when('$').run(function() { ... });
</script>
Multi-line */
function when(lib) {
var _cb, _ival = setInterval(function(){
if(self[lib]) {
_cb();
clearInterval(_ival);
}
}, 20);
return {
run: function(cb) { _cb = cb; }
}
}
/* Single-line
function when(lib){ var _cb, _ival=setInterval(function(){ if(self[lib]) { _cb(); clearInterval(_ival); } }, 20); return{ run: function(cb) { _cb=cb; } } }
Similar to an amd require, this is a really light library that will load some js, given that it's not loaded before.
Pretend you have a debug library you want to pull in before running some code. You know that when it's loaded
it will define dbg
in the global namespace. Pretend the code is at //example.com/dbg.js
.
req('//example.com/dbg.js', 'dbg', function() { ... });
There are very cathedral ways of doing this ... but let's not do that.
Note: This method won't load a script if the object you are looking for is defined. If the functionality was brought in elsewhere, this thing isn't stupid enough to need its own copy to consider it "loaded".
Multi-line */
function req(url, obj, cb) {
req[url] = self[obj] || req[url] || (document.body.appendChild(document.createElement('script')).src = url);
var _ival = setInterval(function(){
if(self[obj]) {
cb(obj);
clearInterval(_ival);
}
}, 20);
}
/* Single-line
function req(url, obj, cb){ req[url]=self[obj] || req[url] || (document.body.appendChild(document.createElement('script')).src=url); var _ival=setInterval(function() { if(self[obj]){ cb(obj); clearInterval(_ival); } }, 20); }
Some libraries don't allow an easy way to specify an Array
of callbacks as opposed to just one. multi
allows any number of functions to be glued together and executed in sequence
Pretend you have some old crufty code that wants you to specify things like this:
flashObject.onclick = fn0;
But you also want it to run fn1, fn2, and fn3.
With multi you can do this like so:
flashObject.onclick = multi(
fn0, fn1, fn2, fn3);
The arguments
and this
pointer are of course maintained.
Multi-line */
function multi() {
var list = Array.prototype.slice.call(arguments);
return function() {
var args = arguments;
list.forEach(function(cb) {
cb.apply(this, args);
}, this);
};
}
/* Single-line
function multi(){var list=Array.prototype.slice.call(arguments); return function(){var args=arguments; list.forEach( function(cb){cb.apply(this, args);}, this);} }
Chain is like multi only the return value of each function gets passed through to the next ... this is similar to a middleware in ruby. It's worth noting that there's just a slight change from the multi() above.
Pretend you needed to do some processing on something before passing it on to the next function. For instance, say you are listening to a dom node and then want the value to be passed through to the lower function. We'll start with a wrapper.
function on_change(dom_node, user_defined_cb) {
$(dom_node).change(chain(
function() { return this.value; },
user_defined_cb
));
}
Now we'll listen on that
on_change('#my-input', function(what) {
console.log("the new value of my-input is " + what);
});
Multi-line */
function chain() {
var list = Array.prototype.slice.call(arguments);
return function() {
var args = arguments[0];
list.forEach(function(cb) {
args = cb.call(this, args);
}, this);
};
}
/* Single-line
function chain(){var list=Array.prototype.slice.call(arguments); return function(){var args=arguments[0]; list.forEach( function(cb){args=cb.call(this, args);}, this);} }
Sometimes there's bad code that runs a function many times and you want some bandaid that makes it only run once. There's too many other things you'll break if you try to "refactor" the "masterpiece" in front of you.
once()
permits that function to only run one time. It has the assumption that
fn.once
is not defined- The function's scope is
self[fn]
In practice, these are generally pretty safe assumptions in instances where this functionality is necessary.
Pretend you have an ajax call that doesn't have any immediately UI feedback (there's a very fancy name for this that the front-end 'frameworks' like to use to make themselves look like they are super smart...).
Anyway, so you have a button that the user will click like 4 or 5 times getting no UI feedback and thus posts a bunch of times ... yeah, we've all been there. Here's how you fix that here:
Run this:
once("email_ten_thousand_people");
And then it will "wrap" the actual implementation in a function that makes sure it doesn't get run more than once. You'll see in the multi-line below. There's a magical regex that once could use to remove the requirement of the quotations, but that makes the implementation harder to understand for future you and in violation of the principle of this library. However, feel free to put that check in if you want.
Multi-line */
function once(fn) {
var _fn = self[fn];
return self[fn] = function() {
self[fn] = function() { return _fn; };
return _fn = _fn.apply(this, arguments);
};
}
/* Single-line
function once(fn){var _fn=self[fn]; return self[fn]=function(){self[fn]=function(){return _fn;}; return _fn=_fn.apply(this, arguments);}; }
At the console you can use this to wrap around a function name specified by a string in order to see when its run and what arguments its run with. As it turns out, some of the browser debuggers are buggy and broken ... this makes it simple to log things and interact with the site at the same time.
If you have a function, say
var cat = {
init: function() { ... }
}
You can do
wrapfn('cat.init');
With no callbacks to console.log ... or you can specify your own callbacks. If you want multiple callbacks, then there's other tools in this document, such as multi, that you can include to do that.
Multi-line */
function wrapfn(what, cb) {
if(!wrapfn[what]) {
cb = cb || function(args) { console.log(what, args) };
wrapfn[what] = eval(what);
eval(what + '=' + (function(){
var args = Array.prototype.slice.call(arguments);
cb(args);
wrapfn[what].apply(this, args);
}));
}
}
/* Single-line
function wrapfn(what,cb){if(!wrapfn[what]){cb=cb || function(args){console.log(what,args)}; wrapfn[what]=eval(what); eval(what+'='+(function(){var args=Array.prototype.slice.call(arguments); cb(args); wrapfn[what].apply(this, args); })); } }
*/