Metaprogramming with JavaScript

JavaScript, even more so than VB, has to be the Rodney Dangerfield of programming languages.  I'm going to blow whatever credibility I might have by saying "I actually like programming in JavaScript."  I'm actually building a lot of the screens in StoryTeller with JavaScript running in a WebBrowser control because I think I can do dynamic layout much faster with JavaScript than a WinForms screen.  Besides, the point of a "side" project is to do stuff you don't get to do at your day job.

Check out this link from AdamLogic.  Granted, I don't see a bunch of people running out to write applications in JavaScript alone, but I think Metaprogramming is going to become more common in the near future.  I'm intrigued by all the ways people are creating little lexical languages to make the code get closer to the actual problem domain.  Little ol' JavaScript is actually decent for building little embedded languages and using Metaprogramming.

As a thought exercise last year, I played around with using RoR style declarative metaprogramming with JavaScript classes to see if I could build DHTML controls and widgets faster.  Take the example of a JavaScript control that has to create and control an internal hierarchy of DOM elements.  Here was my attempt to pull some of the grunt work of DOM manipulation up into a common superclass:

function act_as_compound(target){

    target.appendNamedChild = function(name, tag, props){

        var child = document.createElement(tag);

        this.appendChild(child);

 

        // EDIT:  removed the eval() call

        this[name] = child;

 

        if (props != null){

            Object.extend(child, props);

        }

 

        return child;

    }

 

    target.appendSpan = function(name, props){

        return this.appendNamedChild(name, 'SPAN', props);

    }

 

    target.appendDiv = function(name, props){

        return this.appendNamedChild(name, 'DIV', props);

    }

 

    target.appendButton = function(name, props){

        var btn = this.appendNamedChild(name, 'button', props);

        return btn;

    }

 

    return target;

}

As an example, here's *part* of a Pager class I'm using inside StoryTeller.  The Pager control is a DIV element with 4 child buttons and a SPAN element to show the current page.  The usage of "acts_as_compound" inside Pager looks like this:

function Pager(pager, controller){

    if (!pager){

        pager = document.createElement('div');

    }

 

    // Metaprogramming constructs — add in the methods to support the

    // compound element

    act_as_compound(pager);

    controlled_by(pager, controller);

 

    var pageFunction = function(){

        controller.goToPage(this.pageNumber);

    }

 

    // Use the methods on act_as_compound to add child elements to the parent div

    pager.appendButton('firstButton', {innerHTML: '|<', pageNumber: 1});

    pager.appendButton('previousButton', {innerHTML: '<<'});

    pager.appendSpan('pageSpan');

    pager.appendButton('nextButton', {innerHTML: '>>'});

    pager.appendButton('lastButton', {innerHTML: '>|'});

 

    pager.buttons = [pager.firstButton, pager.previousButton, pager.nextButton, pager.lastButton];

    pager.buttons.each( function(button){

        button.controller = controller;

        button.onclick = function(){

            this.controller.goToPage(this.pageNumber);

        }

    });

 

 

 

    return pager;

}

In particular, look at a single call to "appendButton."  That method does a couple different things.  It creates a new button element, sets any of the properties ({innerHTML: '|<', pageNumber: 1} is effectively an anonymous object ala C# 3 defined in JSON), then use the eval() method to set a new member on the Pager object to the new button like this:  this.firstButton = btn;  Other methods in the Pager class contain code like "this.firstButton.enabled = true."

If you're interested, this JavaScript control is in the StoryTeller SVN repository in the folder:  http://storyteller.tigris.org/svn/storyteller/trunk/src/StoryTeller.JavaScript/GreenJello.js.  All of the JavaScript in StoryTeller leverages the Prototype library.

 

And yes, this is definitely a case of Safety (C# 2) versus Power (JavaScript or any other dynamic typed language + Obj C & C# 3 to some degree).

 

P.S.  If you puked as soon as you saw the word "JavaScript,"  make sure to clean up;) 

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in StoryTeller. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://adamlogic.com Adam McCrea

    Count me in for some JavaScript love! I think a lot of what makes JavaScript painful for some people (verbosity of working with the DOM, hideous object-oriented constructs) can be eliminated by using one of the many popular libraries available. I choose Prototype just because it goes hand-in-hand with Rails, but I’m sure the others out there do a fine job as well. This, of course, assumes that no one should use a JS library if they don’t understand what it does.

    For me, JS is second only to Ruby.

    I’m glad you found my presentation inspiring. Happy JavaScripting!!

  • http://codebetter.com/blogs/jeremy.miller jmiller

    Karanjude,

    There is a link to that very post from AdamLogic up above. What do you think inspired me to write this yesterday?

  • http://www.hairy-spider.com Rhys Jeremiah

    I totally agree with wcpierce. Javascript is the most underrated of languages, especially given its penetration.

    It’s also ideal for confusing single language developers, the look on their faces when you use prototype is priceless.

  • karanjude

    no offence , but check out

    http://www.adamlogic.com/wp-content/uploads/2007/03/metaprogramming-javascript.pdf

    this is what i cool a really cool DSL demo

  • http://www.lazycoder.com Scott

    There is something about seeing the “this” keyword used in JS functions that makes me itchy. Like if you aren’t careful and watch your scope when you use it, you end up with side effects that are hard to track down.

    I’ve heard the case made that Objective C isn’t dynamically typed, it’s dynamic dispatch. The types are strongly defined, but if they can’t respond to a message, they look at their “isa” property and work up their chain until they either find an object that can respond to the message or throw an exception if no one can. I think, functionally, it works about the same way.

  • http://www.sergiopereira.com/articles/ sergiopereira

    I can’t say I have always enjoyed writing JavaScript, but just like anything else, the more you understand it, the more you tolerate it. Eventually one starts liking it.
    I went from avoiding it to writing about it.

  • http://codebetter.com/blogs/jeremy.miller jmiller

    Chris,

    Everybody deserves a guilty pleasure that they’re ashamed to admit (like owning an Ace of Base CD).

    Jeremy

  • http://www.chrisbrandsma.com Chris Brandsma

    – hack — gag — cough –

    By way of principle I hate JavaScript. Netscape made me hate JavaScript (somewhere in the 10 versions between 4.04 and 4.83).

    But, I’m always in favor of using the best tool for th job, and there are many jobs that JavaScript is the best answer for.

    I would put another much hated language in that list: VB6. I generally wont touch it with a 10 foot pole — but every once in a while it does fill a need.

    Anyway, back to my weapon of choice: C#.

  • http://blogs.magiconsoftware.com/bdiaz Bobby Diaz

    DOH, looks like someone caught it before I was able to finish typing! LOL

  • http://blogs.magiconsoftware.com/bdiaz Bobby Diaz

    I also think JavaScript is an awesome tool! We have been creating cutting edge web applications using AJAX since 1999-2000, before it was even called AJAX.

    By the way, you should be able to remove the call to eval(command) by using the following instead:

    this[name] = child;

    Regards,
    Bobby

  • http://codebetter.com/blogs/jeremy.miller jmiller

    Ryan,

    Because I forgot you could do that? Thanks for the tip.

    Jeremy

  • ryan

    Cool. But why did you do this:
    var command = ‘this.’ + name + ‘ = child’;
    eval(command);

    instead of this:
    this[name] = child;

    Is there a difference I don’t see?

  • http://blogs.meetandplay.com/wpierce/ wcpierce

    I’ll stand by you. I love JavaScript and think it is one of the most underrated languages in the toolbox. I’m always learning new things you can do with it and am constantly amazed by how powerful it is.