Monday, September 19, 2016

Adding Attributes on option tag of dropdown list box through reflection and Lambda Expression

Using Html Helper and Lambda expression for creating dropdown list and applying attributes on options tag of dropdown list


Traditional way of doing it.

<select name="listbox" id="listbox">
    @foreach (var item in Model)
           {

                   <option value="@item.UserRoleId" data-name="@item.Name" data-class="@item.ClassName">
                      @item.UserRole 
                   </option>                  
           }
    </select>

Instead of iterating through and attaching attributes inside foreach look
We can do like below

@Html.DropDownList(x => x.ReportName, Model.AvailableReports, x => x.TextField, x => x.NameField, "-- Select --", 
new Dictionary<string, Expression<Func<ReportSummaryModel, object>>>{ {"data-name", x => x.ReportActualName}, {"data-class", x => x.ReportClassName}})

It will output the select list with option tag and attribute on each option tag

Output


var s = new string();
<select>
 <option>--Select--</option>
 <option value="1" data-name="NameA" data-class="classA">A</option>                  
    <option value="2" data-name="NameB" data-class="classB">B</option>                  
    <option value="3" data-name="NameC" data-class="classC">C</option>                  
    <option value="4" data-name="NameD" data-class="classD">D</option>                  
</select>

Generic Tempelate
DropDownList<TPageModel, TSelectListModel, TProperty>
  • TPageModel : Model of Page
  • TSelectListModel : Model of Select List Item
  • TProperty: Will be object so can use any type
The parameters of custom dropdown list

public static MvcHtmlString DropDownList<TPageModel, TSelectListModel, TProperty>
(this HtmlHelper<TPageModel> htmlHelper, 
  Expression<Func<TSelectListModel, TProperty>> expression, IEnumerable<TSelectListModel> selectList, 
Expression<Func<TSelectListModel, TProperty>> textField, 
  Expression<Func<TSelectListModel, TProperty>> valueField, string optionLabel, IDictionary<string, 
Expression<Func<TSelectListModel, TProperty>>> optionsAttributes)


Parameters Descriptions
Expression<Func<TSelectListModel, TProperty>> expression
This is to provide the input property for which we are showing dropdown (Rename if want)


IEnumerable<TSelectListModel> selectList
List of class of which text and value field you will be using


Expression<Func<TSelectListModel, TProperty>> textField
Provide the text field property from the TSelectListModel


Expression<Func<TSelectListModel, TProperty>> valueField
Provide the value field property from the TSelectListModel


string optionLabel
Default Option Text for dropdown list box


IDictionary<string, Expression<Func<TSelectListModel, TProperty>>> optionsAttributes
This is to provide the attributes to be appear on option tag from the SelectListModel


public static MvcHtmlString DropDownList<TPageModel, TSelectListModel, TProperty>(this HtmlHelper<TPageModel> htmlHelper, Expression<Func<TSelectListModel, TProperty>> expression, IEnumerable<TSelectListModel> selectList, Expression<Func<TSelectListModel, TProperty>> textField, Expression<Func<TSelectListModel, TProperty>> valueField, string optionLabel, IDictionary<string, Expression<Func<TSelectListModel, TProperty>>> optionsAttributes)
        {
            string name = ExpressionHelper.GetExpressionText(expression);
            string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            string defaultValue = string.Empty;
            ModelState modelState;
            if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
            {
                if (modelState.Value != null)
                {
                    defaultValue = (string)modelState.Value.ConvertTo(typeof(string), null /* culture */);
                }
            }
            var listItemBuilder = new StringBuilder();
            // Make optionLabel the first item that gets rendered.
            if (optionLabel != null)
            {
                listItemBuilder.AppendLine(ListItemToOption(optionLabel, string.Empty, false, null));
            }
            /* Loop through each options
             * Convert each ListItem to an <option> tag */
            foreach (var listItem in selectList)
            {
                string text = listItem.GetType()
                                      .GetProperties()
                                      .Single(p => p.Name.Equals(ClassHelper.PropertyName(textField)))
                                      .GetValue(listItem, null)
                                      .ToString();
                string value = listItem.GetType()
                                       .GetProperties()
                                       .Single(p => p.Name.Equals(ClassHelper.PropertyName(valueField)))
                                       .GetValue(listItem, null)
                                       .ToString();
                bool isSelected = value.Equals(defaultValue);
                var htmlAttributes = new Dictionary<string, string>();
                foreach (var option in optionsAttributes)
                {
                    string propertyName = ClassHelper.PropertyName(option.Value);
                    htmlAttributes.Add(option.Key, listItem.GetType()
                                                             .GetProperties()
                                                             .Single(p => p.Name.Equals(propertyName))
                                                             .GetValue(listItem, null)
                                                             .ToString());
                }
                listItemBuilder.AppendLine(ListItemToOption(text, value, isSelected, htmlAttributes));
            }
            var tagBuilder = new TagBuilder("select")
            {
                InnerHtml = listItemBuilder.ToString()
            };
            tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
            return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
        }

Private method copied from mvc built in helper to generate option attribute

 internal static string ListItemToOption(string text, string value, bool isSelected, IDictionary<string, string> htmlAttributes)
        {
            var builder = new TagBuilder("option")
            {
                InnerHtml = HttpUtility.HtmlEncode(text)
            };
            if (value != null)
            {
                builder.Attributes["value"] = value;
            }
            if (isSelected)
            {
                builder.Attributes["selected"] = "selected";
            }
            if (htmlAttributes != null)
            {
                builder.MergeAttributes(htmlAttributes);
            }
            
            return builder.ToString(TagRenderMode.Normal);
        }


Utility function to get property name

public class ClassHelper
    {
        public static string PropertyName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
        {
            var body = expression.Body as MemberExpression;
            if (body == null)
            {
                body = ((UnaryExpression)expression.Body).Operand as MemberExpression;
            }
            return body.Member.Name;
        }
    }

No comments:

Post a Comment