Link here

Link here

Editing a variable length list, ASP.NET MVC 2-style

MVC, UI, jQuery 17 Comments »

A while back I posted about a way of editing a list of items where the user can add or remove as many items as they want. Tim Scott later provided some helpers to make the code neater. Now, I find myself making use of this technique so often that I thought it would be worthwhile providing an update to show how you can do it even more easily with ASP.NET MVC 2 because of its strongly-typed and templated input helpers.

Download the demo project or read on for details.

Getting Started

For this example I’m going to go with the same theme as last time and build a gift list editor. Our model object can simply be the following:

public class Gift
{
    public string Name { get; set; }
    public double Price { get; set; }
}

Displaying the Initial UI

To display the initial data entry screen, add an action method that renders a view and passes some initial collection of Gift instances.

public ActionResult Index()
{
    var initialData = new[] {
        new Gift { Name = "Tall Hat", Price = 39.95 },
        new Gift { Name = "Long Cloak", Price = 120.00 },
    };
    return View(initialData);
}

Next, add a view for this action, and make it strongly-typed with a model class of IEnumerable<Gift>. We’ll create an HTML form, and for each gift in the collection, we’ll render a partial to display an editor for that gift.

<h2>Gift List</h2>
What do you want for your birthday?
 
<% using(Html.BeginForm()) { %>
    <div id="editorRows">
        <% foreach (var item in Model)
            Html.RenderPartial("GiftEditorRow", item);
        %>
    </div>
 
    <input type="submit" value="Finished" />
<% } %>

I know it would be possible to use ASP.NET MVC 2’s Html.EditorFor() or Html.EditorForModel() helpers, but later on we’re going to need more control over the HTML field ID prefixes so in this example it turns out to be easier just to use Html.RenderPartial().

Next, to display the editor for each gift in the sequence, add a new partial view at /Views/controllerName/GiftEditorRow.ascx, strongly-typed with a model class of Gift, containing:

<% using(Html.BeginCollectionItem("gifts")) { %>
    <div class="editorRow">
        Item: <%= Html.TextBoxFor(x => x.Name) %> 
        Value: $<%= Html.TextBoxFor(x => x.Price, new { size = 4 }) %> 
    </div>        
<% } %>

Here’s where we get to start using ASP.NET MVC 2 features and make things slightly easier than before. Notice that I’m using strongly-typed input helpers (Html.TextBoxFor()) to avoid the need to build element IDs manually. These helpers are smart enough to detect the “template context” in which they are being rendered, and use any field ID prefix associated with that template context.

You might also be wondering what Html.BeginCollectionItem() is. It’s a HTML helper I made that you can use when rendering a sequence of items that should later be model bound to a single collection. You give it some name for your collection, and it opens a new template context for that collection name, plus a random unique field ID prefix. It also automatically renders a hidden field, which in this case is called gifts.index, populating it with that unique ID, so when you later model bind to a list, ASP.NET MVC 2 will know that all the fields in this context should be associated with a single .NET object.

And now, if you visit the Index() action, you should see the editor  as shown below. (I’ve added some CSS styles, obviously.)

image

Receiving the Form Post

To receive the data posted by the user, add a new action method as follows.

[HttpPost]
public ActionResult Index(IEnumerable<Gift> gifts)
{
    // To do: do whatever you want with the data
}

How easy is that? Because Html.BeginCollectionItem() observes ASP.NET MVC 2 model binding conventions, you can receive all the items in the list without having to do anything funky.

You could also achieve the same with less code by using the built-in Html.EditorFor() or Html.EditorForModel() helpers, but because these use a different indexing convention (an ascending sequence, not a set of random unique keys), things would get more difficult when you try to add or remove items dynamically.

Dynamically Adding Items

If the user wants to add another item, they’ll need something to click to say so. Let’s add an “Add another…” link. Add the following to the main view, just before the “Finished” button.

<%= Html.ActionLink("Add another...", "BlankEditorRow", null, new { id = "addItem" }) %>

This is a link to an action called BlankEditorRow which doesn’t exist yet. The idea is that the BlankEditorRow action will return the HTML markup for a new blank row. We can fetch this markup via Ajax and dynamically append it into the page.

To make this Ajax call and append the result into the page, make sure you’ve got jQuery referenced, and create a click handler similar to this:

$("#addItem").click(function() {
    $.ajax({
        url: this.href,
        cache: false,
        success: function(html) { $("#editorRows").append(html); }
    });
    return false;
});

Note that it’s very important to tell jQuery to tell the browser not to re-use cached responses, otherwise those unique IDs won’t always be so unique… And before we forget, we’ll need to put the BlankEditorRow action in place:

