23

How can I change this DropDownList declaration so that the disabled attribute is enable/disabled conditionally?

<%= 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) %>
Community
  • 1
  • 1
Todd Smith
  • 17,084
  • 11
  • 59
  • 78
  • Hi, I want to disable/enable the dropdown for specific pages only based on the value,i am passing it through model. i tried passing true / false to the disabled but it's not working .can you help in this – ravithejag Sep 12 '14 at 05:58

8 Answers8

34

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.

AlexB
  • 7,302
  • 12
  • 56
  • 74
erasmus
  • 925
  • 2
  • 9
  • 16
  • 1
    Probably this is a simplest solution, but code is so unreadable and ugly, it hurts my eyes. And it gets even uglier if you have some other attribute parameters. So IMO it's not best answer. – Antonio Bakula Mar 21 '18 at 22:31
21

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.

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 3
    You cannot unit test views due to the nature of the WebForms engine. You could though test extension methods. This way your view will no longer contain conditional logic worth unit testing. By testing the extension method you make sure that all views using it will behave as you expect and have the exact markup depending on the `CanEdit` property on your model. – Darin Dimitrov Jan 18 '10 at 22:54
  • And how will this code look like if I need helper method with this signature? `public static string DropDownList(this HtmlHelper html, string name, SelectList values, object htmlAttributes, bool canEdit)` – Pavel Surmenok Mar 12 '11 at 12:07
  • @Pavel Surmenok, you could use the `DropDownList` overload which takes a `RouteValueDictionary` allowing you to merge html attributes together. – Darin Dimitrov Mar 12 '11 at 12:12
11

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.

Mir
  • 2,429
  • 1
  • 29
  • 34
  • 1
    This is the only answer that takes under consideration possibility to pass more htmlAttributes than one and works for all HtmlHelpers. Thumb up. – Michal Krawiec Jun 29 '16 at 12:42
6
@bool IsEditable=true;

@if (IsEditable)
{
    Html.DropDownListFor(m => m, selectList);
}
else
{
    Html.DropDownListFor(m => m, selectList, new { disabled = "disabled" })
}
Horizon_Net
  • 5,959
  • 4
  • 31
  • 34
SumanReddy
  • 61
  • 1
  • 4
0

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" });
    }
proggrock
  • 3,239
  • 6
  • 36
  • 51
0

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)
Antonio Bakula
  • 20,445
  • 6
  • 75
  • 102
0

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.

fsbflavio
  • 664
  • 10
  • 22
-4

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"}) %>
bobince
  • 528,062
  • 107
  • 651
  • 834
  • 3
    No, you can't do that. You get a compile-time error: "Type of conditional expression cannot be determined because there is no implicit conversion between 'AnonymousType#1' and 'AnonymousType#2'" – Alex York Sep 11 '12 at 11:55
  • You would need to add casting to anonymousTypes 'as object' than it'll work – Michal Krawiec Jun 29 '16 at 12:35