Posts Tagged ‘jQuery’

Replacing text in the DOM… it’s not that simple!

Posted in 'JavaScript' by James on July 7th, 2010

Finding and replacing strings of text in a DOM document is very tricky, especially if you’re looking to do it properly and entirely unobtrusively.

In this context, “unobtrusive” means affecting the page in a minimally invasive manner — minimal DOM destruction, no un-called-for normalizing of text nodes (i.e. joining separate text-nodes together) etc.

Essentially, what I’m talking about is finding a specific piece of text in a page or a piece of text that matches a pattern and then doing something with it — possibly wrapping it in an element or changing it in some other way.

Imagine that we have the following paragraph:

<p>This order's reference number is RF83297.</p>

We want to locate the reference number and wrap it in an anchor that leads to the order’s page (pretend that we’re making a bookmarklet to enhance the admin panel of some online store). Given what we know about the HTML structure this can be quite easy:

(Using jQuery)

jQuery('p').each(function(){
 
    var p = $(this);
 
    p.html(
        p.text().replace(
            /\bRF\d{5}/g,
            '<a href="/order/$&">$&</a>'
        )
    )
 
});

The problem with this approach is that it breaks once you add any elements to any of the paragraphs on the page. It sets the content of the the element to the (replaced) text of that very element, by definition destroying any non-text nodes in the process, or, to put it poetically, it flattens the DOM structure of any given paragraph.

Additionally, the above technique does not care to avoid paragraphs that do not contain a match for the pattern — it’ll blindly flatten all elements within the paragraphs…

Library foundation code

Posted in 'Code Snippets, JavaScript' by James on April 26th, 2010

It should now be universally accepted that extending the DOM directly (via Element.prototype, Node.prototype etc.) is a bad idea. To combat the inherent problems with doing this, quite a few libraries (jQuery, BBC Glow, etc.) employ the “wrapper” technique which involves taking a bunch of DOM elements and stuffing them into an array-like object that inherits from a safely-extendible prototype.

If you want to have a go at creating your own jQuery-like DOM library, here’s something to get you started:

window.myWrapper = (function(){
 
    // "cache" useful methods, so they don't
    // have to be resolved every time they're used
    var push = Array.prototype.push,
        slice = Array.prototype.slice,
        toString = Object.prototype.toString,
 
        isArray = function(o) {
            toString.call(o) === '[object Array]';
        },
 
        toArray = (function(){
 
            try {
 
                // Return a basic slice() if the environment
                // is okay with converting NodeLists to
                // arrays using slice()
 
                slice.call(document.childNodes);
 
                return function(arrayLike) {
                    return slice.call(arrayLike);
                };
 
            } catch(e) {}
 
            // Otherwise return the slower approach
 
            return function(arrayLike) {
 
                var ret = [], i = -1, len = arrayLike.length;
 
                while (++i < len) {
                    ret[i] = arrayLike[i];
                }
 
                return ret;
 
            };
 
        })();
 
    function NodeList(elems) {
 
        this.length = 0;
 
        push.apply(
            this,
            elems.nodeType ?
                [elems] // Single node
                : isArray(elems) ?
                    elems // Real array
                    : toArray(elems) // NodeList/Array-like-object
        );
 
    }
 
    function myWrapper(elems) {
        // Instantiate and return new NodeList
        return new NodeList(elems);
    }
 
    myWrapper.NodeList = NodeList;
 
    NodeList.prototype = {
 
        each: function(fn) {
 
            for (var i = -1, l = this.length; ++i < l;) {
                fn.call(this[i], this[i], i, l, this);
            }
 
            return this;
 
        }
 
    };
 
    return myWrapper;
 
})();

There’s no selector engine, but it’s easy enough to slot one in:

function myWrapper(elems) {
    // E.g. using Sizzle as the selector engine
    return new NodeList( typeof elems === 'string' ? Sizzle(elems) : elems );
}

Example of usage:

myWrapper(document.getElementsByTagName('div')).each(function(){
    alert(this.id);
});
// Etc.

Extending NodeList:

myWrapper.NodeList.prototype.applyCSS = (function(){
 
    var hasOwn = Object.prototype.hasOwnProperty;
 
    return function(props) {
 
        for (var i = -1, l = this.length; ++i < l;) {
            for (var p in props) {
                if (hasOwn.call(props, p)) {
                    this[i].style[p] = props[p];
                }
            }
        }
 
        return this;
 
    };
 
}());
 
// Usage:
myWrapper(document.body).applyCSS({
    background: 'red',
    color: 'yellow'
});

76 bytes for faster jQuery

Posted in 'JavaScript' by James on March 31st, 2010

When jQuery fires a callback function, whether it is an event handler, an each iterator, or a filter function, it will normally give you a DOM element as the function’s context, accessible via the this keyword. It’s common practice to subsequently wrap this in jQuery(...) resulting in a newly constructed jQuery instance.

