I’ve got a complicated form, which needs to be used by both javascript and non-javascript users. My application is .NET MVC 3.
The form has plenty of fields, which have to be filled in if they answer a particular way to a previous question. With javascript enabled, I’m able to show or hide the relevant question based on a user interaction. With no javascript I would like to add more detail to the question label itself because by default all of the questions are shown (much like filling in a paper version of the form).
Ideally I would like the label to be something like:
<label><span class="non-js">If you were a smoker, when</span><span class="js">When</span> did you stop smoking?</label>
This way I can switch off the relevant text in CSS (I’m hoping a screenreader can cope with this).
So javascript users get:
When did you stop smoking?
and non-javascript users get:
If you were a smoker, when did you stop smoking?
My question is how would I go about doing this, as LabelFor helpers don’t allow html in the string.
Update
Sorry, I forgot one crucial bit of info, in that my label is currently being populated by a [Display(Name = “When did you stop smoking?”)] annotation in the model. Idelly I would like to keep this here and have something like:
[Display(JS = "When", NonJS = "If you were a smoker, when", Both="did you stop smoking?")]
Is this possible?
Update 2
OK here’s what I’ve got so far. This is my attribute:
public class MultipleDisplayNameAttribute : Attribute, IMetadataAware
{
public string CustomerDisplayName { get; set; }
public string CustomerJSAlternative { get; set; }
public string CustomerNonJSAlternative { get; set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
string jsPart;
string nonJsPart;
jsPart = (CustomerJSAlternative != null) ? String.Format("<span class='spnJs'>{0}</span>", CustomerJSAlternative) : "";
nonJsPart = (CustomerNonJSAlternative != null) ? String.Format("<span class='spnNonJs'>{0}</span>", CustomerNonJSAlternative) : "";
metadata.DisplayName = String.Format("{0}{1}{2}", jsPart, nonJsPart, CustomerDisplayName);
}
}
Unfortunately I’m now stuck as this is displayed unencoded on the screen. i.e. it is actually coming out like this on screen:
<span class="non-js">If you were a smoker, when</span><span class="js">When</span> did you stop smoking?
Is there any way to change the displayName metadata property to cope with this?
Update 3 and solution
With the help of the answers and http://weblogs.asp.net/imranbaloch/archive/2010/07/03/asp-net-mvc-labelfor-helper-with-htmlattributes.aspx I managed to get the solutions I was after:
My attribute:
public class MultipleDisplayNameAttribute : Attribute, IMetadataAware
{
public string CustomerDisplayName { get; set; }
public string CustomerJSAlternative { get; set; }
public string CustomerNonJSAlternative { get; set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["JS"] = (CustomerJSAlternative != null) ? String.Format("<span class='spnJs'>{0}</span>", CustomerJSAlternative) : "";
metadata.AdditionalValues["NoJS"] = (CustomerNonJSAlternative != null) ? String.Format("<span class='spnNonJs'>{0}</span>", CustomerNonJSAlternative) : "";
metadata.DisplayName = CustomerDisplayName;
}
}
My helper:
public static MvcHtmlString JsAndNonJsCheckFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
HtmlString jsLabel = new HtmlString("");
HtmlString noJsLabel = new HtmlString("");
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
if (String.IsNullOrEmpty(labelText))
return MvcHtmlString.Empty;
if(metadata.AdditionalValues.ContainsKey("JS"))
jsLabel = new HtmlString((string)metadata.AdditionalValues["JS"]);
if (metadata.AdditionalValues.ContainsKey("NoJS"))
noJsLabel = new HtmlString((string)metadata.AdditionalValues["NoJS"]);
TagBuilder tagBuilder = new TagBuilder("label");
tagBuilder.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
tagBuilder.InnerHtml = String.Format("{0}{1}{2}", jsLabel, noJsLabel, labelText);
return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal));
}
Job done (eventually!) Thanks for everyone’s help!
Your current problem is just how
LabelForworks. This is the decompiled source code forLabelFor(from MVC2. The actual source is probably nicer, and is freely available for download):You could basically copy this code, change the method name, and change the second last line of code to be:
And it you will have a working solution which doesn’t escape your HTML.