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
:
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.
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…
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.
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..
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.
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.
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.
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.
$(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.
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 !!! :-)