Created
March 28, 2011 15:49
-
-
Save Integralist/890700 to your computer and use it in GitHub Desktop.
Generic JavaScript library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(window, document, undef) { | |
// Generic library | |
var mylib = (function(){ | |
// Private implementation | |
var __mylib = { | |
/** | |
* Following property indicates whether the current rendering engine is Trident (i.e. Internet Explorer) | |
* | |
* @return v { Integer|undefined } if IE then returns the version, otherwise returns 'undefined' to indicate NOT a IE browser | |
*/ | |
isIE: (function() { | |
var undef, | |
v = 3, | |
div = document.createElement('div'), | |
all = div.getElementsByTagName('i'); | |
while ( | |
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->', | |
all[0] | |
); | |
return v > 4 ? v : undef; | |
}()), | |
// Errors | |
errors: [], | |
/** | |
* Listens for when the DOM is ready to be interacted with. | |
* Then processes queued functions. | |
* | |
* @param fn { Function } a function to be executed when the DOM is ready. | |
* @return anonymous { Function } immediately-invoked function expression which returns a Function to be executed. | |
*/ | |
domready: (function(){ | |
// Variables used throughout this script | |
var queue = [], | |
exec, | |
loaded, | |
original_onload; | |
// Private inner function which is called once DOM is loaded. | |
function process() { | |
// Let the script know the DOM is loaded | |
loaded = true; | |
// Cleanup | |
if (document.addEventListener) { | |
document.removeEventListener("DOMContentLoaded", process, false); | |
} | |
// Move the zero index item from the queue and set 'exec' equal to it | |
while ((exec = queue.shift())) { | |
// Now execute the current function | |
exec(); | |
} | |
} | |
return function(fn) { | |
// if DOM is already loaded then just execute the specified function | |
if (loaded) { | |
return fn(); | |
} | |
if (document.addEventListener) { | |
// Any number of listeners can be set for when this event fires, | |
// but just know that this event only ever fires once | |
document.addEventListener("DOMContentLoaded", process, false); | |
} else { | |
// All browsers support document.readyState (except Firefox 3.5 and lower, but they support DOMContentLoaded event) | |
/loaded|complete/.test(document.readyState) ? process() : setTimeout("__mylib.domready(" + fn + ")", 10); | |
} | |
// Fall back to standard window.onload event | |
// But make sure to store the original window.onload in case it was already set by another script | |
original_onload = window.onload; | |
window.onload = function() { | |
// Note: calling process() now wont cause any problem for modern browsers. | |
// Because the function would have already been called when the DOM was loaded. | |
// Meaning the queue of functions have already been executed | |
process(); | |
// Check for original window.onload and execute it | |
if (original_onload) { | |
original_onload(); | |
} | |
}; | |
// As the DOM hasn't loaded yet we'll store this function and execute it later | |
queue.push(fn); | |
}; | |
}()), | |
/** | |
* XMLHttpRequest abstraction. | |
* | |
* @return xhr { XMLHttpRequest|ActiveXObject } a new instance of either the native XMLHttpRequest object or the corresponding ActiveXObject | |
*/ | |
xhr: (function() { | |
// Create local variable which will cache the results of this function | |
var xhr; | |
return function() { | |
// Check if function has already cached the value | |
if (xhr) { | |
// Create a new XMLHttpRequest instance | |
return new xhr(); | |
} else { | |
// Check what XMLHttpRequest object is available and cache it | |
xhr = (!window.XMLHttpRequest) ? function() { | |
return new ActiveXObject( | |
// Internet Explorer 5 uses a different XMLHTTP object from Internet Explorer 6 | |
(__mylib.isIE < 6) ? "Microsoft.XMLHTTP" : "MSXML2.XMLHTTP" | |
); | |
} : window.XMLHttpRequest; | |
// Return a new XMLHttpRequest instance | |
return new xhr(); | |
} | |
}; | |
}()), | |
/** | |
* A basic AJAX method. | |
* | |
* @param settings { Object } user configuration | |
* @return undefined { } no explicitly returned value | |
*/ | |
ajax: function(settings) { | |
// JavaScript engine will 'hoist' variables so we'll be specific and declare them here | |
var xhr, url, requestDone; | |
// Load the config object with defaults, if no values were provided by the user | |
config = { | |
// The type of HTTP Request | |
method: settings.method || 'POST', | |
// The data to POST to the server | |
data: settings.data || '', | |
// The URL the request will be made to | |
url: settings.url || '', | |
// How long to wait before considering the request to be a timeout | |
timeout: settings.timeout || 5000, | |
// Functions to call when the request fails, succeeds, or completes (either fail or succeed) | |
onComplete: settings.onComplete || function(){}, | |
onError: settings.onError || function(){}, | |
onSuccess: settings.onSuccess || function(){}, | |
// The data type that'll be returned from the server | |
// the default is simply to determine what data was returned from the and act accordingly. | |
dataType: settings.dataType || '' | |
}; | |
// Create new cross-browser XMLHttpRequest instance | |
xhr = __mylib.xhr(); | |
// Open the asynchronous request | |
xhr.open(config.method, config.url, true); | |
// Determine the success of the HTTP response | |
function httpSuccess(r) { | |
try { | |
// If no server status is provided, and we're actually | |
// requesting a local file, then it was successful | |
return !r.status && location.protocol == 'file:' || | |
// Any status in the 200 range is good | |
( r.status >= 200 && r.status < 300 ) || | |
// Successful if the document has not been modified | |
r.status == 304 || | |
// Safari returns an empty status if the file has not been modified | |
navigator.userAgent.indexOf('Safari') >= 0 && typeof r.status == 'undefined'; | |
} catch(e){ | |
// Throw a corresponding error | |
throw new Error("httpSuccess = " + e); | |
} | |
// If checking the status failed, then assume that the request failed too | |
return false; | |
} | |
// Extract the correct data from the HTTP response | |
function httpData(r, type) { | |
// Get the content-type header | |
var ct = r.getResponseHeader("content-type"), | |
data = !type && ct && ct.indexOf("xml") >= 0; // If no default type was provided, determine if some form of XML was returned from the server | |
// Get the XML Document object if XML was returned from the server, | |
// otherwise return the text contents returned by the server | |
data = (type == "xml" || data) ? r.responseXML : r.responseText; | |
// If the specified type is "script", execute the returned text response as if it was JavaScript | |
if (type == "script") { | |
eval.call(window, data); | |
} | |
// Return the response data (either an XML Document or a text string) | |
return data; | |
} | |
// Initalize a callback which will fire within the timeout range, cancelling the request (if it has not already occurred) | |
window.setTimeout(function() { | |
requestDone = true; | |
}, config.timeout); | |
// Watch for when the state of the document gets updated | |
xhr.onreadystatechange = function() { | |
// Wait until the data is fully loaded, and make sure that the request hasn't already timed out | |
if (xhr.readyState == 4 && !requestDone) { | |
// Check to see if the request was successful | |
if (httpSuccess(xhr)) { | |
// Execute the success callback | |
config.onSuccess(httpData(xhr, config.type)); | |
} | |
// Otherwise, an error occurred, so execute the error callback | |
else { | |
config.onError(httpData(xhr, config.type)); | |
} | |
// Call the completion callback | |
config.onComplete(); | |
// Clean up after ourselves, to avoid memory leaks | |
xhr = null; | |
} else if (requestDone && xhr.readyState != 4) { | |
// If the script timed out then keep a log of it so the developer can query this and handle any exceptions | |
__mylib.errors.push(url + " { timed out } "); | |
// Bail out of the request immediately | |
xhr.onreadystatechange = null; | |
} | |
}; | |
// Get if we should POST or GET... | |
if (config.data) { | |
// Settings | |
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); | |
// Establish the connection to the server | |
xhr.send(config.data); | |
} else { | |
// Establish the connection to the server | |
xhr.send(null); | |
} | |
}, | |
// Event management | |
events: { | |
/** | |
* | |
*/ | |
prepareHandler: function(fn, model) { | |
var e = (model) ? e : window.event; | |
return function(e) { | |
// Execute handler function, passing it a normalised version of the event object | |
fn(__mylib.events.standardize(e)); | |
}; | |
}, | |
/** | |
* | |
*/ | |
checkHandler: function(fn, operation, model) { | |
var ch = this.checkHandler, list, map, handler, i, index = -1; | |
// Maintain a static function list | |
// If no list property exists then we'll create one | |
if (!ch.list) { | |
ch.list = []; | |
} | |
list = ch.list; | |
// Loop through our list looking for the item | |
// We cache the handler so we don't have to keep checking the 'standardise' method every time the handler is called | |
for (i = list.length - 1; i >= 0; i -= 1) { | |
map = list[i]; | |
if (map && map.original === fn) { | |
index = i; | |
handler = map.generated; | |
break; | |
} | |
} | |
if (operation == 'remove') { | |
if (index !== -1) { | |
list[index].counter -= 1; | |
if (list[index].counter <= 0) { | |
delete list[index]; | |
} | |
} | |
} else if(operation == 'add') { | |
if (index !== -1) { | |
list[index].counter += 1; | |
} else { | |
handler = __mylib.events.prepareHandler(fn, model); | |
list[list.length] = { | |
original: fn, | |
generated: handler, | |
counter: 1 | |
}; | |
} | |
} | |
console.log(list); | |
return handler; | |
}, | |
/** | |
* The add method allows us to assign a function to execute when an event of a specified type occurs on a specific element | |
* | |
* @param element { Element/Node } the element that will have the event listener attached | |
* @param eventType { String } the event type, e.g. 'click' that will trigger the event handler | |
* @param handler { Function } the function that will execute as the event handler | |
* @return __add { Function } this immediately invoked function expression returns a bridge function which calls the private implementation | |
*/ | |
add: (function() { | |
var __add, eventType, fn; | |
if (document.addEventListener) { | |
// Rewrite add method to use W3C event listener | |
__add = function(element, eventType, handler) { | |
// Because we're using an anonymous function below (within the addEventListener method - this is so we can standardise the event object), | |
// removing the event listener wont work because you must use a Function Declaration/Expression (not an anonymous function). | |
// A way to explain this would be: var x = {}, y = {}; (x !== y) they may *look* the same but there is no way to tell. | |
// So to work around this we need to prepare(store) the handler before specifying it as the handler. | |
fn = __mylib.events.checkHandler(handler, 'add', 1); | |
eventType = eventType.toLowerCase(); | |
element.addEventListener(eventType, fn, false); | |
}; | |
} | |
else if (document.attachEvent) { | |
// Rewrite add method to use Internet Explorer event listener | |
__add = function(element, eventType, handler) { | |
// Keep reference to the handler | |
fn = __mylib.events.checkHandler(handler, 'add', 0); | |
eventType = eventType.toLowerCase(); | |
element.attachEvent("on" + eventType, fn); | |
}; | |
} | |
return function(element, eventType, handler) { | |
__add(element, eventType, handler); | |
}; | |
}()), | |
/** | |
* The remove method allows us to remove previously assigned code from an event | |
* | |
* @param element { Element/Node } the element that will have the event listener detached | |
* @param eventType { String } the event type, e.g. 'click' that triggered the event handler | |
* @param callback { Function } the function that was to be executed as the event handler | |
* @return __remove { Function } this immediately invoked function expression returns a bridge function which calls the private implementation | |
*/ | |
remove: (function() { | |
var __remove, eventType, fn; | |
if (document.removeEventListener) { | |
// Rewrite remove method to use W3C event listener | |
__remove = function(element, eventType, handler) { | |
fn = __mylib.events.checkHandler(handler, 'remove', 1); | |
eventType = eventType.toLowerCase(); | |
element.removeEventListener(element, eventType, handler); | |
}; | |
} | |
else if (document.detachEvent) { | |
// Rewrite remove method to use Internet Explorer event listener | |
__remove = function(element, eventType, handler) { | |
fn = __mylib.events.checkHandler(handler, 'remove', 0); | |
eventType = eventType.toLowerCase(); | |
element.detachEvent("on" + eventType, handler); | |
}; | |
} | |
return function(element, eventType, handler) { | |
__remove(element, eventType, handler); | |
}; | |
}()), | |
/** | |
* The standardize method produces a unified set of event properties, regardless of the browser | |
* | |
* @param event { Object } the event object associated with the event that was triggered | |
* @return anonymous { Object } an un-named object literal with the relevant event properties normalised | |
*/ | |
standardize: function(event) { | |
// These two methods, defined later, return the current position of the | |
// mouse pointer, relative to the document as a whole, and relative to the | |
// element the event occurred within | |
var page = this.getMousePositionRelativeToDocument(event), | |
offset = this.getMousePositionOffset(event); | |
// Let's stop events from firing on element nodes above the current... | |
// W3C method | |
if (event.stopPropagation) { | |
event.stopPropagation(); | |
} | |
// Internet Explorer method | |
else { | |
event.cancelBubble = true; | |
} | |
// We return an object literal containing seven properties and one method | |
return { | |
// The target is the element the event occurred on | |
target: this.getTarget(event), | |
// The relatedTarget is the element the event was listening for, | |
// which can be different from the target if the event occurred on an | |
// element located within the relatedTarget element in the DOM | |
relatedTarget: this.getRelatedTarget(event), | |
// If the event was a keyboard- related one, key returns the character | |
key: this.getCharacterFromKey(event), | |
// Return the x and y coordinates of the mouse pointer, | |
// relative to the document | |
pageX: page.x, | |
pageY: page.y, | |
// Return the x and y coordinates of the mouse pointer, | |
// relative to the element the current event occurred on | |
offsetX: offset.x, | |
offsetY: offset.y, | |
// The preventDefault method stops the default event of the element | |
// we're acting upon from occurring. If we were listening for click | |
// events on a hyperlink, for example, this method would stop the | |
// link from being followed | |
preventDefault: function() { | |
// W3C method | |
if (event.preventDefault) { | |
event.preventDefault(); | |
} | |
// Internet Explorer method | |
else { | |
event.returnValue = false; | |
} | |
} | |
}; | |
}, | |
/** | |
* The getTarget method locates the element the event occurred on | |
* | |
* @param event { Object } the event object associated with the event that was triggered | |
* @return target { Element/Node } the element that was the target of the triggered event | |
*/ | |
getTarget: function(event) { | |
// Internet Explorer value is srcElement, W3C value is target | |
var target = event.srcElement || event.target; | |
// Fix legacy Safari bug which reports events occurring on a text node instead of an element node | |
if (target.nodeType == 3) { // 3 denotes a text node | |
target = target.parentNode; // Get parent node of text node | |
} | |
// Return the element node the event occurred on | |
return target; | |
}, | |
/** | |
* The getCharacterFromKey method returns the character pressed when keyboard events occur. | |
* You should use the keypress event as others vary in reliability | |
* | |
* @param event { Object } the event object associated with the event that was triggered | |
* @return character { String } the character pressed when keyboard events occurred | |
*/ | |
getCharacterFromKey: function(event) { | |
var character = ""; | |
// Internet Explorer | |
if (event.keyCode) { | |
character = String.fromCharCode(event.keyCode); | |
} | |
// W3C | |
else if (event.which) { | |
character = String.fromCharCode(event.which); | |
} | |
return character; | |
}, | |
/** | |
* The getMousePositionRelativeToDocument method returns the current mouse pointer position relative to the top left edge of the current page. | |
* | |
* @param event { Object } the event object associated with the event that was triggered | |
* @return anonymous { Object } the x and y coordinates | |
*/ | |
getMousePositionRelativeToDocument: function(event) { | |
var x = 0, y = 0; | |
// pageX gets coordinates of pointer from left of entire document | |
if (event.pageX) { | |
x = event.pageX; | |
y = event.pageY; | |
} | |
// clientX gets coordinates from left of current viewable area | |
// so we have to add the distance the page has scrolled onto this value | |
else if (event.clientX) { | |
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; | |
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; | |
} | |
// Return an object literal containing the x and y mouse coordinates | |
return { | |
x: x, | |
y: y | |
}; | |
}, | |
/** | |
* The getMousePositionOffset method returns the distance of the mouse pointer from the top left of the element the event occurred on | |
* | |
* @param event { Object } the event object associated with the event that was triggered | |
* @return anonymous { Object } the x and y coordinates of the mouse relative to the element | |
*/ | |
getMousePositionOffset: function(event) { | |
var x = 0, y = 0; | |
if (event.layerX) { | |
x = event.layerX; | |
y = event.layerY; | |
} | |
// Internet Explorer proprietary | |
else if (event.offsetX) { | |
x = event.offsetX; | |
y = event.offsetY; | |
} | |
// Returns an object literal containing the x and y coordinates of the mouse relative to the element the event fired on | |
return { | |
x: x, | |
y: y | |
}; | |
}, | |
/** | |
* The getRelatedTarget method returns the element node the event was set up to fire on, | |
* which can be different from the element the event actually fired on | |
* | |
* @param event { Object } the event object associated with the event that was triggered | |
* @return relatedTarget { Element/Node } the element the event was set up to fire on | |
*/ | |
getRelatedTarget: function(event) { | |
var relatedTarget = event.relatedTarget; | |
// With mouseover events, relatedTarget is not set by default | |
if (event.type == "mouseover") { | |
relatedTarget = event.fromElement; | |
} | |
// With mouseout events, relatedTarget is not set by default | |
else if (event.type == "mouseout") { | |
relatedTarget = event.toElement; | |
} | |
return relatedTarget; | |
} | |
}, | |
utilities: { | |
/** | |
* The toCamelCase method takes a hyphenated value and converts it into a camel case equivalent. | |
* e.g. margin-left becomes marginLeft. | |
* Hyphens are removed, and each word after the first begins with a capital letter. | |
* | |
* @param hyphenatedValue { String } hyphenated string to be converted | |
* @return result { String } the camel case version of the string argument | |
*/ | |
toCamelCase: function(hyphenatedValue) { | |
var result = hyphenatedValue.replace(/-\D/g, function(character) { | |
return character.charAt(1).toUpperCase(); | |
}); | |
return result; | |
}, | |
/** | |
* The toHyphens method performs the opposite conversion, taking a camel case string and converting it into a hyphenated one. | |
* e.g. marginLeft becomes margin-left | |
* | |
* @param camelCaseValue { String } camel cased string to be converted | |
* @return result { String } the hyphenated version of the string argument | |
*/ | |
toHyphens: function(camelCaseValue) { | |
var result = camelCaseValue.replace(/[A-Z]/g, function(character) { | |
return ('-' + character.charAt(0).toLowerCase()); | |
}); | |
return result; | |
} | |
}, | |
css: { | |
/** | |
* The getAppliedStyle method returns the current value of a specific CSS style property on a particular element | |
* | |
* @param element { Element/Node } the element we wish to find the style value for | |
* @param styleName { String } the specific style property we're interested in | |
* @return style { String } the value of the style property found | |
*/ | |
getAppliedStyle: function(element, styleName) { | |
var style = ""; | |
if (window.getComputedStyle) { | |
// W3C specific method. Expects a style property with hyphens | |
style = element.ownerDocument.defaultView.getComputedStyle(element, null).getPropertyValue(__mylib.utilities.toHyphens(styleName)); | |
} | |
else if (element.currentStyle) { | |
// Internet Explorer-specific method. Expects style property names in camel case | |
style = element.currentStyle[__mylib.utilities.toCamelCase(styleName)]; | |
} | |
return style; | |
}, | |
/** | |
* The getArrayOfClassNames method is a utility method which returns an array of all the CSS class names assigned to a particular element. | |
* Multiple class names are separated by a space character | |
* | |
* @param element { Element/Node } the element we wish to retrieve class names for | |
* @return classNames { String } a list of class names separated with a space in-between | |
*/ | |
getArrayOfClassNames: function(element) { | |
var classNames = []; | |
if (element.className) { | |
// If the element has a CSS class specified, create an array | |
classNames = element.className.split(' '); | |
} | |
return classNames; | |
}, | |
/** | |
* The addClass method adds a new CSS class of a given name to a particular element | |
* | |
* @param element { Element/Node } the element we want to add a class name to | |
* @param className { String } the class name we want to add | |
* @return undefined { } no explicitly returned value | |
*/ | |
addClass: function(element, className) { | |
// Get a list of the current CSS class names applied to the element | |
var classNames = this.getArrayOfClassNames(element); | |
// Add the new class name to the list | |
classNames.push(className); | |
// Convert the list in space-separated string and assign to the element | |
element.className = classNames.join(' '); | |
}, | |
/** | |
* The removeClass method removes a given CSS class name from a given element | |
* | |
* @param element { Element/Node } the element we want to remove a class name from | |
* @param className { String } the class name we want to remove | |
* @return undefined { } no explicitly returned value | |
*/ | |
removeClass: function(element, className) { | |
var classNames = this.getArrayOfClassNames(element), | |
resultingClassNames = []; // Create a new array for storing all the final CSS class names in | |
for (var index = 0, len = classNames.length; index < len; index++) { | |
// Loop through every class name in the list | |
if (className != classNames[index]) { | |
// Add the class name to the new list if it isn't the one specified | |
resultingClassNames.push(classNames[index]); | |
} | |
} | |
// Convert the new list into a space- separated string and assign it | |
element.className = resultingClassNames.join(" "); | |
}, | |
/** | |
* The hasClass method returns true if a given class name exists on a specific element, false otherwise | |
* | |
* @param element { Element/Node } the element we want to check whether a class name exists on | |
* @param className { String } the class name we want to check for | |
* @return isClassNamePresent { Boolean } if class name was found or not | |
*/ | |
hasClass: function(element, className) { | |
// Assume by default that the class name is not applied to the element | |
var isClassNamePresent = false, | |
classNames = this.getArrayOfClassNames(element); | |
for (var index = 0, len = classNames.length; index < len; index++) { | |
// Loop through each CSS class name applied to this element | |
if (className == classNames[index]) { | |
// If the specific class name is found, set the return value to true | |
isClassNamePresent = true; | |
} | |
} | |
// Return true or false, depending on if the specified class name was found | |
return isClassNamePresent; | |
} | |
} | |
}; | |
// Return public API | |
return { | |
load: __mylib.domready, | |
ajax: __mylib.ajax, | |
events: __mylib.events, | |
css: __mylib.css | |
}; | |
}()); | |
// Expose st to the global object | |
window.st = mylib; | |
}(this, this.document)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment