/** is(), XO, @author bibby @requires XUtil.js **/ /** @author Crockford , bibby A better typeof. Accurately returns null and array. Can also be used as an "is_a" or a simple test for truth. examples: var foo = 'bar'; is(foo); // true; is(foo,'string'); // true is(foo,'type'); // string @param mixed variable @param string (optional) data type **/ var is = function (v,type) { switch(typeof type) { case 'undefined': return !!v; break; case 'function': return v instanceof type ? true : false; } type=type.toLowerCase(); if(type == 'nan') { return isNaN(v); } var vt = typeof v; if(vt === 'object') { if(v) { if (typeof v.length === 'number' && !(v.propertyIsEnumerable('length')) && typeof v.splice === 'function') vt = 'array'; // last chance to return new Number() as a number :P if(v instanceof Number) vt = 'number'; if(v instanceof String) vt = 'string'; if(v instanceof Boolean) vt = 'boolean'; } else { vt = 'null'; } } if(type=="type") return vt; return vt == type ? true : false; }; /** XO - eXtendable Object .extend // extend this instace from object .extendClass // extend this instance from class .xo // static (new XO's don't have this) Accepts an object and gives it extend and extendClass .x // static (new XO's don't have this) Extends an object by another object without giving it extend() The alternative to Object.prototype.extend , which can create as many problems as it solves XO may be used to create a singleton by making a new XO: var foo = new XO(members); // members being an object of members {} That object may now extend from other objects ( foo.extend(bar) ) or from classes that take no parameters to be constructed ( foo.extendClass( Bar ) ) XO may be used in a class to have its instances be extendable using xo(): function className(members) { XO.xo(this).extend(members); } XO may also use its extend methods to apply members to new Objects without gifting the extend functions themselves with XO.x(obj,members) You do NOT want to extend the var XO. Make your own instance [ new XO() ] or use .xo , .x "Yes, we're tired. Yes, there is no relief. Yes, the Cylons keep coming after us time after time after time. And yes, we are still expected to do our jobs!" - Saul Tigh, XO */ var XO = function(members) { /* @param object to extend from @param boolean , overwrite existing members? (default is false) */ this.extend = function(o,over) { if(!is(o,'object')) return this; for(var i in o) if(is(over) || (!is(over) && !(i in this))) this[i]=o[i]; return this; }; // extend XO further this.extend({ /* object extend from a class name by making a new instance @param mixed function (class) or object to extend from @param boolean , overwrite existing members? */ extendClass:function(cls,over) { if(is(cls,'function')) { try { this.extend(new cls,over); } catch(e) { return e; } } else if(!is(cls,'object')) this.extend(cls,over); return this; } }); // finally, if any members were given to the XO constructor, apply these. if(is(members,'object')) this.extend(members,true); }; // STATIC functions given to objects from the constructor /* use XO to make an object extendable itself @param object , any @return object , same, only with extend methods */ XO.xo=function(obj) { if(!is(obj,'object')) obj={}; if(is(obj.extend,'function')) return obj; xo = new XO(); obj.extend = xo.extend; obj.extendClass = xo.extendClass; return obj; }, /* use XO to extend another object without giving it extend() @param object to extend @param object members/methods to grant */ XO.x=function(obj,members) { if(!is(members,'object')) return obj; for(var i in members) { try{obj[i]=members[i]} catch(e){ console.log('counldnt copy '+members[i], i, e);}; } return obj; } /* Array modifications Object.prototype was the devil. Array prototype not so much (except when things that "look" like Arrays aren't. There is XUtil.toArray() for that ) */ if(is(Array.prototype.filter,'undefined')) { //This prototype is provided by the Mozilla foundation and //is distributed under the MIT license. //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license if (!Array.prototype.filter) { Array.prototype.filter = function(fun /*, thisp*/) { var len = this.length; if (typeof fun != "function") throw new TypeError(); var res = new Array(); var thisp = arguments[1]; for (var i = 0; i < len; i++) { if (i in this) { var val = this[i]; // in case fun mutates this if (fun.call(thisp, val, i, this)) res.push(val); } } return res; }; } } XO.x(Array.prototype, { /* just a numer to help next() and prev() */ pointer:0, /** map an array though a function. You can also pass an additional object as a reference that you'll during each pass (like, the original array). Your callback then could take 0-2 arguments. callback = function(i,ref) { this == the value of the array at the index i == the index ref == whatever else you wanted to pass along } @param function callback @param mixed */ each:function(callback,reference) { var ret; if(is(callback,'function')) { loop:for(var i = 0; i < this.length; i++) { ret=callback.call(this[i],i,reference); if(ret===false) break loop; } return ret; } else Error.IllegalArgument(new Error, 'callback function', callback); }, /* resets the array pointer @return void */ reset:function() { this.pointer = 0; }, /* the value at the current position @return mixed */ current:function() { return this[this.pointer]; }, /* From the current position, has next item? @return boolean */ hasNext:function() { return is(this[this.pointer+1]); }, /* From the current position, has previous item? @return boolean */ hasPrev:function() { return is(this[this.pointer-1]); }, /* Increments the pointer and returns the new current value @return mixed */ next:function() { if(this.hasNext() == false) return false; else { this.pointer++; return this.current(); } }, /* Decrements the pointer and returns the new current value @return mixed */ prev:function() { if(this.hasPrev() == false) return false; else { this.pointer--; return this.current(); } }, /* Returns the first array index containing the value. equiv to PHP: current(array_keys($A,$val)) @param mixed @return int */ key:function(val) { var ret = false; for(var index=0; indexb?[b,a]:[a,b], i; for(i = Math.floor(c[0]); i<=Math.floor(c[1]); i++) ret.push(i); return ret; }, rand:function(a,b) { if(!is(a,'number') || !is(b,'number')) return false; if(a==b) return a; var c = a>b?[b,a]:[a,b] return Math.floor((Math.random()*((c[1]+1)-c[0])) + c[0]); }, /* add up values @param mixed array:sums array values , or sums up numeric arguments to any end @return int Math.sum(1,2,3,4); // same as Math.sum([1,2,3,4]); */ sum:function(args) { if(!is(args,'array')) args = XUtil.toArray(arguments); var s=0, vals = args.filter(function(i){ return is(i,'number'); }); for(var i = 0; i tags. The innerHTML is now a node tree you can manipulate. @param string SGML @return object DIV */ stringToNode:function(string) { var div = document.createElement('DIV'); div.style.display='inline'; if(!is(string.replace,'function')) Error.IllegalArgument( new Error("XUtil.stringToNode") , 'String' , string); else div.innerHTML = string.replace(//g, ""); return div; }, /* A slightly shorter way to do obj.getElementsByTagName(tag), but with the added benefit of having the return object be a real array and not a enumerable object, allowing use the native and extended Array methods. */ getTags:function(obj,tag) { if(!is(tag) && is(obj,'string')) // perhaps we said ('li') meaning (document, 'li') { tag = obj; obj = document; } if(!is(obj,'object')) return Error.IllegalArgument( new Error("XUtil.getTags") , 'HTML Object' , obj); if(!is(tag,'string')) return Error.IllegalArgument( new Error("XUtil.getTags") , 'String - tagName' , tag); return XUtil.toArray( (function(){ return this.getElementsByTagName(tag)}).call(obj) ); }, /** Still use obj.getAttribute(a) to get a value for a single attribute. Use getAttribs(obj) to attempt to get them all (including any DOM property that is scalar). @param object @return object property:value pairs */ getAttribs:function(obj) { if(!is(obj,'object')) return Error.IllegalArgument( new Error("XUtil.getAttribs") , 'HTML Object' , obj); var attribs={}; for(var a in obj) { if(is(obj[a]) && (is(obj[a],'string') || is(obj[a],'number'))) { eval('attr={'+a+':obj[a]}'); XO.x(attribs,attr); } } return attribs; }, /* Returns, as best it can, the true four corner position of an object @param object @return object {top , bottom, left, right} */ getPosition:function(obj) { if(!is(obj,'object')) return Error.IllegalArgument( new Error("XUtil.getPosition") , 'HTML Object' , obj); var left = 0, top = 0, ret = {}; o=new XO(obj); while (is(o.offsetParent)) { left += o.offsetLeft; top += o.offsetTop; o = o.offsetParent; } left += o.offsetLeft; top += o.offsetTop; ret.left=left; ret.top=top; var css = XUI.getCSS(obj); ret.right = ret.left + this.toNum(css['width']); ret.bottom = ret.top + this.toNum(css['height']); return ret; }, keys:function(obj) { if(is(obj,'array')) return Math.range(0,obj.length-1); var r=[]; if(is(obj,'object')) { for(var i in obj) r.push(i); } return r; }, sprintf:function() { /** (modified) * Javascript sprintf * http://www.webtoolkit.info/ **/ if (typeof arguments == "undefined") { return null; } if (arguments.length < 1) { return null; } if (typeof arguments[0] != "string") { return null; } if (typeof RegExp == "undefined") { return null; } var string = arguments[0]; var exp = new RegExp(/(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g); var matches = new Array(); var strings = new Array(); var convCount = 0; var stringPosStart = 0; var stringPosEnd = 0; var matchPosEnd = 0; var newString = ''; var match = null; var convert = function(match, nosign){ if (nosign) { match.sign = ''; } else { match.sign = match.negative ? '-' : match.sign; } var l = match.min - match.argument.length + 1 - match.sign.length; var pad = new Array(l < 0 ? 0 : l).join(match.pad); if (!match.left) { if (match.pad == '0' || nosign) { return match.sign + pad + match.argument; } else { return pad + match.sign + match.argument; } } else { if (match.pad == '0' || nosign) { return match.sign + match.argument + pad.replace(/0/g, ' '); } else { return match.sign + match.argument + pad; } } } while (match = exp.exec(string)) { if (match[9]) { convCount += 1; } stringPosStart = matchPosEnd; stringPosEnd = exp.lastIndex - match[0].length; strings[strings.length] = string.substring(stringPosStart, stringPosEnd); matchPosEnd = exp.lastIndex; matches[matches.length] = { match: match[0], left: match[3] ? true : false, sign: match[4] || '', pad: match[5] || ' ', min: match[6] || 0, precision: match[8], code: match[9] || '%', negative: parseInt(arguments[convCount]) < 0 ? true : false, argument: String(arguments[convCount]) }; } strings[strings.length] = string.substring(matchPosEnd); if (matches.length == 0) { return string; } if ((arguments.length - 1) < convCount) { return null; } var code = null; var match = null; var i = null; for (i=0; i $Id$ Inspired by Knickers Errors, this javascript aims to provide better detail about things that go wrong. Unlike Knickers, however, the first argument of your Error should be a "new Error()" - this gets you the backtrace ability. You may optionally provide a message in that error, but one will likely be generated based on the type of error you call. example use: if(!foo) Error.IllegalArgument( new Error() , 'a number!' , foo); */ // include a backtrace? Error.trace = true; /* Only members of Error should really be using this show errors in the firebug console (TODO: or some place useful for non FB'ers?) @param mixed (object Error , string) @return void */ Error.log = function(err) { // got firebug? if(typeof console == 'undefined') { // you have no firebug, and I'll have to figure something out for you //besides, alert(err.message || err); } else if(is(console,'object') && is(console.error,'function')) { console.error( err.message || err); if(Error.trace) console.trace(err); } }; /* Gets details like line number and filename from the error object @param object Error @return string */ Error.details = function(errorObj) { return "\nLine "+errorObj.lineNumber + ' of ' + errorObj.fileName + ( errorObj.message=='' ? '' : "\n"+errorObj.message); } /** * Error for 'illegal argument' error types. * These types are meant to be used when a parameter of the wrong kind is passed to a function * They can also be used when a function is operating on a variable that does not have the correct properties @param object Error @param string What the function was expecting @param mixed Whatever the function received instead (message will show type and contents) @return object Error */ Error.IllegalArgument=function(errorObj, expected,received) { errorObj.message = 'Illegal Argument : Expected ' +expected+'. Received '+is(received,'type') + ' ( '+(is(received,'function')?'function()':received)+" )" + Error.details(errorObj); Error.log( errorObj ); return; }; /** * Error for 'illegal procedure' error types. * This type is meant to be used when a function is called "out of turn"; * that is, some other prerequisite function must be called first. @param object Error @param string message @return object Error */ Error.IllegalProcedure=function(errorObj , message) { errorObj.message = 'Illegal Procedure : '+message + Error.details(errorObj); Error.log( errorObj ); return; } /** Coax IE into telling you a little more about an object. It wont tell you what it *is*, but it'll tell you what it *has*, and maybe that'll be enough */ /** // uncomment as needed Error.dumpObj=function(obj) { var d = ""; for(var i in obj) { d+=i+' : '+ obj[i]+"\n"; } alert(d); }//*/ // CONSOLE FOR IE? /** // yeah right , bbby :P if(typeof console == 'undefined') { console = document.createElement('DIV'); console.id = "debug_console"; XUI.css(console,{ position:'fixed', bottom:'0px', left:'0px', width:'100%', height:'80px', overflow:'scroll', border:'0', borderTop:'2px groove black' }); console.log = function() { this.show(); this.innerHMTL += "
"+XUtil.toArray(arguments).join("    "); }; console.error = function() { this.show(); this.innerHMTL +="
!!! "+XUtil.toArray(arguments).join("    "); }; console.trace = function(){ return; }; console.show = function() { if(!is(document.getElementById(this.id))) { document.body.appendChild(this); var close = document.createElement('SPAN'); close.onclick = function(){ console.hide() }; close.innerHTML = 'hide'; XUI.css(close, { color:'red', cursor:'pointer' }); this.appendChild(close); } this.style.display = 'block'; }; console.hide = function() { this.style.display = 'none'; }; } //*/ /* Handy dandy SassieX UI Object @requires XO.js @requires Error.js @author bibby $Id$ */ var XUI= { /* If you want show-hideable elements, pass them through XUI.visibilityToggle, and they'll learn how. XUI.visibilityToggle( obj ); obj may now show(), hide(), showhide() "showhide" was chosen over "toggle" to avoid conflicts To begin in a hidden state, call hide() as soon as it is able (or better yet in your css): XUI.visibilityToggle( obj ).hide(); elements may also control the visibility of objects other than themselves by supplying the controlling object as an argument. XUI.visibilityToggle( obj_tar , obj_ctrl) obj_tar now has show(), hide(), showhide(). obj_ctrl can toggle obj_tar with _show() , _hide(), _showhide(); The reason for the underscores in the naming is to allow foo to also control itself (or be controlled by something else) without conflict. Controllers aren't given "hide me" functions, though. so, no underscore == toggle this with underscore == toggle bound element @param object target to show hide @param object (optional) object to control the target */ visibilityToggle:function(target, controller) { if(!is(target,'object')) return Error.IllegalArgument(new Error(), 'visibilityToggle HTML Element', target); if( is(controller,'object') ) { XO.x(controller , { _showhideTarget:target, _showhide:function() { this._showhideTarget.showhide(); return this; }, _show:function() { this._showhideTarget.showhide(false); return this; }, _hide:function() { this._showhideTarget.showhide(true); return this; } }); } XO.x(target, { hidden:false, showhide:function(hidden) { if(is(hidden,'boolean')) this.hidden=hidden; else this.hidden = !this.hidden; // toggle if(!is(this.style)) Error.IllegalProcedure(new Error(),' visibilityToggle not yet bound to an element (that has .style)'); else this.style.display = this.hidden? 'none' : ''; return this; }, show:function() { this.showhide(false); return this; }, hide:function() { this.showhide(true); return this; } }); return target; }, /** Determines if the three arguments are of the proper types @access private (by intent) @param string event type @param object event target @param function event action @return boolean */ _validEvent:function(type, obj, fn) { if(!is(obj,'object')) return Error.IllegalArgument( new Error() , 'object' , obj); if(!is(type,'string')) return Error.IllegalArgument( new Error() , 'string' , type); if(!is(fn,'function')) return Error.IllegalArgument( new Error() , 'function' , fn); // if( ![ *list of event types?* ].in(type.toLowerCase)) // return Error.IllegalArgument( new Error() , 'valid event type ("click", not "onclick")' , type); return true; }, /* var to store which event model we are to use (Mozilla vs Microsoft deathmatch) */ eventModel:null, /* Creates a test object to test for event methods, and sets this.eventModel according to what it found. @return int representing a model */ getEventModel:function() { var sp = document.createElement('SPAN'); this.eventModel=0; if(sp.addEventListener) this.eventModel = 1; // FF else if(sp.attachEvent) this.eventModel = 2; // IE else this.eventModel = -1; // ?? XUI.getEventModel = function(){ return this.eventModel; }; return this.eventModel; }, /* Events may be bubbling (and certainly are with IE). This method returns the highest level "true target", the smallest bubble. This function is used as a workaround to IE's using the "this" keyword as window in attached Events. @param event @return object */ getEventTarget:function(e) { var targ; if (!e) var e = window.event; if (e.target) targ = e.target; else if (e.srcElement) targ = e.srcElement; if (targ.nodeType == 3) // defeat Safari bug targ = targ.parentNode; return targ; }, /** Add an event listener to the object. Unlike the native addEventListener (which is still used here), we're going to register the event in a member array in the object. obj.events -> .click , .mouseover , etc That way, we have easy access to it (especially when removing); More importantly, the registered event executes the function stored in .events, so it can be changed on the fly! @param string event type @param object event target @param function event action @param boolean Execute during capture phase (true) or bubble phase (false) @return EventStub */ addEvent:function(type, obj, fn, capture) { if(is(this.eventModel,'null')) this.getEventModel(); if(this._validEvent(type, obj, fn) !== true) return false; type = type.toLowerCase(); if(!is(obj.events,'object')) obj.events = {}; if(!is(obj.events[type],'array')) obj.events[type] = []; var eventIndex = obj.events[type].push([fn,capture]) - 1; var eventFn = function(obj,type,eventIndex,e) { obj = XUI.getEventTarget(e); if(obj && obj.events && obj.events[type] && obj.events[type][eventIndex]) obj.events[type][eventIndex][0].call(obj,e); else if((is(obj.parentNode))) { do { obj = obj.parentNode; if(obj && obj.events && obj.events[type] && obj.events[type][eventIndex]) { obj.events[type][eventIndex][0].call(obj,e); obj=false; } } while(is(obj.parentNode)) } }; switch(this.eventModel) { case 1: obj.addEventListener(type , (function(e){ eventFn(this,type,eventIndex,e)}) , capture); break; case 2: obj.attachEvent('on'+type, (function(e){ eventFn(this,type,eventIndex,e)}) ); break; } var stub = {'type':type , index:eventIndex }; if(is(obj.id)) stub.id = obj.id; else if(is(obj.name)) stub.name = obj.name; else stub.obj = obj; return new XUI.EventStub(stub); }, /** A constructor for stubs, packets that are returned by registering events. Should you need to reference an Event for replacing or removing, please keep your stub. @access private (but needs to be a member of XUI so that we can check instanceof) @param @return object instanceof XUI.EventStub */ EventStub:function(stub) { XO.x(this,stub); }, /** Once you've registered for event, you might want to know where it went. This function returns what is stored in .events with just an object an function "type" and "index" corelate to obj.events. If type were 'click' and index 1 , you'll get: obj.events.click[0] ( == [fn,capture] ) @access public @param object @param function @return array of stored event vars [ type , fn , capture, index] */ getEvent:function(obj , fn) { if(!is(obj,'object')) return Error.IllegalArgument( new Error() , 'object' , obj); if(obj instanceof XUI.EventStub) return this.getStubEvent(obj); if(!is(fn,'function')) return Error.IllegalArgument( new Error() , 'function' , fn); if(!is(obj.events,'object')) return false; var ret = false; for(var t in obj.events) { obj.events[t].each(function(i) { if(this[0] == fn) { // [type, fn, capture, index?] ret = { type:t, fn:this[0], capture:this[1], index:i }; } }); } return ret; }, /** gets an Event from an EventStub @access private @param */ getStubEvent:function(stub) { var obj = false; if(stub.id) obj = document.getElementById(stub.id); else if(stub.name) { var names = XUtil.toArray(document.getElementsByName(stub.name)); if(names.length == 0) return Error.IllegalArgument(new Error() , 'HTMLElement matching stub.name', stub.name); obj = names[0]; } else if(is(stub.obj,'object')) obj = stub.obj; if(!is(obj.events) || !is(obj.events[stub.type]) || !is(obj.events[stub.type][stub.index])) return Error.IllegalArgument(new Error() , 'HTMLElement with event matching stub', obj); var e = obj.events[stub.type][stub.index]; return { type:stub.type, fn:e[0], capture:e[1], index:stub.index, obj:obj }; }, /** Dropping an event listener means remembering the object, function, type, and capture phase. This function lets you skip two and drop by object and function. I *hope* you kept a reference to that fn ;) getEvent() returns the missing pieces @param object @param function @return boolean */ dropEvent:function(obj, fn) { var e = this.getEvent(obj,fn); if(is(e,'object')) { if(is(e.obj,'object')) { obj=e.obj; } return this._dropEvent( obj, e); } return false; }, /** The interal function to finally drop the event from the object. @access private (by intent) @param object @param array of stored event vars [ type , fn , capture, index] */ _dropEvent:function(obj, e) { if(this._validEvent(e.type, obj, e.fn) !== true) return false; if(obj['obj']) { e = new XO(obj); obj = e['obj']; } switch(this.eventModel) { case 1: obj.removeEventListener(e.type, e.fn, e.capture); break; case 2: obj.detachEvent('on'+e.type, e.fn); break; } delete obj.events[e.type][e.index]; return true; }, /** Removes event listeners @param object @param string type to clear ( !Warning! giving no type clears all events!) */ clearEvents:function(obj,type) { if(!is(obj,'object')) return Error.IllegalArgument( new Error() , 'object' , obj); if(is(type) && !is(type,'string')) return Error.IllegalArgument( new Error() , 'string' , type); if(!is(obj.events,'object')) return; for(var t in obj.events) { if(is(type) && t != type.toLowerCase()) continue; obj.events[t].each(function() { XUI.dropEvent(t,obj,this[0],this[1]); }); } }, /** Replace one event with another @param object @param function Old function @param function New function @param boolean , capture|bubble phase. If omitted, stays the same */ replaceEvent:function(obj, oldFn, fn, capture) { //work arounds for stubs for now. if(obj instanceof XUI.EventStub) { var stub = this.getStubEvent(obj); fn = oldFn; obj = stub.obj; obj.events[stub.type][stub.index]=[fn,stub.capture]; return; } if(!this._validEvent('foo', obj, fn)) return false; if(!is(oldFn,'function')) return Error.IllegalArgument( new Error() , '"old" function' , oldFn); var e = this.getEvent(obj,oldFn); if(!is(e,'array')) return false; obj.events[e[0]][e[3]] =[ fn , (is(capture,'boolean') ? capture : e[2])] ; return true; }, /** wax on , wax off, only with the mouse. Applies two event listeners for rollovers @param object @param function mouseover @param function mouseout */ hover:function(obj, onFn, offFn) { if(!is(obj, 'object')) return Error.IllegalArgument( new Error , 'object' , obj); if(!is(onFn, 'function')) return Error.IllegalArgument( new Error , 'function (onFn)' , onFn); if(!is(offFn, 'function')) return Error.IllegalArgument( new Error , 'function (offFn)' , offFn); // two EventStubs return [this.mouseover( obj, onFn ) , this.mouseout( obj, offFn )]; }, // ### EVENT TYPES ### // // see http://www.quirksmode.org/dom/events/ for compatibility comparisons /** register a blur event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ blur:function(obj, fn, capture) { return this.addEvent('blur', obj , fn , !!capture); }, /** register a change event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ change:function(obj, fn, capture) { return this.addEvent('change', obj , fn , !!capture); }, /** register a click event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ click:function(obj, fn, capture) { return this.addEvent('click', obj , fn , !!capture); }, /** register a contextmenu event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ contextmenu:function(obj, fn, capture) { return this.addEvent('contextmenu', obj , fn , !!capture); }, /** register a copy event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ copy:function(obj, fn, capture) { return this.addEvent('copy', obj , fn , !!capture); }, /** register a cut event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ cut:function(obj, fn, capture) { return this.addEvent('cut', obj , fn , !!capture); }, /** register a dblclick event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ dblclick:function(obj, fn, capture) { return this.addEvent('dblclick', obj , fn , !!capture); }, /** register a focus event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ focus:function(obj, fn, capture) { return this.addEvent('focus', obj , fn , !!capture); }, /** register a keydown event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ keydown:function(obj, fn, capture) { return this.addEvent('keydown', obj , fn , !!capture); }, /** register a keypress event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ keypress:function(obj, fn, capture) { return this.addEvent('keypress', obj , fn , !!capture); }, /** register a keyup event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ keyup:function(obj, fn, capture) { return this.addEvent('keyup', obj , fn , !!capture); }, /** get a key from an onkey event @param event */ getKey:function(e) { var NS = (window.Event) ? 1 : 0; var evt = (NS) ? e : event; var code = (NS) ? e.which : event.keyCode; var key = String.fromCharCode(code); return key; }, /** register a mousedown event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ mousedown:function(obj, fn, capture) { return this.addEvent('mousedown', obj , fn , !!capture); }, /** register a mousemove event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ mousemove:function(obj, fn, capture) { return this.addEvent('mousemove', obj , fn , !!capture); }, /** register a mouseout event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ mouseout:function(obj, fn, capture) { return this.addEvent('mouseout', obj , fn , !!capture); }, /** register a mouseover event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ mouseover:function(obj, fn, capture) { return this.addEvent('mouseover', obj , fn , !!capture); }, /** register a mouseup event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ mouseup:function(obj, fn, capture) { return this.addEvent('mouseup', obj , fn , !!capture); }, /** register a mousewheel event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ mousewheel:function(obj, fn, capture) { return this.addEvent('mousewheel', obj , fn , !!capture); }, /** register a paste event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ paste:function(obj, fn, capture) { return this.addEvent('paste', obj , fn , !!capture); }, /** register a reset event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ reset:function(obj, fn, capture) { return this.addEvent('reset', obj , fn , !!capture); }, /** register a resize event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ resize:function(obj, fn, capture) { return this.addEvent('resize', obj , fn , !!capture); }, /** register a scroll event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ scroll:function(obj, fn, capture) { return this.addEvent('scroll', obj , fn , !!capture); }, /** register a select event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ select:function(obj, fn, capture) { return this.addEvent('select', obj , fn , !!capture); }, /** register a submit event to an object. @param object @param function @param boolean Capture phase (true) or bubble phase (false). Default is false */ submit:function(obj, fn, capture) { return this.addEvent('submit', obj , fn , !!capture); }, // ### /EVENTS // // ## CSS /* obj.style is only aware of style rules set by javascript, and not those set inline or by style sheets. This method attempts to get the fully rendered "computed style" of the object - the real style. ! not fully tested for IE (obj.currentStyle) @param object @return object property:value pairs */ getCSS:function(obj) { if(!is(obj,'object')) return Error.IllegalArgument( new Error() , 'HTML Object' , obj); if(document.defaultView && document.defaultView.getComputedStyle) return document.defaultView.getComputedStyle(obj,''); else if(obj.currentStyle) { return obj.currentStyle; } }, /* Get or Set a css rule. First param is the object. If second param is a string, say 'color', the color value will be returned from the real computed style. If second param is an object of property:value's , these will be set into the style. ! not fully tested for IE (obj.currentStyle) @param object @param mixed (string gets, object sets) @return object */ css:function(obj , getset) { if(!is(obj,'object') || !is(obj.style)) return Error.IllegalArgument(new Error() , 'HTML Object' , obj); if(!is(getset)) return false; if(is(getset,'string')) { var css = this.getCSS(obj); return is(css[getset]) ? css[getset] : ''; }; if(is(getset,'object')) { for(var css in getset) if(css in obj.style) obj.style[css] = getset[css]; else if(css == 'className' || css =='class') obj.className = getset[css]; } return obj; } }; /** DOM Element creator. Elm is an object that shouldn't be modified. Methods take any number of arguments, however the first should be an object of element attributes Those thereafter will be nodes to append. Elm.p( {id:'chocobo',className:'wark'}, 'WARK! ', Elm.b({},'WARK!'), 'WARK!'); Strings and Numbers are converted into text nodes, and regular objects are simply appended. @author bibby @requires XO.js @requires Error.js @requires XUtil.js */ var Elm = new (function() { var tags=['strong','b','p','em','tt','div','span','a','dd','dt','ul','ol','li','h1','h2','h3','h4','h5','h6','table','tbody','tr','td','th','thead','tfoot','form','input','textarea']; tags.each(function(i, me) { var tag = this.toString(); me[tag]=function(attribs) { var content = XUtil.toArray(arguments); content.shift(); return Elm.createNode(tag, attribs, content); }; }, this); //generic text node this.text = function(txt) { return document.createTextNode(txt); }; // shortcuts this.button = function(attribs, value) { return this.createNode('input', XO.x(attribs, {type:'button', 'value':value}) ); }; this.checkbox = function(attribs, state) { return this.createNode('input', XO.x(attribs, {type:'checkbox', 'checked':!!state}) ); }; this.script= function(src) { return this.createNode('script', {type:'text/javascript', 'src':src} ); }; this.styleSheet= function(cssfile) { return this.createNode('link', {type:'text/stylesheet', 'href':cssfile, media:'all'} ); }; /** @access private @param string tagName @param object html attribute name:value pairs @param array innerHTML parts. May be strings, numbers, objects/nodes/elements */ this.createNode=function(tag, attribs, content) { var elm = document.createElement(tag); if(!is(elm,'object')) return Error.IllegalArgument( new Error('couldn\'t create element!'), 'Elm.createNode HTML tagName', tag); // fix the word 'class' to 'className' if(is(attribs,'object')) { ['class','cls'].each(function() { if(is(attribs[this],'string')) { attribs.className = attribs[this]; } }); } // apply attributes to the element XO.x(elm,attribs); // content is almost guaranteed to be an array if(!is(content,'array')) content=[]; //return Error.IllegalProcedure(new Error(), 'createNode called publically with bad args?!'); //attach child nodes to the new element content.each(function() { elm.appendChild( Elm.parseContent(this) ); }); // give new element new methods Elm.api(elm); return elm; }; /** @access private @param mixed , stuff you want to append @return object DOM node */ this.parseContent = function(content) { switch(is(content,'type')) { case 'array': content = content.join(', '); case 'number': case 'string': content = document.createTextNode(content); case 'object': if(!is(content.tagName,'string')) break; } return content; }; this.api = function(elm) { XO.x(elm,{ attr:function(get,set) { if(is(get,'object')) { XUtil.keys(get).each(function(k,elm) { elm.attr(this,get[this]); },this); return true; } if(is(set,'undefined')) return this.getAttribute(get); else { this.setAttribute(get,set); return set; } }, css:function(req) { if(is(XUI,'object') && is(XUI.css,'function')) return XUI.css(this,req); } }); return elm; } }); Form = function(frm) { if(is(frm)) { if(!is(frm.tagName,'string') || frm.tagName.toUpperCase() !='FORM') return Error.IllegalArgument(new Error(), 'HTML Form Element' , frm); } else return new Form(new Elm.form()); XO.x(frm,{ serialize:function() { var data=[]; this.namedItems().each(function(k,frm) { if(is(frm[this].serialize,'function')) data.push(frm[this].serialize()); },this); return data.filterFalse().join('&'); }, ajax:function(){}, json:function(){}, namedItems:function() { var names=[]; XUtil.toArray(frm.elements).each(function() { if(!names.has(this.name)) names.push(this.name); }); return names; } }); frm.namedItems().each(function(k) { if(!is(frm[this].serialize,'function')) { switch(frm[this].type) { case 'text': case 'textarea': Form.FormInputText(frm[this]); break; case 'select': case 'select-one': Form.FormSelect(frm[this]); break; case 'checkbox': Form.FormCheckbox(frm[this]); break; case 'hidden': Form.FormInput(frm[this]); break; case undefined: if(is(frm[this][0]) && is(frm[this][0].type) && frm[this][0].type.toUpperCase() == 'RADIO') Form.FormRadio(frm[this]); break; } } }); return frm; }; Form.FormInput = function(inp) { XO.x(inp,{ serialize:function() { if(is(this.name)) { return this.name+'='+encodeURIComponent(this.val()); //ref: http://xkr.us/articles/javascript/encode-compare/ } }, val:function() { return '_VALUE_'; } }); Elm.api(inp); return inp; }; Form.FormInput_SingularText = function(inp) { Form.FormInput(inp); XO.x(inp, { //ref: http://blog.vishalon.net/Post/57.aspx getCaret:function() { var caret = 0; if (document.selection) //ie { this.focus (); var sel = document.selection.createRange(); sel.moveStart ('character', -this.value.length); caret = sel.text.length; } else if (this.selectionStart || this.selectionStart == '0') //ff caret = this.selectionStart; return (caret); }, setCaret:function(at) { if(this.setSelectionRange) { this.focus(); this.setSelectionRange(at,at); } else if (ctrl.createTextRange) { var range = this.createTextRange(); range.collapse(true); range.moveEnd('character', at); range.moveStart('character', at); range.select(); } }, insert:function(open,end) { var isIE = (document.all)? true : false; var open = (open)? open : ""; var end = (end)? end : ""; if (isIE) { this.focus(); var curSelect = document.selection.createRange(); curSelect.text = open + curSelect.text + end; } else if (!isIE && typeof this.selectionStart != "undefined") { var selStart = this.value.substr(0, this.selectionStart); var selEnd = this.value.substr(this.selectionEnd, this.value.length); var curSelection = this.value.replace(selStart, '').replace(selEnd, ''); this.value = selStart + open + curSelection + end + selEnd; } else { this.value += open + end; } } }); return inp; }; Form.FormInputText = function(inp) { Form.FormInput_SingularText(inp); XO.x(inp, { val:function(set) { if(!is(set)) return this.value; else { this.value=set; return set; } } }); return inp; } Form.FormSelect = function(inp) { Form.FormInput(inp); XO.x(inp,{ getLabel:function(v) { if(!is(v) && v!=0) v = this.val(); for(var i =0;i+~ ,]+$/, typeSelector : /(^[^\[:]+?)(?:\[|\:|$)/, tag : /^(\w+|\*)/, id : /^(\w*|\*)#/, classRE : /^(\w*|\*)\./, attributeName : /(\w+)(?:[!+~*\^$|=])|\w+/, attributeValue : /(?:[!+~*\^$|=]=*)(.+)(?:\])/, pseudoName : /(\:[^\(]+)/, pseudoArgs : /(?:\()(.+)(?:\))/, nthParts : /([+-]?\d)*(n)([+-]\d+)*/i, combinatorTest : /[+>~ ](?![^\(]+\)|[^\[]+\])/, combinator : /\s*[>~]\s*(?![=])|\s*\+\s*(?![0-9)])|\s+/g, recursive : /:(not|has)\((\w+|\*)?([#.](\w|\d)+)*(\:(\w|-)+(\([^\)]+\))?|\[[^\}]+\])*(\s*,\s*(\w+|\*)?([#.](\w|\d)+)*(\:(\w|-)+(\([^\)]+\))?|\[[^\}]+\])*)*\)/gi } var arrayIt = function(a){ if( !!(window.attachEvent && !window.opera) ) { return function(a){ if( a instanceof Array ) return a; for( var i=0, result = [], m; m = a[ i++ ]; ) result[ result.length ] = m; return result; }; } else { return function(a){ return Array.prototype.slice.call(a); }; } }(); // Filters a list of elements for uniqueness. function filter( a, tag ) { var r = [], uids = {}; if( tag ) tag = new RegExp( "^" + tag + "$", "i" ); for( var i = 0, ae; ae = a[ i++ ]; ) { ae.uid = ae.uid || _uid++; if( !uids[ae.uid] && (!tag || ae.nodeName.search( tag ) !== -1) ) { r[r.length] = uids[ae.uid] = ae; } } return r; } // getAttribute - inspired by EXT -> http://extjs.com // Copyright(c) 2006-2008, Ext JS, LLC. // http://extjs.com/license function getAttribute( e, a ) { if( !e ) return null; if( a === "class" || a === "className" ) return e.className; if( a === "for" ) return e.htmlFor; return e.getAttribute( a ) || e[a]; } function getByClass( selector, selectorRE, root, includeRoot, cacheKey, tag, flat ) { var result = []; if( !!flat ) { return selectorRE.test( root.className ) ? [root] : []; } if( root.getElementsByClassName ) { result = arrayIt( root.getElementsByClassName( selector) ); if( !!includeRoot ) { if( selectorRE.test( root.className ) ) result[ result.length ] = root; } if( tag != "*" ) result = filter( result, tag ); cache[ cacheKey ] = result.slice(0); return result; } else if( doc.getElementsByClassName ) { result = arrayIt( doc.getElementsByClassName( selector ) ); if( tag != "*" ) result = filter( result, tag ); cache[ cacheKey ] = result.slice(0); return result; } var es = (tag == "*" && root.all) ? root.all : root.getElementsByTagName( tag ); if( !!includeRoot ) es[ es.length ] = root ; for( var index = 0, e; e = es[ index++ ]; ) { if( selectorRE.test( e.className ) ) { result[ result.length ] = e; } } return result; } function getById( selector, root, includeRoot, cacheKey, tag, flat ) { var rs, result = []; if( !!flat ) { return getAttribute( root, "id" ) === selector ? [root] : []; } if( root.getElementById ) { rs = root.getElementById( selector ); } else { rs = doc.getElementById( selector ); } if( rs && getAttribute( rs, "id" ) === selector ) { result[ result.length ] = rs; cache[ cacheKey ] = result.slice(0); return result; } var es = root.getElementsByTagName( tag ); if( !!includeRoot ) es[ es.length ] = root ; for( var index = 0, e; e = es[ index++ ]; ) { if( getAttribute( e, "id" ) === selector ) { result[ result.length ] = e; break; } } return result; } function getContextFromSequenceSelector( selector, roots, includeRoot, flat ) { var context, tag, contextType = "", result = [], tResult = [], root, rootCount, rootsLength; reg.id.lastIndex = reg.typeSelector.lastIndex = reg.classRE.lastIndex = 0; if( !reg.tag.test( selector ) ) selector = "*" + selector; context = reg.typeSelector.exec( selector )[1]; roots = roots instanceof Array ? roots.slice(0) : [roots]; rootsLength = roots.length; rootCount = rootsLength - 1; if( reg.id.test( context ) ) { contextType = "id"; tag = (tag = context.match( /^\w+/ )) ? tag[0] : "*"; context = context.replace( reg.id, ""); } else if( reg.classRE.test( context ) ) { contextType = "class"; tag = (tag = context.match( reg.tag )) ? tag[0] : "*"; context = context.replace( reg.tag, "" ); contextRE = persistCache[context + "RegExp"] || (persistCache[context + "RegExp"] = new RegExp( "(?:^|\\s)" + context.replace( /\./g, "\\s*" ) + "(?:\\s|$)" )); context = context.replace( /\./g, " " ) } while( rootCount > -1 ) { root = roots[ rootCount-- ]; root.uid = root.uid || _uid++; var cacheKey = selector + root.uid; if( cacheOn && cache[ cacheKey ] ) { result = result.concat( cache[ cacheKey ] ); continue; } if( contextType === "id" ) { tResult = getById( context, root, includeRoot, cacheKey, tag, flat ); } else if( contextType === "class" ) { tResult = getByClass( context, contextRE, root, includeRoot, cacheKey, tag, flat ); } else { /* tagname */ tResult = arrayIt( root.getElementsByTagName( context ) ); if( !!includeRoot && (root.nodeName.toUpperCase() === context.toUpperCase() || context === "*") ) tResult[tResult.length] = root; } result = rootsLength > 1 ? result.concat( tResult ) : tResult; cache[ cacheKey ] = result.slice(0); } return result; } peppy = { query : function( selectorGroups, root, includeRoot, recursed, flat ) { var elements = []; if( !recursed ) { // TODO: try to clean this up. selectorGroups = selectorGroups.replace( reg.trim, "" ) // get rid of leading and trailing spaces .replace( /(\[)\s+/g, "$1") // remove spaces around '[' of attributes .replace( /\s+(\])/g, "$1") // remove spaces around ']' of attributes .replace( /(\[[^\] ]+)\s+/g, "$1") // remove spaces to the left of operator inside of attributes .replace( /\s+([^ \[]+\])/g, "$1" ) // remove spaces to the right of operator inside of attributes .replace( /(\()\s+/g, "$1") // remove spaces around '(' of pseudos .replace( /(\+)([^0-9])/g, "$1 $2") // add space after + combinator .replace( /['"]/g, "") // remove all quotations .replace( /\(\s*even\s*\)/gi, "(2n)") // replace (even) with (2n) - pseudo arg (for caching) .replace( /\(\s*odd\s*\)/gi, "(2n+1)"); // replace (odd) with (2n+1) - pseudo arg (for caching) } if( typeof root === "string" ) { root = (root = getContextFromSequenceSelector( root, doc )).length > 0 ? root : undefined; } root = root || doc; root.uid = root.uid || _uid++; var cacheKey = selectorGroups + root.uid; if( cacheOn && cache[ cacheKey ] ) return cache[ cacheKey ]; reg.quickTest.lastIndex = 0; if( reg.quickTest.test( selectorGroups ) ) { elements = getContextFromSequenceSelector( selectorGroups, root, includeRoot, flat ); return (cache[ cacheKey ] = elements.slice(0)); } var groupsWorker, groups, selector, parts = [], part; groupsWorker = selectorGroups.split( /\s*,\s*/g ); groups = groupsWorker.length > 1 ? [""] : groupsWorker; // validate groups for( var gwi = 0, tc = 0, gi = 0, g; groupsWorker.length > 1 && (g = groupsWorker[ gwi++ ]) !== undefined;) { tc += (((l = g.match( /\(/g )) ? l.length : 0) - ((r = g.match( /\)/g )) ? r.length : 0)); groups[gi] = groups[gi] || ""; groups[gi] += (groups[gi] === "" ? g : "," + g); if( tc === 0 ) gi++; } var gCount = 0; while( (selector = groups[gCount++]) !== undefined ) { reg.quickTest.lastIndex = 0; if( reg.quickTest.test( selector ) ) { result = getContextFromSequenceSelector( selector, root, includeRoot, flat ) elements = groups.length > 1 ? elements.concat( result ) : result; continue; } reg.combinatorTest.lastIndex = 0; if( reg.combinatorTest.test( selector ) ) { var parts, pLength, pCount = 0, combinators, cLength, cCount = 0, result; parts = selector.split( reg.combinator ); pLength = parts.length; combinators = selector.match( reg.combinator ) || [""]; cLength = combinators.length; while( pCount < pLength ) { var c, part1, part2; c = combinators[ cCount++ ].replace( reg.trim, ""); part1 = result || peppy.query( parts[pCount++], root, includeRoot, true, flat ); part2 = peppy.query( parts[ pCount++ ], c == "" || c == ">" ? part1 : root, c == "" || c == ">", true, flat ); result = peppy.queryCombinator( part1, part2, c ); } elements = groups.length > 1 ? elements.concat( result ) : result; result = undefined; } else { result = peppy.querySelector( selector, root, includeRoot, flat ); elements = groups.length > 1 ? elements.concat( result ) : result; } } if( groups.length > 1 ) elements = filter(elements); return ( cache[ cacheKey ] = elements.slice(0)); }, queryCombinator: function( l, r, c ) { var result = [], uids = {}, proc = {}, succ = {}, fail = {}, combinatorCheck = peppy.simpleSelector.combinator[c]; for( var li = 0, le; le = l[ li++ ]; ) { le.uid = le.uid || _uid++ uids[ le.uid ] = le; } for( var ri = 0, re; re = r[ ri++ ]; ) { re.uid = re.uid || _uid++; if( !proc[ re.uid ] && combinatorCheck( re, uids, fail, succ ) ) { result[ result.length ] = re; } proc[ re.uid ] = re; } return result; }, querySelector : function( selector, root, includeRoot, flat ) { var context, passed = [], count, totalCount, e, first = true, localCache = {}; context = getContextFromSequenceSelector( selector, root, includeRoot, flat ); count = context.length; totalCount = count - 1; var tests, recursive; if( /:(not|has)/i.test( selector ) ) { recursive = selector.match( reg.recursive ); selector = selector.replace( reg.recursive, "" ); } // Get the tests (if there aren't any just set tests to an empty array). if( !(tests = selector.match( /:(\w|-)+(\([^\(]+\))*|\[[^\[]+\]/g )) ) tests = []; // If there were any recursive tests put them in the tests array (they were removed above). if( recursive ) tests = tests.concat( recursive ); // Process each tests for all elements. var aTest; while( (aTest = tests.pop()) !== undefined ) { var pc = persistCache[ aTest ], testFuncScope, testFunc, testFuncKey, testFuncArgs = [], isTypeTest = false, isCountTest = false; passed = []; if( pc ) { testFuncKey = pc[ 0 ]; testFuncScope = pc[ 1 ]; testFuncArgs = pc.slice( 2 ); testFunc = testFuncScope[ testFuncKey ]; } else if( !/^:/.test( aTest ) ) { // attribute var n = aTest.match( reg.attributeName ); var v = aTest.match( reg.attributeValue ); testFuncArgs[ 1 ] = n[ 1 ] || n[ 0 ]; testFuncArgs[ 2 ] = v ? v[ 1 ] : ""; testFuncKey = "" + aTest.match( /[~!+*\^$|=]/ ); testFuncScope = peppy.simpleSelector.attribute; testFunc = testFuncScope[ testFuncKey ]; persistCache[ aTest ] = [ testFuncKey, testFuncScope ].concat( testFuncArgs ); } else { // pseudo var pa = aTest.match( reg.pseudoArgs ); testFuncArgs[ 1 ] = pa ? pa[ 1 ] : ""; testFuncKey = aTest.match( reg.pseudoName )[ 1 ]; testFuncScope = peppy.simpleSelector.pseudos; if( /nth-(?!.+only)/i.test( aTest ) ) { var a, b, nArg = testFuncArgs[ 1 ], nArgPC = persistCache[ nArg ]; if( nArgPC ) { a = nArgPC[ 0 ]; b = nArgPC[ 1 ]; } else { var nParts = nArg.match( reg.nthParts ); if( nParts ) { a = parseInt( nParts[1],10 ) || 0; b = parseInt( nParts[3],10 ) || 0; if( /^\+n|^n/i.test( nArg ) ) { a = 1; } else if( /^-n/i.test( nArg ) ) { a = -1; } testFuncArgs[ 2 ] = a; testFuncArgs[ 3 ] = b; persistCache[ nArg ] = [a, b]; } } } else if( /^:contains/.test( aTest ) ) { var cArg = testFuncArgs[1]; var cArgPC = persistCache[ cArg ]; if( cArgPC ) { testFuncArgs[1] = cArgPC; } else { testFuncArgs[1] = persistCache[ cArg ] = new RegExp( cArg ); } } testFunc = testFuncScope[ testFuncKey ]; persistCache[ aTest ] = [ testFuncKey, testFuncScope ].concat( testFuncArgs ); } isTypeTest = /:(\w|-)+type/i.test( aTest); isCountTest = /^:(nth[^-]|eq|gt|lt|first|last)/i.test( aTest ); if( isCountTest ) testFuncArgs[ 3 ] = totalCount; // Now run the test on each element (keep only those that pass) var cLength = context.length, cCount = cLength -1 ; while( cCount > -1 ) { e = context[ cCount-- ]; if( first ) { e.peppyCount = cCount + 1; } var pass = true; testFuncArgs[ 0 ] = e; if( isCountTest ) testFuncArgs[2] = e.peppyCount; if( !testFunc.apply( testFuncScope, testFuncArgs ) ) { pass = false; } if( pass ) { passed.push(e); } } context = passed; first = false; } return passed; }, simpleSelector: { attribute: { "null": function( e, a, v ) { return !!getAttribute(e,a); }, "=" : function( e, a, v ) { return getAttribute(e,a) == v; }, "~" : function( e, a, v ) { return getAttribute(e,a).match(new RegExp('\\b'+v+'\\b')) }, "^" : function( e, a, v ) { return getAttribute(e,a).indexOf( v ) === 0; }, "$" : function( e, a, v ) { var attr = getAttribute(e,a); return attr.lastIndexOf( v ) === attr.length - v.length; }, "*" : function( e, a, v ) { return getAttribute(e,a).indexOf( v ) != -1; }, "|" : function( e, a, v ) { return getAttribute(e,a).match( '^'+v+'-?(('+v+'-)*('+v+'$))*' ); }, "!" : function( e, a, v ) { return getAttribute(e,a) !== v; } }, pseudos: { ":root" : function( e ) { return e === doc.getElementsByTagName( "html" )[0] ? true : false; }, ":nth-child" : function( e, n, a, b, t ) { // Unobtrusive version // var parent = e.parentNode; // if( !parent ) return false; // // e.uid = e.uid || _uid++; // parent.uid = parent.uid || _uid++; // // var parentCache = cache[ "pos" + parent.uid ]; // // if( !parentCache ) { // var node = e.parentNode.firstChild, // count = 0, // last, // cacheHash = {}, // cacheArr = []; // for( ; node; node = node.nextSibling ) { // if( node.nodeType == 1 ) { // node.uid = node.uid || _uid++; // cacheArr[ count ] = node.uid; // cacheHash[ node.uid ] = ++count; // } // } // parentCache = cache[ "pos" + parent.uid ] = { posList : cacheArr, // posHash : cacheHash, // length : count}; // } // // var position = parentCache.posHash[ e.uid ]; // if( n == "first" ) // return position == 1; // if( n == "last" ) // return position == parentCache.length; // if( n == "only" ) // return parentCache.length == 1; // return (!a && !b && position == n) || // ((a == 0 ? position == b : // a > 0 ? position >= b && (position - b) % a == 0 : // position <= b && (position + b) % a == 0)); // Obtrusive but faster version - the problem with this version ( which is similar to what // is seen in other libraries ) is that as soon as the DOM changes results will potentially be incorrect. // Specifically, if a node in question gets a new sibling, its position will then be different if it is // anything but the first or last child. The nature of this code (to keep it performant) is to not recalculate // a position. The unobtrusive version above is very similar to this, the only difference is that it stores // positions in cache instead of as a property of the element. As soon as the DOM changes the cache is cleared // so with the unobtrusive version the position will be recalculated. if( !e.nodeIndex ) { var node = e.parentNode.firstChild, count = 0, last; for( ; node; node = node.nextSibling ) { if( node.nodeType == 1 ) { last = node; node.nodeIndex = ++count; } } last.IsLastNode = true; if( count == 1 ) last.IsOnlyChild = true; } var position = e.nodeIndex; if( n == "first" ) return position == 1; if( n == "last" ) return !!e.IsLastNode; if( n == "only" ) return !!e.IsOnlyChild; return (!a && !b && position == n) || ((a == 0 ? position == b : a > 0 ? position >= b && (position - b) % a == 0 : position <= b && (position + b) % a == 0)); }, ":nth-last-child" : function( e, n ) { return this[ ":nth-child" ]( e, n, a, b ); }, // TODO: n is not right. ":nth-of-type" : function( e, n, t ) { return this[ ":nth-child" ]( e, n, a, b, t); }, ":nth-last-of-type" : function( e, n, t ) { return this[ ":nth-child" ](e, n, a, b, t ); }, // TODO: n is not right. ":first-child" : function( e ) { return this[ ":nth-child" ]( e, "first" ); }, ":last-child" : function( e ) { return this[ ":nth-child" ]( e, "last" ); }, ":first-of-type" : function( e, n, t ) { return this[ ":nth-child" ]( e, "first", null, null, t ); }, ":last-of-type" : function( e, n, t ) { return this[ ":nth-child" ]( e, "last", null, null, t ); }, ":only-child" : function( e ) { return this[ ":nth-child" ]( e, "only" ); }, ":only-of-type" : function( e, n, t ) { return this[ ":nth-child" ]( e, "only", null, null, t ); }, ":empty" : function( e ) { for( var node = e.firstChild, count = 0; node !== null; node = node.nextSibling ) { if( node.nodeType === 1 || node.nodeType === 3 ) return false; } return true; }, ":not" : function( e, s ) { return peppy.query( s, e, true, true, true ).length === 0; }, ":has" : function( e, s ) { return peppy.query( s, e, true, true, true ).length > 0; }, ":selected" : function( e ) { return e.selected; }, ":hidden" : function( e ) { return e.type === "hidden" || e.style.display === "none"; }, ":visible" : function( e ) { return e.type !== "hidden" && e.style.display !== "none"; }, ":input" : function( e ) { return e.nodeName.search( /input|select|textarea|button/i ) !== -1; }, ":radio" : function( e ) { return e.type === "radio"; }, ":checkbox" : function( e ) { return e.type === "checkbox"; }, ":text" : function( e ) { return e.type === "text"; }, ":header" : function( e ) { return e.nodeName.search( /h\d/i ) !== -1; }, ":enabled" : function( e ) { return !e.disabled && e.type !== "hidden"; }, ":disabled" : function( e ) { return e.disabled; }, ":checked" : function( e ) { return e.checked; }, ":contains" : function( e, s ) { return s.test( (e.textContent || e.innerText || "") ); }, ":parent" : function( e ) { return !!e.firstChild; }, ":odd" : function( e ) { return this[ ":nth-child" ]( e, "2n+2", 2, 2 ); }, ":even" : function( e ) { return this[ ":nth-child" ]( e, "2n+1", 2, 1 ); }, ":nth" : function( e, s, i ) { return s == i; }, ":eq" : function( e, s, i ) { return s == i; }, ":gt" : function( e, s, i ) { return i > s; }, ":lt" : function( e, s, i ) { return i < s; }, ":first" : function( e, s, i ) { return i == 0 }, ":last" : function( e, s, i, end ) { return i == end; } }, combinator : { "" : function( r, u, f, s ) { var rUID = r.uid; while( (r = r.parentNode) !== null && !f[ r.uid ]) { if( !!u[ r.uid ] || !!s[ r.uid ] ) { return (s[ rUID ] = true); } } return (f[ rUID ] = false); }, ">" : function( r, u, f, s ) { return r.parentNode && u[ r.parentNode.uid ] ; }, "+" : function( r, u, f, s ) { while( (r = r.previousSibling) !== null && !f[ r.uid ] ) { if( r.nodeType === 1 ) return r.uid in u; } return false; }, "~" : function( r, u, f, s ) { var rUID = r.uid; while( (r = r.previousSibling) !== null && !f[ r.uid ] ) { if( !!u[ r.uid ] || !!s[ r.uid ] ) { return (s[ rUID ] = true); } } return (f[ rUID ] = false); } } } } // From John Resig -> http://ejohn.org/blog/thoughts-on-queryselectorall/ // Copyright 2008, John Resig (http://ejohn.org/) // released under the MIT License if ( doc.querySelectorAll ) { (function(){ var oldpeppy = peppy.query; peppy.query = function(sel, context){ context = context || doc; if ( context === doc ) { try { return context.querySelectorAll(sel); } catch(e){} } return oldpeppy.apply(oldpeppy, arrayIt(arguments)); }; })(); } else { // If the DOM changes we need to clear the cache because it will no longer be reliable. // Inspired by code from Sizzle -> http://github.com/jeresig/sizzle/tree/master. // Copyright 2008, John Resig (http://ejohn.org/) // released under the MIT License var aEvent = doc.addEventListener || doc.attachEvent; function clearCache(){ cache = {}; } aEvent("DOMAttrModified", clearCache, false); aEvent("DOMNodeInserted", clearCache, false); aEvent("DOMNodeRemoved", clearCache, false); } if( !($ = window.$) ) { $ = peppy.query; } })(); /** * peppy helpers *@author bibby *$Id$ */ /*** $id *** @param string */ function $id(str) { return $first('#'+ str.replace(/\#\s/,'') ); } /*** $first *** @param string selector */ function $first(sel) { return $(sel)[0]; }