This is no fault of jQuery’s but this practice of redundant “wrapping” generally sucks.

I “tweeted” a while ago, complaining of this:

Constructing a new jq obj on each iteration just to access some text seems wrong. jQuery(‘a’).map(function(){ return $(this).text(); });

Paul Irish suggested that I use jQuery.text instead of jQuery.fn.text, meaning that I wouldn’t have to bother with constructing a new jQuery object every single time my function was called. This was a good suggestion but unfortunately not all of jQuery’s methods have corresponding single-node functions, and it would mean not being able to chain methods.

This is a growing problem, and is only worsened by developers’ insistence on constructing multiple jQuery objects with the same element! -

jQuery('a').click(function(){
 
    if (jQuery(this).attr('href') === 'blah') {
        jQuery(this).next('.whatever').slideToggle();
    }
 
    jQuery(this).fadeTo('slow', 0.4);
 
    return false;
 
});

Eew! Not only are there multiple pointless constructors, but Mr. Uber Cool jQuery Developer isn’t accustomed to the DOM, so has absolutely no idea that, in most situations, this.href would suffice for getting the href property value. This kind of misuse has been discussed in step #3 here: http://encosia.com/2010/03/30/5-steps-toward-jquery-mastery/.

The real problem remains that there are three jQuery objects being constructed, — I feel that it is a library’s obligation to protect against misuse like this. Even if you’re only constructing one jQuery object in a callback it’s still somewhat pointless, in that you’re only doing so to call a couple of methods…

Sorting elements with jQuery

Posted in 'Code Snippets, JavaScript' by James on March 19th, 2010

There are quite a few table-sorting functions/plugins out there, but I’ve yet to find something sufficiently low-level for general element-sorting. What I wanted was a simple jQuery plugin that would take a sorting function (just like Array.prototype.sort) as an argument and sort the DOM elements in-place, and would handle situations where the elements didn’t all have the same parent.

To be honest, I didn’t search for very long — I prefer making this kind of stuff myself anyway — so, I present what I think is one of the simplest ways possible to implement a reliable element-sorting function.

Name changed from sort to sortElements as per David’s comment.

/**
 * jQuery.fn.sortElements
 * --------------
 * @param Function comparator:
 *   Exactly the same behaviour as [1,2,3].sort(comparator)
 *   
 * @param Function getSortable
 *   A function that should return the element that is
 *   to be sorted. The comparator will run on the
 *   current collection, but you may want the actual
 *   resulting sort to occur on a parent or another
 *   associated element.
 *   
 *   E.g. $('td').sortElements(comparator, function(){
 *      return this.parentNode; 
 *   })
 *   
 *   The <td>'s parent (<tr>) will be sorted instead
 *   of the <td> itself.
 */
jQuery.fn.sortElements = (function(){
 
    var sort = [].sort;
 
    return function(comparator, getSortable) {
 
        getSortable = getSortable || function(){return this;};
 
        var placements = this.map(function(){
 
            var sortElement = getSortable.call(this),
                parentNode = sortElement.parentNode,
 
                // Since the element itself will change position, we have
                // to have some way of storing its original position in
                // the DOM. The easiest way is to have a 'flag' node:
                nextSibling = parentNode.insertBefore(
                    document.createTextNode(''),
                    sortElement.nextSibling
                );
 
            return function() {
 
                if (parentNode === this) {
                    throw new Error(
                        "You can't sort elements if any one is a descendant of another."
                    );
                }
 
                // Insert before flag:
                parentNode.insertBefore(this, nextSibling);
                // Remove flag:
                parentNode.removeChild(nextSibling);
 
            };
 
        });
 
        return sort.call(this, comparator).each(function(i){
            placements[i].call(getSortable.call(this));
        });
 
    };
 
})();

Features:

  • It’s low-level — you have control over how it sorts (see the “comparator” argument).
  • It lets you sort any arbitrary DOM nodes, as long as none of those nodes are within another one of the nodes to be sorted.
  • It lets you specify what elements will actually be moved (see the “getSortable” argument).

Usage:

Assuming the following markup:

<ul>
    <li>Banana</li>
    <li>Carrot</li>
    <li>Apple</li>
</ul>

You could sort the items alphabetically like so:

$('li').sortElements(function(a, b){
    return $(a).text() > $(b).text() ? 1 : -1;
});

That would result in:

<ul>
    <li>Apple</li>
    <li>Banana</li>
    <li>Carrot</li>
</ul>

See a rather basic demo here.

jQuery.fn.sortElements on Github

jQuery: Novice to Ninja

Posted in 'General, JavaScript' by James on February 26th, 2010

