Viewing entries for the month: January, 2010. Show all entries

FubuToDo – Part 2: Forms, Controllers, View, and jQuery

17 01 2010

This is the second post in a series of posts I am writing on creating a simple “To-Do” application using FubuMVC. I’ve broken down this series into the following:

  1. Part 1: Conventions, Opinions and Bootstrapping
  2. Part 2: Forms, Controllers, Views, and jQuery
  3. Part 3: Persistence

Overview

I wanted to create a simple “To Do” application using FubuMVC. In my previous post, I showed the basics for getting your controllers created, tested, and wired up. I also explained how to setup your conventions and how to bootstrap your application. This time, I’m going to talk about creating forms, actions on controllers to respond to those forms, and how to work in some jQuery.

In order to have a functioning “To-Do” application, we’re missing the following:

  1. Add/Edit/Remove Functionality
  2. Validation
  3. Persistence

We’re going to talk about #1 in this part.

Controllers

As always, let’s start with some tests. I like to group entity-related actions into more semantic urls (e.g., “Items/Add”, “Items/Edit”). I’m just going to group those actions in a single controller so I came up with the following tests for my ItemsController:

  1. When adding an item, the controller calls Insert on the IUnitOfWork
  2. When adding an item, the appropriate ViewModel is returned
  3. When editing an item, the controller calls Update on the IUnitOfWork
  4. When editing an item, the appropriate ViewModel is returned
  5. When removing an item, the controller calls Delete on the IUnitOfWork
  6. When removing an item, the appropriate ViewModel is returned

Now, I know that I want to implement the add/edit/remove functionality via a modal dialogue. I’ve found I can write reusable javascript handlers for responses like this when the result is all the same for CRUD-based operations. To make it easier, I’ve decided to make the Add/Edit/Remove methods return an instance of the JsonUnitOfWorkResult class:

JsonUnitOfWorkResult

/// <summary>
/// Represents the result of a unit of work.
/// </summary>
public class JsonUnitOfWorkResult
{
    /// <summary>
    /// Gets or sets a message describing the result.
    /// </summary>
    public string Message { get; set; }
    /// <summary>
    /// Gets or sets a flag indicating whether the unit of work was successful.
    /// </summary>
    public bool Success { get; set; }
}

And now a sample of our tests:

Test 1

/// <summary>
/// When adding an item, the controller calls Insert on the IUnitOfWork.
/// </summary>
[Test]
public void Add_Item_Calls_Insert()
{
    IUnitOfWork unitOfWork = _mockRepository.StrictMock<IUnitOfWork>();
    ItemsController controller = new ItemsController(unitOfWork,
        _mockRepository.DynamicMock<IToDoItemRepository>());

    using(_mockRepository.Ordered())
    {
        unitOfWork.Expect(uow => uow.Insert(null)).IgnoreArguments();
        unitOfWork.Expect(uow => uow.Commit());
    }

    _mockRepository.ReplayAll();

    AddItemInputModel inputModel = new AddItemInputModel
    {
        Description = "Hello, World!"
    };

    controller.Add(inputModel);

    _mockRepository.VerifyAll();
}

Test 2

/// <summary>
/// When removing an item, the appropriate ViewModel is returned.
/// </summary>
[Test]
public void Remove_Item_Sets_Result()
{
    IUnitOfWork unitOfWork = _mockRepository.StrictMock<IUnitOfWork>();
    ItemsController controller = new ItemsController(unitOfWork,
        _mockRepository.DynamicMock<IToDoItemRepository>());

    RemoveItemInputModel inputModel = new RemoveItemInputModel
    {
        ItemId = 1
    };

    JsonUnitOfWorkResult result = controller.Remove(inputModel);
    Assert.IsNotNull(result);
}

As I’ve mentioned before, Fubu has the notion of “one model in, one model out” – following this gives you a lot of control over your behaviors which I’ll explain later in this post. To make things a little easier, I created an abstract class for all of my CRUD action input models:

ItemInputModel

