|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Microsoft.AspNetCore.Mvc.Rendering;
/// <summary>
/// Represents a list that lets users select multiple items.
/// This class is typically rendered as an HTML <c><select multiple="multiple"></c> element with the specified collection
/// of <see cref="SelectListItem"/> objects.
/// </summary>
public class MultiSelectList : IEnumerable<SelectListItem>
{
private readonly IList<SelectListGroup> _groups;
private IList<SelectListItem> _selectListItems;
/// <summary>
/// Initialize a new instance of <see cref="MultiSelectList"/>.
/// </summary>
/// <param name="items">The items.</param>
public MultiSelectList(IEnumerable items)
: this(items, selectedValues: null)
{
ArgumentNullException.ThrowIfNull(items);
}
/// <summary>
/// Initialize a new instance of <see cref="MultiSelectList"/>.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="selectedValues">The selected values.</param>
public MultiSelectList(IEnumerable items, IEnumerable selectedValues)
: this(items, dataValueField: null, dataTextField: null, selectedValues: selectedValues)
{
ArgumentNullException.ThrowIfNull(items);
}
/// <summary>
/// Initialize a new instance of <see cref="MultiSelectList"/>.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="dataValueField">The data value field.</param>
/// <param name="dataTextField">The data text field.</param>
public MultiSelectList(IEnumerable items, string dataValueField, string dataTextField)
: this(items, dataValueField, dataTextField, selectedValues: null)
{
ArgumentNullException.ThrowIfNull(items);
}
/// <summary>
/// Initialize a new instance of <see cref="MultiSelectList"/>.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="dataValueField">The data value field.</param>
/// <param name="dataTextField">The data text field.</param>
/// <param name="selectedValues">The selected values.</param>
public MultiSelectList(
IEnumerable items,
string dataValueField,
string dataTextField,
IEnumerable selectedValues)
: this(items, dataValueField, dataTextField, selectedValues, dataGroupField: null)
{
ArgumentNullException.ThrowIfNull(items);
}
/// <summary>
/// Initializes a new instance of the MultiSelectList class by using the items to include in the list,
/// the data value field, the data text field, the selected values, and the data group field.
/// </summary>
/// <param name="items">The items used to build each <see cref="SelectListItem"/> of the list.</param>
/// <param name="dataValueField">The data value field. Used to match the Value property of the corresponding
/// <see cref="SelectListItem"/>.</param>
/// <param name="dataTextField">The data text field. Used to match the Text property of the corresponding
/// <see cref="SelectListItem"/>.</param>
/// <param name="selectedValues">The selected values field. Used to match the Selected property of the
/// corresponding <see cref="SelectListItem"/>.</param>
/// <param name="dataGroupField">The data group field. Used to match the Group property of the corresponding
/// <see cref="SelectListItem"/>.</param>
public MultiSelectList(
IEnumerable items,
string dataValueField,
string dataTextField,
IEnumerable selectedValues,
string dataGroupField)
{
ArgumentNullException.ThrowIfNull(items);
Items = items;
DataValueField = dataValueField;
DataTextField = dataTextField;
SelectedValues = selectedValues;
DataGroupField = dataGroupField;
if (DataGroupField != null)
{
_groups = new List<SelectListGroup>();
}
}
/// <summary>
/// Gets the data group field.
/// </summary>
public string DataGroupField { get; }
/// <summary>
/// Gets the data text field.
/// </summary>
public string DataTextField { get; }
/// <summary>
/// Gets the data value field.
/// </summary>
public string DataValueField { get; }
/// <summary>
/// Gets the items.
/// </summary>
public IEnumerable Items { get; }
/// <summary>
/// Gets the selected values.
/// </summary>
public IEnumerable SelectedValues { get; }
/// <inheritdoc />
public virtual IEnumerator<SelectListItem> GetEnumerator()
{
if (_selectListItems == null)
{
_selectListItems = GetListItems();
}
return _selectListItems.GetEnumerator();
}
private IList<SelectListItem> GetListItems()
{
return (!string.IsNullOrEmpty(DataValueField)) ?
GetListItemsWithValueField() :
GetListItemsWithoutValueField();
}
private IList<SelectListItem> GetListItemsWithValueField()
{
var selectedValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (SelectedValues != null)
{
foreach (var value in SelectedValues)
{
var stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);
selectedValues.Add(stringValue);
}
}
var listItems = new List<SelectListItem>();
foreach (var item in Items)
{
var value = Eval(item, DataValueField);
var newListItem = new SelectListItem
{
Group = GetGroup(item),
Value = value,
Text = Eval(item, DataTextField),
Selected = selectedValues.Contains(value),
};
listItems.Add(newListItem);
}
return listItems;
}
private IList<SelectListItem> GetListItemsWithoutValueField()
{
var selectedValues = new HashSet<object>();
if (SelectedValues != null)
{
selectedValues.UnionWith(SelectedValues.Cast<object>());
}
var listItems = new List<SelectListItem>();
foreach (var item in Items)
{
var newListItem = new SelectListItem
{
Group = GetGroup(item),
Text = Eval(item, DataTextField),
Selected = selectedValues.Contains(item),
};
listItems.Add(newListItem);
}
return listItems;
}
private static string Eval(object container, string expression)
{
var value = container;
if (!string.IsNullOrEmpty(expression))
{
var viewDataInfo = ViewDataEvaluator.Eval(container, expression);
value = viewDataInfo.Value;
}
return Convert.ToString(value, CultureInfo.CurrentCulture);
}
private SelectListGroup GetGroup(object container)
{
if (_groups == null)
{
return null;
}
var groupName = Eval(container, DataGroupField);
if (string.IsNullOrEmpty(groupName))
{
return null;
}
// We use StringComparison.CurrentCulture because the group name is used to display as the value of
// optgroup HTML tag's label attribute.
SelectListGroup group = null;
for (var index = 0; index < _groups.Count; index++)
{
if (string.Equals(_groups[index].Name, groupName, StringComparison.CurrentCulture))
{
group = _groups[index];
break;
}
}
if (group == null)
{
group = new SelectListGroup() { Name = groupName };
_groups.Add(group);
}
return group;
}
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
|