Cascading Drop Downs in MonoRail

time to read 3 min | 594 words

I just had this need, and I decided to explore what I could do without referring to what other people has already done. There are probably better ways, but this one has tickled my fancy.

Anyway, the idea here is to display three levels of hierarchy in a profression. Here is the HTML:

<table>
	<tr>
		<td>
			${res.PrimaryProfession}
		</td>
		<td>
			${Form.Select("primaries",PrimaryProfessions, 
{@value: @Id, @text: @Name, @firstoption: res.Select}) } </td> <td> ${res.SecondaryProfession} </td> <td> ${Form.Select("secondaries",SecondaryProfessions,
{@value: @Id, @text: @Name, @firstoption: res.Select}) } </td> <td> ${res.TertiaryProfession} </td> <td> ${Form.Select("tertiaries",TertiaryProfessions,
{@value: @Id, @text: @Name, @firstoption: res.Select}) } </td> </tr> </table>

This is not very fancy, and the CSS guys would be all over me for using a table, but let us leave that aside. Basically, we render three select elements. Now, let us see the server side, shall we?

public void Index()
{
	PropertyBag["PrimaryProfessions"] =
		professionRepository.FindAllPrimaries();

	PropertyBag["SecondaryProfessions"] =
		professionRepository.FindAllSecondaries();

	PropertyBag["TertiaryProfessions"] =
		professionRepository.FindAllTertiaries();
}

The ProfressionRepository uses Repository<T> internally, I want to avoid using Repository<T> directly in my controllers. Now, we need to actually respond to change events on the client side:

$j(document).ready(function()
{
	$j('#primaries').change(function()
	{
		$j.ajax({ 
			url:'GetSecondaryProfressionsByPrimary.ashx',
			data: { id: $j('#primaries').val() },
			dataType: 'script'
		});
	});
	hookSecondariesChangeEvent();
});

function hookSecondariesChangeEvent()
{
	$j('#secondaries').change(function()
	{
		$j.ajax({ 
			url:'GetTertiaryProfressionsBySecondary.ashx',
			data: { id: $j('#secondaries').val() },
			dataType: 'script'
		});
	});
}

This is done using jQuery, to hook the events and issue Ajax requests to the server when they are done. Note that I am registering the seconderies change event in a separate function, it will be soon clear why.

Now, how does the server handles this?

public void GetSecondaryProfressionsByPrimary(Guid id)
{
	PropertyBag["SecondaryProfessions"] =
		professionRepository.FindSecondariesByPrimaryIdOrAll(id);
}

public void GetTertiaryProfressionsBySecondary(Guid id)
{
	PropertyBag["TertiaryProfessions"] =
		professionRepository.FindTertiariesBySecondaryIdOrAll(id);
}

So we just pass the parameters to the repository, which does all the work for us, in this case, getting the children if the id is valid, or all if it isn't. Now let us move to the relevant views, shall we? We are using BrailJS here, so here is GetSecondaryProfressionsByPrimary.brailjs:

page.Replace( 'secondaries', 
	Form.Select('secondaries',SecondaryProfessions, 
		{@value: @Id, @text: @Name, @firstoption: res.Select})			
)
page.Call( @hookSecondariesChangeEvent );

As you can see, I ask the page to replace the element secondaries with the newly rendered select element. I then acll the hookSecondariesChangeEvent function, since I actually replace the entire element, and not just its content. I am doing this because I don't feel like extracting the part that renders just the options from the Form.Select() method, but I will probably will the next time I need it.

The GetTertiaryProfressionsBySecondary.brailjs is actually simpler:

page.Replace( 'tertiaries', 
	Form.Select('tertiaries',TertiaryProfessions, 
		{@value: @Id, @text: @Name, @firstoption: res.Select})			
)

Again, there are probably betters way, but this is the one I just came up with.