To set the tab order, all you need to do is be able to add an extra attribute, tabindex
to the generated field. That's easy enough with something like TextBoxFor
or DropDownListFor
, since they actually take an htmlAttributes
parameter specifically for this purpose:
@Html.TextBoxFor(m => m.Foo, new { tabindex = 1 })
In the past, the same could not be said for EditorFor
. Since it's a "templated" helper, the editor template, not the method call, effects what's generated. You can see this in the definition of EditorFor
, as there's no htmlAttributes
param like the other helpers have, but rather additionalViewData
.
Starting with MVC 5.1, Microsoft made it possible to pass additional HTML attributes to EditorFor
, via a specially named ViewData
key, "htmlAttributes"
. As a result, you can achieve the same thing as when using something like TextBoxFor
, although it's a little more verbose:
@Html.EditorFor(m => m.Foo, new { htmlAttributes = new { tabindex = 1 } })
See, you're still actually passing additionalViewData
here, but that additional view data contains an anonymous object keyed to htmlAttributes
. The built-in editor templates, then, know how to utilize ViewData["htmlAttributes"]
to add additional attributes to the generated element. However, this only applies to the default editor templates because Microsoft has specifically programmed them to use this. As soon as you add your own custom editor templates, you're right back to where you started.
There's a number of ways you could approach this with custom editor templates. First, you could just pass the tab index directly as view data, and utilize that in your template:
@Html.EditorFor(m => m.Foo, new { tabindex = 1 })
Then, in your editor template:
@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { tabindex = ViewData["tabindex"]})
Second, you could mimic EditorFor
's behavior with the default templates:
@Html.EditorFor(m => m.Foo, new { htmlAttributes = new { tabindex = 1 } })
Then, in your editor template:
@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, ViewData["htmlAttributes"])
However, that option doesn't allow you to have "default" attributes. It's an all or nothing approach. To truly be able to utilize ViewData["htmlAttributes"]
as the built-in editor templates do, you'll need to combine the default attributes with the passed in ones, first, and then pass the whole shebang to htmlAttributes
. I've got a blog post that discusses that in depth, but TL;DR: you'll need the following extension:
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
public static partial class HtmlHelperExtensions
{
public static IDictionary<string, object> MergeHtmlAttributes(this HtmlHelper helper, object htmlAttributesObject, object defaultHtmlAttributesObject)
{
var concatKeys = new string[] { "class" };
var htmlAttributesDict = htmlAttributesObject as IDictionary<string, object>;
var defaultHtmlAttributesDict = defaultHtmlAttributesObject as IDictionary<string, object>;
RouteValueDictionary htmlAttributes = (htmlAttributesDict != null)
? new RouteValueDictionary(htmlAttributesDict)
: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributesObject);
RouteValueDictionary defaultHtmlAttributes = (defaultHtmlAttributesDict != null)
? new RouteValueDictionary(defaultHtmlAttributesDict)
: HtmlHelper.AnonymousObjectToHtmlAttributes(defaultHtmlAttributesObject);
foreach (var item in htmlAttributes)
{
if (concatKeys.Contains(item.Key))
{
defaultHtmlAttributes[item.Key] = (defaultHtmlAttributes[item.Key] != null)
? string.Format("{0} {1}", defaultHtmlAttributes[item.Key], item.Value)
: item.Value;
}
else
{
defaultHtmlAttributes[item.Key] = item.Value;
}
}
return defaultHtmlAttributes;
}
}
And then you'll need to add the following to top of your custom editor templates:
@{
var defaultHtmlAttributesObject = new { type = "date", @class = "form-control" };
var htmlAttributesObject = ViewData["htmlAttributes"] ?? new { };
var htmlAttributes = Html.MergeHtmlAttributes(htmlAttributesObject, defaultHtmlAttributesObject);
}
You'd change the defaultHtmlAttributesObject
variable depending on what attributes the generated input should have by default for that particular template.