Sitepoint’s latest title is aimed at the average Front-end developer/designer and promises to teach her the intricate workings of the jQuery library. In fact, if we’re to go by the title, it promises to take her from “novice to ninja”. Let me be honest, I was originally skeptical of the book, but upon finding out that it was co-authored by the esteemed mrspeaker my mind was quickly changed.

jQuery: Novice to Ninja

As with any good book, the first chapter outlines what the subject matter is. The authors talk about jQuery itself and touch briefly on the DOM. You have to remember that this book is aimed at developers or designers that don’t really know that much about JavaScript, the DOM, or jQuery, so the book is accordingly written in such a way that it won’t confuse the pants off your typical photshop-wizz turned HTML/CSS guy.

So if you know JavaScript well, you might not enjoy this book — it will probably annoy you, simply because it doesn’t go into a huge amount of detail on what’s happening behind the scenes. But, for those that it’s aimed at, I think it does a pretty good job.

One thing that we have a problem with, in the jQuery community bubble, is the overzealous generation of redundant nomenclature. This book doesn’t fall short on this front. jQuery’s methods are referred to as “actions”.

The second chapter covers the most basic usages of jQuery, selecting stuff and doing stuff. It’s all innocent and simple — I like it. To keep the reader focused the authors have slipped in “A few tricks”. Among them is the popular add-a-class-on-mouseover, remove-it-on-mouseout.

Nearing the end of chapter two, the book has the following to say about the hover method:

Under jQuery’s bonnet

Posted in 'Cool Stuff, JavaScript' by James on January 31st, 2010

If there’s one thing all library users should be doing more, it’s peeling back the layer of abstraction and seeing what’s really happening underneath. This is the only way to gain a true understanding of what the library provides, and who knows, maybe you’ll find some gems that you didn’t know existed.

Libraries like jQuery aren’t very small when uncompressed. Traversing a long source file trying to look for a specific method’s implementation is far from ideal. I have frequently found myself in this situation with jQuery, so today I decided to do something about it, and the result is viewable at http://james.padolsey.com/jquery.

Preview of the jQuery Source Viewer

It allows you to study specific parts of jQuery’s source. You can type in a method name and you’ll see its implementation straight away, in all its syntax-highlighted glory! It will also link’ify all function names within the presented source:

The css method's source, with certain function names as links

You can link directly to a method using the following URL pattern:

http://james.padolsey.com/jquery/[version/]methodName
 
E.g.
http://james.padolsey.com/jquery/css
http://james.padolsey.com/jquery/1.3.2/attr
http://james.padolsey.com/jquery/jQuery.proxy
 
-OR-
http://james.padolsey.com/jquery/#v=version&fn=methodName

If you don’t specify a version then 1.4 is assumed.

JavaScript’s Dark Alley

Posted in 'JavaScript' by James on January 30th, 2010

So, I happened upon an interesting thread this morning, concerning the recently released jQuery Lint. For those of you who have not dared to look, the comp.lang.javascript newsgroup is truly the proverbial dark alley of JavaScript and DOM development. I can picture it vividly, a dingy dusty cobbled walkway blackened by the thick fog of regressive opinion and unprovoked flame wars, riddled with the unquestioned prerequisite of a newsgroup’s slow death — spam on every corner!

If you have a read through that thread you’ll notice none other than the infamous David Mark. This is the same person that slammed jQuery (oh, and MooTools) for not being able to “do *anything* right” and then, not too long ago, threatened the jQuery project with legal action over some DOM attributes tests.

David had the following to say about jQuery Lint:

Well, sort of. It’s not so much the Johnny-come-lastly advisor plug- in, but the fact that it is just another horrible plug-in for an equally bad pile of JS [he means jQuery]. Anyone who would use jQuery enough to want to write a plug-in is not going to be the best candidate to wtite a browser scripting “lint.” I can’t see it.

He continues:

The idea (which is sound) is to warn developers when they are doing something incorrect (or ill-advised). I haven’t looked at the code for it (and I’m sure I never will). I find I don’t have to do that anymore as the basic rules always apply. :)

David doesn’t stop! He still continues:

[Scott Sauyet]
> It seems to be a tool to help new users learn things like that although this code is legal:

$("selector").css("color", "red").css("margin", 0);
> This would be more efficient:
$("selector").css({color: "red", margin: 0});
[David Mark]
You dump it and replace it with something better (and faster). In this case, something like:
el.style.color = "red"; 
el.style.margin = '0';
That’s smaller, faster, more readable, makes no function calls, creates no new objects and is impervious to upgrades to jQuery (typically poison). And, as we all know, jQuery doesn’t work worth a shit anyway as the “logic” in the script is mostly a diary of confused browser watchers. Get the picture?
> That doesn’t indicate anything wrong with the library any more than > any lint program indicates problems with its target environment.
Of course, you don’t need a lint to see what is wrong with the library. It’s as obviously unsuitable as a dissertation written in crayon.

