MVC3 Conditionally disable Html.TextBoxFor() - asp.net-mvc-3

I have a C# .Net web app. In that app I need to conditionally disable Html.TextBoxFor controls (also Html.DropDownListFor controls) based on who is logged into the system. I tried using
#Html.TextBoxFor(model => model.ProposalName, new { #ViewBag.IsDisabled })
Where #ViewBag.IsDisabled is set to either String.Empty or "disabled" in the Controller. However, this renders as IsDisabled='disabled' or IsDisabled="" so the control is not disabled. When I tried
#Html.TextBoxFor(model => model.ProposalName, new { #ViewBag.Disabled })
The control was always disabled even if ViewBag.Disabled contained no text. How can I conditionally disable the Html.TextBoxFor() controls?

Try
#Html.TextBoxFor(model => model.ProposalName, ViewBag.Disabled ? (object)new { disabled="disabled" } : new {})

The solution posted by #epignosisx works, but it may be a problem if you want to add some other attribute because you will have to add it it both objects (the one with disabled and the one now its empty).
Even worse if you have some other bool property because you will have four different objects, each one for each combination.
The best solution here (with a little more code) is to build an extension method for HtmlHelper to receive your boolean property as a parameter.
public static MvcHtmlString TextBoxDisabledFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, bool disabled, object htmlAttributes = null)
{
return TextBoxDisabledFor(htmlHelper, expression, disabled, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString TextBoxDisabledFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, bool disabled, IDictionary<string, object> htmlAttributes)
{
if (htmlAttributes == null)
htmlAttributes = new Dictionary<string, object>();
if (disabled)
htmlAttributes["disabled"] = "disabled";
return htmlHelper.TextBoxFor(expression, htmlAttributes);
}
Here there is another example

I had this same problem and decided to write my own HtmlHelper extension method.
public static MvcHtmlString Disable(this MvcHtmlString helper, bool disabled)
{
if (helper == null)
throw new ArgumentNullException();
if (disabled)
{
string html = helper.ToString();
int startIndex = html.IndexOf('>');
html = html.Insert(startIndex, " disabled=\"disabled\"");
return MvcHtmlString.Create(html);
}
return helper;
}
This will accept a boolean to indicate if the control should be disabled or not. It just appends disabled="disabled" just inside the first > it comes across in a string.
You can use it like below.
#Html.TextBoxFor(model => model.ProposalName).Disable(true)

Here is the method I use, which doesn't require extensions, and doesn't limit you to only one HTML attribute. It assumes there is a boolean property named "Disabled" in your model, but you could put whatever you wanted in there, as long as it evaluates to boolean for the ternary operator:
#Html.TextBoxFor(model => model.Whatever,
new Dictionary<string, object>() {
{ "size", "5" },
{ "class", "someclasshere" },
{ model.Disabled ? "disabled" : "data-notdisabled", "disabled" }
})
The limitation with the standard shortcut notation is that the name of the attribute cannot be dynamic. By creating a dictionary of the correct type, you can then make the attribute name dynamic, and you then pass that dictionary to the textbox as the dictionary of attributes. When the field is not to be disabled, it passes an attribute named "data-notdisabled" instead of one named "disabled", which the browser will then ignore.

