Perfection kills

Exploring Javascript by example

Archives Posts

So you want more documentation?

September 24th, 2007 by kangax

Have you ever heard of prototype? I’m sure you have, since you’re reading this blog. Many things make this library a weapon of choice for developers all over the world. The way work is done with prototype is simply mind blowing – concise and expressive code, full range of tools for the job just seem to make a dream come true. One day, though, you run into problems – things just don’t work the way they’re supposed to. You open your browser, point it to google and… dark side of prototype reveals itself in its full glory…

No, I’m not trying to sound dramatic, but the fact is that prototype is notoriously famous for the lack of down-to-earth documentation. For javascript ninjas, quick glance over source code should clear all the confusion. For the rest of us, there’s a wonderful place called API reference. This should be your first bookmark on the way to perfection. The sad part is that not everyone understands API reference. People demand easy to follow tutorials, out-of-the-box solutions and lower taxes.

For all of you – lazy bastards – out there, here’s a list of must know resources:

Official documentation

Prototype API reference
Script.aculo.us wiki

News groups:

Google groups, RoR Spinoffs – Number 1 place to look for answers, ask questions, get help quickly. Some really smart folks help hundreds of people with most twisted problems (yes, you’re right, 90% are IE related)

Google groups, Prototype: Core – Once you know prototype’s source code by heart, feel free to join this group to discuss the inner essence of prototype’s magic. Other than that – great place to search for issues that might have already been solved before.

Core developers’ blogs

Tobie Langel
Andrew Dupont
Thomas Fuchs
Justin Palmer
Dan Webb

Not core developers’ blogs (but still very educating)

Matt Foster
Ryan Gahl
Samuel Lebeau

Other resources

prototype @ Ajaxian

Offline documentation

Prototype API
Script.aculo.us Docs

That’s about it.

Did I forget something? Feel free to add more links in comments.

Good luck and happy prototyping…

Archives Posts

Wrap it up!

September 14th, 2007 by kangax

Today I want to talk about new function method, introduced in latest Prototype (1.6). Function#wrap is simply amazing when it comes to flexibility and elegance of code. You will never go back once started using it. Essentially, Function#wrap allows us to augment default function behavior by “wrapping” it with our own logic. Not only we could “wrap” it, but pretty much do anything we want – change in any way we need.

Here’s what 1.6rc0 release notes say about it:

Function#wrap distills the essence of aspect-oriented programming into a single method, letting you easily build on existing functions by specifying before and after behavior, transforming the return value, or even preventing the original function from being called…
Don’t worry, it’s not that complicated and makes perfect sense when explained, so enough theory, let’s see what makes Function#wrap so good in pactice.

Stripping tags

stripTags is a quite useful String method (defined through String.prototype) which simply strips any tags from a given string. The basic syntax is:

'<a>Hello wonderful world!</a>'.stripTags(); //=> 'Hello wonderful world!'

This might be sufficient 90% of the time, but what if we want to strip only particular set of tags? It seems as if the only solution would be to write our own function or change prototype’s source code (which is a really terrible thing to do). This is where wrap comes into play. By wrapping the prototype’s stripTags we keep the same logic in the same place which is a good thing. One of the beauties of javascript’s dynamic nature is that we can define different behavior depending on whether any arguments were passed to the function or not. Let’s say we want out stripTags to be defined like so:

String.prototype.stripTags(); // => Looks just like regular stripTags and should act the same - strip all tags
String.prototype.stripTags(tag1, tag2, ... , tagN); // => Should have augmented behavior to ONLY strip specified tags (tag1, tag2, ... , tagN)

Now, if we needed to strip all <p> and <span> tags, we would simply do:

'<p><a>ninjas gone wild... <span>or javascript on steroids</span></a></p>'.stripTags('p', 'span'); // => '<a>ninjas gone wild... or javascript on steroids</a>'

Ok, looking good so far. Let’s see how this is done, and then I’ll explain what exactly is going on:

String.prototype.stripTags = String.prototype.stripTags.wrap(function(){
	var args = $A(arguments), proceed = args.shift();
	if (args.length > 0) {
		return this.replace(new RegExp('<\/?(' + args.join('|') + ')[^>]*>', 'gi'), '');
	}
	return proceed();
})

1

We change stripTags by assigning same function, but with “wrap” invoked on it. Function passed to “wrap” is where we define all the behavior. First argument of this anonymous function references original function (the one that we’re wrapping – in this case – stripTags). Other arguments correspond to those that are defined within our original function (in this case, though, stripTags does not have any named arguments)

