Skip to content

Instantly share code, notes, and snippets.

@asimjalis
Created August 6, 2013 00:26
Show Gist options
  • Save asimjalis/6160923 to your computer and use it in GitHub Desktop.
Save asimjalis/6160923 to your computer and use it in GitHub Desktop.
JavaScript library by Jeremy Osborne which implements a light-weight version of some ideas from jQuery.
(function() {
/**
* My JavaScript library.
*
* @author [your name here]
*/
/**
* @class A namespace is a collection of functions and classes.
* This collection acts as a name to our library and helps us to keep
* the functions we write from accidentally colliding with other
* functions and objects on the page.
*
* In this case, it is also a helper function that acts as a rudimentary
* selector engine for us.
* @static
*/
window.my$ = function(selector) {
// Our selector engine only works in one of two ways.
// IE < 8 note: can't use string[x] indexing, must use charAt.
if (selector.charAt(0) == "#") {
// byId, trim off the "#" before passing in.
return document.getElementById(selector.substr(1));
}
else {
// We assume it's a tag name.
return document.getElementsByTagName(selector);
}
};
//-------------------------------------------------------- Events
/**
* Slightly normalize some of the more annoying aspects of cross-browser
* event objects.
*/
var fixEvent = function(event) {
var fixedEvent = {},
i;
for (i in event) {
fixedEvent[i] = event[i];
}
// Very simple special additions
if (!fixedEvent.target) {
// Props to jQuery for the following hint on target setting.
fixedEvent.target = event.srcElement || document;
}
if (!fixedEvent.which) {
// Again props to jQuery. All I am looking for here is the
// keycode.
fixedEvent.which = event.charCode || event.keyCode;
}
if (fixedEvent.pageX === undefined && event.clientX !== undefined) {
fixedEvent.pageX = event.clientX + document.body.scrollLeft
+ document.documentElement.scrollLeft;
fixedEvent.pageY = event.clientY + document.body.scrollTop
+ document.documentElement.scrollTop;
}
// Fix up the preventDefault method.
fixedEvent.preventDefault = function() {
if (event.preventDefault) {
// !IE
event.preventDefault();
}
else {
// IE
event.returnValue = false;
}
};
return fixedEvent;
};
/**
* Add an event handler to a DOM element. This function uses the best method
* available to attach a function to an element depending on browser. All
* registered event functions will be passed the event object as the first, and
* only, parameter. On most modern browsers, multiple and different functions can
* be registered to the same event type.
* This is an expansion of John Resig's addEvent function found
* <a href="http://ejohn.org/projects/flexible-javascript-events/">here</a>.
* @param {DOM Object} obj The page element we wish to attach the
* event to.
* @param {string} type The base name of the event. For example,
* pass in "click" for single click events, "onclick" would be incorrect.
* A list of event names can be found
* <a href="http://www.quirksmode.org/dom/events/index.html">here</a>.
* There is no validity checking on the name of the event.
* @param {function} fn The event handling function to attach to this
* event. Irrespective of browser type, all functions will be passed the event
* object as the parameter. Function is still responsible for dealing with
* event object property browser inconsistencies.
* @static
*/
my$.on = function (obj, type, fn) {
if (obj.addEventListener) {
// Modern browser event registry.
obj.addEventListener(type, function(event) {
fn.call(this, fixEvent(event));
}, false);
}
else if (obj.attachEvent) {
// Older IE event registration.
// Don't reregister the same event more than once.
if (obj['e'+type+fn]) {
return;
}
obj['e'+type+fn] = fn;
obj[type+fn] = function(){
obj['e'+type+fn].call(obj, fixEvent(window.event));
};
obj.attachEvent('on'+type, obj[type+fn]);
}
else {
// old style method of adding that overwrites any function already
// added as a listener to this event.
// Make sure your code doesn't require multiple functions to be registered
// at the same time.
obj['on'+type] = fn;
}
};
/**
* Attach drag and drop functionality to a page element.
* Make sure it is logical to drag the object on the page before
* enabling drag and drop on an object.
* @param {DOM Object} obj HTML element to make draggable and droppable.
* @param {Object} [config] Container for optional settings.
* @param {Function} [config.start] A function that will be called when the
* drag event starts.
* @param {Function} [config.stop] A function that will be called when the
* drag event stops.
*/
my$.makeDraggable = function (obj, config) {
// Our initial offsets for our object
var deltaX,
deltaY,
isDragging = false; // Dragging state
var startDrag = function(e) {
if (!e.target === this){
return;
}
// Begin the drag
isDragging = true;
if (typeof config.start == "function") {
// Make the dragging object the "this"
config.start.call(obj);
}
// One trick is that we need to make the element position-able.
// Here we use absolute positioning, which may not be ideal at all
// times.
obj.style.position = "absolute";
deltaX = e.clientX - obj.offsetLeft;
deltaY = e.clientY - obj.offsetTop;
};
var move = function(e) {
var x,
y;
if ( !isDragging ) {
return;
}
x = e.clientX;
y = e.clientY;
obj.style.left = (x - deltaX) + "px";
obj.style.top = (y - deltaY) + "px";
};
var stopDrag = function(e) {
// Stop dragging of this object
isDragging = false;
if (typeof config.stop == "function") {
// Make the dragging object the "this"
config.stop.call(obj);
}
};
config = config || {};
my$.on(obj, "mousedown", startDrag);
my$.on(document.body, "mouseup", stopDrag);
my$.on(document.body, "mousemove", move);
};
//-------------------------------------------------------- Classes
/*
* Build a regular expression that can match an value of an HTML class
* attribute.
*/
var makeClassRE = function(className) {
return new RegExp("(^|\\s)"+className+"(\\s|$)");
};
/*
* If an element has a particular class, remove it.
*/
my$.removeClass = function(el, className) {
var re = makeClassRE(className);
el.className = el.className
// Remove the class
.replace(re, " ")
// Trim any leading or trailing white space
.replace(/^\s+|\s+$/g,"");
};
/*
* If an element does not yet have a particular class, add it.
*/
my$.addClass = function(el, className) {
var re = makeClassRE(className);
if (re.test(el.className) == false) {
if (el.className.length > 0) {
// Some class exists, add a separator
el.className += " " + className;
}
else {
el.className += className;
}
}
};
//--------------------------------------------------------- Generic Animations
/**
* Adapted from ppk and quirksmode article:
* http://www.quirksmode.org/dom/getstyles.html
*
* Recommend only using this functon the most significant rules available.
* It's probably a bad idea to check for "border". It seems to be an okay idea
* to check for "border-bottom-color". It's a bad idea to check for "font".
* It's an okay idea to check for "font-style" or "font-family".
* @param el {HTMLElement} Element on which we wish to check for opacity.
* @return {String} The value of the opacity, in decimal format (from
* 0 to 100).
*/
var getOpacity = function(el) {
var val;
if (el.currentStyle) {
// IE and Opera, style names are JavaScriptese (camel case).
val = /alpha\(opacity=([0-9]+)\)/.exec(el.currentStyle["filter"]);
// If we have a match, we'll get an array match object,
// or we'll get null.
val = val && parseInt(val[1]);
}
else if (window.getComputedStyle) {
// Others, css rules must be hyphenated.
val = getComputedStyle(el, null).getPropertyValue("opacity");
val = parseInt(val) * 100;
}
// Make sure to return a default, but zero is a valid return.
return (val !== null && val !== undefined && !isNaN(val)) ? val : 100;
};
/**
* Fades an element out (by default) or in.
*
* @param el {DOM Element} The element to change the opacity of.
* @param [config] {Object} Container for optional settings.
* @param [config.start] {Function} A function that will be called when the
* animation starts.
* @param [config.stop] {Function} A function that will be called when the animation
* stops.
* @param [config.toOpaque] {Boolean} If true, reverses direction and fades
* in the element.
*/
my$.fade = function(el, config) {
config = config || {};
var delta = config.toOpaque ? 1 : -1,
end = config.toOpaque ? 100 : 0,
start = getOpacity(el),
speed = 20,
opacity = start;
var changeOpacity = function() {
opacity += delta;
// Normalize
opacity = (opacity > 100) ? 100 : opacity;
opacity = (opacity < 0) ? 0 : opacity;
// !IE and IE >= 9
el.style.opacity = (opacity / 100);
// IE < 9
el.style.filter = "alpha(opacity=" + opacity + ")";
if (opacity < 100 && opacity > 0) {
setTimeout(changeOpacity, speed);
}
else {
// We're done
if (typeof config.stop == "function") {
config.stop.call(el);
}
}
};
// Call start, if it exists. Pass the element.
if (typeof config.start == "function") {
config.start.call(el);
}
changeOpacity();
};
//--------------------------------------------------------- AJAX Data Loading
/**
* Asynchronously load an external document and make the content accessible.
* @example
* // Default call, using XMLHTTPRequest
* // myCallback will be passed 1 argument which is the xhr object at
* // the time readyState == 4
* my$.xhr("http://example.com/l33t.html", myCallback);
* @param {string} url The location of the document to load.
* @param {function} callback Handles data retrieved via the XMLHttpRequest.
*/
my$.xhr = function(url, callback){
try {
// for testing with local files from the file:// protocol.
// AJAX suffers from the same domain policy.
// Only need to use in a pinch with CORS implemented on the server
// side (and obviously never used in production).
netscape.security.PrivilegeManager.enablePrivilege("UniversalPreferencesWrite UniversalBrowserWrite UniversalPreferencesRead UniversalBrowserRead");
} catch (e) {}
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
if (!("withCredentials" in xhr) && window.XDomainRequest) {
// XDomainRequest for IE 8 and 9, otherwise IE7 will get the
// old XMLHttpRequest object but will not be able to make
// cross domain requests.
xhr = new XDomainRequest();
}
}
else {
// If the above failed, we must be within an older Internet Explorer
if (window.ActiveXObject) {
try {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e) {}
}
}
if (!xhr) {
alert("You do not have an AJAX capable browser.");
return;
}
xhr.open("GET", url, true);
xhr.onreadystatechange = function(){
if (this.readyState == 4) {
// NOTE: we are leaving out checks for xhr.status
// because we are working with local files
callback(this);
}
}
xhr.send(null);
};
// A number we will increment for each of our jsonp requests.
my$.jsonpSerial = 0;
/**
* Asynchronously load an external document and make the content accessible
* via JSONP.
* @example
* my$.jsonp("http://test.org/test?status=good&callback={callback}", callback);
* @param url {string} The location of the document to load. Must include the
* substring {callback} within the URL.
* @param callback {function} A function to be called when this request
* returns. The callback will be passed the arguments that the JSONP callback
* initially received.
*/
my$.jsonp = function(url, callback) {
var script = document.createElement("script"),
head = document.getElementsByTagName("head")[0],
// Prefix to our global callback name for this call.
functionKey = "jsonpCallback" + (my$.jsonpSerial++);
// Generate our global callback.
window[functionKey] = function(data) {
// Call our callback when we are called.
callback(data);
};
// Rewrite our URL correctly.
url = url.replace("{callback}", functionKey);
if (script.readyState){
// Unload script tag from page in IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange = null;
head.removeChild(script);
// Delete our callback.
delete window[functionKey];
}
};
}
else {
// Unload script tag from page in other browsers
script.onload = function(){
head.removeChild(script);
// Delete our callback.
delete window[functionKey];
};
}
script.onerror = function() {
// Hopefully we never see this
alert("The request to: " + url + " failed.");
};
// Set-up the script and attach to the HTML document
// to trigger the HTTP GET request.
script.type = "text/javascript";
script.src = url;
head.appendChild(script);
};
//--------------------------------------------- Storage Wrappers
// The localStorage object.
// We either use the actual local storage API, or we fallback
// to an in memory cache.
// The in memory cache will not persist across page reloads,
// obviously.
var localStorage = window.localStorage || {};
/**
* Takes a string and attempts to persist it.
* @param key {String} Cache key we are attempting to save.
* @param value {String} What we are attempting to save.
* @return {Boolean} true if we could save the item, false if
* not.
*/
my$.storeSave = function(key, value) {
localStorage[key] = value;
};
/**
* Attempts to load a key.
* @param key {String} Cache key we are attempting to save.
* @return {String|Undefined} A string if the value exists, otherwise
* undefined.
*/
my$.storeLoad = function(key) {
return localStorage[key];
};
/**
* Attempts to remove a specific key from storage.
* @param key {String} Cache key to remove.
*/
my$.storeRemove = function(key) {
delete localStorage[key];
};
/**
* Clears the storage.
*/
my$.storeClear = function() {
if (localStorage.clear) {
// The real local storage requires calling
// of a clear method...
localStorage.clear();
}
else {
// ... but our in memory cache can just
// be garbage collected.
localStorage = {};
}
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment