Posts Tagged ‘X’

X JavaScript: Detecting, Adding and Removing Class Names

I want to post a quick bit here about three methods I use in the X library which make use of element classnames. First a method to detect if an element has a given class:

X.hasClass = function(element, $class) {
    var pattern = new RegExp("(^| )" + $class + "( |$)");
    //ternary to choose
    return pattern.test(element.className) ? true : false;
};

Breaking that down, you have the function taking two arguments. One, the element to check for the presence of the class name and two, the class name itself. Next, a RegEx is defined which will attempt to match the class name you passed in. The RexEx itself is made so that a portion of a string can be matched because of the way that classnames are applied to elements on the DOM, i.e. “spam eggs vikings”. Last, the ternary (return pattern.test…) executes depending on what the test() method returns. Remember, test() is a function of the RegEx object which returns a bool depending on a positive(or lack of) match. Test() is going to return a ‘true’ of the class name is found, ‘false’ if not.If you are unfamiliar with ternary operators, let’s explain by looking at what an if/else doing the same thing would look like.

if (pattern.test(element.className)) {
    return true;
}
else {
    return false;
}

That works, but it’s not the lean and mean js machine we are going for here. Now, the one line of the ternary:

return pattern.test(element.className) ? true : false;

And it works like this:
an action -something to evaluate- ? do if true : do if false

So, in practice you could check if an element had a class name by calling:

X.hasClass(myElement, 'myClass');

hasClass is going to return a Bool though, so usually you will use it as part of an expression:

if(X.hasClass(myElement, 'myClass') {
    do stuff if true
}

I rarely use hasClass outright, but the next function does.

To add a class to a single element or a collection of elements we have this:

X.addClass = function(element, $class) {
    var i;
    //is the element array-like?
    if(element.length) {
        for (i = 0; i < element.length; i++) {
            if (!X.hasClass(element[i], $class)) {
                element[i].className += element[i].className === "" ?
                $class : " "+$class;
            }
        }
    }
    else { //not array-like
        if (!X.hasClass(element, $class)) {
            element.className += element.className === "" ?
                $class : " "+$class;
        }
    }
    return element;
};

In Summary, addClass takes 2 arguments, an element and a class name, checks to see if the element passed in is a single element or a collection of elements, then checks the element(s) to see if they have the passed in class name (via hasClass). If not, element is then checked for the presence of any other classes. Why? If there is another class name already assigned to the element(s) you need to insert a space in front of the new class being appended. Finally the class name you passed in is appended (as long as it’s not already there) and the element is returned. Let’s combine the get method with this in a couple of practical examples:

Say you have an HTML page with these four divs contained in a fifth div:

<div id="stripes">
    <div id="div1"></div>
    <div id="div2"></div>
    <div id="div3"></div>
    <div id="div4"></div>
</div>

And maybe some styles to go with them:

#div1, #div2, #div3, #div4 {
    height: 50px;
    margin-top:10px;
    border-bottom: 1px solid #000;
}
.green {
    background-color: #7f9e7b;
}
.slate {
    background-color: #424242;
}
.grey {
    background-color: #888;
}

So, on a goofy little test page you might have this:

Classless divs

Classless divs


Since the get method returns either a single element or a collection of them, and since our new addClass function can handle either scenario you could grab all those divs and set their background to say, green, in one pass:

X.addClass(X.get('div', X.get('#stripes')), 'green');

Giving you this:

Now with some class (green to be exact)

Now with some class (green to be exact)

Breaking that expression down, recall that the get method will allow you to pass in a ‘context’ to restrain searching. Because I only wanted the divs inside of the div with the id of “stripes” I passed it (‘#stripes’) in along with the search for the generic ‘div’ tagname:

var divs = X.get('div', X.get('#stripes'));

You can break this up into 2 expressions if you want:

var stripes = X.get('#stripes');
var divs = X.get('div', stripes);

After getting the elements, I sent them to addClass. If you used the more verbose code above you would have:

var stripes = X.get('#stripes');
var divs = X.get('div', stripes);
X.addclass(divs, 'green');

In these examples the fact that addClass returns the element passed in is of no consequence. JavaScript functions always return a value, even if one is not specified (undefined in that case), so specifying element as the return value could just be ignored, but we are going to use it next to combine with removeClass.

Here is that removeClass function:

X.removeClass = function(element, $class) {
    var pattern = new RegExp("(^| )" + $class + "( |$)"),
    i;
    //is element array-like?
    if(element.length) {
        for (i = 0; i < element.length; i++) {
            element[i].className = element[i].className.replace(pattern, "$1");
            element[i].className = element[i].className.replace(/ $/, "");            
        }
    }
    else { //nope
        element.className = element.className.replace(pattern, "$1");
        element.className = element.className.replace(/ $/, "");
    }
    return element;
};

Before doing something pedestrian like just removing the ‘green’ classnames from the divs inside of ‘stripes’ let’s swing for the fence and remove the classnames, then add new ones all in one go. weeee!:

X.removeClass(X.addClass(X.get('div', X.get('#stripes')), 'slate'), 'green');

Now that test page would look like:

Class changed to 'slate'

Class changed to 'slate'

This works because of the fact that addClass is returning the element list containing all of the divs we want removeClass to operate on. If you look close you’ll notice that all you needed to do was to take the previous expression we used for adding the green classes at once:

X.addClass(X.get('div', X.get('#stripes')), 'green');

and wrapping it with the removeClass call:

X.removeClass([--previous expression--], 'slate');

There’s more to breakdown, but not too much. The removeClass function just uses the same methodology that addClass does for checking if element is an array-like object (has a length?) then looks for the passed in classname with a RegExp which will replace a match with an empty string using the ‘$1′ backreference (the second call to replace makes sure no whitespace is left over). Finally the element is returned, just like addClass. And, yes, you could invert the order to addClass(removeClass()), it doesn’t matter

All of these examples could have, obviously, been used on single elements. X.get, X.addClass and X.removeClass will take care of that for you. So if you wanted to change the second div back to green you would go:

X.removeClass(X.addClass(X.get('#div2'), 'green'), 'slate');

Notice there is no need to specify a context when using an id (it should be unique).

QueryString Manipulation With The X JavaScript Library

I’ve spent a couple of days lately coding against the Microsoft Bing maps api at work. I just put together a little method this morning to mimic .Net’s ‘Request.Querystring’ functionality because I am needing to grab either a zip code or a latitude/longitude from the URL. The function, named requestQueryString(), expects a string to be passed in which should be a ‘key’ part in one of the key:value pairs located in the url of the page being viewed. In other words, if the URL is

www.apage.aspx?zip=85226&lat=33.372154&lon=-111.754322

the ‘keys’ are ‘zip’, ‘lat’ and ‘lon’, while the values are ’85226′, ’33.372154′ and ‘-111.754322′. To fetch the value associated with zip you would do:

var zipcode = X.requestQueryString('zip');

This will return ’85226′ given the previous URL. For ‘lat’ or ‘lon’ it would be pretty much the same:

var latitude = X.requestQueryString('lat'),
longitude = X.requestQueryString('lat');

Here is the method itself. I’ll update the x.js page with the new code as well.

X.requestQueryString = function(key) {
    //get the whole qs minus the ?
    var qs = window.location.search.substring(1),
    //split at the '&'
    splitArr = qs.split('&'),
    //to hold what we really want
    keys = [],
    values = [],
    //the key passed in to get the value for
    patt = new RegExp("(^| )" + key + "( |$)");
    //remove any whitespace TODO do a bool to see if the qs has any %20's
    for (var i = 0; i < splitArr.length; i++) {
        var trimArr = splitArr[i].split('%');
        //dont want the '%20s'
        splitArr[i] = trimArr[0];
    }
    //split the arr into key & value arrays
    for (var j = 0; j < splitArr.length; j++) {
        keys[j] = splitArr[j].slice(0,splitArr[j].indexOf("="));
        values[j] = splitArr[j].slice(splitArr[j].indexOf("=")+1);
    }
    //look for a match for passed in key, if so...
    for (var k = 0; k < keys.length; k++) {
        //if key is matched, return its value
        if (patt.test(keys[k])) {
            return values[k];
        }
    }
};

It’s an early implementation and a couple of things are worth noting. I had an issue with whitespace because I get the lat/lon numbers from a webservice maintained elsewhere in my company, and elsewhere is not properly constraining the input. You could remove this:

for (var i = 0; i < splitArr.length; i++) {
        var trimArr = splitArr[i].split('%');
        //dont want the '%20s'
        splitArr[i] = trimArr[0];
}

if you know that whitespace will not be an issue. Leaving it in place doesn’t add much of any overhead as the split command will just do nothing as it won’t see any ‘%’.

X: Pt4. Object Literals, Convenience Methods, And Go!

Now that we have a shiny new object created called ‘X’, Let’s use it. I like to organize all of my JavaScripts by incorporating object literals (see Rebecca Murphey’s talk on the closely related object lateral [that's a joke btw, it was a misprint at jconf], the excellent book Simply Javascript, and of course The Good Parts ). Here’s a ‘stub’ for the objects we could use:

    var ObjectName = {
   
        cache: {},
        config: {},
        init: function() {},
        otherThing: function() {}
    }

I will typically use the ObjectName.cache{} to hold a snapshot of the page, or areas of the page that I don’t want to have to re-query the DOM for. If my page is not going to change via AJAX I can store fetched results there for further use/manipulation. The ObjectName.config{} has the same functionality as .cache{}, it’s just my way of seperating out more important info than the generic .cache{}. You could use one, both, or neither, really just a matter of taste.

For this to be of use we need a way to load it once the dom is ready. Let’s add a ‘ready’ type event to the X library then:

X.go = function(obj) {
    X.addEventHandler(window,"load",obj.init);
};

You’ll pass your object literal to the .go() method which will, after the page has loaded, call the init() method of the object you passed to it:

    X.go(ObjectName);

Where this goes depends on how you are constructing your site. For now, we’ll put this line just below each object literal created. Later we’ll look at a site-wide answer to automating the loading of scripts, see Paul Irish’s blogs for more on that…

With a way to bind event handlers, object literals to hold the handlers, and a method to load the scripts, now we can just write some really basic JavaScript to utilize this stuff. So maybe there’s a button that lives on a page:

    <html>
        <head>
            <script src='x.js' type='text/javascript'></script>
        </head>
        <body>
            <input type='button' id='say-hi'>Click Me</input>
            <!--your external .js file linked to this page-->
            <script src="objectName.js" type="text/javascript"></script>
        </body>
    </html>

We need to set an event handler on that button to fire when the user clicks it. In the init() function of objectName do something like:

init: function() {
    var button = X.get("#say-hi");
    X.addEventHandler(button, "click", ObjectName.doClick);
}

I’ll get to the ‘doClick’ function in a moment. First, I want to write myself a convenience method to shorten the verbosity of adding event handlers. This is optional, of course.

    X.$click = function(element, handler) {
        X.addEventHandler(element, 'click', handler);

Now, with the convenience method added to the X library we can revisit what we wrote earlier:

var button = X.get('#say-hi');
X.$click(button, objectName.doClick);

That’s getting better, but we can combine those 2 statements down to:

X.$click(X.get("#say-hi"), objectName.doClick);

I am using a ‘$’ as a sigil in front of the name of the convenience methods throughout ‘X’. I do this to keep any conflicts with keywords like ‘submit’, ‘blur’ or ‘focus’ from happening. Plus all the cool kids are using $…

OK, so about the doClick handler. Remember that X.addEventHandler takes a target element, an event, and a function to call when that event happens on that element. The doClick function call is in object literal notation here, it is an attribute of the object you have made. I’ve been using ObjectName as the object so far (the observant among you may have noticed that the file is objectName.js. I do this in the development phase of a project as I will usually have many separate objects, all of them their own individual .js files. So objectName.js will have one object defined in it, ObjectName. At deploy time, I’ll combine them into one .js file and minify it). ObjectName will have this named function as an attribute:

doClick: function(event) {
    alert("Hi!");
}

The event handler calls ObjectName.doClick() because you bound it to that element. Here’s the whole object then:

var ObjectName = {

    init: function() {
        X.$click(X.get("#say-hi"), objectName.doClick);
    },

    doClick: function(event) {
        alert("Hi!");
    }
};

X.go(ObjectName);

Don’t forget to include the call to the go() method, outside of the variable which passes in ObjectName and tells the browser to call ObjectName’s init() method when the DOM fires the ‘load’ event. The cool thing about an object like this is re-usability. If your site was littered with buttons which needed to say “Hi” when pressed (it could happen) you would only need to attach this object to a master page and it would attach the event to any button with an id of “say-hi”. Now, it is an id so there should only be one per page. If there are going to be multiple instances per page, use a class instead, and iterate over the nodelist that get returns with a for loop to attach events. Assuming there are multiple buttons on the page with a class=”say-hi” attribute you would do:

var ObjectName = {

    config: {
        say: "Hi!"
    },

    init: function() {
        var buttons = X.get(".say-hi");
        for (var i = 0;  i > buttons.length; i++) {
            X.$click(buttons[i], ObjectName.doClick);
        }
    },

    doClick: function(event) {
        alert(ObjectName.config.say);
    }
};

with this you wouldn’t have to worry about the number of elements with a class of “say-hi”, as all of them would be fitted with a click event handler that will call ObjectName.doClick(). Note the use of the config object which is an attribute of ObjectName. You can manipulate this attribute if you wanted, changing it during the page lifecycle, like say adding a toggle-type command in the doClick() function:

doClick: function(event) {
        alert(ObjectName.config.say);
        ObjectName.config.say === "Hi!" ? ObjectName.config.say = "Hi Again!" : ObjectName.config.say = "Hi!";
    }

Now the alert will alternate between “Hi!” and “Hi Again!”. Silly, but you get the idea.

Next, we’ll do a continuation of the object literal using more of the config and cache functionality, I need to get away from this keyboard now!

X: Part Three. The Get() Method.

Before moving into the construction of object literals, which is my preferred method of organizing my scripts, I want to take a post to discuss the get() method in the X library. The functionality the get() method provides is as follows:

  • return an element by its id
  • return an element or elements by class name
  • return an element or elements by tag name
  • given a context, return only elements within it via the above methods

Pretty straightforward yes? To save on the typing, let’s construct the method so that a search for an element use the css selectors ‘#’ for id, and ‘.’(a period) for class like so:

    X.get('#spam');

will return an element with id=”spam”, and to fetch all elements with class=”eggs” you would use:

    X.get('.eggs');

Finally a tag name search would just require the name of the tag:

    X.get('div');

This would return a node list of all div tags on the page. The use of a context argument:

    var divs = X.get('div'),
    //get only a tags in the 3rd div
    var anchors = X.get('a', divs[2]);

You could just pass the function as well:

    //get only a li elements in a certain ul
    var lis = X.get('li', X.get('#certain_ul'));

You get the idea…

Here’s the code for the method:

X.get = function(what, where) {
 
    //context is document by default or self if included
    where = where || document;

    //remove where from an array-like structure if needed
    //this is necessary with recursive use of get()
    if (where.length) {
        where = where[0];
    }

    var selector = what.substr(0, 1),
    name = what.slice(1),i,
    getArray = [], gotArray = [],
    pattern = new RegExp("(^| )" + name + "( |$)");
    //have queryselector or queryselectorall?
    switch (selector) {

        case '#': //is a get by id
            return where.querySelctor ? where.querySelctor(what) :
                where.getElementById(name);
            break;

        case '.': //is a get by class name
            //ie browsers...
            if (typeof document.all != "undefined") {
                //is there queryselectorall?
                if (where.querySelectorAll) {
                    return where.querySelectorAll(what);
                }
                else {
                    getArray = where.all;
                } //no ternary here because of unrelated results  
            }
            else {
                //Is there querySelectorAll()?
                if (where.querySelectorAll) {
                    return where.querySelectorAll(what);
                }
                else { //if not queryselectorall support
                    getArray = where.getElementsByTagName("*");
                } //same as above as far as ternary goes...
            }

            for (i = 0; i < getArray.length; i++) {
                if (pattern.test(getArray[i].className)) {
                    gotArray[gotArray.length] = getArray[i];
                }
            }
            return gotArray;
            break;

        default: //is a get by tag name
            return where.querySelectorAll ? where.querySelectorAll(what) :
                where.getElementsByTagName(what);
    }
};

A selector is gotten by slicing off the first ‘letter’ of the passed in identifier. The switch statement looks at that selector to decide what query to pursue. The root of the query is assigned as document by default (resulting in document.getElement….) or reassigned as the passed in context if one was passed. The getElementById query (‘#…) checks first for the newer w3 querySelector() support which is present in the newest versions of most browsers (including IE8 to some degree), then falls back to the old standard if false. The search by class name is slightly more complex only because there was no standard DOM method to query by class until querySelectorAll() (which, again, is only in the newest of browser versions). When this segment of the case is entered a quick IE vs standards check is conducted (IE will use document.all) and if no querySelectorAll() support is present the 2 arrays created and the RegExp are used to:

  • getArray – Holds all page elements
  • pattern – Test each element for the passed class name
  • gotArray – Hold all elements which pattern.test() returns true

gotArray is then returned. The last piece of the switch, fetch by tag name, is as simple as ID querying because of the presence of the native DOM method getElementsByTagName(). Again used as a fall back here in the case of no querySelectorAll().

I think thats a good enough explanation of this method moving into the next post…

X Project, Pt2: More on Events, Leaks, and IE

Picking up where I left off in ‘Hacking together’, which we’ll now call Pt.1, Let’s look at the entirety of the addEvents section of the library in browsers which aren’t named IE…

//check for standards support first
if (document.addEventListener) {
    //w3 standard events model
    X.addEventHandler = function(element,type,handler){
        element.addEventListener(type,handler,false);
    };

    X.removeEventHandler = function(element,type,handler){
        element.removeEventListener(type,handler,false);
    };

    X.stop = function(event) {
        event.stopPropagation();
    };

    X.prevent = function(event) {
        event.preventDefault();
    };    
} //end

Now to address the IE events model and the (hopefully) soon-to-be-a-thing-of-the-past memory leak issue. If we stroll back in time to 2005 there was a contest held over at Quirksmode for a JavaScript solution to the addEvent challenge. The winner was (to no great suprise) John Resig. That entire post saga is required reading imho. One of the judges of the contest, Dean Edwards wrote this solution, from which I am using a slightly modified version.

    else {
    //IE or other non standards event model-------------
    X.addEventHandler = function(element,type,handler) {
        //check for guid, assign one if not present
        if(!handler.$guid) handler.$guid = X.addEventHandler.guid++;
        //create a hash for the element's event types
        if(!element.events) element.events = {};
        //create a hash of event handlers for each element/event pair
        var handlers = element.events[type];
        if(!handlers) {
            handlers = element.events[type] = {};
            //store the existing handler if exists
            handler[0] = element["on" + type];
        }
        //store the event handler in the hash
        handlers[handler.$guid] = handler;
        element["on" + type] = X._handleEvent; //called when event fires
    };

    X.addEventHandler.guid = 1; //counter to generate the guids

    X.removeEventHandler = function(element,type,handler) {
        //delete a single handler from the hash table
        if(element.events && element.events[type]) {
            delete element.events[type][handler.$guid];
        }
    };

    X.prevent = function(event) {
        event.returnValue = false;
    };

    X.stop = function(event) {
        event.cancelBubble = true;
    };
   
    X._handleEvent = function(event) {
        //need a return val
        var returnVal = true;
        //IE uses a global event object
        event = event || window.event;
        //get a ref to the hash of event handlers
        var handlers = this.events[event.type];
        //execute each one
        for(var i in handlers) {
            this.$handleEvent = handlers[i];
            if(this.$handleEvent(event) === false) {
                returnVal = false;
            }
        }
        return returnVal;
    };
}

IE has a serious flaw if not corrected: When your attached function gets fired, the ‘this’ reference refers to the worthless ‘window’, when, in fact, it should refer to the parent object (I think I’m paraphrasing J.Resig there). This code addresses that by creating a hash table of event handlers with unique ids bound to your target element, assigning a ‘handleEvent’ method to be called when your event fires. This passes the object correctly and maintains scope (this). You should walk through this series of methods with IE’s developer tools JavaScript debugger, which is the best way to see how this works in real-time. Then there is a removeEventHandler to allow assigned handlers to be removed (more on that in a moment), as well as IE methods for preventing default behavior and stopping event propagation.

So, back to removeEvent and the IE memory leak possibility. This code does not use attachEvent() to handle events, which unless I am mistaken was the biggest problem with IE’s memory manager understanding which event handlers could be reclaimed by the garbage collector. See Douglas Crockford’s post here. Admittedly, this little portion of a discussion on the IE memory leak issue is a tiny drop in one seriously big-assed ocean. There is so much material out there on this subject alone you could get lost in it. I don’t really want to go down that path any further here. We’ll bind events without attachEvent, we’ll set a removeEvent handler on the events we bind and we’ll move on. A real solution would be don’t use IE, but I digress…

I have made a seperate page on the blog called ‘x.js’ for the code so far. Next I want to go into using object literals as a way to organize your code which will use the X library and some syntactic sugar to make for less verbose script, until then…

A Strange Idea Of Fun : Hacking Together A Cross-Browser JavaScript Library

As a way to really build-up my JavaScript Ninja Skills I have been foregoing luxuries like jquery and prototype for the strange and harsh reality of pure DOM scripting. I can’t really appreciate what a library is doing for me if I don’t understand whay it’s doing it can I? Over the course of these particular posts I want to explore the making of a small cross-browser .js library, looking at what others have done and maybe get some good feedback in the process.

Why do this? Well it really started at work (I’m a web developer for U-Haul) when I was writing a new AJAX text-based chat system. I used Jquery on that, and it works great, nothing against Jquery or Prototype, or Base2 or whatever flavor of .js library you may use, they are all great tools, and surely much more encompassing that what I’ll cobble together here. I’m just a geek who likes to take things apart, and taking apart these large JavaScript libraries is just too damn complicated for me (plus I was just at jConf in Boston and when John Resig was adressing the possibility of a more modular jquery.js he pretty much shrugged and said it was just too damn complicated ;) ).

So where to start then? How about a unified API for dealing with the differences in creating event listeners for standards compliant browsers vs Interner Explorer using feature detection? The presence of the w3 standard method for binding events to elements could be tested for like so:

if (document.addEventListener) {
    //do stuff for standards compliant browser
}

With the IE version, which uses ‘attachEvent’ like this:

else if (document.attachEvent) {
    //do stuff for IE
}

I’m pretty sure you can use any passed in element to test for this, so you can just test whatever you pass in verses referencing the document object again. So at this point we could combine these two if statements (should we use a ternary? Gotta remember to check that…) together:

function addEventHandler(element, type, handler) {
    if (element.addEventListener) {
        element.addEventListener(type, handler, false*);
    }
}
else if (element.attachEvent) {
    element.attachEvent("on" + type, handler);
}

But that would mean that every binding request would have to pass through this statement right? I’ve tried to avoid this by implementing the functions on an object which tests once when it’s instantiated.
So, in a Douglas Crockford kind of way we’ll make an object and append our methods to it as to avoid global namespace pollution. Now, what do we call this global Object of ours? rquery? base3?…Truth is, call it whatever you want (and the aforementioned Mr.Crockford says do it in all CAPS), for brevity I’m goin’ with X (it looks pretty cool while debugging…).

var X = {};
//check for event binding model here
if (document.addEventListener) {
    X.addEventHandler = function(element,type,handler) {
        element.addEventListener(type,handler,false*);
    }
}
else if (document.attachEvent) {
    X.addEventHandler = function(element,type,handler) {
        //note to self:address soon to be memory leak problem...(still?)
        element.attachEvent("on" + type, handler);
    }
}

This way we can put w3 standard events into one block (the ‘if’ block) and all the other IE specific code related to the adding and removing of Event listeners (and that removing part will probably get involved due to the IE memory leak issue) into the ‘else if’ block. I makes sense to me to place the code that way so my global object only has to do the feature detection once rather than each time I try to bind an event to an element.

This is getting long, so I’m going to save the examples and use for the next post. -rob

Return top