Javascript Zombies

Javascript examples tend to zombify quite fastly. It’s pretty easy to make up a page for showing something, but very soon that page becomes unusable. I’m constantly finding many broken javascript examples in the internet.

And the semantic of a broken javascript example is a logic problem nobody can solve. Is it broken by accident or on purpose? Was it already broken when created? Who, what, where, when did it break? Is it a browser test? Am I doing something wrong?

Maybe we should refrain from presenting online examples.

Metaobjects 1.1 Released Today

The Metaobjects plugin is going to become a jQuery official plugin, starting from the next jQuery 1.1 release, due out in a week or so. Meanwhile I’ve come up with some improvements that I’m going to share with you, releasing a new version of Metaobjects.

Changes

  • Added a new option for selecting the metaobjects to process. The option name is “selector” and its value is a jQuery selector, which defaults to “object.metaobject”, i.e. an OBJECT element with a ‘metaobject’ class (the metaobject definition).

Example: process metaobjects selected by “object.bar”

$.metaobjects({selector: 'object.bar'});
  • Added the concept of metaparam for configuring options local to a metaobject. If a PARAM element is given the ‘metaparam’ id, then it is treated as a configuration element, not a metadata element. Either one or none of the PARAM elements can be a metaparam. Its name is needed but not used. A good choice could be ‘options’ or ‘configuration’, anyway the ‘metaparam’ id garantees that it won’t clash with a metadata name you might need.
  • Fixed a bug that didn’t allow metaobjects to add metadata to elements without a closing tag, like images (thanks to John Resig for pointing it out). Now this is possible by means of the ‘target’ metaparam option, whose value is a jQuery selector, which defaults to the parent of the metaobject. The selector takes the document as its context.

Example: add this metadata to all the elements selected by “img.foo”

<div><object class="metaobject bar">
	<param name='options' value='{target: "img.foo"}' id='metaparam' />
	<param name='title'   value='"What a Foo!"' />
</object></div>

Code of Metaobjects 1.1

/* 
=============================================================================== 
Metaobjects is a jQuery plugin for setting properties of DOM elements  by means 
of metaobjects (OBJECT elements with a 'metaobject' class) 
...............................................................................                                                
                                               Copyright 2007 / Andrea Ercolino 
------------------------------------------------------------------------------- 
LICENSE: http://www.opensource.org/licenses/mit-license.php UPDATES: 
http://noteslog.com/metaobjects/ 
=============================================================================== 
*/

/** 
 * The metadata is added to the XHTML page by means of metaobjects whose PARAM  
 * elements define name/value pairs. The given 'value' attribute is evaluated and  
 * added to the metaobject's parent as a property with the given 'name' attribute.  
 * Finally the metaobject is removed from the DOM. 
 * 
 * Is possible to configure the target of a metaobject by means of a metaparam, 
 * i.e. a PARAM element with a 'metaparam' id, (one for each metaobject). The name 
 * of the metaparam is required, but currently not used, so it can be anything not 
 * null, like 'options'. The value of the metaparam must be an object like this: 
 * {target: selector}. The selector is a jQuery selector used for finding the 
 * target inside the document. For example this is used for targeting all the 
 * images in the current document
 * 
 * <param id='metaparam' name='options' value='{target: "img"}'>
 *  
 * @signature 
 * |* jQuery *| $.metaobjects(  
 *   |* Object *| options = { 
 *     |* Element *| context: document,  
 *     |* Boolean *|   clean: true,
 *     |* String *| selector: "object.metaobject" 
 *   }  
 * ) 
 * 
 * @type  
 *   jQuery 
 * @name 
 *   $.metaobjects 
 * @param 
 *   Object options = {context: document, clean: true, selector: "object.metaobject"} 
 * @option  
 *     Element context The context where the metaobjects are 
 * @option  
 *     Boolean   clean True means 'remove metaobjects after processing' 
 * @option  
 *     String selector The jQuery selector used for finding metaobjects to process
 * @cat  
 *   Plugins/Metadata 
 * 
 * @example  
 *   $.metaobjects(); 
 * @desc  
 *   load meta data from all of the meta objects in the document and remove them 
 *  
 * @before  
 * <html><head><title>Hi There</title> 
 * ... 
 * <script type="text/javascript"> 
 * $( function() {  
 *   $.metaobjects();  
 *   var p1 = $('#one')[0]; 
 *   $( 'body' ) 
 *     .append(  
 *         '<p>'  
 *       + 'width = ' + p1.meta_size.width 
 *       + '<br />' 
 *       + 'height = ' + p1.meta_size.height 
 *       + '</p>' 
 *     ); 
 * } ); 
 * </script> 
 * </head><body> 
 * <p id="one"> 
 *   <object class="metaobject"> 
 *     <param name  = 'meta_size'  
 *            value = '{ width: 400, height: "auto" }' /> 
 *     <param name  = 'title'  
 *            value = 'document.title' /> 
 *   </object> 
 *   Hello World 
 * </p> 
 * </body></html> 
 *  
 * @after 
 * <html><head><title>Hi There</title> 
 * ... 
 * <script type="text/javascript"> 
 * ... 
 * </script> 
 * </head><body> 
 * <p id="one" title="Hi There"> 
 *   Hello World 
 * </p> 
 * <p> 
 * width = 400 
 * <br/> 
 * height = auto 
 * </p> 
 * </body></html> 
 *  
 * @author Andrea Ercolino (http://noteslog.com/) 
 * @version 1.1  
 */ 

( function($) {
	$.metaobjects = function( options ) {

		function getParam( elem ) {
			var $param = $( elem );
			var pName = $param.attr( 'name' );
			if ( '' == pName) return null;
			var pValue = $param.attr( 'value' );
			var data;
			eval( 'data = ' + pValue + ';' );
			return { name: pName, value: data };
		}

		var global_settings = { 
			  context: document
			, clean: true
			, selector: 'object.metaobject' 
		};
		$.extend( global_settings, options );
		var $metaobjects = $( global_settings.selector, global_settings.context );
		$metaobjects.each( function() {
			var settings = {};
			$( '> param#metaparam', this )
				.each( function() {

					var p = getParam( this );
					if( ! p ) return;
					$.extend( settings, p.value );

				} );
			var $target;
			if( settings.target ) {
				$target = $( settings.target );
				if( 0 == $target.length ) {
					return;
				}
			}
			else {
				$target = $( this.parentNode );
			}
			$( '> param', this )
				.not( '#metaparam' )
				.each( function() {
				
					var p = getParam( this ); 
					if( ! p ) return;
					$.map( $target.get(), function( elem ) {
						elem[ p.name ] = p.value;
						return elem;
					} );

				} );
			if( global_settings.clean ) {
				$( this ).remove();
			}
		} );
		return $metaobjects;
	} 
} ) ( jQuery );
Tests

These tests show what happens when the metaobjects() function is called with different options on the same page. (only the name of the file changes)

  • test.hml
  • test1.hml
  • test2.hml
  • test3.hml

XHTML page

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html><head><title>Hi There</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<script type="text/javascript" src="jquery-891.pack.js"></script>
<script type="text/javascript" src="metaobjects.js"></script>
<script type="text/javascript" src="setup.js"></script>
<style type="text/css">
p#comment {
	border: 1px dashed silver;
	padding: 10px;
}
</style>
</head><body>

<p id="one">
	<object class="metaobject">
		<param name='meta_size' value='{ width: 400, height: "auto" }' />
		<param name='title'     value='document.title' />
	</object>
	Hello World
</p>

<p><img class="foo" src="jquery-icon.png" alt="jquery icon" /></p>

<p id="two">
	<object class="metaobject bar">
		<param name='meta_size' value='{ width: "auto", height: 300 }' />
		<param name='title'     value='"Goodbye!"' />
	</object>
	See you soon
</p>

<div><object class="metaobject bar">
	<param name='options' value='{target: "img.foo"}' id='metaparam' />
	<param name='title'   value='"What a Foo!"' />
</object></div>

<p id="comment"></p>

<p>
	<a href="http://validator.w3.org/check?uri=referer"><img
		src="http://www.w3.org/Icons/valid-xhtml10"
		alt="Valid XHTML 1.0 Strict" height="31" width="88" /></a>
</p>

</body></html>

setup.js

$( function() { 
	var loc = document.location.href;
	$( '#comment' ).html( "what happens if $.metaobjects() is not called" );

	if( loc.indexOf( "test1.html" ) >= 0 ) {
		$( '#comment' ).html( "what happens after calling: $.metaobjects()" );
		$.metaobjects();
//		$.metaobjects({selector: 'object.bar'});
//		$.metaobjects({clean: false}).hide(); 
		var p1 = $('#one')[0];
		var p2 = $('#two')[0];
		$( 'body' )
			.append( '<p>' + 'width = ' + p1.meta_size.width + '<br />'
				+ 'height = ' + p1.meta_size.height + '</p>'
			)
			.append( '<p>' + 'width = ' + p2.meta_size.width + '<br />'
				+ 'height = ' + p2.meta_size.height + '</p>'
			)
		;
	}

	if( loc.indexOf( "test2.html" ) >= 0 ) {
		$( '#comment' ).html( "what happens after calling: $.metaobjects({selector: 'object.bar'});" );
//		$.metaobjects();
		$.metaobjects({selector: 'object.bar'});
//		$.metaobjects({clean: false}).hide(); 
		var p1 = $('#one')[0];
		var p2 = $('#two')[0];
		$( 'body' )
//			.append( '<p>' + 'width = ' + p1.meta_size.width + '<br />'
//				+ 'height = ' + p1.meta_size.height + '</p>'
//			)
			.append( '<p>' + 'width = ' + p2.meta_size.width + '<br />'
				+ 'height = ' + p2.meta_size.height + '</p>'
			)
		;
	}

	if( loc.indexOf( "test3.html" ) >= 0 ) {
		$( '#comment' ).html( "what happens after calling: $.metaobjects({clean: false}).hide();" );
//		$.metaobjects();
//		$.metaobjects({selector: 'object.bar'});
		$.metaobjects({clean: false}).hide(); 
		var p1 = $('#one')[0];
		var p2 = $('#two')[0];
		$( 'body' )
			.append( '<p>' + 'width = ' + p1.meta_size.width + '<br />'
				+ 'height = ' + p1.meta_size.height + '</p>'
			)
			.append( '<p>' + 'width = ' + p2.meta_size.width + '<br />'
				+ 'height = ' + p2.meta_size.height + '</p>'
			)
		;
	}


} );