MVC 3: Conditionally Adding the Disabled Attribute with the HtmlHelpers

asp.net Mvcasp.net Mvc-3Html Helper

asp.net Mvc Problem Overview


I have an ASP.Net MVC 3 web application and I am adding a check box to a view page using the HtmlHelper class, like this...

@Html.CheckBox("CheckBox1", true, new { @class = "Class1" })

What I want to do is conditionally add the disabled attribute based on a view state property. Basically the following would be ideal...

@Html.CheckBox("CheckBox1", true, new { @class = "Class1", @disabled = Model.ReadOnly })

Unfortunately, due to the nature of the disabled attribute, this will not work because any value assigned to the disabled attribute (even "false") will be translated as true.

I have already thought of a few solutions to get round this problem, so the question is not how can I do this. But rather, is there a simple way like the desired method above? or do I have to resort to one of the following?..

What I know I could do...

  1. Create an if/else statement and write to different Html.CheckBox lines (not great for readability - and possible with throw a mark-up warning - not sure)

  2. Skip the HtmlHelper class and hand write the tag allowing for better conditionally attributes (keeps the code shorter, but adds inconsistency)

  3. Create a custom helper that takes a "disabled" parameter (the cleanest solution, but requires undesired extra methods - probably the best option so far though)

asp.net Mvc Solutions


Solution 1 - asp.net Mvc

Define this somewhere in your view/helpers

@functions {
 object getHtmlAttributes (bool ReadOnly, string CssClass) 
 {
     if (ReadOnly) {
         return new { @class = CssClass, @readonly = "readonly" };
     }
     return new { @class = CssClass };
 }
}

Then use :

@Html.TextBox("name", "value", @getHtmlAttributes(Model.ReadOnly, "test"))

Solution 2 - asp.net Mvc

Here's my answer from this similar question: https://stackoverflow.com/a/13922813/495000


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 object... which is probably not desirable.

Solution 3 - asp.net Mvc

If you want more terse syntax without requiring a helper function, you could use a ternary statement when defining the dictionary used for the html attributes of the @HTML.Checkbox helper...

@Html.CheckBox("CheckBox1", true, Model.ReadOnly 
       ? new { @class = "Class1", @disabled = Model.ReadOnly } 
       : null)

In this case is Model.ReadOnly is false, null gets passed as the dictionary of html attributes.

Solution 4 - asp.net Mvc

Performing the addition of the disabled attribute client side works for me. Note you should check which fields are allowed to be edited server side, but that is true for where the disabled attribute is declared decoratively also.

In this example I have disabled all childeren of a form using jQuery.

    if (Model.CanEdit)
    {
        <script type="text/javascript">

            $(document).ready(function() {

                $('#editForm *').attr('disabled', true);
            });

        </script>
    }

Solution 5 - asp.net Mvc

What do you think about my simple solution? It works easily with both possible HtmlAttributes types:

  • Dictionary<string, object>
  • Anonymous Object:

First add the following simple extension class to your project:

public static class HtmlAttributesExtensions
{
    public static IDictionary<string, object> AddHtmlAttrItem(this object obj, string name, object value, bool condition)
    {
        var items= !condition ? new RouteValueDictionary(obj) : new RouteValueDictionary(obj) {{name, value}};
        return UnderlineToDashInDictionaryKeys(items);
    }
    public static IDictionary<string, object> AddHtmlAttrItem(this IDictionary<string, object> dictSource, string name, object value, bool condition)
    {
        if (!condition)
            return dictSource;

        dictSource.Add(name, value);
        return UnderlineToDashInDictionaryKeys(dictSource);
    }
    private static IDictionary<string, object> UnderlineToDashInDictionaryKeys(IDictionary<string,object> items)
    {
        var newItems = new RouteValueDictionary();
        foreach (var item in items)
        {
            newItems.Add(item.Key.Replace("_", "-"), item.Value);
        }
        return newItems;
    }
}

Now in View:

Example1 (HtmlAttributes type as Anonymous Object)

@{
  var hasDisabled=true; 
}