/// <summary>
/// Provides a base class for all <see cref="ToDoItem"/> input models.
/// </summary>
public abstract class ItemInputModel
{
    /// <summary>
    /// Gets or sets the unique identifier of the item.
    /// </summary>
    public int ItemId { get; set; }
    /// <summary>
    /// Gets or sets the description of the item.
    /// </summary>
    public string Description { get; set; }
}

The actual controller implementation is incredibly easy (admittedly I could’ve done it better). Here’s the Add method:

/// <summary>
/// Adds a new item.
/// </summary>
/// <param name="itemInputModel">The item to add.</param>
/// <returns></returns>
public JsonUnitOfWorkResult Add(AddItemInputModel itemInputModel)
{
    try
    {
        _unitOfWork.Insert(new ToDoItem
                               {
                                   Description = itemInputModel.Description
                               });

        _unitOfWork.Commit();

        return new JsonUnitOfWorkResult
                   {
                       Success = true,
                       Message = "Item added successfully"
                   };
    }
    catch (Exception exc)
    {
        return new JsonUnitOfWorkResult
                   {
                       Success = false,
                       Message = exc.Message
                   };
    }
}

Forms

Ok, we’ve got a working controller. Now let’s setup our forms. I’m going to use jQuery and jQuery UI to handle my dialogs. Let’s take a look at what I wanted the application to do:

  1. Display a list of to-do items
  2. Allow me to add a new item
  3. Allow me to specify that an item is completed or not
  4. Allow me to edit an existing item
  5. Allow me to remove an existing item

We can already do the first requirement. However, since I’m planning on doing the rest through modals, we know we’re going to have to respond to some commands and refresh that list. Let’s revisit the Home/Index.aspx view and refactor our the displaying of those items:

Step 1: Move to a partial view

ItemListViewModel

/// <summary>
/// Provides a model containing a list of <see cref="ToDoItem"/> entities.
/// </summary>
public class ItemListViewModel
{
    /// <summary>
    /// Gets or sets the associated collection of items.
    /// </summary>
    public IEnumerable<ToDoItem> Items { get; set; }
}

List.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="List.aspx.cs" Inherits="FubuToDo.Web.Controllers.Item.List" %>
<%@ Import Namespace="System.Linq"%>
<% if(Model.Items.Any()) { %>
<ul class="item-list">
<% foreach (var item in Model.Items) { %>
    <li id="<%= item.ItemId %>" class="item">
        <div class="row no-label">
            <input type="checkbox" name="Item-<%= item.ItemId %>" <%= item.IsComplete ? "checked=\"checked\"" : string.Empty %> />
            <label class="checkbox"><a href="javascript:return false;"><%= item.Description %></a></label>
        </div>
        <br class="cboth" />
    </li>
<% } %>
</ul>
<% } else { %>
<span>No items.</span>
<% } %>

Step 2: Defer list rendering to an ajax call

In order to do this, I removed the Items property off of the IndexViewModel and removed the list rendering from the Home/Index.aspx view. Let’s take a look at the following snippets:

Index.aspx

<div id="blog-entry-container">
    <div class="success" style="display:none;"></div>
    <div class="error" style="display:none;"></div>
    <h2>To-Do List</h2>
    <div id="Item-List">
    </div>
    <button class="button-prim-med" type="button" value="Add" id="Add-New">
        <span>Add New</span>
    </button>
</div>
<div id="ItemDialog" style="display: none;">
    <form id="frmItem" method="post">
        <input type="hidden" id="hdnItemId" name="ItemId" />
        <fieldset>
            <div class="row">
                <label>
                    Description:</label>
                <input type="text" id="Item-Description" name="Description" class="text required" />
            </div>
        </fieldset>
    </form>
</div>

Now the Home/Index.aspx also references the Scripts/home/index.js file which will handle loading this information. Let’s take a look:

home.js

