Ayende @ Rahien

It's a girl

A different UI approach

I have been doing some work with MonoRail recently, and I noticed that I am structuring the UI in a completely different way than the way I would using WebForms.

When I used WebForms, I at first tried to make significant use of the builtin controls and components, thinking that they would make my work easier. But it turned out that they added complexity instead of removing it. Then I tried to use WebForms in as pure HTML generation capacity as possible. I made very big use of repeaters and such controls, some of which I built myself.

When I just started to use MonoRail, I had much the same style, but now I am finding out that I am doing things differently. The more recent solutions relies a lot more about generating JS for data, and then generating the UI using JS. The somewhat primitive approach outlined below represent a spike for a new feature, where we simply wanted to see how we can get it started, but I think that this makes the point fairly obvious.

<script type="text/javascript">
	var orders = [];
	var lines = [];
<% 
	for order in orders: 
%>
	orders.push({
			id: "!{order.Id}",
			name: "!{order.Name}",
		});
<%
		for orderLine in order.OrderLines: 
%>
	lines.push({
		id: "!{orderLine.Id}",
		orderId: "!{order.Id}",
		orderName: "!{order.name}",
		cost:	"!{orderLine.Cost}",
		account: "!{account.name}" 
	});
<% 
	end 
end
%>

Event.observe(window, 'load', function(){
	for(var i = 0; i< lines.length; i++)
	{
		var line = lines[i];
		var td = $('orderlinessTR').down('td');
		var div = document.createElement("div");
		div.className = "order";
		div.tag = line;
		div.innerHTML = line.orderName;
		td.appendChild(div);
	}
	
	for(var i = 0;i<orders.length; i++)
	{
		$('ordersDDL').options.add(new Option(orders[i].name, orders[i].id));
	}

	Event.observe($('ordersDDL'), 'change', function(){
		var e = $('ordersDDL');
		var id = e.options[e.selectedIndex].value;
		var divs = $$('div.line');
		for(var i = 0; i< divs.length; i++)
		{
			if(divs[i].tag.orderId == id)
				divs[i].style.background = "red";
		}
	});
});

</script>

As you can see, we start by generating JS for the data, and then process it somehow. I considered using JSON to serialize my entities directly to JSON strings, but I am absolutely against tying the javascript to my domain model. An explicit translation step is much more welcome in this case, although the one above it is not a very robust one, I will admit.

In fact, as a direct correlation of this trend, I find myself doing a lot more work that centers around creating javascript controls. Those are rich, incredibly easy to develop, frustrating at times (usually when I am working on explorer) and framework independent. I have completely buy into Prototype/JQuery (I really should post about that) as an underlying framework to work with, but I have yet to start working with the JS control libraries that I hear other peoples raving about. I made some use of scriptacolous, but that is about it.

Am I doing something strange? Or is it just a common trend that I missed?

Comments

Ishai Sagi
11/23/2007 02:08 AM by
Ishai Sagi

What about accessability? browsers that don't support javascript? basically you may be making your code unusable to blind people.

What about search engines? how will they index the data shown by the javascript?

What bothers me most of all when I do tricks like that is a corrupted something that will throw a javascript error for each line. Then users like me who like to turn on javascript errors in their browsers get bombarded by 'do you want to debug' errors...

Max
11/23/2007 02:13 AM by
Max

Isn't this really slow for any non-trivial number of records?

Steven Radack
11/23/2007 03:46 AM by
Steven Radack

I started along this path as well, once but when I started to notice how slow it was making things, I backed off. It seemed like the difference between the browser rendering the HTML directly vs generating the HTML via javascript was an order of magnitude, but I don't have the data to back this up.

neil
11/23/2007 05:17 AM by
neil

This is the right approach, although the devil is in the details (and the details are easy to fix).

Creating html from js is bad, due to the above-mentioned concerns. But it's fine to emit plain html and attach js to it with those iterators' knowledge of the data. I don't think that accessibility is a valid concern, though, most screen readers just ask a sophisticated dom (mshtml, gecko) for content. The "js == kill the blind" is a false argument I think. SEO, semantics, "view-source" are valid concerns though. It's much better to do:

<% for order in orders: %>

#{order.name}#{order.number}

