Conditionally disable Html.DropDownList
How can I change this DropDownList declaration so that the disabled attribute is enable/disabled conditional开发者_StackOverflow中文版ly?
<%= Html.DropDownList("Quantity", new SelectList(...), new{@disabled="disabled"} %>
non-working example:
<%= Html.DropDownList("Quantity", new SelectList(...), new{@disabled=Model.CanEdit?"false":"disabled"} %>
p.s. adding an if condition around the entire statement is not a desired approach :)
EDIT: based on this extension method from another question I came up with the following extension:
public static IDictionary<string, object> Disabled (this object obj, bool disabled)
{
return disabled ? obj.AddProperty ("disabled", "disabled") : obj.ToDictionary ();
}
which can then be used as
<%= Html.DropDownList("Quantity", new SelectList(...), new{id="quantity"}.Disabled(Model.CanEdit) %>
There is no need to add helper methods, you can just use
<%= Html.DropDownList("Quantity", new SelectList(...), IsEditable == true ? new { @disabled = "disabled" } as object : new {} as object %>
If you were to remove the as object
entries this wouldn't work because by default new {}
is a dynamic object compiled at runtime, therefore the two possible objects must have the same properties. But the Html attributes parameter is actually just an object, so these dynamics can be cast as objects to get around this.
This solution even allows you to use multiple HTML attributes where one is optional and another is not, i.e class='whatever'
is not optional but disabled
is so you put class='whatever'
in both the objects, but the optional one only in the first. Dimitrov's answer does not support any custom attributes other than disabled.
Please don't write spaghetti code. Html helpers are there for this purpose:
public static MvcHtmlString DropDownList(this HtmlHelper html, string name, SelectList values, bool canEdit)
{
if (canEdit)
{
return html.DropDownList(name, values);
}
return html.DropDownList(name, values, new { disabled = "disabled" });
}
And then:
<%= Html.DropDownList("Quantity", new SelectList(...), Model.CanEdit) %>
Or maybe you could come up with something even better (if the model contains the options):
<%= Html.DropDownList("Quantity", Model) %>
You will also get the bonus of having more unit testable code.
One option is creating a custom version of Html.DropDownList that takes an extra parameter and does what you want... but then you would have to make a new one for every type of helper - TextBoxFor, TextAreaFor, CheckBoxFor, etc... and you still have to figure out how to make the guts of it work.
I opted, instead, to create an Html Helper to replace the normal anonymous HtmlAttributes object since then it would be compatible with all of the Helpers that use HtmlAttributes without any special work. This solution also lets you pass through additional Attributes like Class, Name, or whatever you want. It doesn't lock you down to only disabled.
I created the following Helper - it takes a boolean and an anonymous object. If disabled is true, it adds the disabled attribute to the anonymous object (which is actually a Dictionary) with the value "disabled", otherwise it doesn't add the property at all.
public static RouteValueDictionary ConditionalDisable(
bool disabled,
object htmlAttributes = null)
{
var dictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (disabled)
dictionary.Add("disabled", "disabled");
return dictionary;
}
An example of it in action:
@Html.TextBoxFor(m => m.SomeProperty,
HtmlHelpers.ConditionalDisable(true, new { @class = "someClass"))
One huge advantage to this approach for me was that it works with virtually all of the MVC HtmlHelpers since they all have Overloads that accept a RouteValueDictionary instead of an anonymous object.
Caveats:
HtmlHelper.AnonymousObjectToHtmlAttributes() uses some fancy code ninja work to get things done. I'm not entirely sure how performant it is... but it's been sufficient for what I use it for. Your mileage may vary.
I don't especially like the name of it - but I couldn't come up with anything better. Renaming is easy.
I also don't love the usage syntax - but again I couldn't come up with anything better. It shouldn't be difficult to change. An extension method on object
is one idea... you'd end up with new { @class = "someClass" }.ConditionalDisable(true)
but then if you only want the disable attribute and don't have anything additional to add you end up with something gross like new {}.ConditionalDisable(true);
and you also end up with an extension method that shows up for all objects... which is probably not desirable.
@bool IsEditable=true;
@if (IsEditable)
{
Html.DropDownListFor(m => m, selectList);
}
else
{
Html.DropDownListFor(m => m, selectList, new { disabled = "disabled" })
}
Strongly typed verison:
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
string optionText, bool canEdit)
{
if (canEdit)
{
return html.DropDownListFor(expression, selectList, optionText);
}
return html.DropDownListFor(expression, selectList, optionText, new { disabled = "disabled" });
}
For completeness here is one that preservers all parameters and it would post select value to the server:
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes, bool enabled)
{
if (enabled)
{
return SelectExtensions.DropDownListFor<TModel, TProperty>(html, expression, selectList, htmlAttributes);
}
var htmlAttributesAsDict = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
htmlAttributesAsDict.Add("disabled", "disabled");
string selectClientId = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression));
htmlAttributesAsDict.Add("id", selectClientId + "_disabled");
var hiddenFieldMarkup = html.HiddenFor<TModel, TProperty>(expression);
var selectMarkup = SelectExtensions.DropDownListFor<TModel, TProperty>(html, expression, selectList, htmlAttributesAsDict);
return MvcHtmlString.Create(selectMarkup.ToString() + Environment.NewLine + hiddenFieldMarkup.ToString());
}
Usage example, disable drop down if there is only one item in list, that one value is still posted to server with correct client id:
@Html.DropDownListFor(m => m.SomeValue, Model.SomeList, new { @class = "some-class" }, Model.SomeList > 1)
You can do:
var dropDownEditDisable = new { disabled = "disabled" };
var dropDownEditEnable = new { };
object enableOrDisable = Model.CanEdit ?
(object)dropDownEditEnable : (object)dropDownEditDisable;
@Html.DropDownList("Quantity", new SelectList(...), enableOrDisable)
Html.DropDownListFor() can be long, so doing that, there is no need to repeat it.
I don't know if ASP.NET offers a more succinct special-case approach, but presumably you could do:
<%= Html.DropDownList("Quantity", new SelectList(...), Model.CanEdit? new{@class="quantity"} : new{@class="quantity", @disabled:"disabled"}) %>
精彩评论