function reloadItems() {
    var $list = $('#Item-List');
    $list.html('<span>Loading...</span>');

    $.ajax({
        url: '/Items/List',
        type: 'GET',
        contentType: "application/json; charset=utf-8",
        data: { random: new Date().getUTCMilliseconds() },
        success: function(data) {
            $list.html(data);
            bindEditLinks();            bindItemCheckboxes();
        },
        timeout: 5000,
        error: function(xmlObj, textStatus, errorThrown) {
            $list.html('<span>Error</span>');
        }
    });
};

Our Items/List.aspx view returns a simple html snippet. Since we haven’t told Fubu any differently, it treats it as HTML and the appropriate content type is set. Our simple GET request will receive this content and set the contents of that empty div we created in our Home/Index.aspx view. If you take a look at the file a little deeper, you’ll see I call reloadItems on document ready.

You might have noticed the additional “random” parameter that we’re sending in. This this is a GET request, most browsers will cache the result. I threw this in there to avoid bang-head-into-desk syndrome.

Ok, now when I run it I get my items rendering through my ajax call. Now to wire up my dialogs and get it all working.

Adding/Editing an Item

I’ve setup the form in a way that could be used to add or edit an item. Since our input models are virtually identical, all we have to do is change the action of the form. We already have our dialog worked out (see Home/Index.aspx) so let’s wire up our buttons and links:

Wiring up Add Item

function addItem() {
    var itemForm = $('#frmItem');
    var itemDialog = $('#ItemDialog');
    itemForm.clearForm();

    // set up buttons
    itemDialog.dialog('option', 'buttons', {
        'Create': function() {
            itemForm.attr('action', '/Items/Add');
            itemForm.submit();
        },
        Cancel: function() {
            itemForm.clearForm();
            $(this).dialog('close');
        }
    });
    itemDialog.dialog('option', 'title', 'Add Item');

    // show the dialog
    itemDialog.dialog('open');
};

Wiring up Edit Item

function bindEditLinks() {
    $('.item-list > .item > div.row > label > a').click(function() {
        var itemForm = $('#frmItem');
        var itemDialog = $('#ItemDialog');

        var listItemId = $(this).parent().parent().parent().attr('id');
        itemForm.find('#hdnItemId').val(listItemId);
        itemForm.find('#Item-Description').val($(this).html());

        // set up buttons
        itemDialog.dialog('option', 'buttons', {
            'Save': function() {
                itemForm.attr('action', '/Items/Edit');
                itemForm.submit();
            },
            'Remove': function() {
                itemForm.attr('action', '/Items/Remove');
                itemForm.submit();
            },
            Cancel: function() {
                itemForm.clearForm();
                $(this).dialog('close');
            }
        });
        itemDialog.dialog('option', 'title', 'Edit Item');

        // show the dialog
        itemDialog.dialog('open');
    });
};

Wiring up Item Complete:

function bindItemCheckboxes() {
    $(".item-list > .item > div.row > input[type='checkbox']").click(function() {
        var listItemId = $(this).parent().parent().attr('id');
        var description = $(this).parent().find('label > a').html();

        $.ajax({
            url: '/Items/Edit',
            type: 'POST',
            dataType: 'json',
            data: {
                IsComplete: document.getElementById($(this).attr('id')).checked,
                ItemId: listItemId,
                Description: description
            },
            success: function(response) {
                jsonTransactionHandler(response);
            },
            error: function(responseObj, textError, errorCode) {
                $('.error').html(textError).fadeIn('slow', function() {
                    setTimeout(function() { $('.error').fadeOut(); }, 4000);
                });
            }
        });
    });
};

Three things here to note here:

  1. bindEditLinks is called in the reloadItems function.
  2. bindItemCheckboxes is also called in the reloadItems function. This simply submits a POST message to the Edit action with the appropriate fields.
  3. The ‘Remove’ button sets the action of the form to ‘Items/Remove’ and submits it which will successfully remove the item.

Let’s take a look at how we have our form being submitted and handled:

Form Initialization

// initialize the form
$('#frmItem').ajaxSubmit({
    dataType: 'json',
    success: function(response) {
        jsonTransactionHandler(response);
        $('#ItemDialog').dialog('close');
        reloadItems();
    },
    error: function(responseObj, textError, errorCode) {
        $('.error').html(textError).fadeIn('slow', function() {
            setTimeout(function() { $('.error').fadeOut(); }, 4000);
        });
    }
});

