- Home
- XO.js
- protos.js
- XUI.js
- XUtil.js
- Error.js
- Elm.js
- ElmForm.js
What is XO?
Largely, XO is the name that I've given the family of classes that depend on the core XO.js, but at the heart of that object is the ability to extend itself — in fact, that's all it does. XO stands for "extendable object", but it also makes a good Battlestar reference.
Using this object, I'm able to quickly build, extend, or inherit from objects. The project as a whole has grown to include event handling, element creation, and some helper functions for common tasks.
This was a personal experiment that I doubt will ever see a "release". I'm more than happy to share its code, but it's not something that I think anyone will find useful given the abundance of great alternatives.
Why XO?
After finding out what jQuery was all about in mid-2007, I was instantly hooked. I'd always been pretty decent coding in javascript, but jQuery made everything so easy that it became part of my standard practice overnight. That's kind of scary if I think about it.
I took a few days to acquire and familiarize myself with the other "big libs" that were on the scene (prototype, mootools, et al) and began rating their potential in a real world setting. When embarking on these tests, I added two items to the list of things to investigate: "having no lib at all" and "rolling our own".
The road without any library/framework at all was a familiar one, and I know where that road leads — lots of code, occasional compatibility headache.
XO started almost a year after all of this, to answer the final question, "What would I have for myself if none of these libs were on the table?".
The things that I've learned creating this "library" (if that's what it is) were invaluable, and definitely good for me. It's not very finished, and I don't know it will ever be, but an early conclusion that I made was a predictable one: "just use jQuery!" (the Company has finally listened btw, which I applaud). This subsite is basically to document what this stuff does pending the possible (but eventual) abandonment of this effort.
I Can Has It?
Yes! — but hold on a minute. This site was written on 09 NOV 2008, and there still needs to be a consolidation of the instances of XO I have lying around. This consolidation will likely break a few project pages, and I'd really like to show these. In the meantime, there is source available for each file at the top of each page, but please understand that these might not be current or best versions.
Things obviously missing
a-plenty
- selectors (considering adopting peppy!)
- animations (css wtf)
- (wishful) easy key bindings (though I've something else)
view source
XO.js contains a function, is()and the XO class.
is(var (,[string type]))
"is" is a type checking function, or a simple test for "thruthiness", as Doug Crockford uses the word. This function is a modified version of a "better typeof" located in his site.. Here, I feel I have taken this a step father than it's handling of Array and NULL to include other cases.
You'll find that typeof and is may disagree on a lot of things, but that is intentional. I wanted is() to provide an answer of a type given the context of how the variable is intended to be used. Here's one example:
>> var n = new Number(8);
>> typeof n;
<< "object"
>> is(n,"type"); // to ask for its type, supply "type" as an arg.
<< "number"
// this is important because of how I intend to use n
>> n*=2
<< 16
"object" is far to vague and frequent a return of typeof, and doesn't clue me into what methods are available to it, like String.replace or Array.reverse, whereas is aims to. This is especially handy in testing is a variable is a function before you try to use it as such.
Omitting the second argument is a test for truth. Null, undefined, NaN, and empty strings return false; all others true. Valid second arguments are "type" to have the type returned, or be returned a boolean from "string","number","undefined","object","array","function", or a class name (checks if is an instanceof).
>> var a=1, b=function(){}, c=undefined, d=new XO();
>> is(a);
<< true
>> is(c);
<< false
>> is(a,"number");
<< true
>> is(b,"function");
<< true
>> is(c,"undefined");
<< true
>> is(d,XO);
<< true
XO(obj)
XO is the extendable object, and all it can do is extend. You have the option of adding member vars to it as you instantiate it.
>> var foo = new XO();
>> var bar = {bar:"bar", baz:function(){} };
>> foo.extend(bar);
>> console.dir(foo) ; // a firebug command, like var_dump for php
<< bar : bar
<< baz : function()
Originally, it was thought "new XO()" would happen a lot, and there would be a bunch of little objects running around extending themselves — but this didn't end up being the case at all. I gave XO, the namespace that could construct an extendable object when used as a function, was given functions of its own that didn't get applied to new XO()s (I love javascript for this). The equivilent to this in PHP is to call a class method statically, but when you construct an instance, the resulting object doesn't have that mehtod. I did this so as not to crowd the new objects.
XO.x(obj,obj)
I get back and forth with the name x and extend because of jQuery. For all intents and purposes they are the same, and when I type x I'm really saying "extend" to myself. This method crams the second object to make the first one larger.
>> var a={a:1}, b={b:2};
>> XO.x(a,b);
// a ==> {a:1,b:2}
In the other files that are a part of this lib, this is how most of the assignments are made. I very much enjoy the json "singleton" syntax over adding things individually to the prototype. Crockford would have you do:
// the "right" way?
var Foo = function(){};
Foo.prototype.bar = function()
{
return "bar";
};
Foo.prototype.baz = function()
{
return this.bar().replace(/r/g,'z');
}
// with XO we can pass all members at once.
var Foo = function(){};
XO.x(Foo.prototype,{
bar:function()
{
return "bar";
},
baz:function()
{
return this.bar().replace(/r/g,'z');
},
});
In most of my XO experiment pages, I don't even mess with the prototype at all, just the instance, making "classes" just factories for objects larger than their spec. It's hard to say if this is good or bad (probably bad because operations repeat) — so for example's sake:
function BaseBall_Pitcher(members)
{
XO.x(this, new BaseBall_Player(members)); // inherits ability to play ball!
XO.x(this, {
pitch:function(){ /* throw */},
signal:function(){ /* signal */}
});
}
var dicek = new BaseBall_Pitcher({name:"Daisuke Matsuzaka",num:18});
view source
XUI, is an amazing little creation, with event handling that I am very proud of (and could argue is better than most other libs). As a helper object, it became very apparent that some functions were utilities and others just for (as I saw them) interface — so a split was made and they became XUI and XUtil respectively. XUI is a stand alone object, not a constructor.
The problem with Events
Internet Explorer and Firefox are significantly different in this regard. For one, the method names aren't even the same. One "bubbles" where the other "captures" (the order in which functions are called with nested elements), and the syntax that is allowed within the event functions themselves (particularly the "this" keyword) is different.
The solution
XUI bridges that gap with a simple API, and can be trusted for IE and FF (Safari, Opera, I'm sorry, I really have no idea).
Unlike most libs, and definitely unlike setting an event handler directly (foo.onclick=..), this object allows for multiple events
for the same type on the same object. And best of all, you get a "stub" back when you set one, allowing you to reference that event again if you ever have to drop or replace it. The stub itself is an object containing an index and a copy of the function, but in order to use it, you don't need to care what's in it.
EVENT TYPES (browser support may vary, easy to add if one's missing)
blur
change
click
contextmenu
copy
cut
dblclick
focus
keydown
keypress
keyup
mousedown
mousemove
mouseout
mouseover
mousewheel
paste
reset
resize
scroll
select
submit
hover :: a special double of mouseover + mouseout
Each of these are members of XUI and (with the exception of hover) have the same API and return a stub.
var stub = XUI.click( elm , wasClicked );
IE or not, keyword "this" references the object that was clicked.
function wasClicked()
{
if(!is(this.clickedtimes))
this.clickedtimes = 0;
this.clickedtimes++;
alert( 'I was clicked '+this.clickedtimes+' times!');
}
altering events
Once you have an event registered, you can drop or replace it easily* (easier if you keep your stubs).
XUI.clearEvents(obj); // drops all events on the element
XUI.clearEvents(obj,type); // will drop all events on an element of the specified type ('click');
XUI.replaceEvent(obj, oldFn, newFn); // for element, replaces oldFn with newFn.
// provided you have a non-stub reference (say, wasClicked).
// stubs remember what elm and what fn
XUI.replaceEvent( stub, newFn);
XUI.dropEvent(obj , fn); // drop fn from element obj
XUI.dropEvent(stub);
hover takes an additional funciton for mouseout, and returns an array of two EventStubs
var dbl_stub = XUI.hover( elm, waxOn, waxOff);
XUI.dropEvent( dbl_stub[0] );
XUI.dropEvent( dbl_stub[1] );
visibility toggle
Making things show and hide (the concept often abbreviated around the office as "showhide") is a common enough task that was included in XUI.
There are two pieces to this puzzle that I saw : the thing that showhides, and the thing that makes it showhide. First thing to do is pass them both:
XUI.visibilityToggle(target, controller);
What's happened now is that each of these have been given new methods. target has gained show, hide, and showhide (the toggle), and controller gains _show, _hide, and _showhide that both control target as well. This is so that you don't have to remember what you set as a target so long as you know what the controller is. The reason for the underscores is to allow something else to now name the former controller as a new target. Those functions could have easily been renamed showTarget and hideTarget, now that I think about it. (Generally, I notice underscore-prefixed functions to denote that the programmer shouldn't use them directly, like a private function).
XUI.click( controller , function(){ this._showhide(); }); // toggles target when clicked
view source
manipulated native prototypes?! O NOES!
Most of the time, I'm with you on that, especially in the case of Object.prototype . But honestly, Arrays in my mind could use a litle extra love. protos.js is mostly some additional methods for native Array, but also some for Math.
Foremost among them is Array.filter, something that Firefox has natively that IE doesn't. It's super useful, and this file includes the Mozilla distributed version of this method for browsers that don't have it. Other additions are akin to PHP in a way, and some just for "the hammer" (a coworker who like "hasNext()? next()" );
A QUICK RUNDOWN
# iterator
mixed current()
void reset()
bool hasNext()
bool hasPrev()
mixed next()
mixed prev()
void each(callback)
#search
int key(value) // like array_search for php
bool has(value) // like in_array for php
#filter
array even()
array odd()
array filter(callback)
array filterFalse()
array filterOut( val [ ,val ,val ...])
The iterative functions are pretty self explanatory, and honestly I've used none of them except for testing. each() on the other hand is solid gold; it accepts a function to apply to each item of the array.
var concat=new String();
['a','b','c'].each(function(i)
{
concat+= [this,i].join(',');
});
>> console.log(concat);
<< a,0,b,1,c,2
Filters are fun(ctions)! Array.filter takes a function as an argument that returns true or false on whether or not the value should remain.
// get values >= 10
var a = [1,11,2,22,3,33];
var hi = a.filter(function(){return this>=10;});
>>> console.log(hi);
<<< [11,22,33];
But I found myself wanted not to write a unique function for some common things, so I wrote a couple of shortcut methods
[].filterFalse() removes values that don't pass is()
>>> var test = [undefined, false, null, 0, true, 1, [], {} ];
>>> test.filterFalse()
<<< [true, 1, [], Object]
[].filterOut() takes an unlimited number of arguments to kick out.
>>>var test = [1,2,3,4,5,6,7,8,9,5,5,5];
>>> test.filterOut(1,5,7);
<<< [2,3,4,6,8,9];
TODO: unique ?
Math additions
Some addition math helpers
array Math.range(lo,hi) // like range in php
int Math.rand(lo,hi) // like rand in php
float Math.sum( a [,b,c,d,e,..]) // adds args
float Math.avg( a [,b,c,d,e,..]) // averages args
view source
Error
Error is a native javascript object, but different implementations between browsers have them at varying degrees of usefulness. These extentions to Error don't aim to bridge gap separating browsers — but they do intend to help you debug your problem (or at least the lib's problem).
The two biggies were inspired by Knickers, a PHP Framework the company has written (and I'm struggling with learning — my fault).
If set up right, Error.IllegalArgument should report to the user that an error happened, what kind of variable it expected, and what it got instead (type and value).
Error.IllegalProcedure is thrown when something is attempted that is too soon in a sequence of events.
if( !is( foo , 'function') )
return Error.IllegalArgument( new Error(), 'function', foo);
if( !is(content,array) )
return Error.IllegalProcedure(new Error(), 'createNode called with bad args?! Are you using this statically? Don\'t!');
The first argument to either of these should be a new Error() — this is amazing benefits for Firefox with Firebug.
Your message is applied to the Error in the case of IllegalProcedure, but with IllegalArgument more information is given about the received var.
Using the Error that was created in the function call, Firefox will give you the line number and filename, and firebug will backtrace all the function calls leading up to the error. Neat!
Did I think about making more useful Errors for IE?
Yes. For about 10 minutes.
view source
Elm
Elm is all about easy one-line DOM element creation, inspired by this jQuery plugin by Michael Geary.
Instead of $.TD({attribs},content) it's Elm.td({attribs},content)
I didn't notice a method in Geary's for a plain text node, so I made Elm.text for a plain text node (attaching these things expects Elements/Nodes and not willy-nilly strings). I recently made a HTMLTag class for PHP (for fun basically), but I should probably add support for arrays of stuff.
TODO: add support for arrays of child nodes
There are a few shortcut methods for things like buttons and scripts that use generally the same values with a few variable things.
// Elm.tag(obj attribs, mixed innerHTML) for the following:
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
// adding new tags is as easy as tacking it onto the tags array
// SHORTCUTS
text( string)
button(obj attribs,string value)
checkbox(obj attribs, bool checked)
script(string src)
styleSheet(string href)
Gift API
Elements create with Elm have two additional methods, attr and css, and they work mostly like jQuery's, except for a different kind of get-set flexibility.
With jQuery, if css receives one string, it's a get. If it gets an object, it's a set. If it gets two strings, then it's assumed a set with first being key and second the value. These Element's calls to css are
calls back to XUI, and I simply did not add that. So the Elements call to css() knows get from set by the data type it's given - string for get, object for set.
attr on the other hand behaves the same way jQuery does. This inconsistency is noteable as a weakness. To echo Ajaxian, those who love jQuery love the API — and I'm handily one of those.
// color all paragraphs blue
XUtil.getTags('p').each(function(){ this.css({color:'blue'}); });
Now about gifts, Elm has a method that gives these functions to preexisting elements if you feel that it helps you, Elm.api().
// give attr and css to all inputs
XUtil.getTags('input').each( function(){ Elm.api(this) } );
view source
XUtil
XUtil is an object that I'd historically call oddjob. It holds a series of methods that are for conversions or other utility that didn't seem to fit anywhere else. That's not to say there isn't good stuff in here ;)
XUtil.toArray converts objects to numeric arrays. You'd be surprised how often I need this!. Take for example the all-too-common document.getElementsByTagName(). That returns an array of elements, right? WRONG! It's a HTMLCollection,
which is more of a DOM item than it is a javascript one. Sure you can loop over it in a for loop, but you don't get any of the special methods that native Arrays do, including those that XO's protos.js add to arrays.
So this makes it quick and painless to convert that to an array.
var divs = XUtil.toArray(document.getElementsByTagName('div'));
var hidden_divs = divs.filter(function(){return this.style.display=='none';});
That out of the way, XUtil also has a quicker way to get these elements by tag name that converts them to arrays for you, including limit scope!
TODO: frak, reverse (obj,tag)! assume document if undefined
var inputs = XUtil.getTags(document, 'input');
// not only are all the inputs, but they're in a real array with native/extended methods, yay!
XUtil.toNum(string) is an easy way to parse a number out of a string, and is a little more forving about certain things.
TODO: test cases
I wondered for while how jQuery was able to parse arbitrary text into real DOM nodes that could have it's children sought after, and it didn't take long
to discover that a dummy div was being used and having its innerHTML set. The act of div.innerHTML btw is an amazing action that we owe a lot of thanks to browser vendors for;
it parses the text into contextual nodes with which we can operate on later as if they were real objects (because at that point, they are!).
XUtil.stringToNode() aims to copy this.
var html = XUtil.stringToNode("<p><u>foo</u> and <em>maybe</em><strong>bar</strong></p>");
var bold = XUtil.getTags('b',html);
XUtil.getAttributes returns a key-value pair set of attributes in an element
var attribs = XUtil.getAttributes( document.getElementById('foo') )
Xutil.getPosition is a handy little function that returns the final position of an element relative to the top-left corner of the page by transversing the offset from its parent all the way up the node tree. This was found somewhere on the internet, and has been pretty trustworthy.
The only thing that I added was addtional bottom and right attributes that rely on style's width and height, and are unfortunately not so reliable.
var pos = XUtil.getPosition( document.getElementById('foo') );
<<< pos = {left:100,top:20,right:240,bottom:80}
XUtil.keys is like array_keys in PHP, but for javascript objects. It returns an array of key-names, be it an array or actual object.
var categories = XUtil.keys( election_data );
<<< categories == ['candidates','pres','senate','house','summary'];
XUtil.sprintf is simply badass, and does most everything you can do in PHP. This function came from http://www.webtoolkit.info/ .
>>> var foo = XUtil.sprintf("%X",456);
<<< "1C8"
view source
ElmForm
This class aims to emulate jQuery's awesomeness with regards to $('form').seralize();, a way of converting a form's elements in their current state into a querystring suitable for get and post via ajax or direct query. With this, it's even possible to submit forms without a form element, or even create forms on the fly and keep them in memory without being attached to the page at all.
There is a test page here.
The methodology is simple and prototypal. Teach each form element how to conform to the same API: "What is your value?", "Set your value", "Convert to querystring". If you have an actual form element, setting this up is as simple as :
Form(document.forms[0]);
Each of the form's elements are gifted with the appropriate methods for getting, setting, and serializing. The form needs only to ask to be seralized to get its current state in the form of a querystring.
var qs = document.forms[0].serialize();
The beauty of this is that val(), as jQuery uses it, is a member function of each element, type-based, so that it knows how to appropriately respond to gets and sets. The form's serialize is a simple call to each of its elements own serialize function.
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('&');
}
val, as mentioned before is a getter-setter in the style of jQuery.
To supply no argument to the function is to get its current value. To supply an argument is to set the value to that, in accordance with what the element expects as a value.
Some bonus attention was given to text-type fields, thanks in no small part to an entry at http://blog.vishalon.net/Post/57.aspx . getCaret and setCaret reference the position of the marker used while editing text, allowing for smart inserts and wraps, which text type elements now have if Form is called.
If a user had "selected" some text using the mouse (drag) or keys (select-directional, or select-control-directional), then the two values would be added to the boundaries of the selection, else be added as they are alongside each other.
textarea.insert('[b]','[/b]');