WMsearch
/blag/

HyperScript

fri6jul2007—27w187d51%—05h16m00s—0utc
epiphanies, portfolio, fun, droll, whimsicist, web, javascript, technolyricism, ★ favorites, programming, controversial, quotes, languages, introductions, starts, metaphors, french, english, original content

A 16-line hack to make the JS DOM API a tad more humane.

…absolutely amazing. I’ve yet to find a smaller and yet more astounding example of how you can encapsulate functionality within JavaScript and create brand new APIs on the fly.

Web pages are written in HTML WP but as they have become more and more complex, they now tend to be written, clientside, through Javascript WP, which can manipulate and insert HTML. Google Images, for instance, uses Javascript to write the HTML that displays your image results.

Yes, it’s roundabout, but it’s due to the nature of the languages: Javascript does stuff, HTML displays stuff. When you want the browser to do things (instead of merely displaying dumbly what it receives) and when these things themselves involve a lot of displaying, you end up writing HTML through Javascript.

It’s a little like writing French through English (André went to Marie and said: “Bonjour! Ça va, ma chérie?”) and just as frustrating, particularly because you sometimes have to narrate whole scenes in French (pidgin tends to be painfully verbose) and your English self is left completely in the dark — so you end up naming things in both French and English and it gets as ugly as you can imagine.

HyperScript is a bizarre and quixotic attempt to write French in English; that is, HTML in Javascript. Basically, you do what went on in the Norman conquest of England WP: you anglicize as many French words as you can; that is, you turn into Javascript as many HTML words as you can.

The lark itself takes gratefully (and rather surpisingly) only 16 paltry lines of Javascript code (highlighting thanks to Mark “Tarquin” Wilton-Jones.):

function each(a, f)) { for(var i=0, l=a.length; i<l; i++)) f(a[i])) };
each('a big blockquote br b center code div em form h1 h2 h3 h4 h5 h6 hr img iframe input i li ol option pre p script select small span strong style sub sup table tbody td textarea tr ul u'.split(' ')),
    function(label){
        window[label]=function(){
            var tag=document.createElement(label));
            each(arguments, function(arg){ 
                if(arg.nodeType))										 tag.appendChild(arg));
				else if(typeof arg=='string' || typeof arg=='number'))	tag.innerHTML+=arg;
				else for(var attr in arg){
						if(attr=='style')) for(var sty in arg[attr])) tag[attr][sty]=arg[attr][sty];
						else tag[attr]=arg[attr];
				};
            }));
            return tag;
        };
    }));
and you can play with it right here, right now:


Test Area:

does it work now?

The translation between HTML and Hyperscript is straightforward, where you would have written

<b>Hello world!</b>,

you now write,

b(‘Hello World!’).

Instead of

<em style=“background-color:yellow”>Hello world!</em>,

now it’s,

em({style:{backgroundColor:’yellow’}},‘Hello World!’).

And so on.

HTML in a Javascript syntax. Enjoy!

𝕏 Follow me  |  ⌂ Back to ELZR.com

16 Comments

Peter Danenberg

Why not? They’ve been doing it with sexps for awhile.

Landlord Insurance

That is really impressive. Is there a way to put it into a function i.e.

html.div(b(“hello”),br()br())
Grimboy

Nice, but mochikit did it first. 18 lines is pretty impressive through.

elzr

@Grimboy: Yes, I didn’t know it but as the reddit comments show, this class of programs even has a name: a DomBuilder. It’s even about to make it into Prototype. Ah well, I didn’t know it, :), hope it at least has the merit of being short and barebones (as shortest and barebonest as it can be made?).

@Landlord Insurance: Ah! Do you mean being able to insert HyperScript with a clean function call? Something like this?

<div><br>
<script><br>
html.b("hello")<br>
</script><br>
</div>

If so, it has been a constant nightmare of mine. I don’t know of even a remotely elegant way to do that. Anyone?

RantingJerk

Uhm well, heres the same function but it works with nesting functions that was constantly nightmaring you…
Omg in less lines too! fapfap

<pre>’a big blockquote br b center code div em form h1 h2 h3 h4 h5 h6 hr img iframe input i li ol option pre p script select small span strong style sub sup table tbody td textarea tr ul u’.split(’ ‘).forEach(function(item) {     window[item] = function() {         var tag = document.createElement(item);         for (var i=0; i < arguments.length; i++) {             if (arguments[i] instanceof Element)                 tag.appendChild(arguments[i]);             else if (arguments[i] instanceof Object)                 for (a in arguments[i]) tag.setAttribute(a, arguments[i][a]);             else                 tag.innerHTML += arguments[i]         }         return tag;     } });</pre>