Result Handler

function jsonTransactionHandler(response) {
    if (response.Success) {
        $('.success').html(response.Message);
        $('.success').fadeIn('slow', function() {
            setTimeout(function() { $('.success').fadeOut(); }, 4000);
        });
    } else {
        $('.error').html(response.Message);
        $('.error').fadeIn('slow', function() {
            setTimeout(function() { $('.error').fadeOut(); }, 4000);
        });
    }
};

Any successful HTTP response from the controller will trigger the jsonTransactionHandler (ff you remember, we set up our JsonUnitOfWorkResult to let us handle all responses the same way). The dialog then gets closed and the items get reloaded to reflect the changes.

Getting it to run

There are only two changes we’ll need to make to get these changes up and running w/ Fubu:

FubuStructureMapBoostrapper (line 32):

ObjectFactory.Initialize(x =>
                             {
                                 x.For<IToDoItemRepository>().Use<FakeToDoItemRepository>();
                                 x.For<IUnitOfWork>().Use<FakeUnitOfWork>();
                             });

We’re registering a fake unit of work for our ItemsController.

FubuToDoRegistry (last line of the constructor):

JsonOutputIf.WhenTheOutputModelIs<JsonUnitOfWorkResult>();

This tells Fubu that we want a JSON response whenever it encounters our JsonUnitOfWorkResult class.

That’s it! We’re all done. In my next part I’ll talk implementing validation and switching over our repository and unit of works classes to enable some real persistence.

Source code

You can download the source for this part at: http://svn.joshua-arnold.com/fubutodo/tags/part2

Technorati Tags: , ,

Comments 4 Comments

FubuToDo – Part 1: Conventions, Opinions, and Bootstrapping

16 01 2010

With the recent activity from the FubuMVC team, I thought that I would help others get started by providing some samples. Tim Tyrrell started us off with his two posts:

  1. “Hello World” with FubuMVC (Super Quick Start)
  2. “Hello World” with FubuMVC (without the training wheels)

Building off of Tim’s directions, I created a simple but more involved application to show some jQuery magic and really just get started with the framework. I’ve broken down this series into the following:

  1. Part 1: Conventions, Opinions and Bootstrapping
  2. Part 2: Forms, Controllers, Views, and jQuery
  3. Part 3: Persistence

Overview

I wanted to create a simple “To Do” application using FubuMVC. I threw together some quick css and figured I’d make something similar to figure 1.1.:

Figure1.1

Figure 1.1 – Wireframe

One important thing to note is that I built this application using a very specific approach: don’t think too much. A lot of the refactoring that you’ll see me do came to mind before I wrote the original pieces (and I’m sure you’ll see it too) but I decided to just stay the course and see what Fubu can do.

Each “Part” in this series has a corresponding tag in the repository I used which I’ll link to at the end of each article.

Getting Started

I started with the tests for my home controller. I wasn’t too sure how I wanted everything to work just yet so I came up with the following cases to test against:

  1. Given some input model (“IndexInputModel”), the Home controller should return a instance of “IndexViewModel” and it should not be null.
  2. When the home controller creates the “IndexViewModel”, it should set the “Items” property.

Let’s take a look at those tests:

Test 1

/// <summary>
/// Given some input model ("IndexInputModel"), the Home controller should return a instance of "IndexViewModel" and it should not be null.
/// </summary>
[Test]
public void Index_Sets_ViewModel()
{
    IToDoItemRepository itemRepository = _mockRepository.DynamicMock<IToDoItemRepository>();
    HomeController controller = new HomeController(itemRepository);

    IndexViewModel viewModel = controller.Index(new IndexInputModel());
    Assert.IsNotNull(viewModel);
}

Test 2

