Prototypejs Salvage #2: Element .update()

January 30, 2012

I chose this method because not only do I use it constantly, but the source code is a bastion of browser-compatibility. In the future, perhaps there will be universal DOM standards, but for now, we have to either write our own compatibility or use a library that does. Something as vital to DOM scripting and UI building as .update() deserves some recognition.

What does it do?

it clears out the children of the element you’re running it on, and then inserts whatever you provided as an argument as it’s new children.

How do you use it?

Just select whatever you want’s contents to update, and then provide it with what you want to update

$("#someid").update("<span>some new content</span>");

Alternatively, you can even use this method with Prototype Salvage #1‘s subject, .invoke() like this!

$("p").invoke("update","I LEIK PARAGRAPHS");

Sounds pretty vanilla right? Okay hold your horses, I’m about to explain why this method is so cool.

Why is it cool?

If you’ve ever tried to do any serious DOM scripting without a library, you have probably encountered the fact that putting stuff inside another element is one of the most diverse aspects of everyone’s distribution of the DOM *COUGH-IE-COUGH*. Prototype’s .update() takes care of all that for you. How? That’s the most interesting part! This one is a little bit of a biggin, so I’m going to paste the whole thing and then break it down just below


update : (function() {

	var SELECT_ELEMENT_INNERHTML_BUGGY = (function() {
		var el = document.createElement("select"), isBuggy = true;
		el.innerHTML = "<option value=\"test\">test</option>";
		if(el.options && el.options[0]) {
			isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
		}
		el = null;
		return isBuggy;
	})();

	var TABLE_ELEMENT_INNERHTML_BUGGY = (function() {
		try {
			var el = document.createElement("table");
			if(el && el.tBodies) {
				el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
				var isBuggy = typeof el.tBodies[0] == "undefined";
				el = null;
				return isBuggy;
			}
		} catch (e) {
			return true;
		}
	})();

	var LINK_ELEMENT_INNERHTML_BUGGY = (function() {
		try {
			var el = document.createElement('div');
			el.innerHTML = "<link>";
			var isBuggy = (el.childNodes.length === 0);
			el = null;
			return isBuggy;
		} catch(e) {
			return true;
		}
	})();

	var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY;

	var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function() {
		var s = document.createElement("script"), isBuggy = false;
		try {
			s.appendChild(document.createTextNode(""));
			isBuggy = !s.firstChild || s.firstChild && s.firstChild.nodeType !== 3;
		} catch (e) {
			isBuggy = true;
		}
		s = null;
		return isBuggy;
	})();

	function update(element, content) {
		element = $(element);
		var purgeElement = Element._purgeElement;

		var descendants = element.getElementsByTagName('*'), i = descendants.length;
		while(i--)purgeElement(descendants[i]);

		if(content && content.toElement)
			content = content.toElement();

		if(Object.isElement(content))
			return element.update().insert(content);
		content = Object.toHTML(content);

		var tagName = element.tagName.toUpperCase();

		if(tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
			element.text = content;
			return element;
		}

		if(ANY_INNERHTML_BUGGY) {
			if( tagName in Element._insertionTranslations.tags) {
				while(element.firstChild) {
					element.removeChild(element.firstChild);
				}
				Element._getContentFromAnonymousElement(tagName, content.stripScripts()).each(function(node) {
					element.appendChild(node)
				});
			} else if(LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf('<link') > -1) {
				while(element.firstChild) {
					element.removeChild(element.firstChild);
				}
				var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true);
				nodes.each(function(node) {
					element.appendChild(node)
				});
			} else {
				element.innerHTML = content.stripScripts();
			}
		} else {
			element.innerHTML = content.stripScripts();
		}

		content.evalScripts.bind(content).defer();
		return element;
	}

	return update;
})()

Okay so you’ll notice right at line #1 that we’re starting off with a (function(){ which signifies a closure. This tells you right away that this is going to do some pre-processing before it even gives you the real .update() function.

That pre-processing is testing! It’s testing whether certain elements are buggy to insert into with .innerHTML and returning the result into some constant-style variables.

At line #3, it’s testing whether or not select elements are buggy by trying to insert an tag. using .innerHTML

It then goes on to test whether other types of elements are buggy until line #53 where function update(element, content) appears. It’s important to note that these tests above line #53 don’t run every time you use .update(); It only runs when Prototype.js initializes. But due to the magic of closures, the .update() function still has access to those tests’ results even after the function ends and returns the update function.

So at the end of all the testing, it initializes a new variable called ANY_INNERHTML_BUGGY which it will use later inside .update().

.update() itself starts out pretty standardly. It gathers all of the element’s descendants on like #57 and purges them with the funky while loop on line #58. Then from line #60-#64, it wants to see if the browser supports directly converting the content string directly into an HTML element. If it can, it will go ahead and insert it and then exit the function early.

If we’re not so lucky to have this functionality, we continue. After that there is some stuff handling if you’re trying to put stuff into a script tag.

Now, at line #74 is where shit gets real. This is where it used all the pre-processing tests it did upon init of Prototype to decide how to do what you need it to do. I won’t bore you with the intricacies of this logic block, but if anything was buggy, it handles it how it needs to do get handled and gets what you need it to do done.

I love seeing code like this. it might be a crutch for the disaster that is the DOM, but it still handles its business.

I actually found a picture of the spokesperson for Internet Explorer:

Internet Explorer Spokesman

Internet Explorer Spokesdog. True Story.

Prototypejs Salvage #1: .invoke()

January 29, 2012

At work, the main Javascript library we use is Prototype for general day-to-day Javascript. While there are many, many better and well-documented Javascript libraries out there, we are pretty much stuck with it. That being said, over the last 14 months of working with it I have grown a strange fondness for it. Mostly because I like the source code and its direct DOM extensions. Unfortunately, due to many factors, Prototypejs is a falling star. But all is not lost. In this post, we will be taking things from the Prototypejs source code and breaking them apart and telling you how awesome they are so that future generations may take what is awesome and leave what is not.

So this post is the first in the Prototypejs Salvage Series. In this series we will be looking at the awesome .invoke() element array method.

What does it do?

Invoke basically means “run action provided as an argument on every item in this collection.” So you basically create an array of elements (usually) using a selector and you can run an action on all of them.

How do you use it?

First, get a collection of elements in the dom:

var indented_paragraphs = $$("p.indent");

Now you can run invoke on your indented_paragraphs variable!

indented_paragraphs.invoke("removeClassName","indent");

Why is it cool?

Some of you might be saying:

“Why use invoke? Why not just .each() and run the method in the anonymous function?”

I actually said that exact sentence when I first encountered one of my co-workers using it. Well, lets take a look at the source code!


function invoke(method) {
	var args = $A(arguments).slice(1);
	return this.map(function(value) {
		return value[method].apply(value, args);
	});
}

Pretty simple – In fact, it’s actually just an alias using the .map() function. In order to see how it works, we have to look into the .map() function! But wait! It seems the .map() function is actually just an alias for the collect() function. So lets look at that:


function collect(iterator, context) {
	iterator = iterator || Prototype.K;
	var results = [];
	this.each(function(value, index) {
		results.push(iterator.call(context, value, index));
	});
	return results;
}

So basically, .invoke() is passing a function to collect which is identified as the “context” argument. It then starts an .each() loop and uses the .call() method to give the item in the collection context and pushes the results into the returned results array.

So essentially, .invoke() takes a cumbersome thing like:


$$("p.indent").each(function(item) {
    item.removeClassName("indent");
})

and turns it into this:


$$("p.indent").invoke("removeClassName","indent");

So invoke() is essentially a layer of abstraction from .each()?

Yes. Anticlimactic? maybe. The point is, I use .invoke() all the time. Could I do the same thing with .each()? of course. However, that clutters your code with unneeded anonymous functions and the code within. That gets pretty old quick.

Javascript: The Good Parts

January 5, 2011

