Castle Demo AppGetting serious, the first real page
Okay, so far we played around with MonoRail, and I, at least, has quite a bit of fun. Just to point something out before we carry on: While MonoRail has strong scaffolding capabilities (the ability to generate pages for CRUD operations automatically), I don't like the idea of using them, we're going to build everything from scratch.
The first thing that we need to do is to add is some wrapper HTML so our pages will not be HTML fragements (like the previous one). We do it by specifying a layout. Layouts are very similar to master pages in ASP.Net 2.0. We will build a very simple one before we more on to more complex stuff. A layout usually resides in the Views\Layouts folder, since I aspire to be original and surprising, I decided to call mine Default.boo. (Bet you didn't see that coming). Here is the boring layout:
<!DOCTYPE xhtml
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<body>
${ChildOutput}
</body>
</html >
Notice the part where it says ${ChildOutput}? That is the part where the view data will go. Now what? Shall we start with administration? Why not. Here is what we are trying to achieve. We'll have a secured part of our site where an administrator can manage users and projects. We'll ignore security for now, (I'll show how to add that on later on), so let's start doing something that you can show around in the office.
As usual, we'll start by creating a class, this time, it's the AdminControll class. Right now it is going to have only two methods, Users() and Projects():
[Layout("Admin")]
public class AdminController : SmartDispatcherController
{
public void Users()
{
User[] users = User.FindAll();
PropertyBag.Add("allUsers", PaginationHelper.CreatePagination(users, 25));
}
public void Projects()
{
Project[] projects = Project.FindAll();
PropertyBag.Add("allProjects", PaginationHelper.CreatePagination(projects, 25));
}
}
I'm using SmartDispatcherController as the base class because I'll later want the functionality it provides (match request parameters to method overload). For the moment, we can ignore the PaginationHelper calls. We add the Admin.boo layout to Views\Layouts, this one just proclaims that this is the administrator section:
<!DOCTYPE xhtml
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<title>Administrator Section</title>
<body>
${ChildOutput}
</body>
</html >
We create a new directory called Admin under Views, and create two files there, Users.boo and Projects.boo. Here is how the Users.boo view looks like. It's quite a bit of code, but there is nothing complicate to it:
<p>Our users:</p>
<table border="1" width="70%">
<tr>
<td>
<B>Id</B>
</td>
<td>
<B>Name</B>
</td>
<td>
<B>Email</B></td>
</tr>
<%
for user in allUsers:
%>
<tr>
<td>
${user.Id}
</td>
<td>
${user.Name}
</td>
<td>
${user.Email}
</td>
</tr>
<%
end
%>
</table>
<p>Pagination Data:</p>
<div class="pagination">
<table width="70%" >
<tr>
<td>Showing Users: ${allUsers.FirstItem} - ${allUsers.LastItem} of ${allUsers.TotalItems}</td>
<td>
<%
if allUsers.HasFirst:
output PaginationHelper.CreatePageLink( 1, "first" )
else:
output "first"
end
%>
</td> <td>
<%
if allUsers.HasPrevious:
output PaginationHelper.CreatePageLink( allUsers.PreviousIndex, "prev" )
else:
output "prev"
end
%>
</td> <td>
<% if allUsers.HasNext:
output PaginationHelper.CreatePageLink( allUsers.NextIndex, "next" )
else:
output "next"
end
%>
</td> <td>
<%
if allUsers.HasLast:
output PaginationHelper.CreatePageLink( allUsers.LastIndex, "last" )
else:
output "last"
end
%>
</td>
</tr>
</table>
</div>
The code above should be very familiar for anyone who ever used a <Repeater> on ASP.Net. Most of the code here is simplly to deal with the pagination. We will remove this code in the near future and put it where it belongs, in a reusable method. But for now, it does the job. You get a nice list of users (well, one user, but you get the point). We will return to this page in the future, and see how pagination is handled automatically by the framework, without us needing to do anything about it.
The Projects.boo view is nearly identical, but there is a piece of logic here that we can push outside. Just like in the rest of our code, we want to avoid duplication. We will now seperate the code to a seperate re-usable function (can you hear the OO fanatics pant in excitement in the back of the room?)
Brail will make any methods or classes that are stored in Views\CommonScripts to all the views in the application. So we start by creating this directory and then the script: "Views\CommonScripts\PaginationFooter.boo". Common Scripts are different than views, since they don't have a default output and they can't mix markup and code like views can do. This require some minor changes to the pagination footer:
import System.Text
import Castle.MonoRail.Framework.Helpers
def PaginationFooter(page as Page, pagingHelper as PaginationHelper):
output = StringBuilder()
output.Append("""<p>Pagination Data:</p> <div class="pagination"> <table width="70%" >
<tr> <td>Showing: ${page.FirstItem} - ${page.LastItem} of ${page.TotalItems}</td>
<td>""")
if page.HasFirst:
output.Append( pagingHelper.CreatePageLink( 1, "first" ) )
else:
output.Append( "first" )
end
output.Append(" </td> <td> ")
if page.HasPrevious:
output.Append( pagingHelper.CreatePageLink( page.PreviousIndex, "prev" ) )
else:
output.Append( "prev" )
end
output.Append(" </td> <td> ")
if page.HasNext:
output.Append( pagingHelper.CreatePageLink( page.NextIndex, "next" ) )
else:
output.Append( "next" )
end
output.Append(" </td> <td> ")
if page.HasLast:
output.Append( pagingHelper.CreatePageLink( page.LastIndex, "last" ) )
else:
output.Append( "last" )
end
output.Append(""" </td> </tr> </table> </div>""")
return output.ToString()
end
We needed to move from markup & code to explicit string building, which we return to the caller. Just to show you the difference, check out how th Project.boo page looks like now:
<p>Our Projects:</p>
<table border="1" width="70%">
<tr>
<td>
<B>Id</B>
</td>
<td>
<B>Name</B>
</td>
<td>
<B>Default Assignee</B></td>
</tr>
<%
for project in allProjects:%><tr>
<td>
${project.Id}
</td>
<td>
${project.Name}
</td>
<td>
${project.DefaultAssignee.Name}
</td>
</tr>
<%
end
%>
</table>
${PaginationFooter(allProjects, PaginationHelper)}
I made similar modifications to the Users.boo view, cutting its length it by more than half. It's pretty late around here, so I think I'll finish with this for today.
Future topics:
- Adding edit / create / update functionality for both users and projects.
- Using Ajax to connect users to projects.
- Adding security to the application.- this one will touch on more subjects than just MonoRail
In the meantime, have a good day, and don't let Mrs. WebForms catch you looking at prettier options :-)
More posts in "Castle Demo App" series:
- (03 Mar 2006) ViewComponents, Security, Filters and Friends
- (01 Mar 2006) Code Update
- (01 Mar 2006) Queries and Foreign Keys
- (28 Feb 2006) Complex Interactions
- (25 Feb 2006) CRUD Operations on Projects
- (22 Feb 2006) Let There Be A New User
- (22 Feb 2006) Updating our Users
- (20 Feb 2006) Getting serious, the first real page
- (20 Feb 2006) MonoRail At A Glance
- (20 Feb 2006) The First MonoRail Page
- (19 Feb 2006) Many To Many Relations
- (19 Feb 2006) Lazy Loading and Scopes
- (17 Feb 2006) Active Record Relations
- (17 Feb 2006) Getting Started With Active Record
Comments
Comment preview