/// <summary>
/// When the home controller creates the "IndexViewModel", it should set the "Items" property.
/// </summary>
public void Index_Sets_ViewModel_Items()
{
    IToDoItemRepository itemRepository = _mockRepository.DynamicMock<IToDoItemRepository>();
    HomeController controller = new HomeController(itemRepository);

    using(_mockRepository.Ordered())
    {
        itemRepository.Expect(r => r.GetAll()).Return(new List<ToDoItem>());
    }

    _mockRepository.ReplayAll();

    IndexViewModel viewModel = controller.Index(new IndexInputModel());
    Assert.IsNotNull(viewModel.Items);
}

Simple, right?

Now, let’s take a look at the other classes:

IndexInputModel

/// <summary>
/// Provides an input model for the home index view.
/// </summary>
public class IndexInputModel
{
}

IndexViewModel

/// <summary>
/// Provides a model for the home index view.
/// </summary>
public class IndexViewModel
{
    /// <summary>
    /// Gets or sets the associated collection of <see cref="ToDoItem"/> entities.
    /// </summary>
    public IEnumerable<ToDoItem> Items { get; set; }
}

ToDoItem

/// <summary>
/// Represents a to-do item.
/// </summary>
public class ToDoItem
{
    /// <summary>
    /// Gets or sets the unique identifier of the item.
    /// </summary>
    public virtual int ItemId { get; set; }
    /// <summary>
    /// Gets or sets a description of the item.
    /// </summary>
    public virtual string Description { get; set; }
    /// <summary>
    /// Gets or sets a flag indicating whether the item has been completed.
    /// </summary>
    public virtual bool IsComplete { get; set; }
}

HomeController

/// <summary>
/// Provides a controlling mechanism for the home area.
/// </summary>
public class HomeController
{
    private readonly IToDoItemRepository _itemRepository;
    /// <summary>
    /// Initializes a new instance of the <see cref="HomeController"/> class with the specified dependency.
    /// </summary>
    /// <param name="itemRepository">The item repository.</param>
    public HomeController(IToDoItemRepository itemRepository)
    {
        _itemRepository = itemRepository;
    }
    /// <summary>
    /// Prepares the index view model.
    /// </summary>
    /// <param name="model">The input model.</param>
    /// <returns></returns>
    public IndexViewModel Index(IndexInputModel model)
    {
        return new IndexViewModel
                   {
                       Items = _itemRepository.GetAll()
                   };
    }
}

Piecing it together

Ok, so now I had a working controller. What I needed to do next was move my classes around, create a view, and then wire everything up. I went with three projects:

  1. FubuToDo
    1. Domain
    2. Repositories
    3. Infrastructure code will go here too
  2. FubuToDo.Tests
  3. FubuToDo.Web
    1. Controllers – Folder for each
    2. Common – Master pages
    3. Images
    4. Styles
    5. Infrastructure – Bootstrapping, registries

I took up some habits from the Dovetail guys and organized my views, models, and controllers in the same folder. Fubu uses the notion of “one model in, one model out” and the controllers are intended to be very light. Given those conventions, it’s pretty safe to assume that your folders won’t blow up with a ton of files and it’s just easier to find everything. I ended up with this:

Figure1.2

Figure 1.2 – Solution structure

The Index view doesn’t have anything fancy. The code-behind class inherits from FubuPage<IndexViewModel> and here’s the code for rendering the items:

    <div id="blog-entry-container">
        <h2>To-Do List</h2>
        <div id="Item-List">
            <% if(Model.Items.Any()) { %>
            <ul class="item-list">
            <% foreach (var item in Model.Items) { %>
                <li id="<%= item.ItemId %>" class="item">
                    <div class="row no-label">
                        <input type="checkbox" name="Item-<%= item.ItemId %>" <%= item.IsComplete ? "checked=\"checked\"" : string.Empty %> />
                        <label class="checkbox"><%= item.Description %></label>
                    </div>
                    <br class="cboth" />
                </li>
            <% } %>
            </ul>
            <% } else { %>
            <span>No items.</span>
            <% } %>
        </div>
    </div>

Getting it to run

