CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Jeremy D. Miller -- The Shade Tree Developer

Under the hood and working with .Net, TDD, Software Design, and Agile Stuff

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;) 



Comments

wcpierce said:

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.

# March 26, 2007 2:12 PM

ryan said:

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?

# March 26, 2007 2:42 PM

Jeremy D. Miller said:

Ryan,

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

Jeremy

# March 26, 2007 2:48 PM

Bobby Diaz said:

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

# March 26, 2007 2:51 PM

Bobby Diaz said:

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

# March 26, 2007 2:53 PM

Chris Brandsma said:

-- 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#.

# March 26, 2007 5:07 PM

Jeremy D. Miller said:

Chris,

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

Jeremy

# March 26, 2007 9:13 PM

sergiopereira said:

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.

# March 26, 2007 9:56 PM

Scott said:

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.

# March 27, 2007 12:23 AM

karanjude said:

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

# March 27, 2007 12:39 AM

Rhys Jeremiah said:

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.

# March 27, 2007 3:14 AM

Jeremy D. Miller said:

Karanjude,

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

# March 27, 2007 5:11 AM

Adam McCrea said:

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!!

# March 27, 2007 10:49 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About Jeremy D. Miller

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 previously worked as a systems architect building mission critical supply chain software for a Fortune 100 company and learned agile development practices as a .Net consultant at ThoughtWorks, one of the pioneers of agile development. Jeremy is the author of the open source StructureMap (http://structuremap.sourceforge.net) tool for Dependency Injection with .Net and the forthcoming StoryTeller (http://storyteller.tigris.org) tool for supercharged FIT testing in .Net. Jeremy's thoughts on just about everything software related can be found on his weblog "The Shade Tree Developer" at http://codebetter.com/blogs/jeremy.miller, part of the popular CodeBetter site. Jeremy is a Microsoft MVP for C#. Check out Devlicio.us!

This Blog

Syndication

News

All opinions expressed here constitute my (Jeremy D. Miller's) personal opinion, and do not necessarily represent the opinion of any other organization or person, including (but not limited to) my fellow employees, my employer, its clients or their agents.

About Me

"Best Of" Compendium

StructureMap (Dependency Injection for .Net)

StoryTeller (Supercharged Fit)

Build your own Cab

TestDriven

MVP