MonoRail JS generation

December 29th, 2006

Now that Ayende made it somewhat a public, I’ll try to explain what is the new MonoRail Javascript generation support. Before that, pardon for not blogging more regularly, the reason is that Castle Stronghold is growing up with a big demand for projects. I’d never know that there are companies out there that doesn’t know Castle, doesn’t know me but trusted us to rewrite some applications because they can’t stand the poor quality of the existing solution. Castle Stronghold is, without any marketing effort, positioning itself as a premium custom software development company, and everything is happening so fast that me head is spinning. In addition I was invited to write for an ‘Architecture’ column in a brazilian .net magazine. Needless to say I’m a week overdue in my article due to the fact that I’m dealing with four projects at the same time.

Back to the subject. Ruby on Rails has introduced the notion of JS generation. That didn’t make much sense to me ‘util I have to deal with more complex ajax application. What is not complex? When an action updates an element, and|or evaluates some script. But applications often need more than that and mix a more complex presentation logic. So a given action on the web application sparks two elements being updates and some javascript being evaluated to update a client-side object model, for example. That’s when the JS generation comes to the rescue.

Exemplifying

Simple example: a list of customers, you want to allow adding new customers to the list (ajax style). Not a real complex usage, I know, but I really don’t want to start a big lecture here.

Add your view and a LinkToRemote for an “Add Customer” link. Here’s the view:

<table>
<tbody id="maintbody">
	<tr>
		<th> </th>
		<th>Name</th>
		<th>Contact</th>
	</tr>
#foreach($customer in $customers)
	<tr>
		<td>$customer.id</td>
		<td align="center">$customer.name</td>
		<td align="center">$customer.email</td>
	</tr>
</tbody>
#end
</table>
<div>
	#component(DiggStylePagination with "page=$customers")
</div>

<br/>

<div id="container" style="display: none;"></div>

<p id="addnewcustomerlink">
	$Ajax.LinkToRemote("Add customer", "NewCustomer.rails", "%{}")
</p>

The ‘container’ div will be used to add some dynamic content based on the ajax invocation. Now, when the user click ‘Add Customer’ we want a form so he can enters the information

On the controller side, we would add a NewCustomer action, nothing new:

public void NewCustomer()
{
}

The new thing is that instead of creating a .vm file, we would use a .njs file. This is a JS generation file for NVelocity view engine. Each view engine now supports a standard view and a special JS generation view. The JS generation view does not renders a content the usual way, instead it just make invocations on the page object which generate JS calls.

For the NewCustomer, we would create a newcustomer.njs:

$page.Hide('addnewcustomerlink')

$page.replacehtml('container', "%{partial='home/_addcustomer.vm'}")

$page.visualeffect('toggleappear', 'container')

Easy to understand what will happen here: we will hide the ‘addnewcustomerlink’ paragraph, then render the partial view ‘_addcustomer.vm’ on the ‘container’ div and finally use a nice effect to show the ‘container’ div. We could use some more complex presentantion logic here. For example, if the user is not authorized to add more than five customers:

#if($canAddMore)
	$page.Hide('addnewcustomerlink')
	$page.replacehtml('container', "%{partial='home/_addcustomer.vm'}")
	$page.visualeffect('toggleappear', 'container')
#else
	$page.Alert('Sorry mate, you have added enough customers already')
#end

The partial view for _addcustomer.vm couldn’t be any simpler. Only pay attention to the fact that we’re rendering a form remote, so the form submission will also be an ajax post.

<div style="width: 400px; background: #ffffef; border: 1px solid gray; padding: 10px;">
	$Ajax.BuildFormRemoteTag("AddCustomer.rails", "%{}")
	
	<div style="float: right; width: 90px; text-align:center;">
		<input type="submit" value="Save" /> <br/><br/>
		<input type="button" value="Cancel" onclick="Effect.toggle('container', 'appear');Element.show('addnewcustomerlink');" /> <br/>
	</div>
	
	<div style="padding: 3px; width: 300px;">
		Id: $Form.NumberField("customer.id") 
	</div>
	<div style="padding: 3px; width: 300px;">
		Name: $Form.TextField("customer.name") <br/>
	</div>
	<div style="padding: 3px; width: 300px;">
		E-mail: $Form.TextField("customer.email") <br/>
	</div>
	
	</form>
