tooltip for each item in dynamically generated @html.dropdownfor control in mvc3

4.9k Views Asked by At

I'm working on web project in MVC 3 with Razor c#.

I have used @Html.DropDownListFor that display item dynamically. I want to set tooltip for every item of @Html.DropDownListFor.

My line of code as below

@Html.DropDownListFor(m => m.Type, new SelectList(Model.Types, "Value", "Text", Model.Type), 
new { @class = "Type"})
3

There are 3 best solutions below

0
On

I felt curious about this question so just tried to achieve it. I created a simple example and made it work to display different tooltip on each select item hover.

Note: I am not an expert in JS side and not sure if it is an ideal way.

Here is my example code:

MyController.cs

    public ActionResult LoadCountries()
    {
        List<SelectListItem> li = new List<SelectListItem>();
        li.Add(new SelectListItem { Text = "Select", Value = "0" });
        li.Add(new SelectListItem { Text = "India", Value = "1" });
        li.Add(new SelectListItem { Text = "Srilanka", Value = "2" });
        li.Add(new SelectListItem { Text = "China", Value = "3" });
        li.Add(new SelectListItem { Text = "Austrila", Value = "4" });
        li.Add(new SelectListItem { Text = "USA", Value = "5" });
        li.Add(new SelectListItem { Text = "UK", Value = "6" });
        ViewData["country"] = li;
        return View();
    }

LoadCountries.cshtml

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="~/Scripts/TooltipDefinition.js"></script>

@{
    ViewBag.Title = "title";
}

<h2>Just For Demo</h2>

@using (Html.BeginForm())
{
    @Html.DropDownList("Countries", ViewData["country"] as List<SelectListItem>)
}

<h2 id="tooltipData"></h2>

<script type="text/javascript">

    $("#Countries > option").each(function () {
        var item = $(this).text();

        if (item !== "Select") {
            var tooltipText = MouseHoverText(item);
            $(this).attr("title", tooltipText);
        }
    });

</script>

TooltipDefinition.js

var MouseHoverText = function(id) {
    switch (id) {
        case "India":
            return "You have selected India - A country full of opportunities.";
        break;
    case "Srilanka":
        return "You have selected Srilanka - God's own country.";
        break;
    default:
        return "You have selected " + id;
    }
};

I guess I am not doing anything different than what you attempted. I am capturing all select items and attaching title attribute when items are loaded. The tooltip text comes from JS file. You may well be able to pass it through Model.

Sharing as it may well suits your need.

7
On

You can't do it out of the box. One way is to use js described in this answer.

The other opportunity is to create your own Type like SelectListItem, but with Title property and create EditorTemplate for your Type. For this variant i provide some code:

Your Templave ViewModel:

using System.Collections.Generic;
using System.Web.Mvc;

namespace Templates.ViewModels
{
    public class SelectListItemWithTitle: SelectListItem
    {
        public string Title { get; set; }
    }

    public class SelectListWithTitle
    {
        public SelectListWithTitle(IEnumerable<SelectListItemWithTitle> items)
        {
            Items = items;
        }
        public IEnumerable<SelectListItemWithTitle> Items { get; set; }
    }
}

Your EditorTemplate that you should place in /Views/Shared/EditorTemplates with name SelectListWithTitle.cshtml

@model Templates.ViewModels.SelectListWithTitle

@{
    Layout = null;
}

<select id="@ViewData.TemplateInfo.HtmlFieldPrefix" name="@ViewData.TemplateInfo.HtmlFieldPrefix">
    @foreach (var item in Model.Items)
    {
        <option value="@item.Value" selected="@item.Selected" title="@item.Title">@item.Text</option>
    }
</select>

Your ViewModel like this:

public class ViewModel
{
    public SelectListWithTitle Meal { get; set; }
}

Your controller:

public ActionResult Index()
{
    List<SelectListItemWithTitle> selectListItemsWithTitle = new List<SelectListItemWithTitle>();
    selectListItemsWithTitle.Add(new SelectListItemWithTitle { Text = "Apple", Value = "0", Title = "Fresh" });
    selectListItemsWithTitle.Add(new SelectListItemWithTitle { Text = "Orange", Value = "1", Title = "Jussy" });
    selectListItemsWithTitle.Add(new SelectListItemWithTitle { Text = "Banana", Value = "2", Title = "Yellow", Selected = true });
    selectListItemsWithTitle.Add(new SelectListItemWithTitle { Text = "Vodka", Value = "3", Title = "Russian" });
    selectListItemsWithTitle.Add(new SelectListItemWithTitle { Text = "Beer", Value = "4", Title = "Cold" });

    SelectListWithTitle sl = new SelectListWithTitle(selectListItemsWithTitle);

    ViewModel model = new ViewModel
    {
        Meal = sl
    };
    ViewData.Model = model;

    return View();
}

And your View Index:

@model MvcApplication1.Models.ViewModel

@Html.EditorFor(m => m.Meal)

Don't forget to change your namespaces.

2
On

I have done something similar

I declared two classes one for model and one for holding values for each options tag of dropdown list

public Class Report
{
  public ReportName {get; set;}
  public IList<ReportSummaryModel> AvailableReports {get; set;}
}

public class ReportSummaryModel
{
  public ReportActualName {get; set;}
  public ReportClassName {get; set;}    
  public TextField {get; set;}
  public ValueField {get; set;}
}

I wanted to output ReportActualName and ReportClassName onto the options tag of dropdown list and these values varies for each options.

So therefore I wrote below HTML helper function

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

The actual call to function is mentioned below

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

The rest of the utility functions are described below

public static MvcHtmlString DropDownList<TPageModel, TSelectListModel, TProperty>(this HtmlHelper<TPageModel> htmlHelper, Expression<Func<TPageModel, 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;
        }
    }

Output is

<select name="ReportName">
 <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>