Category Archives: Uncategorized

Correcting MVC 3 EditorFor Template Field Names When Using Collections

So, I recently ran into a problem with ASP.NET MVC 3 and editor templates when dealing with models that contain collections. If you handle the collection directly in the view, it works fine:

@For i as integer = 0 To Model.Locations.Count-1
    Dim index As Integer = i

    @<div>
        @Html.EditorFor(Function(model) model.Locations(i).Name)
        @Html.EditorFor(Function(model) model.Locations(i).Description)
    </div>
Next

In the above example, the fields will receive the names “Locations[0].Name” and “Locations[0].Description”, and so on for each field. This is correct, and will result in the model begin parsed correctly by the DefaultModelBinder if it’s a parameter on your POST action.

However, what if you want to make an editor template that always gets used for a specific collection type?  Let’s say, for example, that the collection in the model is of type LocationCollection, and you make an editor template named LocationCollection:

@ModelType LocationCollection
@For i as integer = 0 To Model.Count-1
    Dim index As Integer = i
    @<div>
        @Html.EditorFor(Function(model) model(i).Name)
        @Html.EditorFor(Function(model) model(i).Description)
    </div>
Next

Then you reference it in your view like this:

@Model.EditorFor(Function(model) model.Locations)

In this case, the fields will actually have the incorrect names on them, the will be named “Locations.[0].Name” and “Locations.[0].Description”.  Notice the extra period before the array index specifier.  With these field names, the model binder won’t recognize the fields when they come back in the post.

The solution to this problem is a little cumbersome, due to the way the field names are built.  First, the prefix is passed down to the editor template in ViewData.TemplateInfo.HtmlFieldPrefix.  However, this prefix is passed down WITHOUT the period.  The period is added as a delimiter by the ViewData.TemplateInfo.GetFullHtmlFieldName function, which is used by all of the editor helpers, like Html.TextBox.

This means that we actually can’t fix it in the editor template shown above.  Instead, we need TWO editor templates.  The first one for LocationCollection, and then a second one for Location:

@ModelType LocationCollection
@For i as integer = 0 To Model.Count-1
    Dim index As Integer = i
    @Html.EditorFor(Function(model) model(index))
Next
@ModelType Location
@Code
    ViewData.TemplateInfo.FixCollectionPrefix()
End Code
<div>
    @Html.EditorFor(Function(model) model(i).Name)
    @Html.EditorFor(Function(model) model(i).Description)
</div>

By breaking this up into two editor templates, we can now correct the prefix in the second template.  The first editor template receives an HtmlFieldPrefix of “Locations”, so we can’t do anything with that.  However, the Location editor template receives an HtmlFieldPrefix of “Locations.[0]“, so all we need to do is remove the extra period and the problem is solved.  As you can see in my example above, I’m calling a method on TemplateInfo, FixCollectionPrefix.  This is a simple extension method which corrects prefix, which you just need implement somewhere in an imported namespace:

<Extension()>
Public Sub FixCollectionPrefix(templateInfo As TemplateInfo)
Dim prefix As String = templateInfo.HtmlFieldPrefix
If Not String.IsNullOrEmpty(prefix) Then
templateInfo.HtmlFieldPrefix = Regex.Replace(prefix, ".(\[\d+\])$", "$1")
End If
End Sub

And that’s all there is to it. I certainly hope that the MVC team fixes this problem internally for their next release, but until then we’ll just have to keep working around it.

Detecting Original HTML5 Input Types On Unsupported Browsers

I’ve been doing some work recently on extending jQuery UI to style form elements using the Themeroller.  One thing that I wanted to implement was detection of the new HTML5 input types and choosing the correct widget to use based upon that.  This would provide progressive enhancement of the input element to support the HTML5 input type, even if that type of input isn’t supported on that browser.

By definition, if a browser can’t support the type of an input, it automatically falls back to a text input.  This is great for backwards compatibility, but it gave me a bit of a problem.  If I set type=”number” in my HTML, testing the type of the element in Javascript returns “text” when the number element isn’t supported.  Useful for determining if support is available, not so useful for determining what the original value is in the HTML.

In most browsers, you can find the original value using the attributes collection.  However, this doesn’t work in IE7 and earlier.  And, as everyone knows, we still have to support IE 6 & 7 for most web sites.  I found an alternative for IE, which is to test the outerHTML of the element using a regular expression.  IE will return this with the original attributes intact.

I’ve written a jQuery filter extension that allows you to test any element for the original input type:

// add HTML5 input type expression (still detects HTML5 input types on browsers that don't support them)
$.extend($.expr[':'], {
	inputtype: function(elem, i, type) {
		function getRawAttr() {
			// IE will return the original value in the outerHTML
			var match = /<input.*?\s+type=(\w+)(\s+|>).*?/i.exec(elem.outerHTML);

			if (match && match.length >= 2 && match[1] !== "text") {
				return match[1];
			}

			// for other browsers, test attributes collection (doesn't work in IE<7)
			var attrs = elem.attributes,
				i;

			for (i=0; i<attrs.length; i++) {
				if (attrs[i].nodeName === "type") {
					return attrs[i].nodeValue;
				}
			}

		}

		if (elem.tagName != "INPUT") return false;
		if (elem.type === "text") {
			// could be unsupported type fallback, so do further testing
			return getRawAttr() === type[3];
		} else {
			return elem.type === type[3];
		}
	}
});

To use this extension, just use the :inputtype(type) filter in your jQuery expression.  For example:


$('input:inputtype(number)).width(100);

if ($(elem).is(':inputtype(number)')) {
...
}

Update: Changed to only run the check if the tag is an input tag, in case you run the filter against non-input elements.

jQuery UI Spinner Widget 1.10

I’ve uploaded a revised jQuery UI Spinner widget, version 1.10, based upon a lot of excellent feedback from the jQuery UI team. New features include mousewheel scrolling, decimal support, customizable prefixes/suffixes for currency/percents, improved input masking and maxlength handling, HTML5 markup support, lots of bug fixes, and more.

jquery.ui.spinner Git Repository
jQuery UI Spinner Widget Example

As it turns out, the jQuery UI team already had a spinner widget sitting in their files that hadn’t been worked on in a while, so I’m working with Ca-Phun Ung to see if we can finish getting it up-to-date, polished, and with some of the new features from my widget incorporated into it. Stay tuned for more info.