Skip to content

Instantly share code, notes, and snippets.

@morten-olsen
Last active August 29, 2015 14:01
Show Gist options
  • Save morten-olsen/cafa126d758c6cdba22d to your computer and use it in GitHub Desktop.
Save morten-olsen/cafa126d758c6cdba22d to your computer and use it in GitHub Desktop.
A collection of front end scripts (1.3KB gzipped)
window.R.def('async', [], function (exports) {
exports.handle = function (target, origin) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var data = JSON.parse(xhr.responseText);
for (var i = 0; i < data.changes.length; i++) {
window.R.publish('async.changeRequest', data.changes[i]);
}
};
if (target.tagName = 'form') {
xhr.open('POST', target.action || target.attributes['datahref'], true);
xhr.send(/* SERIALIZE FORM DATA */);
} else {
xhr.open('GET', target.href || target.attributes['datahref'], true);
xhr.send();
}
};
function init () {
window.R.listen('async.changeRequest', function (data) {
var elements = document.querySelectorAll(data.selector);
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
switch (data.type) {
case 'remove':
element.remove();
break;
case 'replace':
element.innerHTML = data.content;
break;
case 'add':
var elm = document.createElement('div');
elm.innerHTML = data.content;
for (var b = 0; b < elm.childNodes.length; b++) {
element.append(elm.childNodes[b];
}
break;
}
}
});
};
init();
return exports;
});
/* Bootloader */
(function () {
'use strict';
var me = window.R.boot = {},
cache = me.cache = {},
loading = me.loading = {},
waiting = me.waiting = [],
version = window.R.jsversion || '0',
lCache = window.R.cache = (window.R.cache || {
get : function () { return false; },
set : function () {}
});
/**
* Actually invoke a waiting call with the dependancies
*
* @public
* @param {object} call - the waiting call
*/
function invoke(call) {
var dep = [],
i = null;
for (i = 0; i < call.dep.length; i += 1) {
// Each dependency is added to the array
dep.push(cache[call.dep[i]]);
}
// the original callback is called with the dependencies as arguments
call.cb.apply(window, dep);
}
/**
* Creates an xhr loader.
* This is selfcontained, since the downloaded code is evaled, and should
* contain a R.def(...)
*
* @public
* @param {string} name - The name of the module to be loaded.
*/
function getLoader(name) {
return function () {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
// This is evil, but nessecary
eval(xhr.responseText);
// Only if the local-storage cache module is loaded
lCache.set(name, xhr.responseText);
};
// Download the module from a versioned url
xhr.open('GET', '/module/' + version + '/' + name, true);
xhr.send();
};
}
/**
* Define a new module.
* @param {string} name - The name of the module
* @param {array} dependencies - A list of modules needed by the module
* @param {function} init - The constructor code
*/
me.def = window.R.def = function (name, dep, init) {
// We actually need to load the module through the loader itself
// to ensure all dependencies are ready, therefor we
// add the creation code as the last element of the dependencies
// so we can use window.R.load('dep1', 'dep2', ..., callback);
dep.push(function () {
var args = Array.prototype.slice.call(arguments, 0),
obj = null,
i = 0,
missing = null;
// Add an empty object to the start of the array, to be 'exports'
args.unshift({});
// Invoke the actual constructor, with all dependancies loaded, and
// with exports as the first item.
obj = init.apply(window, args);
// Add it to the cache, so we don't have to load it again.
cache[name] = obj;
// Next we need to see it this module is the last needed by any of our
// waiting calls, so we can invoke them.
for (i = 0; i < waiting.length; i += 1) {
// Check if the current module is a dependency for the call
if (!!waiting[i].waiting[name]) {
// If it is, remove it form the list of missing modules
delete waiting[i].waiting[name];
// Check if any missing modules left
if (Object.keys(waiting[i].waiting).length === 0) {
// If there are no more modules to wait for, the call is invoked
invoke(waiting[i]);
}
}
}
});
// Invoke the loader
me.load.apply(window, dep);
};
/**
* Fetch a series of module, using load('dep1', 'dep2', ..., function (dep1, dep2, ...) {
* });
*
* @public
*/
me.load = window.R.load = function () {
// Creates a copy of arguments, which we can manipulate
var args = Array.prototype.slice.call(arguments, 0);
// loading waits for dom-ready
window.R.go(function () {
// Move the last argument to callback, so left is only the modules
var cb = args.pop(),
i = 0,
call = null,
data = null;
call = {
cb : cb,
dep : args,
waiting : {}
};
// Loop though all dependencies
for (i = 0; i < args.length; i += 1) {
// Check if local storage caching is enabled
if (lCache.get(args[i])) {
// if, load the module data from it
data = lCache.get(args[i]);
// And load the module, so it is ready
eval(data);
}
// Check if the module is previously loaded
if (!!cache[args[i]]) {
continue;
}
// Add the module as a missing module to be loaded
call.waiting[args[i]] = true;
// See if the module is already loading
if (loading[args[i]]) {
return;
}
// Ensure that multible call to same dependencies,
// only download one
loading[args[i]] = true;
// Create a loader, and start it in a new thread
// note: might not be nessecary to use setTimeout?
setTimeout(getLoader(args[i]), 10);
}
// If no dependencies are missing...
if (Object.keys(call.waiting).length === 0) {
// ...then just invoke the call
invoke(call);
} else {
// Else add it to the list of waiting calls
waiting.push(call);
}
});
};
}());
cat core.js pubsub.js bootloader.js primer.js utils.js | uglifyjs -o r.min.js
/**
* A general placeholder, where all methods/objects are attached to
* so we don't polute the global scope more than absolutely nessecery.
*/
window.R = {};
/* Primer */
(function () {
'use strict';
var events = [
'click',
'keydown',
'keyup',
'mouseover',
'mouseout'
], i;
/**
* Finds the nearest element (searches out) which has a given attribute
* @private
* @param {string} The name of the attribute
* @param {element} The element to start from
*/
function findNearest(attr, elm) {
// If we get to the top, we return null (non in line)
if (!elm) {
return null;
}
// It the element has the attribute, we return it
if (!!elm.attributes[attr]) {
return elm;
}
// If the element has a parent, we continue our search
return findNearest(elm.parentElement);
}
/**
* The handling of an event, where we look for a primed event
* and if, invoke the module and call its handler
* @private
* @param {event} evt - The event
*/
function handle(evt) {
// Find the nearest element with a primer attribute corresponding
// to the eventtype (fx. data-primer-click="async" for a click event)
var target = findNearest('data-primer-' + evt.type, evt.target),
module = null;
// If any primed elements are found:
if (target) {
// Get the module name from the attribute
module = target.attributes['data-primer-' + evt.type].value;
// Load the module, and parse the primed element, and the target
// element of the event (the element where the event was fired from)
window.R.boot.load(module, function (m) {
m.handle(target, evt.target);
});
}
}
// For each of the event type we want to listen to...
for (i = 0; i < events.length; i += 1) {
// ... We add the handler
document.documentElement.addEventListener(events[i], handle);
}
}());
/* Pub/Sub */
(function () {
'use strict';
var me = window.R.pubsub = {},
listeners = {},
stickies = {};
/**
* Subscribe to a notification channel, where callback
* will be called with the options published
*
* @public
* @param {string} channel - the channel name to listen to
* @param {function} callback - the callback to be called when published to channel
*/
me.listen = window.R.listen = function (channel, callback) {
var i;
// Ensures that the channel is created
listeners[channel] = listeners[channel] || [];
//Adds the callback as a listener
listeners[channel].push(callback);
// Check if there are any sticky notification for th given channel
if (!!stickies[channel]) {
for (i = 0; i < stickies[channel].length; i += 1) {
// And appies them to the listener
callback(stickies[channel][i]);
}
}
};
/**
* Removes a callback previous set as a listener.
*
* @public
* @param {string} channel - the name of the channel originally listened to
* @param {function} callback - the original callback
*/
me.ignore = window.R.ignore = function (channel, callback) {
var i;
// Ensures that the channel is created
listeners[channel] = listeners[channel] || [];
for (i = 0; i < listeners[channel].length; i += 1) {
if (listeners[channel][i] === callback) {
// If the listener is found in the channel, we removes it
delete listeners[channel][i];
}
}
};
/**
* Used for calling all listeners on a channel, and send them options
*
* @public
* @param {string} channel - the name of the channel
* @param {object} options - the data to be send to the listeners
* @param {boolean} sticky - if the notification should be stored for later listeners
*/
me.publish = window.R.publish = function (channel, options, sticky) {
var i = 0;
// Ensures that the channel is created
listeners[channel] = listeners[channel] || [];
for (i = 0; i < listeners[channel].length; i += 1) {
// Pass and invoke each listener
listeners[channel][i](options);
}
if (sticky) {
// create a sticky channel if not already created
stickies[channel] = stickies[channel] || [];
// Add the option for later listeners
stickies[channel].push(options);
}
};
/**
* Simple helper function, which works much like jQuerys $(window).ready(fn)
*
* @public
* @param {function} callback - the function to be invoked when content is loaded
*/
window.R.go = function (callback) {
window.R.listen('domready', function () {
callback();
});
};
}());
/* Utils */
(function () {
'use strict';
var me = window.R.utils = {};
/**
* Used to ensure a function is only called when no new calls has been made
* for x time. Used for things like autocomplete, where you don't want to
* get results before after 1 sec after the user has pushed a button.
* @see http://remysharp.com/2010/07/21/throttling-function-calls/
*
* @public
* @param {function} callback - The callback for when the wait period has passed
* @param {integer} wait - The ms which should have passed, before the callback is called
*/
me.debounce = function (callback, wait) {
var timer;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
callback.apply(context, args);
}, wait);
};
};
/**
* Only call the functions once every X, regardless of how often it is called
* Usefull for things like scroll
* @see http://remysharp.com/2010/07/21/throttling-function-calls/
*
* @public
* @param {function} fn - the callback to call
* @param {integer} threshhold - the min. time between calls
*/
me.throttle = function (fn, threshhold) {
threshhold = threshhold || 250;
var last,
deferTimer;
return function () {
var context = this,
now = new Date(),
args = arguments;
if (last && now < last + threshhold) {
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
};
/**
* A cross browser version of log
*
* @public
*/
me.log = function () {
if (!!window.console) {
window.console.log(arguments);
}
};
/**
* We want to log the errors thrown, so therefore we attach
* to consoles error (this only works in IE9+)
*/
(function (errHandler) {
var org = errHandler;
errHandler = function () {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/ajax/logerror');
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify({
err : arguments,
url : window.location.href
}));
org(arguments);
};
}(window.console.error || function () {}));
/**
* A cross browser content loaded method, found on stackoverflow
* @see http://stackoverflow.com/questions/6902280/cross-browser-dom-ready
*
* @private
*/
function contentLoaded(win, fn) {
var done = false, top = true,
doc = win.document, root = doc.documentElement,
add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
pre = doc.addEventListener ? '' : 'on',
init = function (e) {
if (e.type === 'readystatechange' && doc.readyState !== 'complete') {
return;
}
(e.type === 'load' ? win : doc)[rem](pre + e.type, init, false);
if (!done && (done = true)) {
fn.call(win, e.type || e);
}
},
poll = function () {
try { root.doScroll('left'); } catch (e) { setTimeout(poll, 50); return; }
init('poll');
};
if (doc.readyState === 'complete') {
fn.call(win, 'lazy');
} else {
if (doc.createEventObject && root.doScroll) {
try { top = !win.frameElement; } catch (e) { }
if (top) {
poll();
}
}
doc[add](pre + 'DOMContentLoaded', init, false);
doc[add](pre + 'readystatechange', init, false);
win[add](pre + 'load', init, false);
}
}
/**
* Creates a sticky notification, to get R.go(fn) going
*/
contentLoaded(window, function () {
window.R.publish('domready', null, true);
});
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment