Something that bugs me, especially when I'm in the depths of a nasty-looking jQuery chain, is the fact that there's no way to reference the last selection, that is, without explicitly assigning it to a variable... (»)
Posted in 'JavaScript' by James on June 23rd, 2009
I recently came across a jQuery plugin that claims to emulate the functionality of Object.prototype.watch, a method inherent in all objects that enables you to watch for a property change and run a function upon that change. Of course, this isn’t available in all implementations. IE, for example, doesn’t support this but has a DOM equivalent, onPropertyChange. Both methods are useful but their slightly different functionality makes it impossible to use either of them to create a unified solution. Again we’re left to resort to the lowest common denominator, setInterval.
The jQuery plugin I mentioned attempts to use watch() if it’s available, otherwise it uses setInterval, repeatadly iterating through a stack of objects checking for changes. Even this attempt at a unified solution is futile because the functionality behind watch() is fundamentally unachievable with cross-browser JavaScript. According to this MDC documentation, the watch method not only acts as a type of event registrar for property-changes but also has the capability to define the property’s value (following the attempted change). In other words, the psuedo-event from watch() will fire before the property is actually changed, not after - the return value from the handler becomes the actual value of that property. Therefore, any attempts at emulation (for example, with setInterval) will not succeed since we’re only able to fire our psuedo-event after the change.
The following example shows how you would use watch() - at no time does the property actually change to ‘456′, it’s constantly ‘123′:
var obj = { prop: 123 };
obj.watch('prop', function(propertyName, oldValue, newValue){
return oldValue;
});
obj.prop = 456;
alert(obj.prop); // 123
Additionally, watch() does not recognise internal property changes (within the DOM). If you setup a watch on the value property of an input element, even when you type into that field, your watch-handler will not execute. I’m not sure if this is intentional but it makes this technique entirely useless for monitoring DOM properties.
So, it appears that the only way to monitor DOM properties consistently across all browsers is to only use setInterval. The following plugins, watch and unwatch work quite well:
jQuery.fn.watch = function( id, fn ) {
return this.each(function(){
var self = this;
var oldVal = self[id];
$(self).data(
'watch_timer',
setInterval(function(){
if (self[id] !== oldVal) {
fn.call(self, id, oldVal, self[id]);
oldVal = self[id];
}
}, 100)
);
});
return self;
};
jQuery.fn.unwatch = function( id ) {
return this.each(function(){
clearInterval( $(this).data('watch_timer') );
});
};
// Plus, I finally found use for jQuery.data()! ;)
These can be used on any type of object, an example:
$('input').watch('value', function(propName, oldVal, newVal){
log('Value has been changed to ' + newVal);
});
// or...
var obj = { prop: 123 };
$(obj).watch('prop', function(propName, oldVal, newVal){
log('Prop has been changed to ' + newVal);
});
I went ahead and created a new special event for jQuery using the plugin:
jQuery.fn.valuechange = function(fn) {
return this.bind('valuechange', fn);
};
jQuery.event.special.valuechange = {
setup: function() {
jQuery(this).watch('value', function(){
jQuery.event.handle.call(this, {type:'valuechange'});
});
},
teardown: function() {
jQuery(this).unwatch('value');
}
};
// Usage:
$('input').bind('valuechange', function(){
log('Value changed... New value: ' + this.value);
});
This event is useful when you want to be notified every single time the value of an input element changes. No native DOM event will do this.
If there’s one thiing you should take away from all this then it’s this: if you are going to try making a browser-specific feature available across all browsers you should make sure that you match the functionality precisely, otherwise it’s worthless. For example, an Array.prototype.forEach method that passes the index as the first parameter instead of the second is useless. If you can’t match the functionality then simply create your own abstraction.
Posted in 'General' by James on June 18th, 2009
I’m not normally one to protest so distastefully but Microsoft’s last move has really pissed me off. Apparently, their new devil-child, IE8, beats Mozilla Firefox and Google Chrome in areas such as “Reliability”, “Compatibility” and “Developer Tools”; at least, according to this chart (IMG)!
Apparently IE8 considers itself more secure and easier to use than other browsers too!
I can’t even begin to express my absolute opposition to every speck of misinformation on that chart. How dare Microsoft mislead consumers to such a degree - I know they put financial superiority and their monopoly above all other human values but this doesn’t excuse their flagrant disregard of their customers’ intelligence.
Consumerism is only good for one thing in my opinion - creating arrogant, possibly corrupt and more annoyingly, ignorant individuals. The people running Microsoft and the various departments involved must either be unquestionable morons or just so manipulated by the culture of consumerism, personified solely by that retched corporation, that they are beyond help!
In the past I’ve been quite calm and civilian in my dealings with the atrocity known by some only as “IE”, but now I feel a new spark of anger has been born; Microsoft has forever lost my respect and confidence in the prospect of change.
I know, I know! Marketing and advertising, it’s all about lies, spin and manipulation… But, that doesn’t mean it’s right, and yes, there is room in our capitalist cultures for honesty.
I want to have a long and hard chat with the heads of the IE team; I’d love to hear their twisted take on reality - maybe one of them could shed some light on the unacceptable marketing drivel that’s been circling the net, which, by the way, not only includes the chart I mentioned, but check this out! Yes, not only are Microsoft capable of releasing lies in the form of propaganda but they’ve also developed a system that will insult the browser you’ve chosen to use unless, of course, you’re using Internet Explorer, but then why would you?
The marketing team responsible for the latest scam should publicly apologize and then be permanently banned from every touching (or speaking about) anything remotely related to technology again!
It’s fair enough when Microsoft slam their own previous products but attacking Google Chrome and more shockingly, Mozilla Firefox, is way below the belt!
Posted in 'Cool Stuff, JavaScript, News' by James on June 17th, 2009
ThemeForest, one of Envato’s marketplaces, has just launched a “JavaScript” category and within it my new JavaScript component, “Zoomer“. As the description states, “‘Zoomer’ enables your users to closely inspect images by hovering their cursor over them. It’s been built as a robust, unobtrusive and highly customizable jQuery plugin.” Here’s a brief overview of its key features:
- A fully customizable zoomer for use with any image
- Works in all modern browsers (including IE6 )
- This is a progressive enhancement; images will still be viewable when JavaScript is not available.
- The Zoomer itself is highly customizable.
- The Zoomer requires NO images – the shadows/gradients are dynamically generated by JavaScript (using VML /Canvas technologies)
- In-depth documentation including two videos and an FAQ !
See the demo
It’s customizable!
Almost every aspect of the zoomer is customizable, here’s a preview of the jQuery options:
$.zoomer.defaultOptions = {
zoomerClass: '_zoomer',
zoomSrc: '',
height: 150,
width: 150,
mousewheelZoom: true,
defaultZoom: 2,
maxZoom: 5,
minZoom: 1,
feedback: function(x,y,zoom){},
onOver: function(){},
onOut: function(){},
zoomerOverlay: {
className: '_zoomer-overlay',
shadowWidth: 10,
shadowOpacity: 0.5,
/* Must be in "rgb(r,g,b)" format */
shadowColor: 'rgb(0,0,0)',
radialOpacity: 0.4,
/* Must be in "rgb(r,g,b)" format */
radialColor: 'rgb(0,0,0)'
}
};
Even the shadow beneath the Zoomer is customizable - this is possible thanks to canvas/VML technologies.
Buy Zoomer (currently $5)!
Posted in 'HTML/XHTML, JavaScript' by James on June 9th, 2009
Thousands of developers interact with this API on a daily basis yet most forget it is just… an API! Of course, I’m talking about the notorious “Document Object Model” (a.k.a the DOM). With the rise of “Rich Internet Applications” interaction with the DOM has sky-rocketed and as a result it’s being heavily abused!
Have you ever needed to do this? -
// E.g. 1
element.addEventListener('click', function(e){
this.rel += ' clicked' + '(' + e.clientX + ' ,' + e.clientY + ')';
});
// E.g. 2
jQuery('a').mouseover(function(){
var color = jQuery(this).css('color');
jQuery(this).data('beforeColor', color);
changeColor(this);
});
If you have then (in my humble opinion) you’re abusing the DOM!
The first example is obviously bad practice; using HTML attributes to track changes unrelated to the HTML content is a crime of sorts and should never be done! The second example probably doesn’t seem as bad to most developers. jQuery’s “data” method allows you to store expando-like data about an element without obtrusively defining it as a direct property. This technique of storing element data is far better than using the HTML rel attribute. Or is it?
I was an advocate of this particular technique but I’ve come to realise that it is, in most situations, no better than using any random HTML attribute. If we put the unobtrusive-vs-obtrusive argument aside, both techniques are effectively the same. Plus, jQuery’s “data” technique is not really unobtrusive - jQuery still uses an expando property to tie the element to a particular property in a JavaScript object; I’m not saying there’s anything wrong with this, I’m just pointing out the common misconception that using jQuery’s data method is somehow cleaner than using a random or made-up HTML attribute (/DOM property).
The main reason I am now so sceptical about its usage is because, over time, I’ve seen a lot of cases that don’t really require it at all and could quite easily be achieved with a well-thought-out use of JavaScript closures. Obviously it’s not all bad; there are a few valid uses - for example, jQuery currently uses its own “data” method to tie events to elements/objects; a central part of the its event dispatch system.
Magical closures
The nature of JavaScript closures allows us to use regular variables to store progress or states and then access those variables from within nested functions. I’ve put together a short example; this is an outline of a drag-and-drop script:
// Library neutral/agnostic
draggableElements.forEach(function(elem){
var down = false,
y = 0,
x = 0;
elem.bind('mousedown', function(e){
down = true;
x = e.pageX - getOffset(elem).left;
y = e.pageY - getOffset(elem).top;
});
document.bind('mousemove', function(e){
if (down) {
elem.applyCSS({
left: e.pageX - x,
top: e.pageY - y
});
}
});
document.bind('mouseup', function(e){
down = false;
});
});
Every “draggable” element is given its very own closure (through forEach) within which we can define numerous variables accessible in each of the nested “handler” functions (’mousemove’, ‘mousedown’, etc.). No expando properties are required, nada!
I haven’t got much more to say about this; the above code pretty much sums up my point!
I’m interested in valid uses of jQuery’s data() method (other than those used in the core); someone please enlighten me!
Posted in 'JavaScript' by James on June 7th, 2009
“JSLint” is a JavaScript “code quality tool”. If you’ve never heard of it then think of it as the W3C HTML validator, but for JavaScript. Doug Crockford, the genius that created JSLint, frequently claims that JavaScript is “The World’s Most Misunderstood Programming Language”; if you want to know where he’s coming from with this statement then you’ll have to purchase his book, “JavaScript: The Good Parts” or, for a brief overview, watch the Google Tech Talk.
In some ways JavaScript could be considered just as lax as HTML when it comes to what is acceptable and what isn’t. For example, most browsers will let you get away with HTML that looks like this:
<ul
id =list>
<strong><li>One
<strong><li>Two
<strong><li>Three
And, similarly, most, if not all, browsers will let you get away with JavaScript that looks like this:
function muahaha() {
myarray = new Array()
myarray['name1'] = "056"
something = parseInt( myarray['name1'] )
if(something==56)
for (i=0;i<myarray.length;i++) if(myarray[i] != null) return true
return false
};
How many things can you count in the above code that are either bad practice or just incorrect usage of the language? If we go by JSLint then there are a total of 16 problems with it! Plus there are a couple that aren’t noticed by JSLint (using an array as an object & accessing myarray.length on every iteration). That’s 18 altogether! Yet, all browsers will run this code quite happily without throwing a single error.
Technically, it’s all legal JavaScript (minus the extra semi-colon at the end), so I’d expect all browsers to munch it up without complaining.
So, if it works1, then why should you take that extra step to ensure best practices; what’s the point and who will benefit?
Posted in 'JavaScript' by James on June 5th, 2009
Those of you following me on Github may have noticed a recently added project called “prettyPrint“.
“prettyPrint” is an in-browser JavaScript “variable dumper” similar to ColdFusions’s cfdump. It enables you to print out an object of any type in table format for viewing during debugging sessions. In combination with Firebug, “prettyPrint” will make you the best-equipped JavaScript debugger on earth! (not guaranteed)
Some of its key features:
- Entirely independent. It requires NO StyleSheets or images.
- Handles infinitely nested objects.
- All native JavaScript types are supported plus DOM nodes/elements!
- Protects against circular/repeated references.
- Allows you to specify the depth to which the object will be printed.
- Better browser users benefit from gradient column-headers! Thanks to HTML5 and
CANVAS!
- Allows low-level CSS customisation (if such a thing exists).
- Fully validated with JSLint!
I’ve recorded a short screencast demonstrating a couple of prettyPrint’s features:
“prettyPrint” is entirely independent. As mentioned, it requires NO StyleSheets or images. All styles are applied directly (inline) and are customizable via the exposed config object (prettPrint.config.styles).
See the demonstration »
Using it is simple enough:
var table = prettyPrint( anyRandomThing, { /*optional options object */ } );
// Making the table viewable is down to you...
// e.g.
document.body.appendChild(table);
For more information visit the project’s page on Github!
PS. I’m aware that there’s an already existing “port” of ColdFusion’s cfdump - I only created my one as a personal exercise; plus none of the ones I’ve seen so far have depth control or protection against circular references.