Extending #James's answer, I wrote this HtmlHelper extension that updates/removes the disabled attribute if it's already present, or adds it if not:
public static MvcHtmlString Disable(this MvcHtmlString helper, bool disabled) {
string html = helper.ToString();
var regex = new Regex("(disabled(?:=\".*\")?)");
if (regex.IsMatch(html)) {
html = regex.Replace(html, disabled ? "disabled=\"disabled\"" : "", 1);
} else {
regex = new Regex(#"(\/?>)");
html = regex.Replace(html, disabled ? "disabled=\"disabled\"$1" : "$1", 1);
}
return MvcHtmlString.Create(html);
}
It also plays nicely with self-closing tags (like <input />).
Usage is the same:
#Html.TextBoxFor(model => model.PropertyName).Disable(true)
Tested on both #Html.DropDownListFor() and #Html.TextBoxFor().

Using a dictionary for the attributes and conditionally adding the disabled attrib.
<div>
#{
var attribs = new Dictionary<String, object>
{
{ "class", "form-control" }
,{ "style", "display:inline; width:100px;"}
};
if (Model.IsFieldDisabled)
{
attribs.Add("disabled", true);
}
}
#Html.TextBoxFor(m => m.MinValue, attribs)
to
#Html.TextBoxFor(m => m.MaxValue, attribs)
</div>
Adding this really only to add a more literal version of #ITFlyer's answer above. His method of conditionally changing the attribute name rather than the value is crafty and still allows for inlining. However, when I first looked at it I mistakenly thought that it wasn't conditional so I thought a more verbose version was still of value even if it largely just redirects to that answer.
An additional benefit is that a separate variable also allows for reuse over multiple controls (which I needed in my case

Related

How can I set the width of a textbox in asp.net mvc 3?

How can I set the width of a textbox in asp.net mvc 3? this does not work:
#Html.TextBox("Name", new { style = "width:500px" })
try this, this should work..
#Html.TextBox("Name", new { #class= "mySize" })
.mySize
{
width: 500px;
}
also in your code,try adding a semicolon and see if that works, something like this
#Html.TextBox("Name", new { style = "width:500px;" })
I'm surprised the answer given by #Yasser works. Since these extension methods are overloaded with functions that can take several anonymous objects, it's easy inadvertently use the wrong one.
From MSDN, it looks like you're calling this method:
public static MvcHtmlString TextBox(
this HtmlHelper htmlHelper,
string name,
Object value)
where value is:
The value of the text input element. If this value is null, the value of the element is retrieved from the ViewDataDictionary object. If no value exists there, the value is retrieved from the ModelStateDictionary object.
So value is used to populate the input. Instead, I think you want this extension:
public static MvcHtmlString TextBox(
this HtmlHelper htmlHelper,
string name,
Object value,
Object htmlAttributes)
Then, use it like this (pass null for value) to add inline styles to the markup:
#Html.TextBox("Name", null, new { style = "width:500px;" })
Or:
#Html.TextBox("Name", null, new { #class = "myStyle" })

How do I call the placeholder watermark in a class?

I do this in an EditorTemplate View:
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
new { placeholder = ViewData.ModelMetadata.Watermark })
I now want to do something in a class file in which I am using TagBuilder and MergeAttribute. I use code, for example, to display an image for "required", and tried to modify it to do the same thing with placeholder. But I can't do ViewData. So this isn't working:
public static MvcHtmlString PlaceholderFor<T, TValue>
(this HtmlHelper<T> html, Expression<Func<T, TValue>> expression)
{
var placeholder = new ModelMetadata([CAN'T FIGURE OUT IN HERE]);
placeholder.MergeAttribute([ALSO CAN'T FIGURE OUT IN HERE]);
return MvcHtmlString.Create(placeholder.ToString());
}
I don't even know if the above would be appropriate so I could do something like this:
input.MergeAttribute += html.PlaceholderFor(expression);
or whether that is also the incorrect approach.
For context I am building an "input" using:
input.InnerHtml += html.EditorFor(expression);
Can any one provide a code example that would work in this context? Sorry if I am not explaining it correctly.
Your question is not very clear about what you are trying exactly to achieve. But if you want to get the contents generated by the TextBox helper inside another custom helper you could simply call this TextBox helper:
public static IHtmlString MyHelperFor<T, TValue>(
this HtmlHelper<T> html,
Expression<Func<T, TValue>> expression,
object htmlAttributes
)
{
var div = new TagBuilder("div");
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var attributes = new RouteValueDictionary(htmlAttributes);
attributes["placeholder"] = metadata.Watermark;
div.InnerHtml = html.TextBoxFor(expression, attributes).ToHtmlString();
return new HtmlString(div.ToString());
}
and then:
#Html.MyHelperFor(x => x.Test, new { #class = "foo" })

How can I add a hash fragment to T4MVC route dictionary ActionResult?

I have an extension method that returns an ActionResult (simplified for demonstration purposes):
public static ActionResult GetTestActionResult(this HtmlHelper htmlHelper, int productId)
{
return MVC.Products.Details(productId);
}
I'm using this in an Html.ActionLink:
#Html.ActionLink("Product Details", Html.GetTestActionResult(Model.ProductId), new { #class = "button blue" });
I'm using a custom jQuery plugin for tabs, that uses these hash fragments for navigation. I want to add the tab which I want to open, by tagging the hash fragment onto the end of the URL.
Html.ActionLink does have an overload for the Fragment, namely:
public static MvcHtmlString ActionLink(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName,
string protocol,
string hostName,
string fragment,
Object routeValues,
Object htmlAttributes
)
However, that is full of nasty magic strings, which T4MVC is designed to remove. Is there anyway to add the fragment to the route dictionary in my static extension method (GetTestActionResult)?
Something like:
return MVC.Products.Details(productId).AddRouteValue(String.Empty, "#tab-similar-products");
I am aware that there are two similar questions and answers on SO, but they don't quite provide me with what I am looking for. I need to wrap the fragment into the ActionResult BEFORE passing it back to the view:
Including hash values in ASP.NET MVC URL routes
Create a T4MVC ActionLink with url fragment
UPDATE:
Using David Ebbo's fix below, I made the following changes. A bit hacky, but it works:
First I altered my internal function that returns an ActionResult so that it would also add the fragment as a route value (not ideal but works):
return MVC.Products.Details(productId).AddRouteValue("tab", "#tab-similar-products");
Then in the view it copies that fragment value out of the route dictionary, then removes that route value for completeness.
// get my ActionResult with the tab fragment tagged on as a route value
var actionResult = Html.GetTestActionResult(item.Key, Model.ClaimId);
// get the tab fragment value
var tabRoute = actionResult.GetRouteValueDictionary().FirstOrDefault(r => r.Key == "tab").Value ?? "none";
// remove the route value, otherwise it will get tagged to the querystring
actionResult.GetRouteValueDictionary().Remove("tab");
// display
#Html.ActionLink("Product Details", Html.GetTestActionResult(Model.ProductId), new { #class = "button blue" }, fragment: tabRoute.ToString());
I'm sure there is a prettier way to return the fragment with the ActionResult, but for the moment this works. Thanks David.
T4MVC needs new overloads to handle this. In T4MVC.tt, try changing:
public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, object htmlAttributes) {
return ActionLink(htmlHelper, linkText, result, new RouteValueDictionary(htmlAttributes));
}
public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, IDictionary<string, object> htmlAttributes) {
return htmlHelper.RouteLink(linkText, result.GetRouteValueDictionary(), htmlAttributes);
}
to
public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, object htmlAttributes, string protocol = null, string hostName = null, string fragment = null) {
return ActionLink(htmlHelper, linkText, result, new RouteValueDictionary(htmlAttributes), protocol, hostName, fragment);
}
public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, IDictionary<string, object> htmlAttributes, string protocol = null, string hostName = null, string fragment = null) {
return htmlHelper.RouteLink(linkText, null, protocol, hostName, fragment, result.GetRouteValueDictionary(), htmlAttributes);
}
you'll then be able to write something like:
#Html.ActionLink("Product Details", Html.GetTestActionResult(Model.ProductId), new { #class = "button blue" }, fragment: "#tab-similar-products")
Let me know if that works, and I'll try to get it added to the main template.

