Created
August 9, 2011 01:14
-
-
Save nfeldman/1133214 to your computer and use it in GitHub Desktop.
PathActions
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
/** | |
* @fileoveriew A very rough sketch for location hooks (or location based | |
* events) system to simplify and structure tying multiple functions to a path. | |
* Can be triggered conditionally by binding init() to an event handler or | |
* on page load. This is version 0.1, so it doesn't do much, but it does | |
* handle wildcards. Examples of use are provided at the end of this gist | |
* TODO convert console copy/paste tests to unit tests | |
* TODO test in IE | |
* TODO create repo for this project | |
* @version 0.1 | |
* @author Noah Feldman | |
* @copyright 2011 | |
*/ | |
// JS Module to simplify client side location relative script execution | |
var MYAPP = {}; // where MYAPP is your namespace | |
MYAPP.pathActions = (function (G, U) { | |
var undef = typeof U, | |
rLPathTrim = /^(\/)/, | |
rRPathTrim = /([/*])$/, | |
rWild = /\*$/, | |
hasOwn = Object.prototype.hasOwnProperty, | |
toString = Object.prototype.toString, | |
slice = Array.prototype.slice, | |
pathname, arrayOrObject, keys, each; | |
// ensure consistently formatted strings for comparisons | |
function prepPath (path) { | |
path = path.length > 1 ? path.replace(rLPathTrim, '').replace(rRPathTrim, '') : '/'; | |
return path; | |
} | |
if (!G.location && !location) // makes mocking a location object even simpler | |
throw new Error('PathActions requires access to a location object.'); | |
pathname = prepPath(G.location || location); | |
// helper for deep copy of an object | |
arrayOrObject = (function () { | |
var arOrObObj = { '[object Array]': 1, '[object Object]': 2 }; | |
return function (obj) { | |
return arOrObObj[toString.call(obj)] || 0; | |
} | |
}()); | |
// merge function extracted from Fortinbras | |
function merge (into, from, shallow) { | |
for (var key in from) { | |
if (hasOwn.call(from, key)) { | |
if (!shallow && arrayOrObject(from[key])) { | |
if (arrayOrObject(from[key]) == 1) | |
into[key] = slice.call(from[key]); | |
else | |
into[key] = merge({}, from[key]); | |
} else { | |
into[key] = from[key]; | |
} | |
} | |
} | |
return into; | |
} | |
// get an array of an object's own keys | |
keys = Object.keys || function (obj) { | |
var ret = []; | |
for (var key in obj) | |
if (hasOwn.call(obj, key)) | |
ret.push(key); | |
return ret; | |
}; | |
// quick foreach, not fully compatible with native forEach but | |
// good enough for our needs. | |
each = (function () { | |
if (!!Array.prototype.forEach) | |
return function(array, callback, thisObject) { | |
return array.forEach(callback, thisObject); | |
} | |
else | |
return function(array, callback, thisObject) { | |
for (var i = 0, len = array.length; i < len; i++) | |
callback.call(thisObject, array[i], i, array); | |
} | |
}()); | |
function eachKey (obj, callback, thisObject) { | |
return each(keys(obj), callback, thisObject); | |
} | |
// export utilities as methods of MYAPP | |
MYAPP.eachKey = eachKey; | |
MYAPP.merge = merge; | |
// return our "Module", this object is what you access at | |
// MYAPP.pathActions | |
return { | |
segments: pathname.length > 1 ? pathname.split('/') : [], | |
pathname: pathname, | |
namedPaths: [], | |
paths: {}, | |
/** | |
* @param {string} name, a label that ties an action to a location | |
* @param {string} path, a location | |
* @param {object} rules, optionally provide callback at the same time | |
*/ | |
// we could have made the paths the keys and stored the actions as | |
// values. That might be a smarter way to go, but this has greater | |
// flexibility. Whether we use it or not is a different question. | |
addPath: function (name, path, rules) { | |
var segs; | |
if (typeof this.paths[name] == undef) { | |
this.paths[name] = {}; | |
this.namedPaths.push(name); | |
this.paths[name].w = rWild.test(path); | |
path = prepPath(path); | |
this.paths[name].path = path; | |
segs = path.length == 1 ? [] : path.split('/'); | |
this.paths[name].l = segs.length; | |
this.paths[name].segs = segs; | |
if (rules) { | |
if (!rules.name) | |
rules.name = name; | |
this.addRule(rules); | |
} | |
return this; | |
} | |
}, | |
/** | |
* @param {object} rules, currently `name` and `callback`, more to come? | |
*/ | |
addRule: function (rules) { | |
var name; | |
if (!rules.name || !this.paths[rules.name]) | |
throw new Error('Path ' + (rules.name || 'unnamed path') + ' not found'); | |
name = rules.name; | |
if (this.paths[name].w) { | |
// a placeholder in case we add black- and/or white-listing with wildcards | |
this.paths[name].exceptions = {}; | |
merge(this.paths[name].exceptions, rules.exception); | |
} | |
this._actions[name] = rules.callback; | |
return this; | |
}, | |
init: function () { | |
var toTest = {}, | |
paths = this.paths, | |
segs = this.segments, | |
path = this.pathname, | |
names = []; | |
// first we'll collect potential matches | |
each(this.namedPaths, function(name, i) { | |
if (paths[name].w) { | |
if (segs.length >= paths[name].l) | |
toTest[name] = paths[name]; | |
} else if (segs.length == paths[name].l) { | |
toTest[name] = paths[name]; | |
} | |
}); | |
// if I add variable segments, this won't be sufficient, which is | |
// the other reason the segment arrays already exist, but for now | |
// this is good. | |
eachKey(toTest, function(name) { | |
var rTest = RegExp('(?:' + paths[name].path + ')'); | |
if (paths[name].w && rTest.test(path)) | |
names.push(name); | |
else if (toTest[name] === path) | |
names.push(name); | |
}); | |
if (names) { | |
for (var i = 0, len = names.length; i < len; i++) | |
this._actions[names[i]](); | |
return true; | |
} | |
return; // no registered actions for this location | |
}, | |
_actions: {} | |
} | |
}(window || this)); | |
/** | |
* @example add paths either like this: | |
MYAPP.pathActions.addPath('test1', '/research/current-research-projects/') | |
.addRule({ | |
name: 'test1', | |
callback: function () { | |
var el = document.getElementById('main'); | |
el.style.opacity = '.5'; | |
console.log('test1 matched!'); | |
} | |
}); | |
* | |
* or this: | |
MYAPP.pathActions.addPath('test2', 'research/*', { | |
callback: function () { | |
console.log('wildcard test passed') | |
} | |
}); | |
* or this: | |
MYAPP.pathActions.addPath('test3,' '/products/electronics/brands/*'); | |
// ... do stuff | |
MYAPP.pathActions.addRule({name: 'test3', callback: someFunction}); | |
* and cram as many named paths as you want in there, then call | |
* MYAPP.pathActions.init() onload (or click, or whatever) | |
* This puts all your location based logic in a single object. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment