I am developing a huge form with a lot of elements in it. The purpose of this form is editing one domain entity. Now, I finished developing the whole form and it just shows the bunch of text boxes, text areas, radio buttons and so on, allowing user to change values of entities. When user submits the form, it goes to server and server processes the data.
The form is based on Razor's HTML helpers:
(... code omitted for shortness ...)
<tr data-id="@row.Id" class="entity-row">
<td style="width: 475px">
<span class="num">
@Html.TextBoxFor(x => Model.Entities[i].Address, new { @class = "field field-normal", style = "text-align: right;width:475px", Id = "Address" })
</span>
</td>
<td>
<span class="num">
@Html.TextBoxFor(x => Model.Entities[i].SupplyDate, "{0:dd.MM.yyyy}", new { @class = "field field-normal field-date has-datepicker", style = "text-align: right;", Id = "SupplyDate" })
</span>
</td>
<td>
<span class="num">
@Html.TextBoxFor(x => Model.Entities[i].Quantity, new { @class = "field field-normal field-num", style = "text-align: right;", Id = "Quantity" })
</span>
</td>
</tr>
(... code omitted for shortness ...)
the next request that I got is to make user can just view the data and cannot edit this. So I should make the elements on the form disabled based on some condition. I inject special property to view model indicating ability to edit data:
public class MyEntitiesViewModel
{
(... code omitted for shortness ...)
public Entity[] Entities { get; set; }
public bool Editable { get; set; }
(... code omitted for shortness ...)
}
Ok, now I would crawl over each element in the view and set disabled
attribute for needed elements like this:
@{
object elementAttributes;
if (Model.Editable)
{
elementAttributes = new {@class = "field field-normal", style = "text-align: right;width:475px", Id = "Address"};
}
else
{
elementAttributes = new {@class = "field field-normal", style = "text-align: right;width:475px", Id = "Address", disabled = "disabled"};
}
Html.TextBoxFor(x => Model.Entities[i].Address, elementAttributes);
}
Or even better I could use IDictionary<string, object>
for elementAttributes
variable and get code shorter.
But I thought about introducing new helpers that accepts bool
last argument indicating is element disabled (disableness
of element).
Here is that I meant:
@Html.TextBoxFor(x => Model.Entities[i].Address, new { @class = "field field-normal", style = "text-align: right;width:475px", Id = "Address" }, !Model.Editable)
The last argument here !Model.Editable
indicate should be or should not be the element be disabled.
Here is a set of HTML helpers I introduced to project and they are looks like working successfully:
private static IDictionary<string, object> _GetDisabledAttributesDictionary(object attributes)
{
var htmlAttributesDictionary = attributes is IDictionary<string, object> ? (IDictionary<string, object>)attributes : HtmlHelper.AnonymousObjectToHtmlAttributes(attributes);
if (htmlAttributesDictionary.ContainsKey("disabled"))
{
htmlAttributesDictionary.Remove("disabled");
}
htmlAttributesDictionary.Add("disabled", "disabled");
return htmlAttributesDictionary;
}
private static MvcHtmlString _DisableHtmlElementIfNeeded<TModel, TProperty>(
Expression<Func<TModel, TProperty>> expression,
object value,
object attributes,
bool disabled,
Func<Expression<Func<TModel, TProperty>>, object, object, MvcHtmlString> renderWithObject,
Func<Expression<Func<TModel, TProperty>>, object, IDictionary<string, object>, MvcHtmlString> renderWithDictionary)
{
if (!disabled)
return renderWithObject(expression, value, attributes);
var htmlAttributesDictionary = _GetDisabledAttributesDictionary(attributes);
return renderWithDictionary(expression, value, htmlAttributesDictionary);
}
private static MvcHtmlString _DisableHtmlElementIfNeeded<TModel, TProperty>(
Expression<Func<TModel, TProperty>> expression,
string format,
IDictionary<string, object> attributes,
bool disabled,
Func<Expression<Func<TModel, TProperty>>, string, IDictionary<string, object>, MvcHtmlString> renderWithDictionary)
{
if (!disabled)
return renderWithDictionary(expression, format, attributes);
var htmlAttributesDictionary = _GetDisabledAttributesDictionary(attributes);
return renderWithDictionary(expression, format, htmlAttributesDictionary);
}
private static MvcHtmlString _DisableHtmlElementIfNeeded<TModel, TProperty>(
Expression<Func<TModel, TProperty>> expression,
string format,
object attributes,
bool disabled,
Func<Expression<Func<TModel, TProperty>>, string, object, MvcHtmlString> renderWithObject,
Func<Expression<Func<TModel, TProperty>>, string, IDictionary<string, object>, MvcHtmlString> renderWithDictionary)
{
if (!disabled)
return renderWithObject(expression, format, attributes);
var htmlAttributesDictionary = _GetDisabledAttributesDictionary(attributes);
return renderWithDictionary(expression, format, htmlAttributesDictionary);
}
private static MvcHtmlString _DisableHtmlElementIfNeeded<TModel, TProperty>(
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
object attributes,
bool disabled,
Func<Expression<Func<TModel, TProperty>>, IEnumerable<SelectListItem>, object, MvcHtmlString> renderWithObject,
Func<Expression<Func<TModel, TProperty>>, IEnumerable<SelectListItem>, IDictionary<string, object>, MvcHtmlString> renderWithDictionary)
{
if (!disabled)
return renderWithObject(expression, selectList, attributes);
var htmlAttributesDictionary = _GetDisabledAttributesDictionary(attributes);
return renderWithDictionary(expression, selectList, htmlAttributesDictionary);
}
private static MvcHtmlString _DisableHtmlElementIfNeeded<TModel, TProperty>(
Expression<Func<TModel, TProperty>> expression,
object attributes,
bool disabled,
Func<Expression<Func<TModel, TProperty>>, object, MvcHtmlString> renderWithObject,
Func<Expression<Func<TModel, TProperty>>, IDictionary<string, object>, MvcHtmlString> renderWithDictionary)
{
if (!disabled)
return renderWithObject(expression, attributes);
var htmlAttributesDictionary = _GetDisabledAttributesDictionary(attributes);
return renderWithDictionary(expression, htmlAttributesDictionary);
}
private static MvcHtmlString _DisableHtmlElementIfNeeded<TModel, TProperty>(
Expression<Func<TModel, TProperty>> expression,
IDictionary<string, object> attributes,
bool disabled,
Func<Expression<Func<TModel, TProperty>>, IDictionary<string, object>, MvcHtmlString> renderWithDictionary)
{
if (!disabled)
return renderWithDictionary(expression, attributes);
var htmlAttributesDictionary = _GetDisabledAttributesDictionary(attributes);
return renderWithDictionary(expression, htmlAttributesDictionary);
}
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes, bool disabled)
{
return _DisableHtmlElementIfNeeded(
expression,
htmlAttributes,
disabled,
helper.TextBoxFor);
}
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes, bool disabled)
{
return _DisableHtmlElementIfNeeded(
expression,
htmlAttributes,
disabled,
helper.TextBoxFor,
helper.TextBoxFor);
}
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string format, object htmlAttributes, bool disabled)
{
return _DisableHtmlElementIfNeeded(
expression,
format,
htmlAttributes,
disabled,
helper.TextBoxFor,
helper.TextBoxFor);
}
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string format, IDictionary<string, object> htmlAttributes, bool disabled)
{
return _DisableHtmlElementIfNeeded(
expression,
format,
htmlAttributes,
disabled,
helper.TextBoxFor);
}
public static MvcHtmlString RadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object value, object htmlAttributes, bool disabled)
{
return _DisableHtmlElementIfNeeded(
expression,
value,
htmlAttributes,
disabled,
helper.RadioButtonFor,
helper.RadioButtonFor);
}
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes, bool disabled)
{
return _DisableHtmlElementIfNeeded(
expression,
selectList,
htmlAttributes,
disabled,
helper.DropDownListFor,
helper.DropDownListFor);
}
As you could see they are acting like a proxy for genuine helpers just modifying incoming sets of attributes based on condition and passes attributes to base helpers.
So, although the code works, I found it very messy and here is points of possible improvements:
Repitable behavior like:
if (!disabled) return renderWithObject(expression, format, attributes); var htmlAttributesDictionary = _GetDisabledAttributesDictionary(attributes); return renderWithDictionary(expression, format, htmlAttributesDictionary);
The algorithm here is simple:
- if disabled then render with helper that accepts attributes in form of object
otherwise make sure the attributes have
disabled
attribute and render with helper that accepts attributes in form of dictionary.Configuration of base helpers in this lines:
return _DisableHtmlElementIfNeeded( expression, selectList, htmlAttributes, disabled, helper.DropDownListFor, helper.DropDownListFor);
is there any way to map helpers more elegantly? I thought of using attributes or any kind of Map
methods in a way mappers configured.
So any other recommendations and advices are very welcomed!