String.prototype.stripTags = String.prototype.stripTags.wrap(function(){
...

2

What we have here is a common technique used internally in prototype when working with unknown number of arguments. First, we extend “arguments” variable with Array’s enumerable methods, then we reference first argument from that array, shifting it from the array at the same time. What we have now is our first argument (which, as you remember, references original function) and the rest of the arguments, which in our case are just tag names.

...
var args = $A(arguments), proceed = args.shift();
...

3

Here comes the modified part. If the function had any arguments passed into it, we replace the string using regular expression (which matches all those tags) and return modified string.

...
if (args.length > 0) {
	return this.replace(new RegExp('<\/?(' + args.join('|') + ')[^>]*>', 'gi'), '');
}
...

4

If this is not the case – there are no arguments passed – we simply invoke original function, as if nothing had ever happened : )

...
return proceed();

That’s all there is to it. Simple, effective and sort of mind-blowing, don’t you think?

Later, I will post some more examples on what becomes possible using this technique, but as for now, I hope this one clears some potentially confusing parts and you can start experimenting with it right now.

Enjoy!

Archives Posts

Proto.Lazy – do we really need lazy image loading?

September 11th, 2007 by kangax

It looks like the “Lazy load” plugin for jQuery (released about a week ago) got quite of attention. I personally don’t find it much useful but the idea is pretty cool. Can we do something like this with prototype? Easy! Let’s see how.

One of the things I found to be challenging was calculating whether element is positioned within viewport. Prototype does not have such method (as far as I know) but it does provide us with something that makes it quite trivial. Let’s look at ingredients:

Note: The following snippets require prototype 1.6rc0 or higher

 
// returns viewport dimensions
// alternatively there are explicit getHeight() and getWidth() methods
document.viewport.getDimensions(); // {width: 1024, height: 768}
 
// returns scroll offsets of viewport (how much the page was scrolled)
document.viewport.getScrollOffsets(); // [0, 10]
 
// returns element's position relative to a PAGE
element.cumulativeOffset(); // [120, 560]
 
// getDimensions() - alternative for getHeight/getWidth (in the end I'll explain why use one over another):
element.getDimensions(); // {width: 100, height: 310}

OK. Those only look scary, but are actually quite simple to use.

I decided to wrap the entire script into “Proto” namespace to be nice with window object (you are wrapping your stuff into namespace, aren’t you?) and define “Lazy” class in that namespace. Two actions will trigger image loading – window’s scroll and resize events (I’m not sure if original plugin checks window for resize but I think it makes more sense this way). Alternatively, if “event: ‘click’” option is set, pictures will be revealed on click (ignoring window’s events). Here’s a short breakdown of what’s going on:

  • Iterate over all images on a page.
  • If image is NOT within viewport at the moment, empty its “src” attribute and store it as a custom property of an element (this prevents it from being loaded).
  • If event: ‘click’ is set, attach click handler to reveal image else:
  • Attach event handlers to window’s scroll and resize events
  • Once any of window events occur (scrolled or resized) check whether image is within viewport and if so, put its original “src” attribute back from custom property, delete that property.

That’s all there is to it, 40 lines of happyness…

if (Object.isUndefined(Proto)) {var Proto = {}}
Proto.Lazy = Class.create({
    initialize: function(options) {
        this.options = options || {};
        $$('img').each(function(el){
            if (!this.withinViewport(el)) {
                el._src = el.src;
                el.src = this.options.placeHolder || '';
	        if (this.options.event === 'click') {
	            el.observe('click', function(){
		        if (this._src) { this.src = this._src; delete this._src }
		    })
	        }
	    }
	}.bind(this));
	if (this.options.event !== 'click') {
	    Event.observe(window, 'scroll', this.load.bind(this));
	    Event.observe(window, 'resize', this.load.bind(this));
	}
    },
    load: function(el) {
	$$('img').each(function(el){
	    if (el._src && this.withinViewport(el)) { el.src = el._src; delete el._src }
	}.bind(this))
    },
    withinViewport: function(el) {
        var elOffset = el.cumulativeOffset(),
             vpOffset = document.viewport.getScrollOffsets(),
             elDim = el.getDimensions(),
             vpDim = document.viewport.getDimensions();
    	if (elOffset[1] + elDim.height < vpOffset[1] || elOffset[1] > vpOffset[1] + vpDim.height ||
	    elOffset[0] + elDim.width < vpOffset[0]  || elOffset[0] > vpOffset[0] + vpDim.width) {
        	return false;
    	    }
    	return true;
    }
})

After including Proto.Lazy.js, all that’s left to do is instantiate object (preferably before images are loaded which is right after ‘contentloaded’ event is fired):

document.observe('contentloaded', function(){
        new Proto.Lazy();
        // new Proto.Lazy({event: 'click'})
        // new Proto.Lazy({placeHolder: 'images/default.png'})
})

Here’s a simple demo page.

It’s pretty clear that withinViewport function should be fast, since it’s being called on EVERY scroll/resize event for EVERY image. That’s a LOT of calls, considering the dynamic nature of resize/scroll events. Knowing that getHeight() and getWidth() methods actually invoke getDimensions() method (and read corresponding property from returned object), we could save some time by calling getDimesnions() explicitly and store returned object for later use.
I also have a feeling that image iteration could be done in a more efficient way. Maybe it would make sense to break out of a loop once first hidden image is found, since viewport is always occupying few closely positioned images. The problem though is that this would only work with images positioned linearly and in the same order as defined in a document.
In the end, I’m not quite sure how fast this implementation is (some tests with bunch of thumbnails would be nice to have) but it seems to be doing its job.

I hope this was a good example of defining a class in prototype as well as working with viewport and element positioning. I also encourage everyone to try to think about speed issues when developing any kind of user interfaces (no, seriously, nothing is more annoying than slow and unresponsive app).

Happy prototyping!

« Previous Entries