</div>

Time to add the AddCustomer action to the controller. As we’re not dealing with any database, it is pretty empty:

public void AddCustomer([DataBind("customer")] Customer customer)
{
	PropertyBag["customer"] = customer;
}

This action will also generates JS, which will hide the container, show back the ‘Add customer’ link and insert the newly created customer into the table:

$page.InsertHtml('bottom', 'maintbody', "%{partial='home/_customerrow.vm'}")
$page.VisualEffect('Highlight', "row${customer.id}")
$page.visualeffect('toggleappear', 'container')
$page.Show('addnewcustomerlink')

You don’t always need to create JS based on an Ajax request. Two view components were introduced: UpdatePage and UpdatePageTag. Those give a page instance to render the block, so you can use it like this:

#blockcomponent(UpdatePageTag)
	$page.visualeffect('toggleappear', 'container')
#end

Which will render

In the end, here’s our project structure. You can download the sample (fix the references using the References Path tab).

How it works

The first thing I changed on MonoRail was the view engine support, the Composite view engine is gone for good. You can now register more than one view engine using the following syntax on the web.config

<viewEngines viewPathRoot="views">

	<add type="ViewEngine.Type.Name1, Assembly" xhtml="false" />
	<add type="ViewEngine.Type.Name2, Assembly" xhtml="false" />

</viewEngines>

Note the plural form. The old ‘viewEngine’ is still supported for backward compatibility.

The generation is based on dynamic dispatches. NVelocity was changed to support duck typing, and the invocations are forwarded to the generation objects. This was an interesting exercise as we can mimic a dynamic language. The sad side is how much trouble and lines of code we need to add to create something you have for free in Ruby and Python.

NVelocity also didn’t get the better syntax. For example, to change an element properties:

$page.el('myelement').style.color.set('"blue"')

While with brail you would use

$page['myelement'].style.color = 'blue'

Which is much cleaner. Aside from this, NVelocity generation is just fine.

While the documentation is yet to come, I’d invite you to check the source code to know what operations are supported. I’m also considering working with a plugin style extension that will allow you to plug more dispatchable methods on the generation classes.

13 Responses to “MonoRail JS generation”

Claudio Says:

I’ ve downloaded your example. I’ve updated the references path but I still miss an assembly ( the libs folder contains only the file NVelocity.pdb)

This is the error I get if I run the default.aspx page:

Could not load file or assembly ‘NVelocity, Version=0.5.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc’ or one of its dependencies. The system cannot find the file specified.

thk

hammett Says:

Sorry about that. The file is fixed and the link updated. Thanks

knocte Says:

This is very interesting.

However, if I had time for contributing, I would go for a different approach. Instead of using a .njs file with that horrible syntax, why don’t use C# too instead, for this logic?

If this language was used, we could add this logic to the compilation process and then the AJAX logic would be also statically typed. After compiling, we could extract the logic from the DLL using a JavaScript code generator like jscsharp.

What do you think?

hammett Says:

Since when C# supports duck typing?

Assy McRibbits Says:

Looks like one could implement duck typing following this example Code Project DuckTyping

There are some limitations tho.

Christopher Bennage Says:

It just keeps getting better. This is fantastic!

Zen and the art of Castle maintenance » Archives » Web Client Software Factory vs MonoRail Says:

[...] – UI Responsiveness: is that Ajax? MonoRail just relies on prototype to offer ajax support, although you can use any library you want. MonoRail also supports JS generation for more complex scenarios. [...]

Samir Says:

Fantastic!

Diego Guidi Says:

JS generators are amazing and introduces a lot of possibilities :)

Steve Gentile Says:

Thank you for this excellent sample and description.

Also, thanks for including the libraries you used :)

{ null != Steve } » Blog Archive » Monorail Samples Says:

[...] I’d recommend looking at Hammett’s ‘Monorail JS Generation’ sample. This includes a n Ajax enabled Monorail sample. [...]

Brian Donahue Says:

I can’t believe I am just finding this now. I saw that Monorail had JS Generation support, but never had a chance to really figure it out, and never found this well-written example until today. Thanks!

Zen and the art of Castle maintenance » Blog Archive » jQuery and MonoRail Says:

[...] JS generation (njs, brailjs) [...]

Leave a Reply