According to David we should be sparing ourselves the obvious folly of abstraction and spend more time writing “smaller, faster, more readable” JavaScript:

jQuery Lint

Posted in 'JavaScript' by James on January 18th, 2010

jQuery Lint is a simple script you can download and use with jQuery. It works over the top of jQuery and diligently reports errors and any incorrect usage of jQuery. It will also, to some extent, offer guidance on best practices and performance concerns.

Unlike JSLint, jQuery Lint is a runtime reporter. To use it, you need to include it, after jQuery, in your document:

<script src="jquery.js"></script>
<script src="jquery.lint.js"></script>

jQuery lint’s main objective is to notify you of incorrect usages of jQuery’s API. So, if you pass incorrect arguments to any method then jQuery Lint will let you know. It compares your arguments to the argument signatures in jQuery’s API. It reports via Firebug, although you can quite easily plug-in your own console mechanism.

It has four different error-reporting levels (accessible via jQuery.LINT.level), zero reports nothing, three will report everything, including small things like using css().css().css() instead of css({...}). It’s quite configurable too. You can add your own checks. E.g.

jQuery.LINT.special[1].jQuery = jQuery.LINT.special[1].jQuery || [];
 
// Add check on error-reporting level one.
// Check jQuery method.
jQuery.LINT.special[1].jQuery.push(function(selector, context) {
 
    if (selector === '*') {
        return "Don't use the universal selector!";
    }
 
});

jQuery Lint tries to help you in determining where the problem occurred in your code. It’s not much help to you if it just says, “Err, you called css() incorrectly!”. If it occurred as a result of an event then Lint will say so, and if you’re using a browser that provides a stack-trace as part of its Error object (like Firefox) then Lint will also provide you with the file-name and line number. E.g.

jquery Lint - Reporting line number and file name where problem occured.

You can read more about jQuery Lint and download it at Github:

jQuery Lint @ Github

The idea of a lint-like script for jQuery has been floating around for some time. I want to thank Dave Methvin in particular, for it was his idea that sparked my interest originally.

This is quite a young project, so there will be bugs. Please report them!

Cross-domain requests with jQuery

Posted in 'Code Snippets, JavaScript' by James on January 12th, 2010

Chris Heilmann recently posted on how to use YQL to make cross-domain requests, which would usually be prohibited due to the same-domain-policy. I already knew about YQL, but I had no idea that it allowed retrieval of HTML from other sites, via JSON, returned as a single string!

Instead of asking for JSON format, ask for XML, but also add a callback parameter to your query. Voila!

So, in short, YQL allows us to make cross-domain GET requests!

Chris also posted a demo!

With a bit of hacking, we can make jQuery work with YQL for all cross-domain GET requests. UPDATE: I’ve decided to put this in my “jQuery Plugins” repo at Github:

Cross-Domain Ajax mod @ Github

With this mod, any GET request made via jQuery.ajax to another domain will work!

$('#container').load('http://google.com'); // SERIOUSLY!
 
$.ajax({
    url: 'http://news.bbc.co.uk',
    type: 'GET',
    success: function(res) {
        var headline = $(res.responseText).find('a.tsh').text();
        alert(headline);
    }
});
 
// Works with $.get too!

Have fun!

jQuery code smells

Posted in 'JavaScript' by James on January 5th, 2010
jQuery code smells

Here’s a quick line-up of some smelly jQuery code! “Code smells” are pieces of code that do for your eyes what bad smells do for your nostrils, and usually result in erroneous or harder-to-maintain code. I have no doubt that at least half of you will think that I’m wrong about at least half of these.

Before you stop paying attention, I’d like to wish everyone a Happy New Year! I hope you find the new design more consistent and cleaner.

May the flaming commence…

Prefixxing all jQuery objects with “$”

It’s ugly, and my text editor doesn’t like it! But mainly, it’s ugly, and is only useful for beginners. Hungarian notation is a hotly debated technique — like Marmite (and Jade Goody?), you either love it or hate it!

var $body = $('body');
$body.click(function(){
    $this = $(this);
});

I won’t say much more; I appreciate that it’s a sensitive topic for some and is mostly about personal preference.

Concatenating a bunch of things together to make a selector

I see a lot of this! It looks dirty, and just screams, “I’m succumbing to the API because I don’t know what else to do!”

$('.' + className + '[' + attrLookup + '^=' + attrPrefix ']:not(.' + notClass + ')');

This is why I think jQuery needs some other way of filtering DOM elements (possibly something like my filter hack). For now, seperating out our selectors will have to suffice:

$('.' + blah.className)
    .filter('[' + attrLookup + '^=' + attrPrefix ']')
    .not('.' + notClassName);

To be truthful, I’m on the fence with this… Maybe I’m just against string concatenation!