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 of the 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;
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);
Lets 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 gonna do is to 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 it, the control is ready to post your text….
But you dont forget to post your comment as well !!! :-)