And they go ahead and write Outlook Web Access behind your back...
I am going over the code, and I am simply amazed. JavaScript & HTML component, using DomainJSon Generation and some additional smarts around that idea.
And they go ahead and write Outlook Web Access behind your back...
I am going over the code, and I am simply amazed. JavaScript & HTML component, using DomainJSon Generation and some additional smarts around that idea.
I was explaining to a couple of team members about our stack (NHibernate, Castle, Boo, Rhino) and how the different pieces are hooked together.
When I got to talk to Boo, I explained that it is just like Brail, which we are using for the views. One of the guys then asked if Boo stands for Brail, Object Oriented
This post by Ben Scheirman is interesting. He points out that the MS.MVC stuff is targeted toward a different crowd than the one who is using MonoRail, it is targeted toward the corporate developers and the All-Microsoft Shops.
The question of support has been raised again, and it prompted this post. It seems that there isn't a lot of awareness that there are commercial support options for those tools.
I am actually not very interested in getting support for those, so it is entirely possible (and likely) that I missed some. Most of the active members of the community are members of consultancies that are capable offering support, but those are the one that I am aware of.
* Full disclosure: I work there.
Apparently I am suffering from the Bug Duplication Syndrom. Shortly after posting about cascading drop downs in MonoRail, I realized that I have actually duplicated a bug that I have run into while using the cascading drop down extender in the ASP.Net Ajax toolkit. That is not a fixable bug, btw, and has to do with what happens when you have more than a single parent. You can see my post about this scenario here, but be aware that the solution in that post is vulnerable to race conditions, and I have finally declared that unpracticle to use.
At any rate, I have duplicate the inability to have multiply parents in my previous solution using MonoRail. And I decided that instead of fixing it in place, I'll show the steps neccesary to fix the problem. Basically, we want to update both the secondaries select and the tertiaries select, which means that our GetSecondaryProfressionsByPrimary.brailjs will now contains:
page.Replace( 'secondaries',
Form.Select('secondaries',SecondaryProfessions,
{@value: @Id, @text: @Name, @firstoption: res.Select})
)
page.Replace( 'tertiaries',
Form.Select('tertiaries',TertiaryProfessions,
{@value: @Id, @text: @Name, @firstoption: res.Select})
)
page.Call( @hookSecondariesChangeEvent );
And that the GetSecondaryProfressionsByPrimary action is now:
public void GetSecondaryProfressionsByPrimary(Guid id) { PropertyBag["SecondaryProfessions"] = professionRepository.FindSecondariesByPrimaryIdOrAll(id); PropertyBag["TertiaryProfessions"] = professionRepository.FindTertiariesByPrimaryIdOrAll(id); }
Oh, and that was it.
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.
I run into the issue of having to alert the user of some error, and it brought home the fact that English & Hebrew are not easy to mix. In fact, code such as this is consider a very bad sign, usually code that you don't really want to touch, see or believe in:
So, we need some sort of a way to externalize those strings, even if the only language that this application is going to use is Hebrew, simply because of the pain of mixing the two together.
After thinking about it for a while, I decided to create this view (JavascriptResources/index.brail):
<% for de in controller.Resources: resourceName = de.Key %> var ${resourceName} = { <% for item in GetParameter(resourceName): %> ${item.Key}: '${item.Value.Replace("'","\\'") }', <% end %> Empty: '' }; <% end %>
And here is the definition of the controller:
[Resource("Processes", "MyApp.Resources.Javascript.Processes")] [Resource("Errors", "MyApp.Resources.Javascript.Errors")] [Resource("Messages", "MyApp.Resources.Javascript.Messages")] public class JavascriptResourcesController : Controller { public void Index() { } }
But what the hell does this do, anyway? Not much, actually, but it will iterate over all the registered resources for the controller, and output something that looks like this for each of those resources:
var Processes = {
EmploymentType: 'Employment Type',
Details: 'Details',
Dates: 'The dates',
Empty: ''
};
The nice thing is that I can now write: alert(Errors.RecursiveError);*. As a side affect (as I said, the application is just in Hebrew so I don't care about that much), I also get the usual benefits of localization.
* You have to read Hebrew to get the joke, I am afraid.
I had time today to sit on several Brail bugs. I keep getting more and more and more amazed by the power that Boo is giving me.
In order to fix some of those bugs, I had to literally change the meaning of the if statement. (I defined my own nullable propagator, which I had to implement deeply into the language).
Damn, I love this language.
Oh, and FYI, if 2+2 == 4 will not do the expected thing anymore :-)
As I mentioned, I build a very quick & dirty solution to display a collection of scheduled task descriptions. The end result looked like this:
This works, and it should start producing value as of next week, but I didn't really like the way I built it.
Here is the original view code:
<table cellspacing="0" cellpadding="0"> <thead> <tr> <th> Name:</th> <th> Occurances</th> <th> </th> </tr> </thead> <tbody> <% for task in tasks: %> <tr> <td> ${Text.PascalCaseToWord(task.Name)} </td> <td> Every ${task.OccuranceEvery} </td> <td> ${Html.LinkTo("Execute", "ScheduledTasks", "Execute", task.FullName)} </td> </tr> <% end %> </tbody> </table>
This work, it is simple and easy to understand, but it still bothered me. So I replaced it with this:
<% component SmartGrid, {@source: tasks} %>
Well, that was much shorter, but the result was this...
I am cropping things, because it is a fairly long picture, but it should be clear that this is not a really nice UI to use.
This was my second attempt;
<% component SmartGrid, {@source: tasks, @columns: [@Name, @OccuranceEvery] } %>
And it produced this:
Better, but not really that much, let us try to have nicer names there, shall we?
<% component SmartGrid, {@source: tasks, @columns: [@Name, @OccuranceEvery] }: section Name: %> <td>${Text.PascalCaseToWord(value)}</td> <% end end %>
And this produced:
That is much better on the name side, but we still have the "Occurance Every" column to fix...
<% component SmartGrid, {@source: tasks, @columns: [@Name, @OccuranceEvery] }: section OccuranceEveryHeader: %> <th>Occurances</th> <% end section Name: %> <td>${Text.PascalCaseToWord(value)}</td> <% end section OccuranceEvery: %> <td>Every ${value}</td> <% end end %>
With the result being:
One last thing that we have left is the additional column at the end, we can manage it like this:
<% component SmartGrid, {@source: tasks, @columns: [@Name, @OccuranceEvery] }: section OccuranceEveryHeader: %> <th>Occurances</th> <% end section MoreHeader: %> <th></th> <% end section Name: %> <td>${Text.PascalCaseToWord(value)}</td> <% end section OccuranceEvery: %> <td>Every ${value}</td> <% end section More: %> <td>${Html.LinkTo("Execute", "ScheduledTasks", "Execute", item.FullName)}</td> <% end end %>
So here is the final result:
Now that I did that, I am looking at both pieces of code and wondering:
Granted, this is a fairly specialized case, but in terms of LoC, the second approach is actually longer, and the "major" benefit here is that I get less HTML in the view, but that is not a really major consideration.
The SmartGrid would produce a pager if needed, but that about it with regards to the differences in their abilities.
Here is a small, but interesting tidbit. Yesterday I have finally sat down and documented a lot of the changes that I made in Brail recently, I was surprised to see how much I had to document:
The last part is what I want talk about now. Brail is a .Net language, which means that something like this:
output user.Parent.Name
will raise NullReferenceException if the user's parent is null. That can be somewhat of a pain in many scenarios. Brail now has a better syntax for this:
output ?user.Parent.Name
will ignore any null values that it encounter in the way. It is will either output the parent's name or nothing at all.
The small print:
This will work only for variables that you get from the controller via the PropertyBag or Flash. You can't use it on variables that you define in the view. However, that is rare enough that I don't think that it is going to be a problem.
Note that I didn't document the DSL support in Brail yet, that is going to wait until it is stabilized a bit.
Harris has more details about the internal implementation of Brail's DSL Support
Following the same line of thoughts, Alex has a similar implementation using C# 3.0. Personally, I think that Alex's implementation is cool, but it looks really similar to LISP, and that sort of scares me. I am currently reading about ANTLR, so it actually makes sense, in a weird way, except that I keep looking for the lower level implementation.
And as long as I am busy in link aggregation, Tom Opgenorth has posted a guide to Binsor, including a Getting Started example!
No future posts left, oh my!