linkify(order_#{order.id});

<% end %>

Also, agree 100% with "I am absolutely against tying the javascript to my domain model." It's tempting, and it may save some work, but it's wrong. I believe there are some GOF patterns that address this very issue.

Ayende Rahien
11/23/2007 07:50 AM by
Ayende Rahien

Ishai,

Good points.

Accessibility - screen readers can do JS, as was already mentioned.

SEO is an important point. I am using this technique in the more complex parts of the application, where I am doing a lot of manipulations. Never the less, since that data exists in there, I don't think that they will have a problem to index it.

Bad JS - not my concern. I don't intend to release broken JS out there :-)

Max,

I am not talking about a huge number, for that exact reason. We are talking about 10 - 30 records, in a typical paged view.

Ken Egozi
11/23/2007 08:04 AM by
Ken Egozi

As for JSONising the domain:

you have many choices here:

  1. just use an external JSON serializer, (serializer.ToJSON(order))

  2. if you're on 3.5, use Extension Method ToJSON()

  3. if you have a 'BaseEntity' class you can shove this ToJSON method there.

my preference:

2 (if i have 3.5), then 1 , then 3

Paul Cowan
11/23/2007 08:31 AM by
Paul Cowan

Two posts above have mentioned that it is bad to tie the domain model to the domain.

I use this techinique to pass JSON from the HTML to the server via AJAX calls.

I have a JQuery .toJSON extension and then use the JavaScriptSerializer from System.Web.Extensions to deserialise it directly into my domain mode.

This has worked very well for me.

Are you suggesting a layer of indirection to pass something else that will require additional parsing and extra maintenance?

I am very interested as to what is wrong with this approach.

Ko
11/23/2007 08:39 AM by
Ko

Speaking of other JS libraries people are raving about, I'd recommend ExtJS 2.0. The API is very thorough. The documentation is second to none. I found ExtJS lends itself perfectly for larger JS control UIX projects and have successfully completed on one with a modified Jayrock as our JSON serializer (not affecting any of our NH entities).

As for some points mentioned in other comments, I do not believe Javascript being disabled is a problem any longer. It may be elitist or outright ignorant to say that users who expect application-like UIX behavior in their browser require Javascript - those who disable it simply cannot access the application. In the browser, Javascript is as ubiquitous and required as HTML itself!

As for performance or responsiveness of Javascript over large recordsets or seevral hundred dynamic HTML controls, I cannot agree. Progressive loading of JSON records definitely improves performance over any other method I've reviewed on the applications I have built. Responsiveness of complex Javascript powered UIXs can become a concern if developers use ad-hoc pastry. Javascript in the browser is a biest of its own kind and proper knowledge of creating and disposing of objects and the timing thereof is paramount to a well performing and responsive UIX.

What I like most about this approach combined with MonoRail, is the sheer ease to plug any other UIX or direct data retrieval on top of my services. At least with ExtJS, the UIX shell and most of the composite controls are coompletely! static and those few that need to be composed on the server are returned as Javascript callback methods. Plug a different URI routing rule and have the same return HTML if you wish. Plug yet another routing rule and return a data stream directly to a screen reader. Plug a compact binary serializer and have your WinForms smart application use the same services simply over a different serializer.

And last point about SEO: SEO for data centric applications even in the public domain, is irrelevant. For Web sites it is anything but irrelevant, so Javascript controls can obtruse common SEO practices. However, I usually find myself only needing those for forms, settings options and other non-content related functionality of client sites. If it is content-related and still requires use of fancy Javascript, then by all means, render it as you would in plain old HTML and use your Javascript library of choice for further DOM manipulation / decorating. SEO isn't affected in any way by this approach.

To come back to oren's post, I have one disliking which would be the mix of Javascript in a server processed view. In some cases inevitable, but in most cases it's a hugely unreadable mess. Separation of concerns applies here just as well as anywhere else and since Javascript for UIX is browser only (in most cases), I believe that content should be largely or completely separate and oblivious to any UIX processing on the server.

That's just my own experience coming from working with a complex ExtJS driven UIX driven by MonoRail and a lovely NH. If there are good reasons to generate Javascript on the server (except JSON serialization), I'd be interested to learn the reasons behind such decisions.

Lee Henson
11/23/2007 10:18 AM by
Lee Henson

Paul: I'm with you on this one. I'm serializing my ActiveRecord classes directly to Json, as I don't want to have to maintain an entire mapping layer which are essentially duplicates of existing classes. Using Json.Net you can easily enable/disable serialization of individual properties through the simple addition of an attribute. I'm not serializing directly into my domain classes though, as creating entities/value objects is something often best done via the appropriate factories, builders etc, which enforce rules.

Ko: I'm doing a pretty complex Ext 1.1/MonoRail/ActiveRecord project at the moment. It's very cool, and I'm looking forward to upgrading to 2.0 when it RTMs. In terms of expecting JS to be enabled: if you are doing a simple, static-ish, public information site then I would advocate progressive enhancement through JS to give a good experience to all visitors. But for a closed web app that requires an account to access, I am now of the opinion that there's little point in designing for JS-disabled visitors. It's simply not worth the effort and extra complexity.

Marco
11/23/2007 10:40 AM by
Marco

One disadvantage of your sample is caching of javascript files.

I'm using ExtJS. The static javascript is stored in external javascript and i bring in the dynamic part using JSON (during page creating or with ajax).

ps. You should take a look at ExtJS

Thanks

Marco

Ayende Rahien
11/23/2007 11:22 AM by
Ayende Rahien

Paul,

Make a distinction between DTO and the domain model.

I want to be able to modify my domain without UI concerns. As such, I don't want to have to consider the shape of the object when it passed JSON serialization, or have to update a bunch of pages when I do.

Paul Cowan
11/23/2007 11:26 AM by
Paul Cowan

I recently worked on the waitrose site in the UK.

The powers that be decided that it was not accessible to have javascript enabled. The client wanted the site to have all the nice Ajax touches and be accessible with no js.

We ended up having to sniff if .js was enabled and if not we would redirect to a non-js page.

We ended up having to do 2 versions of some of the pages.

Real pain in the ass

Thomas Hansen
11/23/2007 11:37 AM by
Thomas Hansen

I am obviously biased (follow my URL) but I tend to think that JS is a "representation/UI thing" and when people starts considering having entity classes and domain logic in their JS files I get really un-well...

For us JavaScript is a presentation thing and by following that path all the way down we've managed to create a library where JavaScript is used for nothing else than rendering UI Widgets and the complete domain logic resides on the server and uses a rich API towards the browser to send bursts of "update widget" messages that's taken care of in the browser completely without the end user's knowledge or need thereof.

This creates a solution where the end user can have his entire business logic on the server and just set properties and call methods on his widgets from server-side code to let the JS "back-end" take care of the rendering. A nice example of just that is by looking at our samples website;

http://ajaxwidgets.com/AllControlsSamples/

which is written entirely in C# and doesn't contain even one line of "Custom JavaScript". I think of JavaScript as the "assembly programming" language of today, sure your "compiler writer" needs to know about it, but does the "application vendor" need to...?

As in we as a library vendor are a "compiler writer" and you as an "application writer" are an "application vendor"...

But since we're building on top of WebControls the MonoRails and MVC goes out the door ;)

So I guess it all falls down to what's most important, being able to utilize the MVC of MonoRails or being able to "forget" about everything that has to do wth JavaScript...?

BTW, Gaia is built on top of prototype.js and ScriptAculous...

Driesie
11/23/2007 12:22 PM by
Driesie

With regards to performance, I found these benchmarks very useful:

http://www.quirksmode.org/dom/innerhtml.html

I found it amazing that using the DOM to render HTML to the screen was so much slower in IE (30x!!) than writing strings and letting the browser parse them. I would have thought it would be more efficient for the browser to parse a DOM structure than strings, but clearly not.

njy
11/23/2007 01:21 PM by
njy

Hi Ayende,

apart from the base discussion, talking about JS frameworks (Prototype, jQuery and so on) try to take a look at mootools. I find it less "puritan" than prototype for example, but is very well designed and unit -and- behaviour tested (they'e used JSSpec) and support for that is obviously propagated to your own code, based on that.

It is really compact and can do both OO spicy things (such as adding a common eventing system, even for generic class and not just DOm elements, chaining of methods, automatic "options" (presets) to use in classes ctors and so on), and UI/FX things like a base effecting and animation system, some more advanced pre-build effects, controls and other nice features.

Finally you can find if you like a lot of welcomed new features coming soon in their blog, particularly in 1.2 (now in beta1).

Oh well... sorry for the long post... but when I talk about something I like =) ... you know...

Steve
11/23/2007 02:02 PM by
Steve

There's nothing unusual about this. Several people have already started building up little frameworks to simplify and streamline the model.

I made one for MonoRail called jMVC.MonoRail a little while ago, see:

http://blog.codeville.net/2007/11/13/castle-monorail-meets-jmvc/

The basic difference between this and your approach is that instead of loads of document.createElement() calls, you just have a text template. Iteration and recursion can be handled using JS syntax.

Also, what about 2-way databinding? Some JS templating frameworks make that easy as well.

Steve
11/23/2007 02:05 PM by
Steve

Sorry, here's a clickable link:

http://blog.codeville.net/2007/11/13/castle-monorail-meets-jmvc/

Glenn Block
11/23/2007 05:43 PM by
Glenn Block

I agree with Steve on this. People have been doing this approach for years. However, the one concern I have is the testability of the code and also maintainability.

Pedro Teixeira
11/24/2007 02:12 PM by
Pedro Teixeira

I second Marco. I'm currently using Extjs (with the prototype adapter) and it's really good. It's very mature (with best online api documentation) and you get a very complete set of widgets (panels, windows, etc...) that allow to programatically define the layout of the page.

Comments have been closed on this topic.