Reputation: 11895
Is there a way to pass a view model to an @helper function?
I have a custom editor that displays Yes/No/Maybe radio buttons. The code for this editor shown below. Since it requires a custom function plus 3 lines of code to display each radio button, I'd like to encapsulate that code that creates each radio button and its associated label in a global @helper function, but I can't figure out how to move the RadioButtonFor into the global helper function.
Bob
This is the existing code:
@model System.String
@functions {
// Returns an anonymous object that specifies the radio button HTML options
private object HTMLOptions(string id, bool @checked) {
if (@checked) {
return new { id, @checked = "checked" };
} else {
return new { id };
}
}
}
@{
string value = Model + "";
string id;
}
<div class="option-list">
@{id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioYes");}
@Html.RadioButtonFor(q => q, "true", HTMLOptions(id, value == "true"))
@Html.LabelFor(q => q, "Yes", new {@for = id})
<br />
@{id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioNo");}
@Html.RadioButtonFor(q => q, "false", HTMLOptions(id, value == "false"))
@Html.LabelFor(q => q, "No", new {@for = id})
<br />
@{id = ViewData.TemplateInfo.GetFullHtmlFieldId("radioMaybe");}
@Html.RadioButtonFor(q => q, "maybe", HTMLOptions(id, value == "maybe"))
@Html.LabelFor(q => q, "Maybe", new {@for = id})
<br />
</div>
I want to be able to write a general-purpose helper that encapsulates RadioButtonFor and LabelFor, plus my additional code to include the "checked" attribute, in one call, like this:
@model something
<div class="option-list">
@MyRadioButtonAndLabelFor(q => something.x, "Yes", "true") @* (model, label, value) *@
@MyRadioButtonAndLabelFor(q => something.x, "No", "false")
@MyRadioButtonAndLabelFor(q => something.x, "Maybe", "maybe")
</div>
Upvotes: 0
Views: 2115
Reputation: 1038830
Try like this:
@model System.String
@functions {
private object HtmlAttributes(string id, string model, string value) {
if (model == value) {
return new { id, @checked = "checked" };
}
return new { id };
}
}
@helper Radios(HtmlHelper<string> html) {
var model = html.ViewData.Model;
@html.RadioButtonFor(q => q, "true", HtmlAttributes("yes", model, "true"))
@Html.LabelFor(q => q, "Yes", new { @for = "yes" })
<br/>
@html.RadioButtonFor(q => q, "false", HtmlAttributes("no", model, "false"))
@Html.LabelFor(q => q, "No", new { @for = "no" })
<br/>
@html.RadioButtonFor(q => q, "maybe", HtmlAttributes("maybe", model, "maybe"))
@Html.LabelFor(q => q, "Maybe", new { @for = "maybe" })
<br/>
}
<div class="option-list">
@Radios(Html)
</div>
UPDATE:
It seems that I have misread your question. After your update I understand better what you are trying to achieve. In order to achieve it you could write a custom Html helper:
using System;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
public static class HtmlExtensions
{
public static IHtmlString MyRadioButtonAndLabelFor<TModel, TValue>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TValue>> expression,
string label,
object value
)
{
var name = ExpressionHelper.GetExpressionText(expression);
var id = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(
name + value
);
var sb = new StringBuilder();
sb.Append(htmlHelper.RadioButtonFor(expression, value, new { id = id }));
var labelTag = new TagBuilder("label");
labelTag.SetInnerText(label);
labelTag.Attributes["for"] = id;
sb.Append(labelTag.ToString());
return new HtmlString(sb.ToString());
}
}
and then you could have a view model:
public class MyViewModel
{
public string Value { get; set; }
}
a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel
{
Value = "false"
});
}
}
and a corresponding view:
@model MyViewModel
<div class="option-list">
@Html.MyRadioButtonAndLabelFor(q => q.Value, "Yes", "true")
@Html.MyRadioButtonAndLabelFor(q => q.Value, "No", "false")
@Html.MyRadioButtonAndLabelFor(q => q.Value, "Maybe", "maybe")
</div>
Remark: In order for the MyRadioButtonAndLabelFor
custom extension method to be brought in scope you need to add the namespace in which its containing class (HtmlExtensions
) has been declared either in the namespaces tag in your ~/Views/web.config
(not ~/web.config
) or within the view itself by adding a @using ...
directive.
You could also define the helper inline inside your Razor view if you prefer:
@functions {
public IHtmlString MyRadioButtonAndLabelFor<TModel, TValue>(
HtmlHelper<TModel> htmlHelper,
System.Linq.Expressions.Expression<Func<TModel, TValue>> expression,
string label,
object value
)
{
var name = ExpressionHelper.GetExpressionText(expression);
var id = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(name + value);
var sb = new StringBuilder();
sb.Append(htmlHelper.RadioButtonFor(expression, value, new { id = id }));
var labelTag = new TagBuilder("label");
labelTag.SetInnerText(label);
labelTag.Attributes["for"] = id;
sb.Append(labelTag.ToString());
return new HtmlString(sb.ToString());
}
}
and then use it like this:
<div class="option-list">
@MyRadioButtonAndLabelFor(Html, q => q.Value, "Yes", "true")
@MyRadioButtonAndLabelFor(Html, q => q.Value, "No", "false")
@MyRadioButtonAndLabelFor(Html, q => q.Value, "Maybe", "maybe")
</div>
As far as Razor helpers are concerned (@helper
definition), well, they cannot be generic, therefore you will have troubles declaring such a construct.
Upvotes: 2