public ViewResult BlankEditorRow()
{
    return View("GiftEditorRow", new Gift());
}

As you can see, it simply renders the same editor partial, passing a blank Gift object to represent the initial state. I’m very pleased that you can re-use the same editor partial in this way – it means there’s no duplication of view markup and we can stay totally DRY.

And that, in fact, is all you have to do – each time the user clicks “Add another…”, the client-side code will inject a new blank row into the editor. Because each row has its own unique ID, when the user later posts the form, all the data will be model bound into a single IEnumerable<Gift>.

Dynamically Removing Items

Removing items is easier, because all you have to do is remove the corresponding DOM elements from the HTML document. If the elements are gone, their contents won’t be posted to the server, so they won’t be present in the IEnumerable<Gift> that your action receives.

Add a “delete” link to the GiftEditorRow.ascx partial:

<a href="#" class="deleteRow">delete</a>

This needs to go inside the DIV with class “editorRow”, so you can handle clicks on it as follows:

$("a.deleteRow").live("click", function() {
    $(this).parents("div.editorRow:first").remove();
    return false;
});

Notice that this code uses jQuery’s “live” function, which tells it to apply the click handler not only to the elements that exist when the page is first loaded, but also to any matching elements that are dynamically added later.

You’ve now got a working editor with “add” and “delete” functionality.

editor-screenshot.png

Summary

I hope you can see that editing variable-length lists can be very easy. Other than the reusable Html.BeginCollectionItem() helper, I’ve just shown you every line of code needed for this particular strategy, and in total it’s just 36 lines (including the model, action methods, views, and JavaScript, but excluding lines that are purely whitespace or braces).

Download the full demo project

But what about validation?

Yes, I haven’t forgotten about that! You can already do your server-side validation any way you want – by default the model binder will respect any rules associated with your model.

In the next post, I’ll show how you can integrate this list editing strategy with ASP.NET MVC 2’s client-side validation feature.

jQuery Ajax uploader plugin (with progress bar!)

Javascript, MVC, UI, jQuery 88 Comments »

Do your web applications ever involve letting the user upload a file? If so, how’s the end-user experience: do you show a nice progress bar during the upload, or do you just leave the user waiting for minutes, with no clue when (if ever) the upload will complete?

Please show a progress bar, otherwise users will be justified in hating you. Check out this video to see one way it can work:


If you’re not seeing a video here, your feed reader is hiding it. View this post in a browser to see the video.

Those of you who attended my ASP.NET MVC talk at DDD7 last weekend might recognise this ;)

To create this behaviour, I implemented a simple jQuery plugin that replaces normal <input type=”file”/> elements with funky Ajaxy asynchronous uploader widgets. Behind the scenes, it uses the excellent SWFUpload library. All the clever stuff is in SWFUpload; all I did is set up the progress bar / cancellation behaviours, and make it easier to use if you’re already using jQuery.

Notice that it still works if the user doesn’t have JavaScript running in their browser. It gracefully degrades to “traditional” <input type=”file”/> behaviour. This is known as progressive enhancement or unobtrusive JavaScript.

Download

Here are all the files you need to accomplish this: Download jQuery-asyncUpload-0.1.js.

Setup instructions

Uploading files via Ajax, by nature, involves setting things up both on the server and on the client. The most reliable way to get this working successfully in your own app is to download the demo ASP.NET MVC project (see the end of this post) and copy the relevant aspects of its workings into your own app.

Nonetheless, here is an outline of the steps needed to get jQuery-asyncUpload-0.1.js working in your app, assuming you’ve already got jQuery in there:

1.  Add jQuery-asyncUpload-0.1.js, swfupload.js, and swfupload.swf to your project. In an ASP.NET MVC app, you might like to put these in /Scripts.

2.  Add script tags to reference the JavaScript files.

<head>
    <!-- Adjust the file paths as needed for your project -->
    <script src="/Scripts/jquery-1.2.6.min.js"></script>
    <script src="/Scripts/swfupload.js"></script>
    <script src="/Scripts/jquery-asyncUpload-0.1.js"></script>
</head>

3.  Add an old-style HTML file upload control to one of your pages:

<input type="file" id="yourID" name="yourID" />

4. Add a jQuery statement that replaces this file upload control with an asynchronous uploader when JavaScript is available:

<script>
    $(function() {
        $("#yourID").makeAsyncUploader({
            upload_url: "/Home/AsyncUpload", // Important! This isn't a directory, it's a HANDLER such as an ASP.NET MVC action method, or a PHP file, or a Classic ASP file, or an ASP.NET .ASHX handler. The handler should save the file to disk (or database).
            flash_url: '/Scripts/swfupload.swf',
            button_image_url: '/Scripts/blankButton.png'
        });
    });        
