artoo-js
Version:
The client-side scraping companion.
432 lines (359 loc) • 9.41 kB
JavaScript
;(function(undefined) {
'use strict';
/**
* artoo helpers
* ==============
*
* Some useful helpers.
*/
var _root = this;
// Extending Emmett
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
var ee = new artoo.emitter();
Object.setPrototypeOf(artoo, Object.getPrototypeOf(ee));
// Legacy support
// TODO: drop this asap
artoo.hooks = {
trigger: function(name) {
artoo.emit(name);
}
};
/**
* Generic Helpers
* ----------------
*
* Some basic helpers from collection handling to type checking.
*/
// Useless function
function noop() {}
// Recursively extend objects
function extend() {
var i,
k,
res = {},
l = arguments.length;
for (i = l - 1; i >= 0; i--)
for (k in arguments[i])
if (res[k] && isPlainObject(arguments[i][k]))
res[k] = extend(arguments[i][k], res[k]);
else
res[k] = arguments[i][k];
return res;
}
// Is the var an array?
function isArray(v) {
return v instanceof Array;
}
// Is the var an object?
function isObject(v) {
return v instanceof Object;
}
// Is the var a real NaN
function isRealNaN(v) {
return isNaN(v) && (typeof v === 'number');
}
// Is the var a plain object?
function isPlainObject(v) {
return v instanceof Object &&
!(v instanceof Array) &&
!(v instanceof Function);
}
// Is a var non primitive?
function isNonPrimitive(v) {
return isPlainObject(v) || isArray(v);
}
// Is a var primitive?
function isPrimitive(v) {
return !isNonScalar(v);
}
// Get first item of array returning true to given function
function first(a, fn, scope) {
for (var i = 0, l = a.length; i < l; i++) {
if (fn.call(scope || null, a[i]))
return a[i];
}
return;
}
// Get the index of an element in an array by function
function indexOf(a, fn, scope) {
for (var i = 0, l = a.length; i < l; i++) {
if (fn.call(scope || null, a[i]))
return i;
}
return -1;
}
// Retrieve a file extenstion from filename or url
function getExtension(url) {
var a = url.split('.');
if (a.length === 1 || (a[0] === '' && a.length === 2))
return '';
return a.pop();
}
/**
* Document Helpers
* -----------------
*
* Functions to deal with DOM selection and the current document.
*/
// Checking whether a variable is a jQuery selector
function isSelector(v) {
return (artoo.$ && v instanceof artoo.$) ||
(jQuery && v instanceof jQuery) ||
($ && v instanceof $);
}
// Checking whether a variable is a DOM document
function isDocument(v) {
return v instanceof HTMLDocument ||
v instanceof XMLDocument;
}
// Get either string or document and return valid jQuery selection
function jquerify(v) {
var $ = artoo.$;
if (isDocument(v))
return $(v);
return $('<div />').append(v);
}
// Creating an HTML or XML document
function createDocument(root, namespace) {
if (!root)
return document.implementation.createHTMLDocument();
else
return document.implementation.createDocument(
namespace || null,
root,
null
);
}
// Loading an external file the same way the browser would load it from page
function getScript(url, async, cb) {
if (typeof async === 'function') {
cb = async;
async = false;
}
var el = document.createElement('script');
// Script attributes
el.type = 'text/javascript';
el.src = url;
// Should the script be loaded asynchronously?
if (async)
el.async = true;
// Defining callbacks
el.onload = el.onreadystatechange = function() {
if ((!this.readyState ||
this.readyState == 'loaded' ||
this.readyState == 'complete')) {
el.onload = el.onreadystatechange = null;
// Removing element from head
artoo.mountNode.removeChild(el);
if (typeof cb === 'function')
cb();
}
};
// Appending the script to head
artoo.mountNode.appendChild(el);
}
// Loading an external stylesheet
function getStylesheet(data, isUrl, cb) {
var el = document.createElement(isUrl ? 'link' : 'style'),
head = document.getElementsByTagName('head')[0];
el.type = 'text/css';
if (isUrl) {
el.href = data;
el.rel = 'stylesheet';
// Waiting for script to load
el.onload = el.onreadystatechange = function() {
if ((!this.readyState ||
this.readyState == 'loaded' ||
this.readyState == 'complete')) {
el.onload = el.onreadystatechange = null;
if (typeof cb === 'function')
cb();
}
};
}
else {
el.innerHTML = data;
}
// Appending the stylesheet to head
head.appendChild(el);
}
var globalsBlackList = [
'__commandLineAPI',
'applicationCache',
'chrome',
'closed',
'console',
'crypto',
'CSS',
'defaultstatus',
'defaultStatus',
'devicePixelRatio',
'document',
'external',
'frameElement',
'history',
'indexedDB',
'innerHeight',
'innerWidth',
'length',
'localStorage',
'location',
'name',
'offscreenBuffering',
'opener',
'outerHeight',
'outerWidth',
'pageXOffset',
'pageYOffset',
'performance',
'screen',
'screenLeft',
'screenTop',
'screenX',
'screenY',
'scrollX',
'scrollY',
'sessionStorage',
'speechSynthesis',
'status',
'styleMedia'
];
function getGlobalVariables() {
var p = Object.getPrototypeOf(_root),
o = {},
i;
for (i in _root)
if (!~i.indexOf('webkit') &&
!(i in p) &&
_root[i] !== _root &&
!(_root[i] instanceof BarProp) &&
!(_root[i] instanceof Navigator) &&
!~globalsBlackList.indexOf(i))
o[i] = _root[i];
return o;
}
/**
* Async Helpers
* --------------
*
* Some helpful functions to deal with asynchronous matters.
*/
// Waiting for something to happen
function waitFor(check, cb, params) {
params = params || {};
if (typeof cb === 'object') {
params = cb;
cb = params.done;
}
var milliseconds = params.interval || 30,
j = 0;
var i = setInterval(function() {
if (check()) {
clearInterval(i);
cb(null);
}
if (params.timeout && params.timeout - (j * milliseconds) <= 0) {
clearInterval(i);
cb(new Error('timeout'));
}
j++;
}, milliseconds);
}
// Dispatch asynchronous function
function async() {
var args = Array.prototype.slice.call(arguments);
return setTimeout.apply(null, [args[0], 0].concat(args.slice(1)));
}
// Launching tasks in parallel with an optional limit
function parallel(tasks, params, last) {
var onEnd = (typeof params === 'function') ? params : params.done || last,
running = [],
results = [],
d = 0,
t,
l,
i;
if (typeof onEnd !== 'function')
onEnd = noop;
function cleanup() {
running.forEach(function(r) {
clearTimeout(r);
});
}
function onTaskEnd(err, result) {
// Adding results to accumulator
results.push(result);
if (err) {
cleanup();
return onEnd(err, results);
}
if (++d >= tasks.length) {
// Parallel action is finished, returning
return onEnd(null, results);
}
// Adding on stack
t = tasks[i++];
running.push(async(t, onTaskEnd));
}
for (i = 0, l = params.limit || tasks.length; i < l; i++) {
t = tasks[i];
// Dispatching the function asynchronously
running.push(async(t, onTaskEnd));
}
}
/**
* Monkey Patching
* ----------------
*
* Some monkey patching shortcuts. Useful for sniffers and overriding
* native functions.
*/
function before(targetFunction, beforeFunction) {
// Replacing the target function
return function() {
// Applying our function
beforeFunction.apply(this, Array.prototype.slice.call(arguments));
// Applying the original function
return targetFunction.apply(this, Array.prototype.slice.call(arguments));
};
}
/**
* Exportation
* ------------
*/
// Exporting to artoo root
artoo.injectScript = function(url, cb) {
getScript(url, cb);
};
artoo.injectStyle = function(url, cb) {
getStylesheet(url, true, cb);
};
artoo.injectInlineStyle = function(text) {
getStylesheet(text, false);
};
artoo.waitFor = waitFor;
artoo.getGlobalVariables = getGlobalVariables;
// Exporting to artoo helpers
artoo.helpers = {
before: before,
createDocument: createDocument,
extend: extend,
first: first,
getExtension: getExtension,
indexOf: indexOf,
isArray: isArray,
isDocument: isDocument,
isObject: isObject,
isPlainObject: isPlainObject,
isRealNaN: isRealNaN,
isSelector: isSelector,
isNonPrimitive: isNonPrimitive,
isPrimitive: isPrimitive,
jquerify: jquerify,
noop: noop,
parallel: parallel
};
}).call(this);