Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

MVC HtmlHelper: Text Posting Dropdown List

4.56/5 (6 votes)
2 Aug 2012CPOL2 min read 33.2K  
The HTML Helper that enables the dropdownlist to post the text selected in it along with the value !!!

Introduction

As you all know the dropdown only posts the value selected but not the text to the server. In my recent project I needed the text selected in the dropdown to be available along with the value selected.

The first thing most developers, including me, will think is about adding a hidden field to post the text and set the value of the hidden field on change of the dropdown. Yes, I did the same, but in a more reusable way and I thought of sharing it with you guys….

Using the code

Have a close look at the Html Helper DropDownListFor:

C#
Html.DropDownListFor((m) => m.UserId, Model.UserCollection, 
          "--please select--", new { @style = "width:330px" })

What we are missing over here? Hmmm.. we have only the provision to specify the model property for the dropdown value. Yes! That means we need to have one more Expression parameter to specify the text of the dropdown as well. So here goes the signature for our new Html Helper with one additional parameter to key in the text model property.

C#
internal static MvcHtmlString TextPostingDropDownListFor<TModel, TValProperty, TTextProperty>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TValProperty>> valueExpression,
        Expression<Func<TModel, TTextProperty>> textExpression,
        IEnumerable<SelectListItem> selectList,
        string optionLabel,
        IDictionary<string, object> htmlAttributes )
    {

Now as we decided we are going to render one hidden control along with the dropdown to hold the text. What are the attributes required for the hidden field? Yes there you are, Name, Id and Text. Lets get them first…

C#
string textControlName =
            htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName
            (ExpressionHelper.GetExpressionText(textExpression));
        
string textControlId =
            htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId
            (ExpressionHelper.GetExpressionText(textExpression));

object text = ModelMetadata.FromLambdaExpression(textExpression, htmlHelper.ViewData).Model;

The next step is to get the HTML for the dropdown added with some custom attributes and class. Why we need these attributes and class? Well, those are used by our jQuery script to setup the control.

C#
RouteValueDictionary attributes = new RouteValueDictionary(htmlAttributes);
if (attributes.ContainsKey("class"))
{
    attributes["class"] = string.Format("{0} {1}", attributes["class"], "tpddl");
}
else
{
    attributes.Add("class", "tpddl");
}
attributes.Add("tpddl-posting-control-id", textControlId);

MvcHtmlString html = htmlHelper.DropDownListFor(valueExpression, selectList, optionLabel, attributes);

You can see that we have added a class tpddl and an attribute tpddl-posting-control-id with value as the text control ID.

Now we need the hidden control..

C#
if (htmlAttributes.ContainsKey("class"))
            htmlAttributes.Remove("class");
TagBuilder tb = new TagBuilder("input");
tb.MergeAttributes(htmlAttributes);
tb.MergeAttribute("name", textControlName);
tb.MergeAttribute("id", textControlId);
tb.MergeAttribute("type", "hidden");
tb.MergeAttribute("value", (text != null) ? text.ToString() : string.Empty);

Let's merge the dropdown and textbox html to get the final result.

C#
string strHtml = string.Format("{0}{1}", html.ToHtmlString(), tb.ToString(TagRenderMode.SelfClosing));
return new MvcHtmlString(strHtml);

Putting it altogether the final code is as below.

C#
internal static MvcHtmlString TextPostingDropDownListFor<TModel, TValProperty, TTextProperty>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TValProperty>> valueExpression,
        Expression<Func<TModel, TTextProperty>> textExpression,
        IEnumerable<SelectListItem> selectList,
        string optionLabel,
        IDictionary<string, object> htmlAttributes)
{
    string textControlName =
        htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName
        (ExpressionHelper.GetExpressionText(textExpression));
    
    string textControlId =
        htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId
        (ExpressionHelper.GetExpressionText(textExpression));

    object text = ModelMetadata.FromLambdaExpression(textExpression, htmlHelper.ViewData).Model;        

    RouteValueDictionary attributes = new RouteValueDictionary(htmlAttributes);
    if (attributes.ContainsKey("class"))
    {
        attributes["class"] = string.Format("{0} {1}", attributes["class"], "tpddl");
    }
    else
    {
        attributes.Add("class", "tpddl");
    }        
    attributes.Add("tpddl-posting-control-id", textControlId);

    MvcHtmlString html = htmlHelper.DropDownListFor(valueExpression, 
                                    selectList, optionLabel, attributes);

    if (htmlAttributes.ContainsKey("class"))
        htmlAttributes.Remove("class");
    TagBuilder tb = new TagBuilder("input");
    tb.MergeAttributes(htmlAttributes);
    tb.MergeAttribute("name", textControlName);
    tb.MergeAttribute("id", textControlId);
    tb.MergeAttribute("type", "hidden");
    tb.MergeAttribute("value", (text != null) ? text.ToString() : string.Empty);
    string strHtml = string.Format("{0}{1}", html.ToHtmlString(), 
                                   tb.ToString(TagRenderMode.SelfClosing));

    return new MvcHtmlString(strHtml);        
}

Now you can add some public overloads for the convenience of the user.

C#
public static MvcHtmlString TextPostingDropDownListFor<TModel, TProperty, TTextProperty>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> valueExpression,
        Expression<Func<TModel, TTextProperty>> textExpression,
        IEnumerable<SelectListItem> selectList)
{
    var attributes = new RouteValueDictionary();
    return htmlHelper.TextPostingDropDownListFor(
        valueExpression,
        textExpression,
        selectList,
        null,
        attributes);
}

public static MvcHtmlString TextPostingDropDownListFor<TModel, TProperty, TTextProperty>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> valueExpression,
    Expression<Func<TModel, TTextProperty>> textExpression,
    IEnumerable<SelectListItem> selectList,
    string optionLabel)
{
    var attributes = new RouteValueDictionary();
    return htmlHelper.TextPostingDropDownListFor(
        valueExpression,
        textExpression,
        selectList,
        optionLabel,
        attributes);
}

public static MvcHtmlString CMSTextPostingDropDownListFor<TModel, TProperty, TTextProperty>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> valueExpression,
    Expression<Func<TModel, TTextProperty>> textExpression,
    IEnumerable<SelectListItem> selectList,
    string optionLabel,
    object htmlAttributes)
{
    var attributes = new RouteValueDictionary(htmlAttributes);
    return htmlHelper.TextPostingDropDownListFor(
        valueExpression, 
        textExpression, 
        selectList, 
        optionLabel, 
        attributes);
}

The next thing I am going to do is add the jQuery script to make the control functional. Add a script file, say, textpostingdropdown.js, and add the code below in it.

JavaScript
$(document).ready(function () {
    $(".tpddl").setTextPostingDropdown();
}
jQuery.fn.setTextPostingDropdown = function () {
    $(this).each(function () {             
        $(this).makeDropdownTextPostable();
    });
}

jQuery.fn.makeDropdownTextPostable = function () {
    $(this).bind("change", function () {
        var textcontrolid = $(this).attr("tpddl-posting-control-id");        
        var selText = $(this).find("option:selected").text();
        $("#" + textcontrolid).val(selText);
    });
};

Use the control by specifying the Model property for the dropdown text as mentioned below.

C#
Html.TextPostingDropDownListFor((m) => m.UserId,(m) => m.UserName, 
   Model.UserCollection, "--please select--", new { @style = "width:330px" })

Bingo!! That is it, the control is ready to post your text….

But don't forget to post your comment as well !!! :-)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)