</script>

These options are explained later in this blog post. You must make sure to correctly reference the location of swfupload.swf, and put a button image wherever button_image_url specifies.

5. Add some CSS rules to style the progress bar. I’m using the following, though bear in mind it has some nasty hacks to make IE do an inline float properly. CSS gurus might structure this more cleanly.

DIV.ProgressBar { width: 100px; padding: 0; border: 1px solid black; margin-right: 1em; height:.75em; margin-left:1em; display:-moz-inline-stack; display:inline-block; zoom:1; *display:inline; }
DIV.ProgressBar DIV { background-color: Green; font-size: 1pt; height:100%; float:left; }
SPAN.asyncUploader OBJECT { position: relative; top: 5px; left: 10px; }

5. At this point, check you have something working. The visitor should now be able to select a file to upload, and should immediately get an alert box saying “Error 404” – that’s because you’ve configured the control to do an asynchronous upload to /Home/AsyncUpload, but your web app probably doesn’t have anything at that URL.

Also, if you use FireBug to inspect the DOM, you’ll see that your <input type=”file” /> has been dynamically replaced with the following:

<span class="asyncUploader">
   <div class="ProgressBar" style="display: none;"> 
	<!-- This is the progress bar itself - you can style it with CSS -->
   </div>
   <object type="application/x-shockwave-flash" ... >
       ... SWF config here ...
   </object>
   <input type="hidden" name="yourID_filename"/>
   <input type="hidden" name="yourID_guid"/>
</span>

Those two hidden inputs let you keep track of any file that was asynchronously uploaded.

6. Work on your web app so that it *does* handle file uploads to /Home/AsyncUpload (or whatever URL you’ve configured in step 4). The handler should save the uploaded file to disk, then return a unique token, such as a GUID or filename, to will identify the file you just uploaded. See the demo project for a simple way to do this using ASP.NET MVC.

7. When the containing form is finally submitted, check whether a file was sent with the request. This will happen if the user doesn’t have JavaScript enabled, as they’ll revert to traditional uploading behaviour. Also check for the hidden inputs called yourID_guid and yourID_filename – these will be populated if the visitor *does* have JavaScript enabled, and reflect any file that was uploaded asynchronously.

Further configuration

The asynchronous uploader plugin has plenty of properties you can configure in step 4 above:

Property Meaning Example
flash_url Location of swfupload.swf “/Scripts/swfupload.swf”
upload_url URL of the handler to which files will be asynchronously sent

Important: This is not the name of a directory or file on your server’s hard disk. It is the URL of a handler, such as an ASP.NET MVC action method, or a PHP page, or a classic ASP page, or an ASP.NET WebForms .ASHX handler, which will receive the file. It remains your job to implement such a handler and then save the incoming file to disk or database. For an example, see the ASP.NET MVC demo project at the end of this blog post.
“/Home/AsyncUpload”
file_size_limit Files above this size will be rejected before uploading even begins “3 MB”
file_types “Select files” popup will only show files of this type “*.jpg; *.gif”
file_types_description “Select files” popup will use this caption to describe the selectable file types “All images”
button_image_url Location of an image to be used for the “Choose file” button “blankButton.png”
button_image_width, button_image_height Dimensions of “Choose file” button 109
button_text Text that appears on the “Choose file” button "<font face=’Arial’ size=’13pt’>Choose file</font>"
disableDuringUpload Elements matching this jQuery selector will be disabled while an upload is in progress (useful to prevent form submission during async upload). “INPUT[type=’submit’]”
existingFilename Prepopulates the control with the name of a file already uploaded (useful when retaining state across multiple posts) “somefile.zip”
existingGuid Prepopulates the control with the arbitrary unique token you’ve given to a file already uploaded (useful when retaining state across multiple posts) “ec42555e-bfe7-45b0-87bf-36b1299f0398”
existingFileSize Prepopulates the control with the size, in bytes, of a file already uploaded (useful when retaining state across multiple posts) 548293
debug Turns on SWFUpload’s debugging console true

Demo project

The easiest way to understand all this is to check out a completed implementation. Here’s one written for ASP.NET MVC.

To compile and run this, you’ll need Visual Studio 2008 and ASP.NET MVC Beta. It saves asynchronously-uploaded files to the folder ~/App_Data/Files, giving each one a GUID as a filename. When you finally submit the form, it simply displays the filename and GUID of whatever file you uploaded. In a real app, you wouldn’t display the GUID to the end user, but would instead just use it to locate their file later.

kick it on DotNetKicks.com

Site Meter