1

This is a sample of dropdown list in html source. I want to create a html helper for this to use general tree list option with select option! For example, every level just shows nested text like this : A > A1 > A11.


My model

DropdownTreeview{
        public long Id { get; set; }
        public long? IdParent { get; set; }
        public string Name { get; set; }
}


<select class="form-control valid" data-val="true" data-val-required="The Parent category field is required." id="ParentCategoryId" name="ParentCategoryId" aria-describedby="ParentCategoryId-error" aria-invalid="false"><option selected="selected" value="0">[None]</option>
<option value="1">Computers</option>
<option value="2">Computers > Desktops</option>
<option value="3">Computers > Notebooks</option>
<option value="4">Computers > Software</option>
<option value="5">Electronics</option>
<option value="6">Electronics > Camera photo</option>
<option value="7">Electronics > Cell phones</option>
<option value="8">Electronics > Others</option>
<option value="9">Apparel</option>
<option value="10">Apparel > Shoes</option>
<option value="11">Apparel > Clothing</option>
<option value="12">Apparel > Clothing  > Shirt</option>
<option value="13">Apparel > Clothing  > TShirt</option>
<option value="14">Apparel > Accessories</option>
<option value="15">downloads</option>
<option value="16">Books</option>
<option value="17">Jewelry</option>
<option value="18">Gift Cards</option>
</select>

4
  • There's no such thing as a "drop-down tree view" on the web. Please be more specific about what you want to achieve, as well as showing more than just a prerendered <select> for your efforts. Commented Dec 24, 2021 at 20:15
  • I want to pass the hierarchy model (IdParent) to a custom HTML helper for dropdown like a tree with '-' for each level and show select with many options. Commented Dec 25, 2021 at 7:47
  • You can’t do that with native HTML elements: the <select> element only supports up-to 2 levels of nesting via <optgroup>. Commented Dec 25, 2021 at 8:50
  • I mean for every level just show nested text like this : A > A1 > A11. Commented Dec 25, 2021 at 9:30

1 Answer 1

1

You don't need a HtmlHelper, what you actually need is just a way to convert from your DropdownTreeview object-graph to an IEnumerable<SelectListItem> that you can pass into the existing DropDownFor HtmlHelper as well as the (arguably superior) SelectTagHelper (aka <select asp-items>).


Because your class DropdownTreeview items do not contain any object-references to parent class DropdownTreeview items (instead you're using long? IdParent) you can't use existing straightforward methods for traversing the graph. In my approach I convert these to a traversible object-graph which can then be flattened to a list of SelectListItem objects.

I'm going to rename your class DropdownTreeview to class TreeNode because each instance represents a node, not the entire tree.

You can directly copy and paste this into Linqpad to see it run (you'll need to add a NuGet reference to ASP.NET Core to use SelectListItem, of course):

void Main()
{
    TreeNode[] initialNodes = new[]
    {
        new TreeNode(  1, null, "Computers" ),
        new TreeNode(  2,    1, "Desktops" ),
        new TreeNode(  3,    1, "Notebooks" ),
        new TreeNode(  4,    1, "Software" ),
        new TreeNode(  5, null, "Electronics" ),
        new TreeNode(  6,    5, "Camera photo" ),
        new TreeNode(  7,    5, "Cell phones" ),
        new TreeNode(  8,    5, "Others" ),
        new TreeNode(  9, null, "Apparel" ),
        new TreeNode( 10,    9, "Shoes" ),
        new TreeNode( 11,    9, "Clothing" ),
        new TreeNode( 12,   11, "Shirt" ),
        new TreeNode( 13,   11, "TShirt" ),
        new TreeNode( 14,    9, "Accessories" ),
        new TreeNode( 15, null, "Downloads" ),
        new TreeNode( 16, null, "Books" ),
        new TreeNode( 17, null, "Jewelry" ),
        new TreeNode( 18, null, "Gift Cards" )
    };
    
    TreeGraph graph = new TreeGraph( initialNodes );
    
//  graph.EnumerateDepthFirst().Dump();
    graph.AsSelectListItems().Dump();
}

public record TreeNode( Int64 id, Int64? parentId, String name );

public class TreeGraph
{
    private readonly IReadOnlyDictionary<Int64,( TreeNode node, List<TreeNode> children )> nodesById;
    
    public TreeGraph( IEnumerable<TreeNode> initialNodes )
    {
        Dictionary<Int64,( TreeNode node, List<TreeNode> children )> nodesByIdMut = initialNodes
            .ToDictionary( n => n.id, n => ( node: n, children:  new List<TreeNode>() ) );
        
        foreach( TreeNode child in initialNodes.Where( n => n.parentId.HasValue ) )
        {
            nodesByIdMut[ child.parentId!.Value ].children.Add( child );
        }
        
        this.nodesById = nodesByIdMut;
    }
    
    private String GetPath( TreeNode node )
    {
        if( node.parentId == null ) return node.name;
        
        Stack<TreeNode> stack = new Stack<TreeNode>();
        
        TreeNode? n = node;
        while( n != null )
        {
            stack.Push( n );
            
            n = n.parentId.HasValue ? ( nodesById[ n.parentId.Value ].node ) : null;
        }
        
        return String.Join( separator: " > ", stack.Select( n2 => n2.name ) );
    }
    
    private IEnumerable<TreeNode> GetRoots()
    {
        return this.nodesById.Values
            .Where( t => t.node.parentId == null )
            .OrderBy( t => t.node.name )
            .Select( t => t.node );
    }
    
    private IEnumerable<TreeNode> GetChildren( TreeNode node )
    {
        return this.nodesById[ node.id ].children;
    }
    
    public IEnumerable<( TreeNode n, String path )> EnumerateDepthFirst()
    {
        foreach( TreeNode root in this.GetRoots() )
        {
            foreach( ( TreeNode descendant, String path ) pair in this.EnumerateDepthFirstFrom( root ) )
            {
                yield return pair;
            }
        }
    }
    
    private IEnumerable<( TreeNode n, String path )> EnumerateDepthFirstFrom( TreeNode root )
    {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        stack.Push( root );
        
        while( stack.Count > 0 )
        {
            TreeNode n = stack.Pop();
            foreach( TreeNode c in this.GetChildren( n ) )
            {
                stack.Push( c );
            }
            
            String path = this.GetPath( n );
            
            yield return ( n, path );
        }
    }
    
    public IEnumerable<SelectListItem> AsSelectListItems()
    {
        return this.EnumerateDepthFirst()
            .Select( pair => new SelectListItem()
            {
                Text  = pair.path,
                Value = pair.n.id.ToString( CultureInfo.InvariantCulture )
            } )
    }
}

Screenshot proof:

enter image description here


To then use this in your Razor (.cshtml) views, instantiate the TreeGraph somewhere and pass the result of AsSelectListItems() into asp-items, like so:

@model MyPageViewModel

TreeNode[] nodes = this.Model.TreeNodes;

TreeGraph graph = new TreeGraph( nodes );

//

<form>
    
    <select asp-items="@( graph.AsSelectListItems() )"></select>
    
</form>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.