Archive for the ‘Code Snippets’ Category
Posted in 'Code Snippets, Cool Stuff, JavaScript' by James on August 20th, 2010
I originally posted this as a gist, here. I thought it best to keep my readers informed so that’s why I’m posting it here too:
// ----------------------------------------------------------
// A short snippet for detecting versions of IE in JavaScript
// without resorting to user-agent sniffing
// ----------------------------------------------------------
// If you're not in IE (or IE version is less than 5) then:
// ie === undefined
// If you're in IE (>=5) then you can determine which version:
// ie === 7; // IE7
// Thus, to detect IE:
// if (ie) {}
// And to detect the version:
// ie === 6 // IE6
// ie > 7 // IE8, IE9 ...
// ie < 9 // Anything less than IE9
// ----------------------------------------------------------
// UPDATE: Now using Live NodeList idea from @jdalton
var ie = (function(){
var undef,
v = 3,
div = document.createElement('div'),
all = div.getElementsByTagName('i');
while (
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
all[0]
);
return v > 4 ? v : undef;
}());
http://gist.github.com/527683
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'
});
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
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!
Posted in 'Code Snippets, JavaScript' by James on December 16th, 2009
A while ago I wrote a little “pulse” plugin for jQuery which would make an element pulse between two or more states. I won’t bother linking to it because it’s offline. I decided to have another go at developing it, this time with a more refined and intuitive API.
I’ve tried to emulate the characteristics of jQuery’s animate() method as much as possible. There are only a few slight differences. Here’s a typical call to the new “pulse” plugin:
jQuery(element).pulse({
opacity: [0, 1],
backgroundColor: ['red', 'yellow']
}, 1000, 5, 'linear', function(){
alert("I'm done pulsing!");
});
It’s almost exactly the same parameter pattern as animate(), except that you have to specify an array of values for each CSS property, and also an additional numerical parameter, after the duration, which signifies how many times you want the pulse to run (the above code will run the pulse five times).
It also works with an “options” object, just like animate():
jQuery(element).pulse({
opacity: [0,1]
}, {
duration: 100, // duration of EACH individual animation
times: 3, // Will go three times through the pulse array [0,1]
easing: 'linear', // easing function for each individual animation
complete: function() {
alert("I'm done pulsing!");
}
});
You should note that the duration option only defines how long each individual animation will run, not the entire pulse. To dictate the length of the entire pulse you need to use the times option. Also note that the times option refers to an entire run-through of the largest array found in the properties option.
You can “pulse” through as many values as you want:
jQuery(element).pulse({
backgroundColor: ['red', 'yellow', 'green', 'blue'],
opacity: [0, 1],
});
The arrays don’t all have to be of equal length – with the above code, the opacity would keep changing as is defined by its array.
The plugin itself is quite small. I’ve decided to start a new repo at Github, just for small and useful jQuery plugins like this.
Pulse plugin on Github
I haven’t tested it extensively yet, so, as usual please notify me of any bugs/issues. Thanks!
Posted in 'Code Snippets, JavaScript' by James on November 13th, 2009
In Thomas Fuchs’ latest JavaScript performance presentation he talks about the speed gains that can be experienced by using “unrolled” loops.
A conventional loop:
for (var i = 0; i < 10; ++i) {
doFoo(i);
}
The “unrolled” version of that loop:
var i = 0;
doFoo(i++); doFoo(i++); doFoo(i++); doFoo(i++); doFoo(i++);
doFoo(i++); doFoo(i++); doFoo(i++); doFoo(i++); doFoo(i++);
A partially unrolled version:
for (var i = 0; i < 10; ) {
doFoo(i++);
doFoo(i++);
doFoo(i++);
doFoo(i++);
doFoo(i++);
}
Interestingly, speed gains can be experienced dependent on the loop size, albeit marginal at best. I thought about ways to build this into a clever forEach function and came up with something that ‘pre-compiles’ functions containing partially unrolled loops. Have a look:
var forEach = (function() {
var fns = [],
callers = "true",
numberFn = 10,
i = 1;
for ( ; i <= numberFn; ++i ) {
callers += "&&f(a[++i])!==false";
fns[i] = new Function("a", "f", "l", "var i=0;while (i<l) {"+callers+"}");
}
return function( array, fn ) {
var len = array.length,
n = numberFn, i;
while (i = n--) {
if ( len % i === 0 ) {
return fns[i](array, fn, len);
}
}
};
})();
This function will run one of 10 ‘pre-compiled’ functions on the passed array, dependent on the highest factor of the array’s length. I’m only creating 10 different functions in this example, you could create more.
If you were to pass an array with a length of 14, then fns[7] would be used, since 7 is the highest available factor (the highest number below 10 that 14 can be divided by, to gain a whole number). fns[7] looks something like this:
function anonymous(a, f, l) {
var i = 0;
while (i < l) {
true &&
f(a[++i]) !== false &&
f(a[++i]) !== false &&
f(a[++i]) !== false &&
f(a[++i]) !== false &&
f(a[++i]) !== false &&
f(a[++i]) !== false &&
f(a[++i]) !== false;
}
}
The !== false part is used to create the effect of loop-breaking. Notice that the success of this boolean expression is depended upon to continue the chain of expressions (a && b && c)
I’ve only tested it briefly, and to be honest, there doesn’t seem to be a notable benefit. In IE, I can see a bit of improvement over the conventional forEach implementation but only if I’m using arrays with 1000+ lengths. I think this would only be useful in situations where you absolutely have to squeeze every inch of potential performance out of your app. Anyway, it’s still pretty interesting, I wonder what other fancy things can be created by using pre-compiled functions.
Posted in 'Code Snippets, JavaScript' by James on September 11th, 2009
A while ago I posted a method I had been using at the time to remove comments from JavaScript code. It was pretty decent – instead of using a regular expression it steps through each character and removes comments where it finds them.
At the time I thought stepping through a string character-by-character was the only reliable way to solve the “comments problem” but after giving it another attempt I found that it was possible with a only a few regular expressions and a fairly moderate dose of JavaScript’s replace() function.
Here it is:
function removeComments(str) {
var uid = '_' + +new Date(),
primatives = [],
primIndex = 0;
return (
str
/* Remove strings */
.replace(/(['"])(\\\1|.)+?\1/g, function(match){
primatives[primIndex] = match;
return (uid + '') + primIndex++;
})
/* Remove Regexes */
.replace(/([^\/])(\/(?!\*|\/)(\\\/|.)+?\/[gim]{0,3})/g, function(match, $1, $2){
primatives[primIndex] = $2;
return $1 + (uid + '') + primIndex++;
})
/*
- Remove single-line comments that contain would-be multi-line delimiters
E.g. // Comment /* < --
- Remove multi-line comments that contain would be single-line delimiters
E.g. /* // <--
*/
.replace(/\/\/.*?\/?\*.+?(?=\n|\r|$)|\/\*[\s\S]*?\/\/[\s\S]*?\*\//g, '')
/*
Remove single and multi-line comments,
no consideration of inner-contents
*/
.replace(/\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g, '')
/*
Remove multi-line comments that have a replaced ending (string/regex)
Greedy, so no inner strings/regexes will stop it.
*/
.replace(RegExp('\\/\\*[\\s\\S]+' + uid + '\\d+', 'g'), '')
/* Bring back strings & regexes */
.replace(RegExp(uid + '(\\d+)', 'g'), function(match, n){
return primatives[n];
})
);
}
Theoretically this should work perfectly in almost all situations. Don’t bother even trying it with E4X as that definitely won’t work! E.g.
var someE4X = <box>// this is NOT a comment</box>;
It's impossible to cater to E4X with regular expressions because XML is a recursive structure. I'm not bothered though as E4X isn't exactly a widely used extension. It also doesn't play well with conditional compilation but frankly, conditional compilation shouldn't exist anyway.
Anyway, back to the solution. It takes a pretty conventional approach of removing all strings and regular expressions first and then moving on to the comments. Unfortunately comments are not as simple as \/\*.+?\*\/ - there are nested comments within strings, nested comments within literal-regular-expressions and nested comments within other comments.
Posted in 'Code Snippets, JavaScript' by James on August 31st, 2009
Here’s a nifty little trick that allows you to write “contextual” JavaScript. It’s incredibly obtrusive and probably shouldn’t be used at all but it’s still a pretty cool idea:
<div id="some-div">
<script type=":contextual">
alert(this.id); // "some-div" is alerted
</script>
</div>
Instead of the this keyword referencing the global object (window) we can make it reference the parentNode of the script element. Here’s the code that makes it work:
(function(){
var scripts = document.getElementsByTagName('script'),
script, i = 0;
while ( (script = scripts[i++]) ) {
if ( /:contextual$/.test(script.type) ) {
(new Function(script.innerHTML)).call(script.parentNode);
}
}
})();
Take it or leave it! – I’m indifferent either way – yes, it’s obtrusive but you’ve got to admit, contextual JavaScript looks pretty neat!
Posted in 'Code Snippets, JavaScript' by James on August 20th, 2009
Satisfy is a development-only jQuery plugin* that you can use to quickly generate HTML for testing/debugging. The idea is that you provide a CSS selector and then the plugin will “satisfy” it by generating an HTML structure in accordance with that selector.
* – UPDATE: This is no longer just a jQuery plugin – as of version 0.2 Satisfy has no dependencies. See the comments for more info.
For example:
jQuery('div a').satisfy();
… would return the following HTML structure:
A more snazzy example:
jQuery('ul li:5 span[innerHTML="link"]').satisfy();
… which would return the following:
<ul>
<li><span>link</span></li>
<li><span>link</span></li>
<li><span>link</span></li>
<li><span>link</span></li>
<li><span>link</span></li>
</ul>
(Note: It ignores combinators (+|~|>) and pseudo-classes (:pseudo). It adds support for numerical pseudo-classes (e.g. “:5″) which you can use to specify how many of a particular element you want.)
More information and source available at Github – “satisfy” @ GitHub
Posted in 'Code Snippets, JavaScript' by James on August 7th, 2009
This is not a suggestion for the jQuery core; it’s just something I required recently that some of you may find useful/intriguing…
jQuery.fn.map = (function(_map) {
return function(toMap, prop, jQueryOb) {
if (typeof toMap === 'string') {
var parts = toMap.match(/(\\:|[^:])+/g),
method = parts.shift(),
args = parts,
mapped = _map.call(this, function(){
var $this = $(this),
result = $this[method].apply( $this, args );
return prop ? result[prop] : result;
});
return jQueryOb ? mapped : mapped.get();
} else {
return _map.apply(this, arguments);
}
};
})(jQuery.fn.map);
// Note: this only modifies $.fn.map; not $.map
It allows you to pass a string to $(collection).map(...) instead of a function; it’s useful for returning attributes, DOM properties, CSS styles or data tied to each element without having to spend time/space writing a function, e.g.
$('a').map('attr:href'); // => ['http://abc.com', 'http://123.com', ... (all HREFs)
$('p').map('css:fontSize'); // => ['16px', '12px', '19px' ... (all font-sizes)
$('.class').map('offset', 'left'); // => ['200px', '430px', ... (all left offsets)
$('div').map('data:someData'); // => ['data', ... (added via jQuery.fn.data)
The first argument is split at each colon (use \ to escape a colon), the first part of the split string is assumed to be the jQuery method name which you want to call (e.g. "css" => $().css()). All remaining parts are assumed to be arguments for that method. The second argument (to map()) is an optional property to access following retrieval; this is demonstrated with the “offset” example in the code above. The last argument is a boolean; passing true will make it return a jQuery object instead of a real array.