Castle Demo AppGetting serious, the first real page

time to read 62 min | 12260 words

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:

  1. (03 Mar 2006) ViewComponents, Security, Filters and Friends
  2. (01 Mar 2006) Code Update
  3. (01 Mar 2006) Queries and Foreign Keys
  4. (28 Feb 2006) Complex Interactions
  5. (25 Feb 2006) CRUD Operations on Projects
  6. (22 Feb 2006) Let There Be A New User
  7. (22 Feb 2006) Updating our Users
  8. (20 Feb 2006) Getting serious, the first real page
  9. (20 Feb 2006) MonoRail At A Glance
  10. (20 Feb 2006) The First MonoRail Page
  11. (19 Feb 2006) Many To Many Relations
  12. (19 Feb 2006) Lazy Loading and Scopes
  13. (17 Feb 2006) Active Record Relations
  14. (17 Feb 2006) Getting Started With Active Record