views:

218

answers:

2

In MVC2 template, I normally use this.ViewData.TemplateInfo.GetFullHtmlFieldId(fieldName) to generate the id field of an html element. This has worked in most of the cases.

However this method does not actually return valid id field, it merely prefix fieldName with ViewData.TemplateInfo.HtmlFieldPrefix, this is causing problems for me when rendering collections which has [] in the HtmlFieldPrefix.

I have been manually converting those characters to _ where I find the need, but this seems to be not elegant (repeated code), has anyone found a good way to generate id field properly?

+1  A: 

Can you be more specific about the kind of problems do you have?

For example, there is an elegant approach to editing variable length list with validation support provided. While it doesn't use templates still remains DRY with partial views.

While the ids are inconsistent - the names are OK and only problem I encountered is that using jquery.infieldlabel it appeared that label's for attribute (generated by GetFullHtmlFieldId inside LabelFor helper) didn't match id of the appropriate TextBoxFor input. So I created LabelForCollectionItem helper method that just uses the same method for id generation as the TextBox - TagBuilder.GenerateId(fullName)

Maybe the code doesn't correspond to your need but hope it will help somebody since I found your question among the first searching for solution to my problem.

public static class LabelExtensions
{
    /// <summary>
    /// Generates Label with "for" attribute corresponding to the id rendered by input (e.g. TextBoxFor), 
    /// for the case when input is a collection item (full name contains []).
    /// GetFullHtmlFieldId works incorrect inside Html.BeginCollectionItem due to brackets presense.
    /// This method copies TextBox's id generation.
    /// </summary>
    public static MvcHtmlString LabelForCollectionItem<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression,
               string labelText = null, object htmlAttributes = null) where TModel : class
    {
        var tag = new TagBuilder("label");
        tag.MergeAttributes(new RouteValueDictionary(htmlAttributes)); // to convert an object into an IDictionary

        // set inner text
        string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
        string innerText = labelText ?? GetDefaultLabelText(html, expression, htmlFieldName);
        if (string.IsNullOrEmpty(innerText))
        {
            return MvcHtmlString.Empty;
        }
        tag.SetInnerText(innerText);

        // set for attribute
        string forId = GenerateTextBoxId(tag, html, htmlFieldName);
        tag.Attributes.Add("for", forId);

        return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
    }

    /// <summary>
    /// Extracted from System.Web.Mvc.Html.InputExtensions
    /// </summary>
    private static string GenerateTextBoxId<TModel>(TagBuilder tagBuilder, HtmlHelper<TModel> html, string htmlFieldName)
    {
        string fullName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName);
        tagBuilder.GenerateId(fullName);
        string forId = tagBuilder.Attributes["id"];
        tagBuilder.Attributes.Remove("id");
        return forId;
    }

    /// <summary>
    /// Extracted from System.Web.Mvc.Html.LabelExtensions
    /// </summary>
    private static string GetDefaultLabelText<TModel, TValue>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, string htmlFieldName)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
        return labelText;
    }
}
alexander
So in your method, what happens if htmlFieldName contains square brackets? e.g. "Users[0]"
Bill Yang
Html.LabelForCollectionItem(x => x.Users[0].Name) returns<label for="Users_0__Name" ...
alexander
This is an interesting way to do it, helped me to come up with a solution for our specific situation. Thanks! (I'd up vote your answer if you can edit it -- so get around silly stackoverflow rules)
Bill Yang
A: 

alexander, thanks so much for posting this extension class - it was exactly wehat I needed.

Brendan