I just finished reading Javascript: The Good Parts by Douglas Crockford.

Aside from being amazing, it was also very eye-opening and instantly changed the way I code. The book is only like 141 pages, but every word is pure nerdy gold. If you are versed in Javascript but maybe find yourself not using objects quite as often as you think you should, read this book.

If you actively write any size Javascript applications, read this book. Javascript: The Good Parts is just about the best kind of book. Short, sweet and densely knowledge-packed.

Prototype’s Downfall

November 7, 2010

So at my new job I’ve been tasked with learning the Prototype javascript library. Now previously (like every other javascript developer ever) I was working with jQuery. Theres a few great points about jQuery that i’d like to address here.

The first, is that jQuery is great. It’s sizzle selectors, it’s animation functions, blah blah blah. I don’t really need to tell the js community that jQuery is great, because it is. The second, is the documentation. the jQuery documentation is very very good. Some of the best documentation out there for Javascript in general. Possibly the best. And here’s where this blog post goes full-circle.

Prototype. First, I’d like to highlight some of the cool things about prototype. To start, the Class implementation is top notch. It makes your js more like Ruby, which I think javascript as a whole benefits greatly from. Secondly, the object extensions are great and they easily rival jQuery’s. Now that I’ve sufficiently ass-slapped Prototype, I think it’s time to address why Prototype fails.

Say it with me, documentation. It’s horrible and borderline useless. on many many important points, it doesn’t have examples or why you would use it or how – just the syntax structure of how you would type in the parameters. The second is that Prototype is borderline too much. And I don’t say that because it gives me too many nice things, I say that because it’s huge. Compared to jQuery it’s just massive. That being said, I LIKE prototype. I really do. If it cleaned up a little bit and had some real web-based documentation I think it could once again be the big player that it was in 2006.

Couch Camp Recap

October 3, 2010

Kinda late, but I didn’t have a blog until now! So I went to Couch Camp, which I should mention is totally a camp. It was this incredible place (Walker Creek Ranch) in the California mountains outside the bay area and it was beautiful. Furry animals everywhere, beautiful scenery, and endless brilliant nerds.

The talks were kicked off with one from Damien the night we all arrived. It was inspiring, and it’s so great so see someone so proud of his work and the community behind it.

The next day got kicked off with Josh Berkus from PostgreSQL. He’s your quintessential database guy, and he shows it with his vast knowledge of all things database. SQL and otherwise. His talk was about CouchDB not making the same mistakes as SQL databases. Learning from their flaws, and taking from their strengths. It was a great talk.

Next came Stuart Langridge from Canonical (creators of Linux Ubuntu). He was unapologetically British and also awesome and funny. His talk was about how and why Ubuntu has included CouchDB into their release, and the future of CouchDB in Ubuntu, and the problems that lie within. Talking about Ubuntu One, and CouchDB being their replication flagship was very exciting, and we’ll see some great things come from that in the future.

Then Max Ogden talked about all the cool stuff he was doing with CouchDB. This guy is nothing short of genius. He’s creating applications off of a platform built on top of CouchDB called GeoCouch. It’s basically mapping software, storing the points in CouchDB. He has created some excellent stuff with it. Too many to mention here, but I recommend you check them out on his site.

Next up was Selena Deckelmann, another PostgreSQL employee. Her talk was about how PostgreSQL handles the MVCC model. Much of it was over my head, seeing as I am not a SQL db guru by any stretch of the imagination, but I still learned tons from her presentation.

After that came Ted Leung’s talk. This talk was mostly about community. How to stay strong and keep building. It was both funny and inspiring and I was really proud to be among the people recieving that talk. Ted has been with CouchDB since it’s first adoption by Apache. He works with Apache, and has basically been it’s advocate and mentor since. It was great to hear about the inner workings of Apache and the trials that CouchDB has gone through therein. I really enjoyed this one.

The rest of the time at Couch Camp was spent drinking, playing D&D, and more drinking. I had an incredible time there, met great people (big ups to the Germans) and can’t wait to go back next year.