// ==UserScript== // @name JoeSimmons' Library // @namespace http://userscripts.org/users/23652 // @description A JavaScript library used by JoeSimmons // @include * // @copyright JoeSimmons // @author JoeSimmons // @version 1.3.0 // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html // @grant GM_addStyle // ==/UserScript== /** WIKI ==> https://github.com/joesimmons/jsl/wiki/ **/ (function (window, undefined) { 'use strict'; // use strict mode in ECMAScript-5 var version = '1.3.0'; // this will be used for JSL.prototype.version var intervals = []; // for the setInterval/clearInterval methods // regular expressions var rSelector = /^\*|^\.[a-z][\w\d-]*|^#[^ ]+|^[a-z]+|^\[a-z]+/i; // matches a CSS selector var rXpath = /^\.?\/{1,2}[a-zA-Z\*]+/; // matches an XPath query var rHTML = /<[^>]+>/; // matches a string of HTML var rHyphenated = /-([a-zA-Z])/g; // matches alphabetic, hyphenated strings var rElementObject = /^\[object HTML([a-zA-Z]+)?Element\]$/; // matches the toString value of an element var rWindowObject = /^\[object Window\]$/; // matches the toString value of a window object var rValidVarname = /^[a-zA-Z$_][a-zA-Z0-9$_]*$/; // matches a valid variable name // compatibility methods for browsers that don't support ECMAScript-5 completely var compat = { 'arr_indexOf' : function (searchElement, fromIndex) { var index = parseInt(fromIndex || 0, 10), len = this.length; index = index < 0 ? len + index : index; // handle negative fromIndex index = !(index > 0) ? 0 : index; // handle out of range and/or NaN fromIndex while (index < len && index >= 0) { if (this[index] === searchElement) { return index; } index += 1; } return -1; }, /* 'filter' : function (fn, oThis) { var index, value, len = this.length, ret = []; for (index = 0; index < len; index += 1) { value = this[index]; if ( fn.call(oThis, value, index, this) ) { ret.push(value); } } return ret; }, */ 'forEach' : function (fn, oThis) { var index, len; for (index = 0, len = this.length; index < len; index += 1) { fn.call(oThis, this[index], index, this); } }, 'map' : function (fn, oThis) { var index, newArr = [], len; for (index = 0, len = this.length; index < len; index += 1) { newArr[index] = fn.call(oThis, this[index], index, this); } return newArr; }, 'reduce' : function (fn, initialValue) { var index, len, value, isValueSet = false; if (arguments.length > 1) { value = initialValue; isValueSet = true; } for (index = 0, len = this.length; index < len; index += 1) { if (isValueSet) { value = fn(value, this[index], index, this); } else { value = this[index]; isValueSet = true; } } return value; } }; // gets a method from an object's prototype. returns undefined if not found var getMethod = function (obj, method) { if (typeof XPCNativeWrapper === 'function' && typeof XPCNativeWrapper.unwrap === 'function') { obj = XPCNativeWrapper.unwrap(obj); } else if (obj.wrappedJSObject) { obj = obj.wrappedJSObject; } if (obj.prototype && typeof obj.prototype[method] === 'function') { return obj.prototype[method]; } }; // original methods for some common uses var core = { // array 'arr_indexOf' : getMethod(Array, 'indexOf') || compat.arr_indexOf, 'concat' : getMethod(Array, 'concat'), 'filter' : getMethod(Array, 'filter') || compat.filter, 'forEach' : getMethod(Array, 'forEach') || compat.forEach, 'map' : getMethod(Array, 'map') || compat.map, 'reduce' : getMethod(Array, 'reduce') || compat.reduce, 'slice' : getMethod(Array, 'slice'), // object 'hasOwnProperty' : getMethod(Object, 'hasOwnProperty'), 'toString' : getMethod(Object, 'toString'), }; var JSL = function JSL(selector, context) { return new JSL.fn.init(selector, context); }; // a simple class for dealing with event listener handlers var handlers = { stack : [], add : function (thisElement, type, fn) { this.stack.push({ element : thisElement, type : type, fn : fn }); }, get : function (thisElement, type) { var events = []; type = typeof type === 'string' ? type : '*'; JSL.each(this.stack, function (thisEventObj) { if (thisElement === thisEventObj.element) { if (type === '*' || thisEventObj.type === type) { events.push(thisEventObj); } } }); return events; }, remove : function (thisElement, type) { var handlerIndices = [], that = this; // find all the indices of what we need to remove JSL.each(handlers.get(thisElement, type), function (thisEventObj, index, array) { handlerIndices.push( core.arr_indexOf.call(that.stack, thisEventObj) ); }); // remove all the indices here, using a separate array of indices // we can't do this as we loop over the (stack) array itself, because // we would be removing values as they are being iterated through JSL.each(handlerIndices, function (thisIndex) { that.stack.splice(thisIndex, 1); }); } }; // Node.prototype.matchesSelector compat for vendor prefixes function matchesSelector(element, selector) { if (element && typeof selector === 'string') { if (typeof element.mozMatchesSelector === 'function') { // Mozilla return element.mozMatchesSelector(selector); } else if (typeof element.webkitMatchesSelector === 'function') { // Webkit return element.webkitMatchesSelector(selector); } else if (typeof element.oMatchesSelector === 'function') { // Opera return element.oMatchesSelector(selector); } else if (typeof element.msMatchesSelector === 'function') { // IE return element.msMatchesSelector(selector); } } return false; } // calls 'this' with the first parameter as the first argument function call(a) { return this(a); } function toCamelCase(string) { return string.replace(rHyphenated, function (fullMatch, firstGroup) { return firstGroup.toUpperCase(); }); } // walkTheDom by Douglas Crockford function walkTheDom(node, func) { func(node); node = node.firstChild; while (node) { walkTheDom(node, func); node = node.nextSibling; } } // can pluck a key out of an object function pluck(obj) { var subs = this.split('.'), ret = obj, i; for (i = 0; i < subs.length; i += 1) { ret = ret[ subs[i] ]; if (ret == null) { return ''; } } return ret; } function sum(curValue, nextValue) { return curValue + nextValue; } function sumInt(curValue, nextValue) { return parseInt(curValue, 10) + parseInt(nextValue, 10); } // internal function for throwing errors, so the user gets // some sort of hint as to why their operation failed function error(errorString) { if (typeof console !== 'undefined' && typeof console.error === 'function') { console.error(errorString); } return null; // always return null } // will copy an element and return a new copy with the same event listeners function cloneElement(thisElement) { var newElement = thisElement.cloneNode(true); // clone event listeners of element JSL.each(handlers.get(thisElement), function (thisEventObj) { JSL.addEvent(newElement, thisEventObj.type, thisEventObj.fn); }); return newElement; } function getEachElements(array, selector, key, type) { var newElementsArray = [], isValidSelector = typeof selector === 'string' && selector.trim() !== ''; JSL.each(array, function (currentElement) { while ( currentElement = currentElement[key] ) { // note: intentional assignment if (type > 0 ? currentElement.nodeType === type : true) { if ( isValidSelector === false || JSL(currentElement).filter(selector).exists ) { newElementsArray.push(currentElement); return; } } } }); return newElementsArray; } // this will take function doElementOperationOnEach(args, op) { var newElementsArray = [], newElement, passedElements = JSL.create.apply(JSL, args); if (this.exists) { if (JSL.typeOf(passedElements) === 'array') { this.each(function (thisElement) { JSL.each(passedElements, function (passedElement) { // clone the element var newElement = cloneElement(passedElement); // add the new elements to an array newElementsArray.push(newElement); // perform the passed operation on the element op(thisElement, newElement); }); }); } else { this.each(function (thisElement) { // clone the element var newElement = cloneElement(passedElements); // add the new elements to an array newElementsArray.push(newElement); // perform the passed operation on the element op(thisElement, newElement); }); } } return newElementsArray; } // define JSL's prototype, aka JSL.fn JSL.fn = JSL.prototype = { isJSL : true, constructor : JSL, length : 0, version : version, // similar to jQuery. JSL is just the init constructor init : function (selector, context) { var selectorStringValue = core.toString.call(selector), that = this, elems = []; switch (typeof selector) { case 'string': { // -- STRING -- if ( selector.match(rXpath) ) { // handle an XPath expression elems = JSL.xpath({expression : selector, type : 7, context : context}); } else if ( selector.match(rHTML) ) { // reserved for html code creation // not sure if I want to implement it } else if ( selector.match(rSelector) ) { if (JSL.typeOf(context) === 'array') { // handle an array being passed as the context return that.find.call(context, selector); } else if (typeof context === 'string') { // handle a selector being passsed as the context context = JSL(context); if (context.exists) { return JSL(selector, context[0]); } } else if (context != null && context.isJSL === true && context.exists) { // handle a JSL object being passsed as the context return JSL( selector, context[0] ); } else { // handle a regular element being passed as the context context = context != null && context.querySelectorAll ? context : document; elems = context.querySelectorAll(selector); } } break; } // --------------------------------------------------- case 'object': { // -- OBJECT -- if (selector != null) { if (selector.isJSL === true) { // handle a JSL object return selector; } else if ( core.hasOwnProperty.call(selector, 'length') ) { // handle an array-like object elems = selector; } else if ( selectorStringValue.match(rElementObject) || selectorStringValue.match(rWindowObject) ) { // handle a single element elems = [selector]; } } break; } // --------------------------------------------------- default: { // -- UNKNOWN -- if ( selectorStringValue.match(rElementObject) || selectorStringValue.match(rWindowObject) ) { // handle elements that are typeof === 'function' // e.g., object, applet, embed elems = [selector]; } } } // define the length property of our object wrapper that.length = elems.length; // bind the elements to array-like key:value pairs in our wrapper // e.g., this[0] ==> element JSL.each(elems, function (value, index) { that[index] = value; }); return that; }, // --- STARTING LINE FOR THE JSL WRAPPER METHODS add : function (selector, context) { var newElements = JSL(selector, context).raw(), allElements = core.concat.call(this.raw(), newElements); return JSL(allElements); }, addEvent : function (type, fn) { return this.each(function (thisElement) { JSL.addEvent(thisElement, type, fn); }); }, after : function () { var newElementsArray = doElementOperationOnEach.call(this, JSL.toArray(arguments), function (baseElement, newElement) { var parent = baseElement.parentNode, next = baseElement.nextSibling; if (parent) { if (next) { // add the newElement after the current element parent.insertBefore(newElement, next); } else { // nextSibling didn't exist. just append to its parent parent.appendChild(newElement); } } }); return JSL(newElementsArray); }, append : function () { var newElementsArray = doElementOperationOnEach.call(this, JSL.toArray(arguments), function (baseElement, newElement) { baseElement.appendChild(newElement); }); return JSL(newElementsArray); }, attribute : function (name, value) { var ret = '', valueIsValid = value != null; if ( typeof name === 'string' && this.exists ) { this.each(function (elem) { if (valueIsValid) { elem.setAttribute(name, value); } else { ret += elem.getAttribute(name) || ''; } }); } return valueIsValid ? this : ret; }, before : function () { var newElementsArray = doElementOperationOnEach.call(this, JSL.toArray(arguments), function (baseElement, newElement) { var parent = baseElement.parentNode; // add the newElement before the current element if (parent) { parent.insertBefore(newElement, baseElement); } }); return JSL(newElementsArray); }, center : function () { return this.each(function (thisElement) { thisElement = JSL(thisElement); thisElement.css('position', 'fixed'); thisElement.css('top', Math.floor( (window.innerHeight - thisElement.height) / 2 ) + 'px'); thisElement.css('left', Math.floor( (window.innerWidth - thisElement.width) / 2 ) + 'px'); }); }, clone : function () { var clonedElements = core.map.call(this, cloneElement); // variable for clarity return JSL(clonedElements); }, css : function (name, value) { if (typeof name === 'string') { // convert the hyphenated string to camel-case name = toCamelCase(name); if (typeof value === 'string') { return this.each(function (thisElement) { if (name in thisElement.style) { thisElement.style[name] = value; } }); } return core.map.call(this, pluck, 'style.' + name).join(''); } else { return error('.css() was not passed a string for the first argument.'); } }, each : function (fn, oThis) { if (this.exists) { JSL.each(this, fn, oThis); } return this; }, get exists() { return this.length > 0 && this[0] != null; }, filter : function (selector) { var newElementsArray = []; if (typeof selector === 'string') { this.each(function (thisElement) { if ( matchesSelector(thisElement, selector) ) { newElementsArray.push(thisElement); } }); } // returns an empty JSL object if no elements are matched return JSL(newElementsArray); }, find : function (selector) { var arrayOfMatchesArrays = core.map.call(this, function (thisElement) { var matches = thisElement.querySelectorAll(selector); return JSL.toArray(matches); }); var singleArrayOfMatches = arrayOfMatchesArrays.length > 0 ? core.reduce.call(arrayOfMatchesArrays, function (a, b) { return core.concat.call(a, b); }) : []; return JSL(singleArrayOfMatches); }, first : function () { return this.get(0); }, get : function (index) { index = index === 'first' ? 0 : index === 'last' ? -1 : parseInt(index, 10); if ( !isNaN(index) ) { return JSL( index < 0 ? this[this.length + index] : this[index] ); } return JSL.toArray(this); }, get height() { var arrayOfElemHeights = core.map.call(this, pluck, 'offsetHeight'); return core.reduce.call(arrayOfElemHeights, sum); }, has : function (selector) { var newElementsArray = []; if ( typeof selector === 'string' && selector.match(rSelector) ) { this.each(function (thisElement) { if ( JSL(selector, thisElement).exists ) { newElementsArray.push(thisElement); } }); } return JSL(newElementsArray); }, hide : function () { return this.css('display', 'none'); }, /* get inView(passedContainer) { var isInView = false; this.each(function (thisElement) { var container = passedContainer || thisElement.parentNode; var visible = !!( (container.scrollTop + container.offsetHeight) >= thisElement.offsetTop && (container.scrollTop - thisElement.offsetHeight) <= thisElement.offsetTop ); if (visible) { isInView = true; return 'stop'; } }); return isInView; }, */ is : function (selector) { for (var i = 0; i < this.length; i += 1) { if ( matchesSelector(this[i], selector) ) { return true; } } return false; }, isnt : function (selector) { return !this.is(selector); }, last : function (selector) { return this.get(-1); }, next : function (selector) { return JSL( getEachElements(this, selector, 'nextSibling', 1) ); }, not : function (selector) { var newElementsArray = []; if ( typeof selector === 'string' && selector.match(rSelector) ) { this.each(function (thisElement) { if ( JSL(thisElement).isnt(selector) ) { newElementsArray.push(thisElement); } }); } return JSL(newElementsArray); }, parent : function (selector) { return JSL( getEachElements(this, selector, 'parentNode', 1) ); }, prepend : function () { var newElementsArray = doElementOperationOnEach.call(this, JSL.toArray(arguments), function (baseElement, newElement) { var firstChild = baseElement.firstChild; if (firstChild) { baseElement.insertBefore(newElement, firstChild); } }); return JSL(newElementsArray); }, prev : function (selector) { return JSL( getEachElements(this, selector, 'previousSibling', 1) ); }, prop : function (name, value) { var valueIsValid = value != null, ret; if (typeof name === 'string' && this.exists) { this.each(function (thisElement) { if (valueIsValid) { thisElement[name] = value; } else { if (typeof ret === 'undefined') { ret = thisElement[name]; } else { ret += thisElement[name]; } } }); } return valueIsValid ? this : ret; }, raw : function () { return core.slice.call(this, 0); }, remove : function () { return this.each(function (element) { var parent = element.parentNode; if (element && parent) { parent.removeChild(element); } }); }, removeAttribute : function (attributeName) { if (typeof attributeName === 'string') { return this.each(function (thisElement) { thisElement.removeAttribute(attributeName); }); } else { return error('.removeAttribute() was not passed a string.'); } }, removeEvent : function (type) { if (typeof type === 'string') { return this.each(function (thisElement) { JSL.removeEvent(thisElement, type); }); } else { return error('.removeEvent() was not passed a string.'); } }, replace : function () { var newElementsArray = doElementOperationOnEach.call(this, JSL.toArray(arguments), function (baseElement, newElement) { var parent = baseElement.parentNode; if (parent) { parent.replaceChild(newElement, baseElement); } }); return JSL(newElementsArray); }, show : function (value) { value = typeof value === 'string' ? value : 'inline'; return this.css('display', value); }, text : function (passedText, append) { // handle setting text if (typeof passedText === 'string') { if (append !== true) { this.each(function (thisElement) { JSL('.//text()', thisElement).each(function (textNode) { textNode.data = ''; }); }); } this.append('text', passedText); return this; } // handle getting text return core.reduce.call(this, function (curValue, nextElement) { return curValue + nextElement.textContent; }, ''); }, toggle : function () { return this.each(function (thisElement) { thisElement = JSL(thisElement); if (thisElement.visible) { thisElement.hide(); } else { thisElement.show(); } }); }, value : function (passedValue) { var elem = this[0], tagName = elem && elem.tagName || '', selectedOptions = [], rInputTypeBlacklist = /button|checkbox|file|image|radio|reset|submit/, passedValueType = JSL.typeOf(passedValue); if (passedValue == null) { // no arguments were passed, return a value if (tagName === 'SELECT') { if ( elem.hasAttribute('multiple') ) { JSL.each(elem.options, function (thisOption) { if (thisOption.selected) { selectedOptions.push(thisOption.value); } }); return selectedOptions; } else { return elem.options[elem.selectedIndex].value; } } else if ( tagName === 'INPUT' && !elem.type.match(rInputTypeBlacklist) ) { return elem.value; } if (tagName === 'TEXTAREA') { return elem.value; } } else { // an argument was passed, set the value on each element return this.each(function (thisElement) { var tagName = thisElement.tagName; if (tagName === 'SELECT') { if (thisElement.hasAttribute('multiple') && passedValueType === 'array') { JSL.each(thisElement.options, function (thisOption) { JSL.each(passedValue, function (thisPassedValue) { if (thisOption.value == thisPassedValue) { thisOption.selected = true; return 'stop'; } else { thisOption.selected = false; } }); }); } else { JSL.each(thisElement.options, function (thisOption) { thisOption.selected = thisOption.value == passedValue; }); } } else if (tagName === 'INPUT') { if ( !thisElement.type.match(rInputTypeBlacklist) ) { thisElement.value = passedValue; } else if (thisElement.type === 'checkbox' || thisElement.type === 'radio') { if (passedValueType === 'array') { JSL.each(passedValue, function (thisPassedValue) { if (thisElement.value == thisPassedValue) { thisElement.checked = true; return 'stop'; } else { thisElement.checked = false; } }); } else if (thisElement.value == passedValue) { thisElement.checked = true; } } } else if (tagName === 'TEXTAREA') { thisElement.value = passedValue; } }); } return null; }, get visible() { return Math.max(this.width, this.height) > 0; }, get width() { var arrayOfElemHeights = core.map.call(this, pluck, 'offsetWidth'); return core.reduce.call(arrayOfElemHeights, sum); }, }; // give the init function the JSL prototype for later instantiation JSL.fn.init.prototype = JSL.fn; // extend method. can extend any object it's run upon JSL.fn.extend = JSL.extend = function (obj) { var name, copy; for (name in obj) { copy = obj[name]; if ( !core.hasOwnProperty.call(this, name) && typeof copy !== 'undefined' ) { this[name] = copy; } } }; // --- STARTLING LINE FOR THE DIRECT JSL METHODS JSL.extend({ addEvent : function addEvent(thisElement, type, fn) { if (thisElement != null && typeof type === 'string' && typeof fn === 'function') { if (typeof thisElement.addEventListener === 'function') { thisElement.addEventListener(type, fn, false); } else if (typeof thisElement.attachEvent === 'function') { type = 'on' + type; thisElement.attachEvent(type, fn); } else { return; } handlers.add(thisElement, type, fn); } }, addScript : function addScript(contents, id, node) { var newElement = document.createElement('script'); newElement.id = id || ( 'jsl-script-' + JSL.random(999) ); newElement.innerHTML = contents; node = node || document.head || document.querySelector('html > head'); node.appendChild(newElement); return { remove : function () { node.removeChild(newElement); } }; }, addStyle : function addStyle(css, id, node) { id = id || ( 'jsl-style-' + JSL.random(999) ); node = node || document.head || document.querySelector('html > head'); if (node) { node.appendChild( JSL.create('style', {id : id, type : 'text/css'}, [ JSL.create('text', css) ] ) ); } }, alias : function alias(newAlias) { if (typeof newAlias === 'string' && newAlias.match(rValidVarname) && typeof window[newAlias] === 'undefined') { window[newAlias] = JSL; } }, clearInterval : function clearInterval(index) { if (typeof index === 'number' && index < intervals.length) { window.clearTimeout( intervals[index] ); intervals[index] = null; } }, create : function create(elementName, descObj, kidsArray) { var argsLength = arguments.length, typeValue, prop, val, HTMLholder, ret, i; if (argsLength === 2 && elementName === 'text' && typeof descObj === 'string') { // handle text node creation return document.createTextNode(descObj); } else if ( argsLength === 1 && typeof elementName === 'string' && elementName.match(rHTML) ) { // handle HTML strings // take the HTML string and put it inside a div HTMLholder = document.createElement('div'); HTMLholder.innerHTML = elementName; // add each childNode to an array to return ret = []; ret.push.apply(ret, HTMLholder.childNodes); return ret.length > 0 ? (ret.length === 1 ? ret[0] : ret) : null; } else if (argsLength > 1 && typeof elementName === 'string' && typeof descObj === 'object') { // handle the normal element name and descriptor object ret = document.createElement(elementName + ''); for (prop in descObj) { if ( core.hasOwnProperty.call(descObj, prop) ) { val = descObj[prop]; if (prop.indexOf('on') === 0 && typeof val === 'function') { JSL.addEvent(ret, prop.substring(2), val); } else if ( prop !== 'style' && prop !== 'class' && prop in ret && typeof ret[prop] !== 'undefined' ) { ret[prop] = val; } else { ret.setAttribute(prop, val); } } } if (JSL.typeOf(kidsArray) === 'array') { JSL.each(kidsArray, function (kid) { var val, item, i; if (typeof kid === 'string') { val = JSL.create(kid) if (JSL.typeOf(val) === 'array') { for (i = 0; i < val.length; i += 1) { ret.appendChild( val[i] ); } } else if (JSL.typeOf(kid) === 'element') { ret.appendChild(kid); } } else if (JSL.typeOf(kid) === 'element') { ret.appendChild(kid); } }); } return ret; } else if (argsLength === 1 && JSL.typeOf(elementName) === 'element') { // handle an element return elementName; } }, each : function each(passedArray, fn, oThis) { var isOthisUndefined = typeof oThis !== 'undefined', index, len, otherThis, value; for (index = 0; index < passedArray.length; index += 1) { value = passedArray[index]; otherThis = isOthisUndefined ? oThis : value; if (fn.call(otherThis, value, index, passedArray) === 'stop') { break; } } }, loop : function loop(maxIterations, fn) { var args = JSL.toArray(arguments), i; if (typeof maxIterations === 'number' && maxIterations > 0 && typeof fn === 'function') { args = args.slice(2); for (i = 0; i < maxIterations; i += 1) { fn.apply(null, args); } } }, random : function random(maxInteger, minInteger) { var rand = -1; while (rand < 0 || rand > maxInteger || rand < minInteger) { rand = Math.floor( Math.random() * maxInteger ) + Math.round( Math.random() ); } return rand; }, removeEvent : function removeEvent(thisElement, type) { JSL.each(handlers.get(thisElement, type), function (thisEventObj) { if (typeof thisElement.removeEventListener === 'function') { thisEventObj.element.removeEventListener(thisEventObj.type, thisEventObj.fn, false); } else if (typeof thisElement.detachEvent === 'function') { type = 'on' + type; thisEventObj.element.detachEvent(thisEventObj.type, thisEventObj.fn); } handlers.remove(thisElement, type); }); }, runAt : function runAt(state, func, oThis) { var args = JSL.toArray(arguments), intv, // compose a list of the 4 states, to use .indexOf() upon later states = ['uninitialized', 'loading', 'interactive', 'complete'], // in-case they pass [start/end] instead of [loading/complete] state = state.replace('start', 'loading').replace('end', 'complete'); // this will run their function with the specified arguments, if any, // and a custom 'this' value, if specified function runFunc() { func.apply( oThis, args.slice(3) ); } // this will run on each state change if the specified state is // not achieved yet. it will run their function when it is achieved function checkState() { if (document.readyState === state) { runFunc(); JSL.clearInterval(intv); } } if ( core.arr_indexOf.call(states, state) <= core.arr_indexOf.call(states, document.readyState) ) { // we are at, or have missed, our desired state // run the specified function runFunc(); } else { intv = JSL.setInterval(checkState, 200); } }, setInterval : function setInterval(func, delay) { var index = intervals.length, delay_orig = delay, count = 1, startTime; function doRe(func, delay) { return window.setTimeout(function () { // drift accomodation var difference = ( new Date().getTime() ) - startTime, correctTime = delay_orig * count, drift = difference - correctTime; // execute the function before setting a new timeout func.call(null); // fix for when a timeout takes longer than double the original delay time to execute if (drift > delay_orig) { drift = delay_orig; } // save the reference of the new timeout in our 'intervals' stack if (intervals[index] !== null) { intervals[index] = doRe(func, delay_orig - drift); } count += 1; }, delay); } startTime = new Date().getTime(); intervals[index] = doRe(func, delay_orig); return index; }, toArray : function toArray(arr) { var newArr = [], // new array to store the values into len = arr.length || arr.snapshotLength, item, i; if (typeof len === 'number' && len > 0) { if (typeof arr.snapshotItem === 'function') { for (i = 0; ( item = arr.snapshotItem(i) ); i += 1) { newArr.push(item); } } else { // if the specified 'list' is array-like, use slice on it // to convert it to an array newArr = core.slice.call(arr, 0); } } return newArr; }, toString : function toString(item) { var key, value, values = []; function stringifyValue(val) { var typeOfVal = JSL.typeOf(val), toStringValue = core.toString.call(val); if (typeOfVal === 'null' || typeOfVal === 'undefined') { val = typeOfVal; } else if (typeof val === 'string') { if (val.length > 15) { // truncate strings longer than 15 characters val = '"' + val.substring(0, 12) + '"...'; } else { val = '"' + val + '"'; } } else if (typeOfVal === 'function') { val = val.toString().substring(0, 20); } else if (typeOfVal !== 'number' && typeOfVal !== 'boolean') { val = toStringValue; } return val; } switch( JSL.typeOf(item) ) { case 'object': { for (key in item) { if ( item.hasOwnProperty(key) ) { value = stringifyValue( item[key] ); values.push( '"' + key + '" : ' + value ); } } return '{\n ' + values.join(',\n ') + '\n}'; } // -------------------------------------- case 'array': { item = core.map.call(item, function (thisValue) { return stringifyValue(thisValue); }); return '[\n ' + item.join(',\n ') + '\n]'; } // -------------------------------------- case 'string': { return '"' + item + '"'; } // -------------------------------------- case 'number': { item = parseInt(item, 10); if ( isNaN(item) ) { // no ternary operator, for clarity return 'NaN'; } else { return item.toString(); } } // -------------------------------------- case 'regexp': { if (item.toString().length <= 20) { item.toString(); } else { return '[object RegExp]'; } } // -------------------------------------- case 'function': case 'boolean': { return item.toString(); } // -------------------------------------- case 'null': { return 'null'; } // -------------------------------------- case 'undefined': { return 'undefined'; } // -------------------------------------- default: { return core.toString.call(item); } } }, // typeOf by Douglas Crockford. modified by JoeSimmons typeOf : function typeOf(value) { var s = typeof value, ostr = core.toString.call(value); if (s === 'object' || s === 'function') { if (value) { if (ostr === '[object Array]') { s = 'array'; } else if ( ostr === '[object Text]' || ostr.match(rElementObject) ) { s = 'element'; } else if (ostr === '[object HTMLCollection]') { s = 'collection'; } else if (ostr === '[object NodeList]') { s = 'nodelist'; } else if (ostr === '[object Arguments]') { s = 'arguments'; } else if (ostr === '[object RegExp]') { s = 'regexp'; } } else { s = 'null'; } } return s; }, waitFor : function waitFor(info) { var verifier = function () { return true; }, done = info ? info.done : null, i, selector, context, waitForInterval; if (info == null || typeof done !== 'function') { return; } switch ( JSL.typeOf(info.selector) ) { case 'string': case 'element': case 'array': { selector = info.selector; break; } default: { return error('Invalid selector passed to JSL.waitFor()'); } } switch ( JSL.typeOf(info.context) ) { case 'string': case 'element': case 'array': { context = info.context; } } if (typeof info.verifier === 'function' && info.verifier.toString().indexOf('return ') !== -1) { verifier = info.verifier; } function clear() { JSL.clearInterval(waitForInterval); } function check() { var elem = JSL(selector, context); if (elem.exists && verifier(elem) === true) { done(elem); return clear(); } if (i >= 150) { // check for 30 seconds max return clear(); } i += 1; } waitForInterval = JSL.setInterval(check, 200); }, xpath : function xpath(obj) { var type = obj.type || 7, types = { '1' : 'numberValue', '2' : 'stringValue', '3' : 'booleanValue', '8' : 'singleNodeValue', '9' : 'singleNodeValue' }, expression = obj.expression, context = obj.context || document, doc = document, xp; if (typeof context.evaluate === 'function') { doc = context; } else if (typeof context.ownerDocument.evaluate === 'function') { doc = context.ownerDocument; } xp = doc.evaluate(expression, context, null, type, null); if (!expression) { error('An expression must be supplied for JSL.xpath()'); return null; } if ( types[type] ) { return xp[ types[ type ] ]; } else { return JSL.toArray(xp); } } }); // assign JSL to the window object window.JSL = window._J = JSL; // just for testing purposes //unsafeWindow.JSL = unsafeWindow._J = JSL; }(window)); /* // JSL test button // use it to test code on user click (non-automatic) (function () { var mo = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { var target = mutation.target; if (mutation.attributeName === 'value' && target.value !== 'Run JSL test') { target.value = 'Run JSL test'; } }); }); JSL(document.body).append( 'input', { id : 'jsl_user_test', type : 'button', value : 'Run JSL test', style : 'display: block; position: fixed; top: 4px; right: 4px; z-index: 999999; padding: 2px 14px; font-size: 11pt; font-family: Arial, Verdana;', onclick : function () { // ---- ENTER ONCLICK CODE HERE ---- window.setTimeout(function () { JSL(document.body).append('
I\'m a JSL.waitFor() test DIV!
'); }, 1500); JSL.waitFor({ selector : '#waitForTest', done : function (elem) { alert('#waitForTest is loaded!'); } }); // --------------------------------- } } ); mo.observe( JSL('#jsl_user_test')[0], { attributes : true } ); }()); */