@Html.CheckBox("CheckBox1"
              , true
              , new { @class = "Class1"}
               .AddHtmlAttrItem("disabled", "disabled", hasDisabled))
.

Example 2 (HtmlAttributes type as Dictionary<string, object>)

@Html.CheckBox("CheckBox1"
              , true
              , new Dictionary<string, object> { { "class", "Class1" }
               .AddHtmlAttrItem("disabled", "disabled", hasDisabled))
.

> Now just change the hasDisabled value to true or false!


Example3 (Multiple conditional properties)

@{
  var hasDisabled=true;
  var hasMax=false ;
  var hasMin=true ;
}

@Html.CheckBox("CheckBox1"
              , true
              , new { @class = "Class1"}
               .AddHtmlAttrItem("disabled", "disabled", hasDisabled)
               .AddHtmlAttrItem("data-max", "100", hasMax)
               .AddHtmlAttrItem("data-min", "50", hasMin))
.

Solution 6 - asp.net Mvc

@Html.TextBoxFor(m => m.FieldName, Html.FixBoolAttributes(new {
	@class = "myClass",
	@readonly = myFlag	
}))


public static class BooleanAttributeFix
{
	/// <summary>
	/// Normalises HTML boolean attributes so that readonly=true becomes readonly="readonly" and
	/// readonly=false removes the attribute completely.
	/// </summary>
	/// <param name="htmlHelper"></param>
	/// <param name="htmlAttributes"></param>
	/// <returns></returns>
	public static RouteValueDictionary FixBoolAttributes(this HtmlHelper htmlHelper, object htmlAttributes)
	{
		var attrs = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);

		foreach(var attrName in new[] { "disabled", "readonly" })
		{
			object value;
			if(attrs.TryGetValue(attrName, out value))
			{
				if(isTruthy(value))
				{
					// Change from readonly="true" to readonly="readonly"
					attrs[attrName] = attrName;	
				}
				else
				{
					// Remove attribute entirely
					attrs.Remove(attrName);	
				}
			}
		}
		return attrs;
	}

	/// <summary>
	/// Apply similar loose rules like javascript does for whether a value is true or not.
	/// e.g. 1 = true, non-empty string = true and so on.
	/// </summary>
	/// <param name="val"></param>
	/// <returns></returns>
	private static bool isTruthy(object val)
	{	
		if(val == null)
			return false;

		if(val is string)
		{
			return !String.IsNullOrEmpty((string)val);
		}

		Type t = val.GetType();

		if(t.IsValueType && Nullable.GetUnderlyingType(t) == null)
		{
			// If a non-nullable value type such as int we want to check for the
			// default value e.g. 0.
			object defaultValue = Activator.CreateInstance(t);

			// Use .Equals to compare the values rather than == as we want to compare
			// the values rather than the boxing objects.
			// See http://stackoverflow.com/questions/6205029/comparing-boxed-value-types
			return !val.Equals(defaultValue);
		}

		return true;
	}
}

Solution 7 - asp.net Mvc

I liked @gb2d answer, here it is in JS using getElementsByClassName taken from here https://stackoverflow.com/questions/2565909/getelementbyclass-setattribute-doesnt-work

<script type="text/javascript">
    var list, index;
    list = document.getElementsByClassName("form-control");
    for (index = 0; index < list.length; ++index) {
        list[index].setAttribute('disabled', true);
    }
</script>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmusefanView Question on Stackoverflow
Solution 1 - asp.net MvcBigMikeView Answer on Stackoverflow
Solution 2 - asp.net MvcMirView Answer on Stackoverflow
Solution 3 - asp.net MvcAndy BrudtkuhlView Answer on Stackoverflow
Solution 4 - asp.net Mvcgb2dView Answer on Stackoverflow
Solution 5 - asp.net MvcRamin BateniView Answer on Stackoverflow
Solution 6 - asp.net MvcAlan SingfieldView Answer on Stackoverflow
Solution 7 - asp.net Mvcsam80eView Answer on Stackoverflow