Reputation: 258
I have a List of Categories (entities) with each of them having parents. Here is the Model for the category :
public class Category { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int CategoryId { get; set; } public int? ParentId { get; set; } public string Name { get; set; } public virtual Category Parent { get; set; } }
I need to populate a SelectList such that the Parents come first and the childrens go indented Like:
I have to use this selectlist to poulate a dropdown in the edit and update forms. How do I go about doing this? clientside? serverside?
Thanks for any help you can offer.
Upvotes: 0
Views: 2108
Reputation: 7076
This got me thinking about implementing this without the dependency of an interface
, which I still think is reasonable. Here is an alternative solution using an extension method that does not require an interface
to be implemented.
Extension Method
public static class ExtensionMethods
{
/// <summary>
/// Returns a single-selection select element containing the options specified in the items parameter.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
/// <param name="helper">The class being extended.</param>
/// <param name="items">The collection of items used to populate the drop down list.</param>
/// <param name="parentItemsPredicate">A function to determine which elements are considered as parents.</param>
/// <param name="parentChildAssociationPredicate">A function to determine the children of a given parent.</param>
/// <param name="dataValueField">The value for the element.</param>
/// <param name="dataTextField">The display text for the value.</param>
/// <returns></returns>
public static MvcHtmlString DropDownGroupList<T>(
this HtmlHelper helper,
IEnumerable<T> items,
Func<T, bool> parentItemsPredicate,
Func<T, T, bool> parentChildAssociationPredicate,
string dataValueField,
string dataTextField)
{
var html = new StringBuilder("<select>");
foreach (var item in items.Where(parentItemsPredicate))
{
html.Append(string.Format("<optgroup label=\"{0}\">", item.GetType().GetProperty(dataTextField).GetValue(item, null)));
foreach (var child in items.Where(x => parentChildAssociationPredicate(x, item)))
{
var childType = child.GetType();
html.Append(string.Format("<option value=\"{0}\">{1}</option>", childType.GetProperty(dataValueField).GetValue(child, null), childType.GetProperty(dataTextField).GetValue(child, null)));
}
html.Append("</optgroup>");
}
html.Append("</select>");
return new MvcHtmlString(html.ToString());
}
}
Usage based on your Category
class
@this.Html.DropDownGroupList(YourCollection, x => !x.ParentId.HasValue, (x, y) => { return x.ParentId.Equals(y.CategoryId); }, "CategoryId", "Name")
By the time I finished writing this post I wasn't so sure this was all that valuable but thought I'd post it anyways.
As you can see, your class
must know the id of it's parent and the display name of both the child and parent should use the same property as indicated by the dataTextField
parameter. So, essentially, your class
needs the properties: Id, ParentId, and Name and you use the Func<T, bool>
and Func<T, T, bool>
parameters to determine relationships.
Don't forget to add in the necessary validation!
Upvotes: 3
Reputation: 7076
I would do this server-side using optgroup
like this in razor
syntax.
<select>
@foreach(var parent in categories.Where(x => !x.ParentId.HasValue)
{
<optgroup label="@parent.Name">
@foreach(var child in categories.Where(x => x.ParentId.Equals(parent.CategoryId))
{
<option value="@child.CategoryId">@child.Name</option>
}
</optgroup>
}
</select>
I would also make this an extension methods so it would like the other available HTML helper methods.
Edit
An extension method could accept a collection of items which implement a simple interface like this:
public interface IGroupable
{
int Id { get; set; }
string Name { get; set; }
int? ParentId { get; set; }
}
public class Category : IGroupable
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; set; }
public int? ParentId { get; set; } //implements IGroupable.ParentId
public string Name { get; set; } //implements IGroupable.Name
public virtual Category Parent { get; set; }
#region [IGroupable Specific Implementation]
public int Id { get { return this.CategoryId; } }
#endregion
}
public static class ExtensionMethods
{
public static MvcHtmlString DropDownGroupList(this HtmlHelper helper, IEnumerable<IGroupable> items)
{
var html = new StringBuilder("<select>");
foreach (var item in items.Where(x => !x.ParentId.HasValue))
{
html.Append(string.Format("<optgroup label=\"{0}\">", item.Name));
foreach(var child in items.Where(x => x.ParentId.Equals(item.Id)))
{
html.Append(string.Format("<option value=\"{0}\">{1}</option>", child.Id, child.Name));
}
html.Append("</optgroup>");
}
html.Append("</select>");
return new MvcHtmlString(html.ToString());
}
}
Upvotes: 1