elzr

Nice code RantingJerk! I didn’t even know about the forEach function. Unfortunately neither it, nor setAttribute, nor instanceof Element, work in IE, which is to be expected, since they’re all very elegant.

Anyway, I pruned the code a little, changed this[item] to windows[item], and dropped my abstruse one-letter variable names. Oddly, the result was 2 lines less.

This might well be the place to point out belatedly that so far I’ve tested the code in Firefox 2, IE 7, Opera 9.1, and Safari 2.0.4 and everything went just fine. The only dialectal quirk so far is silly IE demanding explicit tbody tags inside tables.
guest

This is very interesting, but what about a version that doesn’t use the evil innerHTML property?

elzr

innerHTML is only there as a just-in-case backdoor. Doing away with it would just be a matter of substituting

<span class=”hyperscript-hyperscript”>tag.innerHTML+=arg</span>

with
<span class=”hyperscript-hyperscript”>tag.appendChild(document.createTextNode(arg))</span>.


And nothing but the backdoor would be lost. :)

Tjerk
Altough it looks nice and short: you do not want to mix JavaScript
with HTML. If you want to do it this way you should but it in it’s own namespace,
for example: function html.b in stead of function b for boldface.

I use the following functions for creating html:

<notextile>
# Easy functions to create xml elements
  1. @param name the tagname of the element
  2. @param attributes a object containing a map (key,value) pairs for the attributes
  3. @param children (optional) a list of child elements function xmlElement(name, attributes, children) {   var element=document.createElement(name);   if(attributes) {     for(attName in attributes) {       if(attName==”clazz”) { element.className=attributes[attName];       } else if(attName==”onclick”) { element.onclick=attributes[“onclick”];       } else { element.setAttribute(attName, attributes[attName]);       }     }   }   if(children) {     for(var i=0;i<children.length;i++) {       var child=children[i];       element.appendChild(child);     }   }   return element; }
  4. Easy function to create a text node
  5. @param string the text in the node function textNode(text) {   return document.createTextNode(text); }

</notextile>

Damn too bad your forms are fucked up.. the code is really ugly now.

But it works like this

<span class=”hyperscript-hyperscript” style=”white-space:pre”>xmlElement(“b”, false, [
  xmlElement(“div”, {clazz:”someClass”})
]);
</div>

To create this:

<span class=”hyperscript-html”>
</div>
MatthewRudy

This looks very much like HAML, which is an alternative templating engine for ruby on rails,
of course, it’s evaluated on the server,
but nevertheless,
a similar concept and syntax.

http://haml.hamptoncatlin.com/

Hemant

Agreed that this is a cool code. But my take is that markup and code must be separate. I would be really happy to see it the way ASP.Net handles this. Going fowrard XAML is also separating markup and code. Although it is completely possible to generate same output using code w/o markup. Generating markup from code is bad idea as it is very tough to manage changes.

elzr

@MatthewRudy: Ooh aah! Didn’t know about HAML—it’s pure beauty!

@Tjerk: You’re right, you may want to implement this inside its own safe namespace. As for your code, it’s practically equal to HyperScript modulo the syntactic sugar. Sorry for the crappy comment form, I reformatted your comment as I thought you meant to.

@Hemant: I can imagine extreme edge cases where mixing markup and code could cause problems but people tend to be quite moralistic about it without giving concrete examples. How could it make it very tough to manage changes?
Jeroen Brattinga

Why not use JSON as the notation form? So you could write something like:

{tag: “table”, id: “myTable”, contents: {tag: “tr”, contents: [{tag: “td”, contents: “Cell1Stuff”}, {tag: “td”, contents: “Cell2Stuff”}] } }


If properly formatted (how do you format code in these comments, btw?) it’s very readable.

It’s what I use to create innerHTML or DOM nodes (whichever is more appropriate or faster in the browser)
elzr

Using JSON is not a bad idea at all since it is indeed readable. Unfortunately, it is also a tad too verbose and, well, lacks flair, doesn’t it? ;)

l.m.orchard

Not to complexify your elegance here, but beware of gotchas.  Creating elements via DOM does not work 100% of the time.  Behold:

http://cf-bill.blogspot.com/2006/03/another-ie-gotcha-dynamiclly-created.html

MSIE6 hates making useful radio buttons via DOM.
dynamik

Nifty, but if I may be the devils advocate…

Javascript and HTML for the most part should be separate. For the same reasons we separate HTML and styles (CSS); To separate Form from Function.

To use this in an include/purpose built routine may be appropriate, but to design an index page from ground up with it would be a (re)design nightmare.. and I wonder how it goes for SEO.

...food for thought. :)