We’re almost there, I promise. We’ve got everything working. Now we just need to tell Fubu what to do with it all. Let’s take a look at the Global.asax.cs file:

Global.asax.cs

/// <summary>
/// Provides a global application class for the to-do list application.
/// </summary>
public class Global : HttpApplication
{
    /// <summary>
    /// Called by the ASP.NET Framework on application start.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void Application_Start(object sender, EventArgs e)
    {
        var routes = RouteTable.Routes;
        FubuStructureMapBootstrapper.Bootstrap(routes);
    }
}

We’re deferring all of the initialization and registry calls to our FubuStructureMapBootstrapper class. It’s got a method called “Bootstrap” that just takes in our route collection. Let’s take a look under the hood (if you’ve read Tim’s post, this should look extremely familiar):

FubuStructureMapBoostrapper

/// <summary>
/// Simple StructureMap bootstrapper.
/// </summary>
public class FubuStructureMapBootstrapper : IBootstrapper
{
    private readonly RouteCollection _routes;
    /// <summary>
    /// Initializes a new instance of the <see cref="FubuStructureMapBootstrapper"/> class.
    /// </summary>
    /// <param name="routes"></param>
    public FubuStructureMapBootstrapper(RouteCollection routes)
    {
        _routes = routes;
    }
    /// <summary>
    /// Initializes the bootstrapping process.
    /// </summary>
    public void BootstrapStructureMap()
    {
        UrlContext.Reset();

        ObjectFactory.Initialize(x => x.For<IToDoItemRepository>().Use<FakeToDoItemRepository>());

        Bootstrap(ObjectFactory.Container, _routes);
    }
    /// <summary>
    /// Bootstraps the application with the specified container.
    /// </summary>
    /// <param name="container"></param>
    /// <param name="routes"></param>
    public static void Bootstrap(IContainer container, RouteCollection routes)
    {
        var bootstrapper = new StructureMapBootstrapper(container, new FubuToDoRegistry());
        bootstrapper.Bootstrap(routes);
    }
    /// <summary>
    /// Bootstraps the application for the specified routes.
    /// </summary>
    /// <param name="routes"></param>
    public static void Bootstrap(RouteCollection routes)
    {
        new FubuStructureMapBootstrapper(routes).BootstrapStructureMap();
    }
}

There are two things to note here: 1) The FakeToDoItemRepository and 2) the FubuToDoStructureMapRegistry classes.

The FakeToDoItemRepository class simply returns a hard-coded List<ToDoItem> full of 3 to-do items. The FubuToDoRegistry class has all of my registry calls which tell Fubu what conventions I would like for my app:

FubuToDoRegistry

/// <summary>
/// Simple registry.
/// </summary>
public class FubuToDoRegistry : FubuRegistry
{
    /// <summary>
    /// Initializes a new instance of the <see cref="FubuToDoRegistry"/> class.
    /// </summary>
    public FubuToDoRegistry()
    {
        IncludeDiagnostics(true);

        Applies.ToThisAssembly();

        Actions
            .IncludeTypesNamed(x => x.EndsWith("Controller"));

        Routes
            .IgnoreControllerNamespaceEntirely();

        Views.TryToAttach(x =>
        {
            x.by_ViewModel_and_Namespace_and_MethodName();
            x.by_ViewModel_and_Namespace();
            x.by_ViewModel();
        });
    }
}

I have used the same conventions that Tim did in his example. He’s got a great explanation on what these do so I strongly recommend reading his post.

Running the application

At the time of this writing, there is no way to specify your default route in your registry. To get around this for now, I created a Default.aspx in the root and did a Response.Redirect(“~/Home/Index”) to force it to my default action. Once that’s in place, hit F5 and you should see this:

Figure1.3

Figure 1.3 – End result

That’s it! We’re all done. In my next part I’ll talk about wiring up forms to your controllers and leverage the UnitOfWork pattern to get the items saving.

Source code

You can download the source for this part at: http://svn.joshua-arnold.com/fubutodo/tags/part1

Technorati Tags: , ,

Comments 1 Comment