Skip to content

Making $.metadata Extensible

With the support for HTML5 data- attributes in jQuery 1.4.3 this plugin is largely obsolete.

Metadata

I put the idea that the metadata plugin should be extensible out on the jquery discussion group, but it got no attention, so I'm documenting it here.

Metadata is information about a DOM element that isn't part of the actual text; e.g. you mention New York City and somehow associate the coordinates [40.783, -73.967] with that. Scripts can use that information to add to the user experience; in this case, you could point the city out on a map.

There are two reasons to use metadata, for good or for evil:

  1. For good: the metadata is semantic, related to the actual information present on the page and the metadata helps build the semantic web.
  2. For evil: as a way of mixing behavior and presentation in the semantic markup. In this sense, in-line style statements and class="draggable" sorts of things are metadata.

I'm obviously being tongue-in-cheek here; mixing presentation and markup isn't really evil. Having everything on one page is both faster to write, and, for small projects, easier to understand.

The metadata plugin

The jquery metadata plugin provides a way of getting metadata out of an element and into a javascript object that you can use. obj = $('#foo').metadata() analyzes the foo element for metadata and returns it. For efficiency's sake, it stores that metadata in the jQuery cache under the key 'metadata', so $('#foo').data('metadata') will get it back again, and in fact metadata first checks for that cache and only analyzes the element if it isn't found. This means the analysis only happens once per element. The key can be changed with the single option (calling it single is a historical accident, from previous versions of metadata that used mulitple expandos on the element or combined them into one expando).

Creating metadata

The problem is that there is no standard for incorporating metadata into your markup. Lots of things have been proposed or used, like custom attributes, pre-determined classes (the microformats approach) and HTML5's data- attributes. The metadata plugin allows for three methods: custom attributes, JSON in the className, and the content of a specified child element (generally a <script> element with an unrecongnized type that will be ignored by the HTML parser). In the words of the plugin documentation, "This means that there is at least one option here that can appease you."

But that clearly isn't true. There are lots more ways to encode metadata, and there's no reason that the plugin should be limited to just three. Therefore, in the spirit of jQuery, I made it freely extensible:

The extensible metadata


$.extend({
	metadata : {
		defaults : {
			type: 'class',
			name: 'metadata',
			cre: /({.*})/,
			single: 'metadata'
		},
		setType: function( type, name ){
			this.defaults.type = type;
			this.defaults.name = name;
		},
		get: function( elem, opts ){
			var opts = $.extend({},this.defaults,opts);
			return $.data(elem, opts.single) || $.data(elem, opts.single, this[opts.type].call(elem,opts));
		},
		asJSON: function(s){
			if (!s) return {};
			if (/^[[{]/.test($.trim(s))) return eval("(" + s + ")");
			return s;
		}
	}
});

$.fn.metadata = function( opts ){
	return $.metadata.get( this[0], opts );
};

and all metadata "analyzing functions"--defined by the options.type string--take an options object and return the metadata object, with this bound to the element.

The original three

For example, the original metadata methods are:


$.metadata["class"] = function (opts){
	var m = opts.cre.exec( this.className );
	if ( m ) return $.metadata.asJSON(m[1]);
	return {};
};

$.metadata.elem = function (opts){
	var e = this.getElementsByTagName(opts.name);
	if ( e.length ) return $.metadata.asJSON(e[0].innerHTML);
	return {};
};

$.metadata.attr = function (opts){
	var attr = this.getAttribute( opts.name );
	// allow for not including the braces, as in the original metadata plugin
	if ( attr ){
		if ( attr.indexOf( '{' ) < 0 ) attr = "{" + attr + "}";
		return $.metadata.asJSON(attr);
	}
	return {};
};

Example: type 'class'


<div class="{ draggable : {axis: 'x', opacity: 0.5}} example">I have data!</div>

Example: type 'attr'


<div coords="{lat: 40, long: -90}" class="example">I have data!</div>

Example: type 'elem'


<div class="example">
<script type="text/json">{first: 1, second: 2}</script>
I have data!
</div>
I have data!

Example: metaobjects

Andrea Ercolino's metaobjects uses an <object> element to insert metadata in a standards-compliant way, with the data in the <param> elements


$.metadata.object = function (opts){
	var ret = {};
	$('> object.metaobject > param', this).each(function(){
		ret[this.name] = $.metadata.asJSON(this.value);
	});
	return ret;
};

<div class="example">I have data!
<object class="metaobject" style="display:none">
	<param name="Full Name" value="Danny W"/>
	<param name="Age" value="too large to count" />
</object>
</div>

Example: Microformats

Microformats are a way of marking up metatdata that is actually present in the text so it becomes machine readable as well. We recognize that "123 Sesame Street" is an address, but our programs aren't so smart unless we tell them: "<span class="street-address">123 Sesame Streetcode></span>". Dan Webb wrote a clever microformat parsing program called Sumo that we'll use:


$.metadata.hcard = function(opts){
	var hcards = HCard.discover(this);
	return hcards.length ? hcards[0] : {};
};

Sumo uses a subset of Prototype; someday I may port it to jQuery.


<div class="example">
  I have data from 
	<span class="vcard">
	  <span class="fn">Danny Wachsstock</span>
		at <span class="tel">555-1212</span>
	</span>!
</div>

HTML 5

HTML5 allows for custom attributes on any element, as long as the name starts with "data-". It's pretty controversial in the standardista community to allow this kind of freedom, but given that people are already doing it with all sorts of attributes, it's a concession to the reality that the standards-setters don't know everything and can't predict everything. John Resig likes it, but it hasn't made it into the metadata plugin yet. Until now.


$.metadata.data = function(opts){
	var ret = {};
	$.each (this.attributes, function(){
		var m = /data-(.*)/.exec(this.nodeName);
		if (m) ret[m[1]] = $.metadata.asJSON(this.nodeValue);
	});
	return ret;
};

<div data-lat="40" data-long="-90" class="example">I have data!</div>

{ 3 } Comments


  1. Fatal error: Uncaught Error: Call to undefined function ereg() in /home/public/blog/wp-content/themes/barthelme/functions.php:178 Stack trace: #0 /home/public/blog/wp-content/themes/barthelme/comments.php(34): barthelme_commenter_link() #1 /home/public/blog/wp-includes/comment-template.php(1469): require('/home/public/bl...') #2 /home/public/blog/wp-content/themes/barthelme/single.php(44): comments_template() #3 /home/public/blog/wp-includes/template-loader.php(74): include('/home/public/bl...') #4 /home/public/blog/wp-blog-header.php(19): require_once('/home/public/bl...') #5 /home/public/blog/index.php(17): require('/home/public/bl...') #6 {main} thrown in /home/public/blog/wp-content/themes/barthelme/functions.php on line 178