{"id":108,"date":"2008-08-29T01:20:59","date_gmt":"2008-08-29T07:20:59","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/?p=108"},"modified":"2010-11-11T23:56:07","modified_gmt":"2010-11-12T05:56:07","slug":"making-metadata-extensible","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2008\/08\/29\/making-metadata-extensible\/","title":{"rendered":"Making $.metadata Extensible"},"content":{"rendered":"<p><strong>With the support for <a href=\"http:\/\/ejohn.org\/blog\/html-5-data-attributes\/\">HTML5 data- attributes<\/a> in <a href=\"http:\/\/api.jquery.com\/data\/\">jQuery 1.4.3<\/a> this plugin is largely obsolete.<\/strong><\/p>\r\n<script src=\"http:\/\/svn.danwebb.net\/external\/microformat\/microformat.js\"><\/script>\r\n<script src=\"http:\/\/svn.danwebb.net\/external\/microformat\/hcard.js\"><\/script>\r\n<script src=\"http:\/\/www.json.org\/json2.js\"><\/script>\r\n<script>\r\n\t$(function(){\r\n\t\t$('.example').css({\r\n\t\t\t'float': 'left',\r\n\t\t\tborder: '2px solid #0f0',\r\n\t\t\tpadding: '5px',\r\n\t\t\tmargin: '5px'\r\n\t\t});\r\n\t\t\r\n\t\t$('.examplebutton').click(function(){\r\n\t\t\tvar opts = eval(\/(\\({.*}\\))\/.exec(this.value)[1]);\r\n\t\t\talert(JSON.stringify($(this).prev('.example').metadata(opts)));\r\n\t\t}).after('<br style=\"clear: both\"\/>');\r\n\t});\r\n<\/script>\r\n<h4>Metadata<\/h4>\r\n<p>I put the idea that the metadata plugin should be extensible \r\nout on <a href=\"http:\/\/groups.google.com\/group\/jquery-en\/browse_thread\/thread\/5ca1030e55c4a561\/c41dc8f214ad7c91\">the \r\njquery discussion group<\/a>, but it got no attention, so I'm documenting it here.<\/p>\r\n<!--more-->\r\n<p>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\r\n[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.<\/p>\r\n<p>There are two reasons to use metadata, for good or for evil:<\/p>\r\n<ol>\r\n\t<li>For good: the metadata is semantic, related to the actual information present on the page and the metadata helps build the \r\n\t<a href=\"http:\/\/en.wikipedia.org\/wiki\/Semantic_Web\">semantic web<\/a>.<\/li>\r\n\t<li>For evil: as a way of mixing behavior and presentation in the semantic markup. In this sense, in-line style statements and \r\n\t<code>class=\"draggable\"<\/code> sorts of things\r\n\tare metadata.<\/li>\r\n<\/ol>\r\n<p>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,\r\nfor small projects, easier to understand.<\/p>\r\n<h4>The metadata plugin<\/h4>\r\n<p>The <a href=\"http:\/\/plugins.jquery.com\/project\/metadata\">jquery metadata plugin<\/a> provides a way of getting metadata out of an element\r\nand into a javascript object that you can use. <code class=\"language-javascript\">obj = $('#foo').metadata()<\/code> analyzes the foo element\r\nfor metadata and returns it. For efficiency's sake, it stores that metadata in the jQuery cache under the key <code>'metadata'<\/code>,\r\nso $('#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.\r\nThis means the analysis only happens once per element.\r\nThe key can be changed with the <code>single<\/code> option (calling it <code>single<\/code> is a historical accident, from previous versions of metadata that\r\nused mulitple expandos on the element or combined them into one expando).<\/p>\r\n<h4>Creating metadata<\/h4>\r\n<p>The problem is that there is no standard for incorporating metadata into your markup. Lots of things have been proposed or used, like custom\r\nattributes, pre-determined classes (the <a href=\"http:\/\/microformats.org\/\">microformats<\/a> approach) and \r\n<a href=\"http:\/\/www.w3.org\/html\/wg\/html5\/#custom\">HTML5's data- attributes<\/a>. The metadata plugin allows for three methods: custom attributes, JSON in the className,\r\nand the content of a specified child element (generally a &lt;script&gt; element with an unrecongnized type that will be ignored by the HTML parser). In the words of\r\nthe <a href=\"http:\/\/plugins.jquery.com\/project\/metadata\">plugin documentation<\/a>, \"This means that there is at least one option here that can appease you.\"<\/p>\r\n<p>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\r\nspirit of jQuery, I made it freely extensible:<\/p>\r\n<h4>The extensible metadata<\/h4>\r\n<pre><code class=\"language-javascript demo\">\r\n$.extend({\r\n\tmetadata : {\r\n\t\tdefaults : {\r\n\t\t\ttype: 'class',\r\n\t\t\tname: 'metadata',\r\n\t\t\tcre: \/({.*})\/,\r\n\t\t\tsingle: 'metadata'\r\n\t\t},\r\n\t\tsetType: function( type, name ){\r\n\t\t\tthis.defaults.type = type;\r\n\t\t\tthis.defaults.name = name;\r\n\t\t},\r\n\t\tget: function( elem, opts ){\r\n\t\t\tvar opts = $.extend({},this.defaults,opts);\r\n\t\t\treturn $.data(elem, opts.single) || $.data(elem, opts.single, this[opts.type].call(elem,opts));\r\n\t\t},\r\n\t\tasJSON: function(s){\r\n\t\t\tif (!s) return {};\r\n\t\t\tif (\/^[[{]\/.test($.trim(s))) return eval(\"(\" + s + \")\");\r\n\t\t\treturn s;\r\n\t\t}\r\n\t}\r\n});\r\n\r\n$.fn.metadata = function( opts ){\r\n\treturn $.metadata.get( this[0], opts );\r\n};\r\n<\/code><\/pre>\r\n<p>and all metadata \"analyzing functions\"--defined by the options.type string--take an options object and return the metadata object, with <code>this<\/code> bound to the element.<\/p>\r\n<h4>The original three<\/h4>\r\n<p>For example, the original metadata methods are:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.metadata[\"class\"] = function (opts){\r\n\tvar m = opts.cre.exec( this.className );\r\n\tif ( m ) return $.metadata.asJSON(m[1]);\r\n\treturn {};\r\n};\r\n\r\n$.metadata.elem = function (opts){\r\n\tvar e = this.getElementsByTagName(opts.name);\r\n\tif ( e.length ) return $.metadata.asJSON(e[0].innerHTML);\r\n\treturn {};\r\n};\r\n\r\n$.metadata.attr = function (opts){\r\n\tvar attr = this.getAttribute( opts.name );\r\n\t\/\/ allow for not including the braces, as in the original metadata plugin\r\n\tif ( attr ){\r\n\t\tif ( attr.indexOf( '{' ) &lt; 0 ) attr = \"{\" + attr + \"}\";\r\n\t\treturn $.metadata.asJSON(attr);\r\n\t}\r\n\treturn {};\r\n};\r\n<\/code><\/pre>\r\n\r\n<h4>Example: type 'class'<\/h4>\r\n<div>\r\n<pre><code class=\"language-html demo\">\r\n&lt;div class=&quot;{ draggable : {axis: 'x', opacity: 0.5}} example&quot;&gt;I have data!&lt;\/div&gt;\r\n<\/code><\/pre>\r\n<input type=\"button\" class=\"examplebutton\" value=\"Show Me the Data: metadata({type: 'class'})\"\/>\r\n<\/div>\r\n\r\n<h4>Example: type 'attr'<\/h4>\r\n<div>\r\n<pre><code class=\"language-html demo\">\r\n&lt;div coords=&quot;{lat: 40, long: -90}&quot; class=&quot;example&quot;&gt;I have data!&lt;\/div&gt;\r\n<\/code><\/pre>\r\n<input type=\"button\" class=\"examplebutton\" value=\"Show Me the Data: metadata({type: 'attr', name: 'coords'})\"\/>\r\n<\/div>\r\n\r\n<h4>Example: type 'elem'<\/h4>\r\n<div>\r\n<pre><code class=\"language-html\">\r\n&lt;div class=&quot;example&quot;&gt;\r\n&lt;script type=&quot;text\/json&quot;&gt;{first: 1, second: 2}&lt;\/script&gt;\r\nI have data!\r\n&lt;\/div&gt;\r\n<\/code><\/pre>\r\n<div class=\"example\" id=\"example3\">\r\n<script type=\"text\/json\">{first: 1, second: 2}<\/script>\r\nI have data!\r\n<\/div>\r\n<input type=\"button\" class=\"examplebutton\" value=\"Show Me the Data: metadata({type: 'elem', name: 'script'})\"\/>\r\n<\/div>\r\n\r\n<h4>Example: metaobjects<\/h4>\r\n<div>\r\n<p>Andrea Ercolino's <a href=\"http:\/\/noteslog.com\/metaobjects\/\">metaobjects<\/a> uses an <code>&lt;object&gt;<\/code> element to insert\r\nmetadata in a standards-compliant way, with the data in the <code>&lt;param&gt;<\/code> elements\r\n<pre><code class=\"language-javascript demo\">\r\n$.metadata.object = function (opts){\r\n\tvar ret = {};\r\n\t$('&gt; object.metaobject &gt; param', this).each(function(){\r\n\t\tret[this.name] = $.metadata.asJSON(this.value);\r\n\t});\r\n\treturn ret;\r\n};\r\n<\/code><\/pre>\r\n<pre><code class=\"language-html demo\">\r\n&lt;div class=&quot;example&quot;&gt;I have data!\r\n&lt;object class=&quot;metaobject&quot; style=\"display:none\"&gt;\r\n\t&lt;param name=&quot;Full Name&quot; value=&quot;Danny W&quot;\/&gt;\r\n\t&lt;param name=&quot;Age&quot; value=&quot;too large to count&quot; \/&gt;\r\n&lt;\/object&gt;\r\n&lt;\/div&gt;\r\n<\/code><\/pre>\r\n<input type=\"button\" class=\"examplebutton\" value=\"Show Me the Data: metadata({type: 'object'})\"\/>\r\n<\/div>\r\n\r\n<h4>Example: Microformats<\/h4>\r\n<div>\r\n<p><a href=\"http:\/\/microformats.org\/\">Microformats<\/a> are a way of marking up metatdata that is actually present in the text so it becomes machine readable as well. <em>We<\/em> recognize that\r\n\"123 Sesame Street\" is an address, but our programs aren't so smart unless we tell them: \"<code>&lt;span class=\"street-address\"&gt;<\/code>123 Sesame\r\nStreetcode>&lt;\/span&gt;<\/code>\". <a href=\"http:\/\/www.danwebb.net\">Dan Webb<\/a> wrote a clever microformat parsing program called \r\n<a href=\"http:\/\/www.danwebb.net\/2007\/2\/9\/sumo-a-generic-microformats-parser-for-javascript\/\">Sumo<\/a> that we'll use:\r\n<pre><code class=\"language-javascript demo\">\r\n$.metadata.hcard = function(opts){\r\n\tvar hcards = HCard.discover(this);\r\n\treturn hcards.length ? hcards[0] : {};\r\n};\r\n<\/code><\/pre>\r\n<p>Sumo uses a subset of <a href=\"http:\/\/www.prototypejs.org\/\">Prototype<\/a>; someday I may port it to jQuery.<\/p>\r\n<pre><code class=\"language-html demo\">\r\n&lt;div class=&quot;example&quot;&gt;\r\n  I have data from \r\n\t&lt;span class=&quot;vcard&quot;&gt;\r\n\t  &lt;span class=&quot;fn&quot;&gt;Danny Wachsstock&lt;\/span&gt;\r\n\t\tat &lt;span class=&quot;tel&quot;&gt;555-1212&lt;\/span&gt;\r\n\t&lt;\/span&gt;!\r\n&lt;\/div&gt;\r\n<\/code><\/pre>\r\n<input type=\"button\" class=\"examplebutton\" value=\"Show Me the Data: metadata({type: 'hcard'})\"\/>\r\n<\/div>\r\n\r\n<h4>HTML 5<\/h4>\r\n<div>\r\n<p>HTML5 allows for <a href=\"http:\/\/www.w3.org\/html\/wg\/html5\/#custom\">custom attributes<\/a> on any element, as long as the name starts with\r\n\"data-\". It's pretty controversial in the standardista community to allow this kind of freedom, but given that people are already doing it with\r\nall sorts of attributes, it's a concession to the reality that the standards-setters don't know everything and can't predict everything.\r\n<a href=\"http:\/\/ejohn.org\">John Resig<\/a>\r\n<a href=\"http:\/\/ejohn.org\/blog\/html-5-data-attributes\/\">likes it<\/a>, but it hasn't made it into the metadata plugin yet. Until now.<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.metadata.data = function(opts){\r\n\tvar ret = {};\r\n\t$.each (this.attributes, function(){\r\n\t\tvar m = \/data-(.*)\/.exec(this.nodeName);\r\n\t\tif (m) ret[m[1]] = $.metadata.asJSON(this.nodeValue);\r\n\t});\r\n\treturn ret;\r\n};\r\n<\/code><\/pre>\r\n<pre><code class=\"language-html demo\">\r\n&lt;div data-lat=&quot;40&quot; data-long=&quot;-90&quot; class=&quot;example&quot;&gt;I have data!&lt;\/div&gt;\r\n<\/code><\/pre>\r\n<input type=\"button\" class=\"examplebutton\" value=\"Show Me the Data: metadata({type: 'data'})\"\/>\r\n<\/div>","protected":false},"excerpt":{"rendered":"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.","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/108"}],"collection":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/comments?post=108"}],"version-history":[{"count":9,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/108\/revisions"}],"predecessor-version":[{"id":1234,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/108\/revisions\/1234"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=108"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=108"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=108"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}