ASP.NET MVC - Extending TextBoxFor without re-writing the method

Is there any possible way to extend the basic html helpers (TextBoxFor, TextAreaFor, etc) using extension methods on their output, instead of just re-writing the entire methods completely? For instance, adding in ...
#Html.TextBoxFor( model => model.Name ).Identity("idName")
I know I can achieve this using the following, already..
#Html.TextBoxFor( model => model.Name, new { #id = "idName" })
But that gets clunky and frustrating to manage when you have to start adding a lot of properties. Is there any way to add extensions to these inherently without just passing in htmlAttributes for every little detail?
As #AaronShockley says, because TextBoxFor() returns an MvcHtmlString, your only option for developing a 'fluid API' style of amending the output would be to operate on the MvcHtmlStrings returned by the helper methods. A slightly different way of doing this which I think approaches what you're after would be to use a 'property builder' object, like this:
public class MvcInputBuilder
{
public int Id { get; set; }
public string Class { get; set; }
}
...and to set up extension methods like this:
public static MvcHtmlString TextBoxFor<TModel, TProp>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProp>> expression,
params Action<MvcInputBuilder>[] propertySetters)
{
MvcInputBuilder builder = new MvcInputBuilder();
foreach (var propertySetter in propertySetters)
{
propertySetter.Invoke(builder);
}
var properties = new RouteValueDictionary(builder)
.Select(kvp => kvp)
.Where(kvp => kvp.Value != null)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
return htmlHelper.TextBoxFor(expression, properties);
}
You can then do stuff like this in your View:
#this.Html.TextBoxFor(
model => model.Name,
p => p.Id = 7,
p => p.Class = "my-class")
This gives you strong typing and intellisense for input properties, which you could customise for each extension method by adding properties to an appropriate MvcInputBuilder subclass.
All of the basic html helpers return an object of type System.Web.Mvc.MvcHtmlString. You can set up extension methods for that class. Here is an example:
public static class MvcHtmlStringExtensions
{
public static MvcHtmlString If(this MvcHtmlString value, bool check)
{
if (check)
{
return value;
}
return null;
}
public static MvcHtmlString Else(this MvcHtmlString value, MvcHtmlString alternate)
{
if (value == null)
{
return alternate;
}
return value;
}
}
Then you can use these in a view like:
#Html.TextBoxFor(model => model.Name)
.If(Model.Name.StartsWith("A"))
.Else(Html.TextBoxFor(model => model.LastName)
To make extension methods that modify attributes on the rendered HTML tag, you'll have to convert the result to a string, and find and replace the value you're looking for.
using System.Text.RegularExpressions;
public static MvcHtmlString Identity(this MvcHtmlString value, string id)
{
string input = value.ToString();
string pattern = #"(?<=\bid=")[^"]*";
string newValue = Regex.Replace(input, pattern, id);
return new MvcHtmlString(newValue);
}
public static MvcHtmlString Name(this MvcHtmlString value, string id)
{
string input = value.ToString();
string pattern = #"(?<=\bname=")[^"]*";
string newValue = Regex.Replace(input, pattern, id);
return new MvcHtmlString(newValue);
}
The id and name attributes are always added by the html helpers, but if you want to work with attributes that may not be there (and you'll have to add them instead of just replacing them), you'll need to modify the code.

Not show label for an empty property with LabelFor?

I'm using MVC3 w/ Razor and I have a model that has quite a few properties that are sometimes empty.
Other than a custom htmlHelper, or using an if/then in the view for every LabelFor/DisplayFor pair, is there a way to not display the LabelFor/DisplayFor for a property that is empty or null?
No.... You need the above mentioned solutions or additional view models. Sorry!
I created my own helper: LabelAndDisplayFor that checks for null/empty and then chooses to display the field.
public static MvcHtmlString LabelAndDisplayFor<tModel, tValue>(this HtmlHelper<tModel> html, System.Linq.Expressions.Expression<Func<tModel, tValue>> field,
bool hideIfEmpty = false) {
if (hideIfEmpty) {
var v = field.Compile()(html.ViewData.Model);
if (v == null || string.IsNullOrWhiteSpace(v.ToString())) {
return MvcHtmlString.Empty;
}
}
StringBuilder result = new StringBuilder();
result.Append("<div class='display-line'>");
result.Append("<div class='display-label'>");
result.Append(html.LabelFor(field));
result.Append("</div>");
// ... etc ...

Resources