Skip to content

Instantly share code, notes, and snippets.

@Olical
Created April 7, 2011 12:48
Show Gist options
  • Save Olical/907707 to your computer and use it in GitHub Desktop.
Save Olical/907707 to your computer and use it in GitHub Desktop.
A pre release of Spark. This is for testing the selector speed.
/**
* @preserve Spark JavaScript Library v3.0.0
* http://sparkjs.co.uk/
*
* Copyright 2011, Oliver Caldwell
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://sparkjs.co.uk/licence.html
*/
(function() {
// Create the object
function Spark(){}
/**
* Adds a variable to Spark's prototype
*
* @param {String} name Name you wish to add your variable under
* @param toAdd Variable you wish to add
*/
Spark.prototype.extend = function(name, toAdd) {
// Add the object
Spark.prototype[name] = toAdd;
};
/**
* Create a clone of the object. This should be done when anything is being stored in it for chaining.
* Otherwise added variables will be there for ever.
* This way they only exist within that chain
*
* @returns {Object} The copy of the object
*/
Spark.prototype.clone = function() {
return new Spark();
};
// Expose the object
window.Spark = new Spark();
// Set up the alias for the find function
window.$ = function(parameters, context) {
return window.Spark.find(parameters, context);
};
}());
/**
* Runs the specified function when the DOM is ready
*
* @param {Function} fn Function to be run when the DOM is ready
*/
Spark.extend('ready', function(fn) {
// Check if we can use addEventListener
if(window.addEventListener) {
// For all browsers except IE
document.addEventListener('DOMContentLoaded', fn, false);
}
else {
// For IE
(function(){
// Create the custom tag
var tempNode = document.createElement('document:ready');
try {
// See if it throws errors until after it is ready
tempNode.doScroll('left');
// Call the function
fn();
}
catch(err) {
setTimeout(arguments.callee, 0);
}
})();
}
});
Spark.extend('ajax', {
/**
* Selects what AJAX object to use
*
* @returns {Object} The correct AJAX object for this browser
*/
initialise: function() {
// Pass back the correct object
return (typeof XMLHttpRequest === 'undefined') ?
new ActiveXObject('Microsoft.XMLHTTP') :
new XMLHttpRequest();
},
/**
* Turns an object of parameters into a string
*
* @param {Object} parameters An object of parameters
* @returns {String} The combined string, ready to be appended to a filename
*/
buildParameterString: function(parameters) {
// Initialise any required variables
var p = null,
built = '';
// Loop through the parameters appending them to the filename
for(p in parameters) {
// Make sure it is not a prototype
if(parameters.hasOwnProperty(p) === true) {
// Add the parameter
built += escape(p) + '=' + escape(parameters[p]) + '&';
}
}
// Remove the trailing ampersand and return the escaped string
return built.slice(0, built.length - 1);
},
/**
* Pass the data to the callback when the request is complete
*
* @param {Object} req The AJAX request object
* @param {Function} callback The callback function that the data should be passed to
*/
handleCallback: function(req, callback) {
// Listen for the change in state
req.onreadystatechange = function() {
// Check if it is finished
if(req.readyState === 4) {
// Check the status
if(req.status === 200) {
// It's all good, Pass the data to the callback
callback(req.responseText);
}
else {
// There was an error so pass false to the callback
callback(false);
}
}
};
},
/**
* Perform a get request with optional parameters either syncronously or asyncronously
*
* @param {String} file Path of the target file
* @param {Object} parameters The arguments you wish to pass to the file
* @param {Function} callback If set, the call become asyncronous and the data is passed to it on completion, it will pass false if it failed
* @returns {String|Boolean} The data retrived from the file if it is a syncronous call, returns false if it failed
*/
get: function(file, parameters, callback) {
// Set up the AJAX object
var req = this.initialise();
// Make sure parameters is an object
if(typeof parameters === 'object') {
// Add the parameters to the file name
file += '?' + this.buildParameterString(parameters);
}
// Check for the callback
if(typeof callback === 'function') {
// It exists, so pass it to the callback handling function
this.handleCallback(req, callback);
}
// Open the request, if the callback is set then make it asyncronous
req.open('GET', file, (typeof callback === 'function') ? true : false);
// Send the request
req.send();
// Check if the callback has not been passed
if(typeof callback === 'undefined') {
if(req.status === 200) {
// Just return the content because it was a syncronous request
return req.responseText;
}
else {
// There was an error so return false
return false;
}
}
},
/**
* Perform a post request with optional parameters either syncronously or asyncronously
*
* @param {String} file Path of the target file
* @param {Object} parameters The arguments you wish to pass to the file
* @param {Function} callback If set, the call become asyncronous and the data is passed to it on completion, it will pass false if it failed
* @returns {String|Boolean} The data retrived from the file if it is a syncronous call, returns false if it failed
*/
post: function(file, parameters, callback) {
// Set up the AJAX object
var req = this.initialise();
// Check for the callback
if(typeof callback === 'function') {
// It exists, so pass it to the callback handling function
this.handleCallback(req, callback);
}
// Open the request, if the callback is set then make it asyncronous
req.open('POST', file, (typeof callback === 'function') ? true : false);
// Set the headers
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
// Only send the data if it is set
if(typeof parameters === 'object') {
req.send(this.buildParameterString(parameters));
}
else {
req.send();
}
// Check if the callback has not been passed
if(typeof callback === 'undefined') {
if(req.status === 200) {
// Just return the content because it was a syncronous request
return req.responseText;
}
else {
// There was an error so return false
return false;
}
}
}
});
/**
* Gets or sets cookies
*
* @param {String} name The name of the cookie you wish to get or set
* @param {String} content If passed the cookie will be set with this as it's content
* @param {Number} duration The amount of miliseconds you wish the cookie to last for, if not set then it will last for the session
* @returns {String} The content of the cookie whos name you specified
*/
Spark.extend('cookie', function(name, content, duration) {
// Initialise any required variables
var cookies = document.cookie.split(';'),
i = null,
cookie = null,
date = new Date();
// Check if we need to get or set
if(typeof content === 'undefined') {
// Get the cookie
// Loop through all the cookies
for(i = 0; i < cookies.length; i++) {
// Grab the current cookie and trim any whitespace
cookie = cookies[i].replace(/^\s+/g, '');
// Check if the cookie contains the name
if(cookie.indexOf(name + '=') === 0) {
return cookie.substring(name.length + 1, cookie.length);
}
}
// Return false if we did not find it
return false;
}
else {
// Set the cookie
// Check for a passed duration
if(typeof duration !== 'undefined') {
// Add on the duration
date.setTime(date.getTime() + duration);
expires = '; expires=' + date.toGMTString();
}
else {
// Otherwise set the expires to nothing
expires = '';
}
// Set the cookie
document.cookie = name + '=' + escape(content) + expires + '; path=/';
}
});
/**
* Find elements that match the specified parameters
*
* @param {Object} parameters The criteria the element must meet to be selected
* @param {Element} context The place you wish to start the search from, defaults to document
* @returns {Object} Returns the Spark object to allow chaining
*/
Spark.extend('find', function(parameters, context) {
// Initialise any required variables
var found = [],
filtered = [],
ctx = (typeof context !== 'undefined') ? context : document,
i = null,
e = null,
tempFound = null,
classes = null,
built = this.clone();
// Check if parameters is not an actual search object
if(typeof parameters === 'object') {
if(typeof parameters.nodeName === 'string') {
// They passed an element, this needs to be adopted into the chain
built[0] = parameters;
built.elements = [parameters];
built.length = 1;
// Return the object with the adopted value
return built;
}
else if(parameters instanceof Array) {
// They passed an array, this needs to be adopted into the chain
for(i = 0; i < parameters.length; i++) {
built[i] = parameters[i];
}
built.length = parameters.length;
// Return the object with the adopted values
return built;
}
}
/**
* Removes duplicate values from an array
*
* @param {Array} target The target array to have duplicates remove from
* @returns {Array} The cleaned array with no duplicate values
*/
function unique(target) {
var a = [],
l = target.length,
j = null,
i = null;
for(i = 0; i < l; i++) {
for(j = i + 1; j < l; j++) {
if(target[i] === target[j]) {
j = ++i;
}
}
a.push(target[i]);
}
return a;
}
/**
* Takes a string, breaks it down into its components and uses them to run the find function
*
* @param {String} selector The selector string
* @param {Object} offset The instance of Spark already containing elements
* @returns {Object} An instance of Spark containing all of the found elements
*/
function parseSelector(selector, offset) {
// Initialise any required variables
var selectors = selector.split(/\s*,\s*/g),
paths = null,
built = Spark.clone(),
i = null,
p = null,
path = null,
found = [],
parameters = null,
tempFound = null,
regexs = [
'^\\[([a-z_][\\-a-z0-9_]+)=[\'"](.*)[\'"]\\]', // Attribute comparison
'^\\[([a-z_][\\-a-z0-9_]+)\\]', // Has attribute
'^([a-z0-9*]+)', // Tag name comparison
'^#([a-z][a-z0-9-_]*)', // ID comparison
'^\\.(-?[_a-z]+[_a-z0-9\\-]*)', // Class comparison
'^\\[([a-z_][\\-a-z0-9_]+)~=[\'"](.*)[\'"]\\]', // Whitespace seperated attribute
'^\\[([a-z_][\\-a-z0-9_]+)\\|=[\'"](.*)[\'"]\\]', // Beginning of attribute with optional hyphen after
'^:first-child' // Element must be the first child of it's parent
],
finders = [];
// Set up all the RegExps
for(i = 0; i < regexs.length; i++) {
finders.push({
search: new RegExp(regexs[i], 'i'),
remove: new RegExp(regexs[i] + '.*', 'i')
});
}
// Loop through the selectors
for(i = 0; i < selectors.length; i++) {
// Grab the paths
paths = selectors[i].replace(/(>|\+)/g, " $1 ").replace(/\s+(>|\+)\s+/g, " $1").split(/\s+/g);
// Reset the parameters
parameters = [];
// Loop through all the paths
for(p = 0; p < paths.length; p++) {
// Grab the path
path = paths[p];
// Add the new object
parameters.push({});
// Keep looping until the string is gone
while(path.length > 0) {
// Check if it is a direct child selector or direct sibling
if(path.indexOf('>') === 0) {
parameters[p].child = true;
path = path.substr(1);
}
else if(path.indexOf('+') === 0) {
parameters[p].sibling = true;
path = path.substr(1);
}
// Do the checks
if(path.match(finders[5].search)) {
// Check if element has whitespace seperated attribute
// Make sure the object exists
if(typeof parameters[p].whiteSpaceAttribute === 'undefined') {
parameters[p].whiteSpaceAttribute = {};
}
// Add the check
parameters[p].whiteSpaceAttribute[path.replace(finders[5].remove, "$1")] = path.replace(finders[5].remove, "$2");
// Remove the selection
path = path.replace(finders[5].search, '');
}
else if(path.match(finders[6].search)) {
// Check if element attribute begins with a string, potentially followed by a hyphen
// Make sure the object exists
if(typeof parameters[p].hyphenAttribute === 'undefined') {
parameters[p].hyphenAttribute = {};
}
// Add the check
parameters[p].hyphenAttribute[path.replace(finders[6].remove, "$1")] = path.replace(finders[6].remove, "$2");
// Remove the selection
path = path.replace(finders[6].search, '');
}
else if(path.match(finders[0].search)) {
// Check if element has attribute
// Make sure the object exists
if(typeof parameters[p].attribute === 'undefined') {
parameters[p].attribute = {};
}
// Add the check
parameters[p].attribute[path.replace(finders[0].remove, "$1")] = path.replace(finders[0].remove, "$2");
// Remove the selection
path = path.replace(finders[0].search, '');
}
else if(path.match(finders[1].search)) {
// Check if element has attribute
// Make sure the object exists
if(typeof parameters[p].attribute === 'undefined') {
parameters[p].attribute = {};
}
// Add the check
parameters[p].attribute[path.replace(finders[1].remove, "$1")] = true;
// Remove the selection
path = path.replace(finders[1].search, '');
}
else if(path.match(finders[2].search)) {
// Element
if(typeof parameters[p].tag === 'undefined') {
parameters[p].tag = path.replace(finders[2].remove, "$1");
}
else {
if(typeof parameters[p].tag === 'string') {
parameters[p].tag = [parameters[p].tag];
}
parameters[p].tag.push(path.replace(finders[2].remove, "$1"));
}
// Remove the selection
path = path.replace(finders[2].search, '');
}
else if(path.match(finders[3].search)) {
// ID
if(typeof parameters[p].id === 'undefined') {
parameters[p].id = path.replace(finders[3].remove, "$1");
}
else {
if(typeof parameters[p].id === 'string') {
parameters[p].id = [parameters[p].id];
}
parameters[p].id.push(path.replace(finders[3].remove, "$1"));
}
// Remove the selection
path = path.replace(finders[3].search, '');
}
else if(path.match(finders[4].search)) {
// Class
if(typeof parameters[p].classes === 'undefined') {
parameters[p].classes = path.replace(finders[4].remove, "$1");
}
else {
if(typeof parameters[p].classes === 'string') {
parameters[p].classes = [parameters[p].classes];
}
parameters[p].classes.push(path.replace(finders[4].remove, "$1"));
}
// Remove the selection
path = path.replace(finders[4].search, '');
}
else if(path.match(finders[7].search)) {
// First child
parameters[p].first = true;
// Remove the selection
path = path.replace(finders[7].search, '');
}
else {
// If it does not match anything return false to stop endless loops
return false;
}
}
}
// So now we have an array of parameter objects
// Set up temp found to search with
tempFound = offset;
// Loop through all of the parameter objects
for(p = 0; p < parameters.length; p++) {
// Now do the search into tempFound
tempFound = tempFound.find(parameters[p]);
}
// When done concat these results to the found array
found = found.concat(tempFound.elements);
}
// Clean the array
found = unique(found);
// Loop through the found adding them to the object
for(i = 0; i < found.length; i++) {
built[i] = found[i];
}
// Add the array version
built.elements = found;
// Add the length
built.length = found.length;
// Return the built object
return built;
}
/**
* Compare the value of a tag or ID to an array or string of comparisons
*
* @param {String|Array} value Either an ID, an array of classes or a tag name to compare
* @param {String|Array} compare The string or array of values to check against
* @param {Boolean} tag If true, the values are converted to uppercase on comparison
* @param {Boolean} space If true, the values are whitespace seperated before comparison
* @param {Boolean} hyphen If true, the value must exactly match or start with followed by a hyphen
* @returns {Boolean} Returns true if it can not be compared or if they match
*/
function compareValue(value, compare, tag, space, hyphen) {
// Initialise any required variables
var i = null,
e = null,
classes = ((value instanceof Array) ? value.join(' ') : false);
// Check what type of search we need to do
if(typeof compare === 'string') {
// Compare the two strings
if(classes) {
if(classes.match(new RegExp('(^| )' + compare + '($| )', 'g'))) {
return true;
}
else {
return false;
}
}
else {
if(value === ((tag) ? compare.toUpperCase() : compare)) {
return true;
}
else {
if(tag && compare === '*') {
return true;
}
return false;
}
}
}
else if(compare instanceof Array) {
// Loop through and compare
for(i = 0; i < compare.length; i++) {
if(classes) {
if(classes.match(new RegExp('(^| )' + compare[i] + '($| )', 'g'))) {
return true;
}
}
else {
if(value === ((tag) ? compare[i].toUpperCase() : compare[i])) {
return true;
}
}
}
// Default to returning false
return false;
}
else if(typeof compare === 'object') {
// It is an object of attributes, loop through and compare
// If any do not match, return false
for(i in compare) {
// Make sure it has the property and so does the object
if(compare.hasOwnProperty(i)) {
// If it is true then all we have to do is see if it exists
if(compare[i] === true) {
// So now if it has the attribute then continue
if(value.getAttribute(i) === null) {
// It doesnt
return false;
}
}
else {
// So now we check if it has the value again, if it does we compare
if(value.getAttribute(i) !== null) {
// It does, check what it is
if(typeof compare[i] === 'string') {
if(hyphen) {
if(value.getAttribute(i) !== compare[i] && value.getAttribute(i).indexOf(compare[i] + '-') !== 0) {
return false;
}
}
else {
if(((space) ? value.getAttribute(i).replace(/\s+/g, '').split('').join(' ') : value.getAttribute(i)) !== ((space) ? compare[i].replace(/\s+/g, '').split('').join(' ') : compare[i])) {
return false;
}
}
}
else if(compare[i] instanceof Array) {
// It is an or statement, so we need do a special check
for(e = 0; e < compare[i].length; e++) {
if(hyphen) {
if(value.getAttribute(i) !== compare[i][e] && value.getAttribute(i).indexOf(compare[i][e] + '-') !== 0) {
return false;
}
}
else {
if(((space) ? value.getAttribute(i).replace(/\s+/g, '').split('').join(' ') : value.getAttribute(i))!==((space) ? compare[i][e].replace(/\s+/g, '').split('').join(' ') : compare[i][e])) {
return false;
}
}
}
}
}
else {
// It doesnt
return false;
}
}
}
}
// Default to returning true
return true;
}
else {
// Default to returning true
return true;
}
}
/**
* Find elements from a context
*
* @param {String} tag The name of the tag you wish to find
* @param {Object} ctx The context you wish to search in
* @param {Boolean} child Only find direct children
* @param {Boolean} sibling Only find the next sibling element
* @param {Boolean} first Only find elements that are the first child
* @returns {Array} Returns an array of the found elements
*/
function findElements(tag, ctx, child, sibling, first) {
// Initialise any required variables
var tempFound = null,
found = [];
// Check what the tag filter is
if(typeof tag === 'string') {
// Perform a basic tag search
tempFound = (sibling === true) ? ctx.parentNode.getElementsByTagName(tag) : ctx.getElementsByTagName(tag);
// Loop through the elements
for(e = 0; e < tempFound.length; e++) {
// Push the found element to found
// Check if it needs to be the first child
if(first === true && tempFound[e] === (tempFound[e].parentNode.firstElementChild || tempFound[e].parentNode.firstChild)) {
// And check if it is a direct child if we need to
if(child === true && tempFound[e].parentNode === ctx) {
found.push(tempFound[e]);
}
else if(sibling === true && (tempFound[e] === ctx.nextElementSibling || tempFound[e] === ctx.nextSibling)) {
found.push(tempFound[e]);
}
else if(!child && !sibling) {
found.push(tempFound[e]);
}
}
else if(!first) {
// And check if it is a direct child if we need to
if(child === true && tempFound[e].parentNode === ctx) {
found.push(tempFound[e]);
}
else if(sibling === true && (tempFound[e] === ctx.nextElementSibling || tempFound[e] === ctx.nextSibling)) {
found.push(tempFound[e]);
}
else if(!child && !sibling) {
found.push(tempFound[e]);
}
}
}
// Return the filtered array
return found;
}
else if(tag instanceof Array) {
// Perform a looping tag search
for(i = 0; i < tag.length; i++) {
// Search into the temporary location
tempFound = (sibling === true) ? ctx.parentNode.getElementsByTagName(tag[i]) : ctx.getElementsByTagName(tag[i]);
// Loop through the elements
for(e = 0; e < tempFound.length; e++) {
// Push the found element to found
// And check if it is a direct child if we need to
if(child === true && tempFound[e].parentNode === ctx) {
found.push(tempFound[e]);
}
else if(sibling === true && (tempFound[e] === ctx.nextElementSibling || tempFound[e] === ctx.nextSibling)) {
found.push(tempFound[e]);
}
else if(!child && !sibling) {
found.push(tempFound[e]);
}
}
}
// Return the found ones
return found;
}
else {
// Default to grabbing all tags
tempFound = (sibling === true) ? ctx.parentNode.getElementsByTagName('*') : ctx.getElementsByTagName('*');
// Loop through the elements
for(e = 0; e < tempFound.length; e++) {
// Push the found element to found
// And check if it is a direct child if we need to
if(child === true && tempFound[e].parentNode === ctx) {
found.push(tempFound[e]);
}
else if(sibling === true && (tempFound[e] === ctx.nextElementSibling || tempFound[e] === ctx.nextSibling)) {
found.push(tempFound[e]);
}
else if(!child && !sibling) {
found.push(tempFound[e]);
}
}
// Return the filtered array
return found;
}
}
// Check if this is part of the chain
if(this.elements instanceof Array) {
// Find from the previously found
// Loop through the elements
for(i = 0; i < this.length; i++) {
tempFound = findElements(parameters.tag, this.elements[i], parameters.child, parameters.sibling, parameters.first);
// Loop through the elements
for(e = 0; e < tempFound.length; e++) {
// Push the found element to found
found.push(tempFound[e]);
}
}
}
else {
// Find from scratch
found = findElements(parameters.tag, ctx, parameters.child, parameters.sibling, parameters.first);
}
// Check if parameters is a string
if(typeof parameters === 'string') {
// If so, then return what is found by the parse selector function
return parseSelector(parameters, this);
}
// Loop through all elements
for(i = 0; i < found.length; i++) {
// Grab the current element
e = found[i];
// Get the classes of the element
classes = e.className.split(/\s+/g);
// Check if the element matches
if(
compareValue(e.nodeName, parameters.tag, true) === true &&
compareValue(classes, parameters.classes) === true &&
compareValue(e.id, parameters.id) === true &&
compareValue(e, parameters.attribute) === true &&
compareValue(e, parameters.whiteSpaceAttribute, false, true) &&
compareValue(e, parameters.hyphenAttribute, false, false, true)
) {
// Add the found element to the filtered array
filtered.push(e);
}
}
// Clean the array
filtered = unique(filtered);
// Loop through the filtered adding them to the object
for(i = 0; i < filtered.length; i++) {
built[i] = filtered[i];
}
// Add the array version
built.elements = filtered;
// Add the length
built.length = filtered.length;
// Check if there is a find parameter
if(typeof parameters.find === 'object') {
// Refind with the passed parameters
built = built.find(parameters.find);
}
// Return the object with all the elements within it
return built;
});
/**
* Loops through all of the elmenets contained in the Spark object passing the to a function
*
* @param {Function} fn Function for the current element to be passed to
* @returns {Object} Returns the Spark object for chaining
*/
Spark.extend('each', function(fn) {
// Initialise any required variables
var i = null;
// Loop through all elements
for(i = 0; i < this.length; i++) {
// Pass the element to the function
fn(this[i]);
}
// Return the Spark object for chaining
return this;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment