嵌套视图中的3级列表

时间:2021-01-30 19:45:32

I have these tables Pages, PageVersions and ElementsOnPageVersions.

我有这些表Pages,PageVersions和ElementsOnPageVersions。

I want to show this on a page all at once, in a collapsible kind of view.

我想在一个可折叠的视图中同时在一个页面上显示这个。

Like this:

  • Page 1

    • Version 1
    • Version 2
    • Version 3
    • Version 4
      • Element 1
      • Element 2
      • Element 3
    • 版本4元素1元素2元素3

  • Page 1版本1版本2版本3版本4元素1元素2元素3

  • Page 2

    • Version 1
    • Version 2
    • Version 3
    • Version 4
      • Element 1
      • Element 2
    • 版本4元素1元素2

  • Page 2版本1版本2版本3版本4元素1元素2

  • Page 3

    • Version 1
      • Element 1
    • 版本1元素1

  • Page 3版本1要素1

  • Page 4

    • Version 1
      • Element 1
    • 版本1元素1

  • Page 4版本1要素1

I'm uncertain on what the best way to retrieve the data and easily show it in the collapsaple layout is.

我不确定检索数据的最佳方法是什么,并在collapsaple布局中轻松显示。

I would do something like this:

我会做这样的事情:

  1. Get all items, all at once. "select * from ElementsOnPageVersion e inner join PageVersions v on e.PageVersion = v.id inner join Pages p on v.PageId = p.id"
  2. 一次性获取所有物品。 “select * from ElementsOnPageVersion e inner join PageVersions v on e.PageVersion = v.id inner join p p.PageId = p.id”

  3. Iterate through all, and build hierachical structure of sorted list to look like the collapsaple layout. PageLists[PagesObject], PagesObject has a sorted list of PageVersionObjects, which has a sorted list of ElementObjects.
  4. 迭代所有,并构建排序列表的层次结构看起来像collapsaple布局。 PageLists [PagesObject],PagesObject有一个PageVersionObjects的排序列表,它有一个ElementObjects的排序列表。

  5. Iterate through the final PagesList list Building the page. Inside this iterating through pageversionslist showing versions for the page, and Again inside every version iterating through elementslist.
  6. 遍历最终的PagesList列表构建页面。在这个迭代通过pageversionslist显示页面的版本,并在每个版本迭代通过elementslist。

This would be the way I would do it, at this moment. But it seems too heavy, and a lot of iterating.

这就是我现在这样做的方式。但它看起来太沉重了,而且很多迭代。

What would be the best way to do this?

最好的方法是什么?

3 个解决方案

#1


1  

I'd think about projecting your three different items to a single self-referencing list. Each item would need an Id, Description, and ParentId. I'd make a view model for this purpose.

我想把你的三个不同的项目投射到一个自引用列表中。每个项目都需要Id,Description和ParentId。我为此目的制作了一个视图模型。

public class TreeItem {
    public int Id {get; set;}
    public string Description {get; set;}
    public int ParentId {get; set;}
}

That would allow for you to leverage either an asp:TreeView in webforms or whatever flavor of jQuery tree / treeview if you're using MVC.

如果你正在使用MVC,这将允许你在webforms中使用asp:TreeView或者jQuery树/ treeview的任何风格。

#2


0  

i actually built something that does something very similar. i can give you the HTML source to mine but i'd need to modify the .NET code to remove company specific, proprietary information before releasing that publicly (plus convert it to C#).

我实际上构建了一些非常相似的东西。我可以给你我的HTML源代码但是我需要修改.NET代码以在公开发布之前删除公司特定的专有信息(并将其转换为C#)。

unfortunately for me to modify mine to your needs, it'd be almost up to one month before i'd be able to get around to it (due to a busy week before we take a 2 week vacation). but you can look at my HTML and see what you can reuse from it. from what i understand of your request, you'd be scratching about half of my JavaScript and its functionality. but if i were in your shoes, i'd appreciate this code to use as a starter and scrap what i didn't want and maybe add something. or you may just think my code is trash and not use it at all. ;P

不幸的是,我要根据你的需要修改我的,这几乎可以在我能够到达之前的一个月(由于我们休假前的一个忙碌的一周)。但您可以查看我的HTML并查看可以从中重用的内容。根据我对您的请求的理解,您将抓住我的一半JavaScript及其功能。但如果我在你的鞋子里,我会很感激这个代码用作启动器并废弃我不想要的东西并且可能添加一些东西。或者您可能只是认为我的代码是垃圾而根本不使用它。 ,P

so here's my HTML & JavaScript so you can run with it. It does handle the collapsing and expanding of nodes tho using ul and ol lists.

所以这是我的HTML和JavaScript,所以你可以运行它。它确实使用ul和ol列表来处理节点的折叠和扩展。

<!DOCTYPE HTML>
<html>
 <head><title>Oz-Med-Hist-VB</title>
  <meta charset='utf-8' />
  <meta http-equiv='X-UA-Compatible' content='IE=EmulateIE8' />
  <style type='text/css'>
    body { background: #CCC; margin-left: 1.5em; }
    li { list-style-type: none; text-indent: -38px; }

    .emptyGrayBox8 {
        background-color: #bbb; /* checked and unchecked background color */
        border: 1px solid #777; /* checked and unchecked border width & style & color */
        padding: 5.5px; display: inline-block; position: relative; margin: 0 3px; margin-top: 3px;
    }

    .checkInsideGrayBox:after {
        content: 'X';
        font-size: 13px;
        position: absolute;
        top: -2px;
        left: 1.5px;
        color: #555;
    }

    .checkInsideGrayBoxInsideLI:after {
        content: 'X';
        font-size: 13px;
        position: absolute;
        top: -2px;
        left: 39.5px;
        color: #555;
    }
  </style>
  <script>

    if (navigator.userAgent.indexOf('MSIE 8.') < 0)
    {
        document.write(
        '  <style>\n' +
        '   .checkInsideGrayBox               { left:  0.5px; }\n' +
        '   .checkInsideGrayBoxInsideLI:after { left: 38.5px; }\n' +
        '  </style>');
    }

var dta =
{   'checkedSubNodeCount--1'   : 0
,   'checkedSubNodeCount--19'  : 0
,   'checkedSubNodeCount--19-a': 0
,   'checkedSubNodeCount--19-b': 0
,   'checkedSubNodeCount--19-c': 0
,   'checkedSubNodeCount--22'  : 0
,   'checkedSubNodeCount--24'  : 0
,   'checkedSubNodeCount--25'  : 0
,   'checkedSubNodeCount--144' : 0
,   'checkedSubNodeCount--1728': 0
};

function chkBox_click(id) // this gets called when user clicks on checkbox or text to right of checkbox.
{ // when checkbox becomes checked, then associated group also becomes shown.
    var chkE = document.getElementById('chkBox--' + id); // CHecKBOX HTML element
    var grpE = document.getElementById('grpBox--' + id); // GRouPbox HTML element (UL element)
    var isChecked = chkE.checked;
    if (isChecked)
    {   // if user just checked the checkbox, and ...
        if (grpE != null) // if an associated (sub)group exists for this checkbox, ...
        { grpE.style.display = ''; } // then expand/show the group element.
    }
    setLabelHtm(id);
    var pid = getParentID(id); // Parent ID
    if (id == null) { return; } // if parent id doesn't exist then we're done here.

    // now 'pid' is parent ID of 'id'. for instance:
    // when id == '19-a'    then pid = '19'
    // when id == '19-a-1-' then pid = '19-a'

    var h = '';
    var maxLoopCount = 12; // infinite loop protection :P
    while (pid != null && --maxLoopCount >= 0)
    {
        chkE = document.getElementById('chkBox--' + pid); // CHecKBOX element *of parent*
        var pKey = 'checkedSubNodeCount--' + pid; // Key for this Parent ID
        if (isChecked) { ++dta[pKey]; } else { --dta[pKey]; }
        setLabelHtm(pid);
        if (h.length > 0) { h += '\n\n'; }
        h += 'id = ' + id + '   isChecked = ' + isChecked
        +   '\npid = ' + pid + '   chkE = ' + chkE
        +   '\ndta[\'' + pKey + '\'] = ' + dta[pKey];

        pid = getParentID(pid);
    }
//  alert(h);
} // function chkBox_click(id)

function chkBox_click8(id)
{
    var chkE = document.getElementById('chkBox--' + id); // CHecKBOX element
    var lblE = document.getElementById('chkLab--' + id); // CHecKbox LABel (HTML 'label' element for the checkbox)
    if (chkE == null || lblE == null) { return; }
    var isChecked = chkE.checked;
    var g = Number(chkE.tag);
    if (isChecked == false) { g = 3; chkE.tag = g; }
    if (isChecked == true ) { g = 2; chkE.tag = g; }
    alert(id + '\nisChecked = ' + isChecked + '\n.tag = ' + g);
} // function chkBox_click8(id)

function chkBox_clickIt(id)
{   var chkE = document.getElementById('chkBox--' + id); // CHecKBOX HTML element
    if (chkE != null) { chkE.click(); }
} // function chkBox_clickIt(id)

function getParentID(id)
{
    var pid = String(id); if (pid.length < 1) { return null; }
    if (pid[pid.length - 1] == '-') { pid = pid.substr(0, pid.length - 1); }
    var x = pid.lastIndexOf('-'); if (x < 0) { return null; }
    pid = pid.substr(0, x); // now pid is id of parent
    return pid;
} // function getParentID(id)

function hdrLab_click(id) // this will switch whether the associated group is hidden or shown
{// var chkE = document.getElementById('chkBox--' + id); // CHecKBOX HTML element
    var grpE = document.getElementById('grpBox--' + id); // GRouPBOX HTML element (UL element)
    if (grpE != null)
    {   if (grpE.style.display == '')
        {   grpE.style.display = 'none'; }
        else
        {   grpE.style.display = ''; }
    }
    setLabelHtm(id);
} // function hdrLab_click(id)

function setLabelHtm(id)
{   var grpE = document.getElementById('grpBox--' + id); // GRouPBOX HTML element (UL element)
    var lblE = document.getElementById('hdrLab--' + id);
    if (lblE == null) { return; }
    var h = null; var suffix = '.';
    if (grpE == null) { h = '<span style="visibility: hidden">+</span>'; }
    else
    {   var grpIsOpen = grpE.style.display == '';
        h = '<b>' + (grpIsOpen ? '−' : '+') + '</b>';
    }
    var s = String(id);
    if (s.length > 0 && s.substr(s.length - 1) == '-') { s = '&bull;'; suffix = ''; }
    else
    {
        var x = s.lastIndexOf('-') + 1;
        if (x > 0) { s = s.substr(x); }
    }
    h += '&nbsp;' + s + suffix;

    var cnt = dta['checkedSubNodeCount--' + id]; // CouNT of checked sub-nodes
    if (cnt != null) // if this node is a parent node
    {   var chkE = document.getElementById('chkBox--' + id); // CHecKBOX HTML element
        if (chkE != null) { chkE.style.display = (cnt > 0) ? 'none' : ''; }
        var hBeg = '<span class="emptyGrayBox8">';
        var hEnd = '</span>';
        if (cnt > 0) { h += hBeg + '<span class="checkInsideGrayBoxInsideLI"></span>' + hEnd; }
        else if (chkE == null) { h += hBeg + hEnd; }
    }
    lblE.innerHTML = h;
} // function setLabelHtm(id)

function init()
{   //alert('yes');
    hdrLab_click('1');
    hdrLab_click('1-a');
    hdrLab_click('19');
    hdrLab_click('19-a');
    hdrLab_click('19-a-1-');
    hdrLab_click('19-a-2-');
    hdrLab_click('19-b');
    hdrLab_click('19-b-1-');
    hdrLab_click('19-b-2-');
    hdrLab_click('19-b-3-');
    hdrLab_click('19-c');
    hdrLab_click('19-c-1-');
    hdrLab_click('19-c-2-');
    hdrLab_click('19-c-3-');
    hdrLab_click('22');
    hdrLab_click('22-a');
    hdrLab_click('22-b');
    hdrLab_click('23');
    hdrLab_click('24');
    hdrLab_click('24-a');
    hdrLab_click('24-b');
    hdrLab_click('24-c');
    hdrLab_click('25');
    hdrLab_click('25-a-');
    hdrLab_click('144');
    hdrLab_click('144-a');
    hdrLab_click('1728');
    hdrLab_click('1728-a');
//  alert(dta['checkedSubNodeCount--19-a']);
}
    window.onload = init;
  </script>
 </head>
 <body>
<ul style='list-style-type: none; margin-left: -1em'>
    <li><label id='hdrLab--1' onclick='hdrLab_click("1")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--1' onclick='hdrLab_click("1")')>&nbsp;Test for short number '1':</label></li>
    <ul id='grpBox--1'>
        <li><label id='hdrLab--1-a' onclick='hdrLab_click("1-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--1-a' onclick='chkBox_click("1-a");' /><label id='txtLab--1-a' onclick='chkBox_clickIt("1-a")')>Test for short number '1' subitem:</label></li>
    </ul>
    <li><label id='hdrLab--19' onclick='hdrLab_click("19")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--19' onclick='hdrLab_click("19")')>&nbsp;Which fruit do you prefer?</label></li>
    <ul id='grpBox--19'>
        <li><label id='hdrLab--19-a' onclick='hdrLab_click("19-a")' tabindex='0' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-a' onclick='chkBox_click("19-a");' /><label id='txtLab--19-a' onclick='chkBox_clickIt("19-a")')>Apples:</label></li>
        <ul id='grpBox--19-a'>
            <li><label id='hdrLab--19-a-1-' onclick='hdrLab_click("19-a-1-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-a-1-' onclick='chkBox_click("19-a-1-");' /><label id='txtLab--19-a-1-' onclick='chkBox_clickIt("19-a-1-")')>Red delicious</label></li>
            <li><label id='hdrLab--19-a-2-' onclick='hdrLab_click("19-a-2-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-a-2-' onclick='chkBox_click("19-a-2-");' /><label id='txtLab--19-a-2-' onclick='chkBox_clickIt("19-a-2-")')>Granny smith</label></li>
        </ul>
        <li><label id='hdrLab--19-b' onclick='hdrLab_click("19-b")' tabindex='0' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-b' onclick='chkBox_click("19-b");' /><label id='txtLab--19-b' onclick='chkBox_clickIt("19-b")')>Bananas:</label></li>
        <ul id='grpBox--19-b'>
            <li><label id='hdrLab--19-b-1-' onclick='hdrLab_click("19-b-1-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-b-1-' onclick='chkBox_click("19-b-1-");' /><label id='txtLab--19-b-1-' onclick='chkBox_clickIt("19-b-1-")')>Green</label></li>
            <li><label id='hdrLab--19-b-2-' onclick='hdrLab_click("19-b-2-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-b-2-' onclick='chkBox_click("19-b-2-");' /><label id='txtLab--19-b-2-' onclick='chkBox_clickIt("19-b-2-")')>Yellow (ripe but not too ripe)</label></li>
            <li><label id='hdrLab--19-b-3-' onclick='hdrLab_click("19-b-3-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-b-3-' onclick='chkBox_click("19-b-3-");' /><label id='txtLab--19-b-3-' onclick='chkBox_clickIt("19-b-3-")')>Brown (<i>very</i> ripe)</label></li>
        </ul>
        <li><label id='hdrLab--19-c' onclick='hdrLab_click("19-c")' tabindex='0' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-c' onclick='chkBox_click("19-c");' /><label id='txtLab--19-c' onclick='chkBox_clickIt("19-c")')>Juice</label></li>
        <ul id='grpBox--19-c'>
            <li><label id='hdrLab--19-c-1-' onclick='hdrLab_click("19-c-1-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-c-1-' onclick='chkBox_click("19-c-1-");' /><label id='txtLab--19-c-1-' onclick='chkBox_clickIt("19-c-1-")')>Orange juice</label></li>
            <li><label id='hdrLab--19-c-2-' onclick='hdrLab_click("19-c-2-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-c-2-' onclick='chkBox_click("19-c-2-");' /><label id='txtLab--19-c-2-' onclick='chkBox_clickIt("19-c-2-")')>Grape juice</label></li>
            <li><label id='hdrLab--19-c-3-' onclick='hdrLab_click("19-c-3-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-c-3-' onclick='chkBox_click("19-c-3-");' /><label id='txtLab--19-c-3-' onclick='chkBox_clickIt("19-c-3-")')>Tomato juice</label></li>
        </ul>
    </ul>
    <li><label id='hdrLab--22' onclick='hdrLab_click("22")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--22' onclick='hdrLab_click("22")')>&nbsp;Which juice do you prefer?</label></li>
    <ul id='grpBox--22'>
        <li><label id='hdrLab--22-a' onclick='hdrLab_click("22-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--22-a' onclick='chkBox_click("22-a");' /><label id='txtLab--22-a' onclick='chkBox_clickIt("22-a")')>Apple juice</label></li>
        <li><label id='hdrLab--22-b' onclick='hdrLab_click("22-b")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--22-b' onclick='chkBox_click("22-b");' /><label id='txtLab--22-b' onclick='chkBox_clickIt("22-b")')>Orange juice</label></li>
    </ul>
    <li><label id='hdrLab--23' onclick='hdrLab_click("23")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--23' onclick='chkBox_click("23");' /><label id='txtLab--23' onclick='chkBox_clickIt("23")')>Single checkmark question with no subnodes</label></li>
    <li><label id='hdrLab--24' onclick='hdrLab_click("24")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--24' onclick='hdrLab_click("24")')>&nbsp;Best OS?</label></li>
    <ul id='grpBox--24'>
        <li><label id='hdrLab--24-a' onclick='hdrLab_click("24-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--24-a' onclick='chkBox_click("24-a");' /><label id='txtLab--24-a' onclick='chkBox_clickIt("24-a")') style='color:green'>Android!</label></li>
        <li><label id='hdrLab--24-b' onclick='hdrLab_click("24-b")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--24-b' onclick='chkBox_click("24-b");' /><label id='txtLab--24-b' onclick='chkBox_clickIt("24-b")') style='color:brown'>Apple</label></li>
        <li><label id='hdrLab--24-c' onclick='hdrLab_click("24-c")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--24-c' onclick='chkBox_click("24-c");' /><label id='txtLab--24-c' onclick='chkBox_clickIt("24-c")')>Linux</label></li>
    </ul>
    <li><label id='hdrLab--25' onclick='hdrLab_click("25")' tabindex='0' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--25' onclick='chkBox_click("25");' /><label id='txtLab--25' onclick='chkBox_clickIt("25")')>Check question with subnode check too.</label></li>
    <ul id='grpBox--25'>
        <li><label id='hdrLab--25-a-' onclick='hdrLab_click("25-a-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--25-a-' onclick='chkBox_click("25-a-");' /><label id='txtLab--25-a-' onclick='chkBox_clickIt("25-a-")')>Sub-node check question</label></li>
    </ul>
    <li><label id='hdrLab--144' onclick='hdrLab_click("144")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--144' onclick='hdrLab_click("144")')>&nbsp;Test for 3-digit number:</label></li>
    <ul id='grpBox--144'>
        <li><label id='hdrLab--144-a' onclick='hdrLab_click("144-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--144-a' onclick='chkBox_click("144-a");' /><label id='txtLab--144-a' onclick='chkBox_clickIt("144-a")')>Test for 3-digit number subitem:</label></li>
    </ul>
    <li><label id='hdrLab--1728' onclick='hdrLab_click("1728")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--1728' onclick='hdrLab_click("1728")')>&nbsp;Test for 4-digit number:</label></li>
    <ul id='grpBox--1728'>
        <li><label id='hdrLab--1728-a' onclick='hdrLab_click("1728-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--1728-a' onclick='chkBox_click("1728-a");' /><label id='txtLab--1728-a' onclick='chkBox_clickIt("1728-a")')>Test for 4-digit number subitem:</label></li>
    </ul>
</ul>
<div style='display: none'>
<script>
    var ua = navigator.userAgent;
    var ieVersion = null;
    var ix = 0;
    for (var v = 7; v <= 99; v++)
    {
        document.write('<!--[if IE ' + v + ']><hr />According to the conditional comment this is IE ' + v + '.<![endif]-->');
        if ((ix = ua.indexOf('MSIE ' + v + '.')) >= 0)
        {   ieVersion = '';
            ix += 5;
            while (ix < ua.length && '1234567890.'.indexOf(ua.charAt(ix)) >= 0)
            {   ieVersion += ua.charAt(ix++);
            }   document.write('<hr />According to JavaScript, this is IE ' + ieVersion);
        }
    }
    if (ieVersion == null)
    {   document.write('<hr />According to JavaScript, this IE version could not be determined.');
    }   document.write('<hr />navigator.userAgent = ' + navigator.userAgent);
</script><hr />
    <button id='webBtn1'>Fire an event</button>
    <button id='btnHi' onclick='window.external.sayHelloFromJavaScript("Hello from JavaScript!", 5);'>Say Hello</button>
</div>
 </body>
</html>

i also note that my project used a WebControl built for IE that needed to work as far back as WinXP. so i was limited in what i could use since it had to work for IE 7, but the nice thing is it works for way back to WinXP's latest working version of IE. i also care about using standards compliant code, so to my knowledge, my JavaScript also works for any standards-compliant browsers. i tested it in latest versions of Chrome and FF, and IE 10, IE 8 and IE 7.

我还注意到我的项目使用了为IE构建的WebControl,它需要像WinXP一样工作。因为我必须为IE 7工作,所以我可以使用的东西有限,但好的是它可以回到WinXP的最新工作版IE。我也关心使用符合标准的代码,因此据我所知,我的JavaScript也适用于任何符合标准的浏览器。我在最新版本的Chrome和FF以及IE 10,IE 8和IE 7中进行了测试。

there's still a good bit of code that's needed to generate the data into an HTML page, but this can solve half of your request.

将数据生成到HTML页面中仍然需要很多代码,但这可以解决一半的请求。

in my .NET code, i had to build those JavaScript and HTML lists, which i can give in a few weeks.

在我的.NET代码中,我必须构建那些JavaScript和HTML列表,我可以在几周内完成。

#3


0  

You can use Dictionary objects to populate hierarchical data. This code sample might be helpful:

您可以使用Dictionary对象填充分层数据。此代码示例可能会有所帮助:

Dictionary<string, Dictionary<string, List<string>>> dictPage = new Dictionary<string, Dictionary<string, List<string>>>();

foreach (DataRow row in dt.Rows)
{
    string sPageID = row["PageName"].ToString();
    string sVersionID = row["VersionName"].ToString();
    string sElementID = row["ElementName"].ToString();

    if (!dictPage.ContainsKey(sPageID))
        dictPage.Add(sPageID, new Dictionary<string, List<string>>());

    if (!dictPage[sPageID].ContainsKey(sVersionID))
        dictPage[sPageID].Add(sVersionID, new List<string>());

    dictPage[sPageID][sVersionID].Add(sElementID);
}

I have used DataTable in this sample code, but you can use the same with SqlDataReader.

我在此示例代码中使用了DataTable,但您可以将其与SqlDataReader一起使用。

To create HTML you can do something like this:

要创建HTML,您可以执行以下操作:

StringBuilder sbHtmlUL = new StringBuilder();

sbHtmlUL.Append("<ul>");

foreach (var page in dictPage)
{
    sbHtmlUL.Append("<li>");
    sbHtmlUL.Append(page.Key);

    sbHtmlUL.Append("<ul>");

    foreach (var version in page.Value)
    {
        sbHtmlUL.Append("<li>");
        sbHtmlUL.Append(version.Key);

        sbHtmlUL.Append("<ul>");

        foreach (var element in version.Value)
        {
            sbHtmlUL.Append("<li>");
            sbHtmlUL.Append(element);
            sbHtmlUL.Append("</li>");
        }

        sbHtmlUL.Append("</ul>");
        sbHtmlUL.Append("</li>");
    }

    sbHtmlUL.Append("</ul>");
    sbHtmlUL.Append("</li>");
}

sbHtmlUL.Append("</ul>");

In this process, you can also apply CSS classes, IDs or simple JavaScript to add a collapse/expand behavior.

在此过程中,您还可以应用CSS类,ID或简单JavaScript来添加折叠/展开行为。

Please note that I did not compare performance of this code with other approaches so I am not so sure about it, but it took 8ms in a test, on an average machine, to process 8000 records.

请注意,我没有将此代码的性能与其他方法进行比较,因此我对此不太确定,但在一台普通计算机上测试需要8毫秒才能处理8000条记录。

#1


1  

I'd think about projecting your three different items to a single self-referencing list. Each item would need an Id, Description, and ParentId. I'd make a view model for this purpose.

我想把你的三个不同的项目投射到一个自引用列表中。每个项目都需要Id,Description和ParentId。我为此目的制作了一个视图模型。

public class TreeItem {
    public int Id {get; set;}
    public string Description {get; set;}
    public int ParentId {get; set;}
}

That would allow for you to leverage either an asp:TreeView in webforms or whatever flavor of jQuery tree / treeview if you're using MVC.

如果你正在使用MVC,这将允许你在webforms中使用asp:TreeView或者jQuery树/ treeview的任何风格。

#2


0  

i actually built something that does something very similar. i can give you the HTML source to mine but i'd need to modify the .NET code to remove company specific, proprietary information before releasing that publicly (plus convert it to C#).

我实际上构建了一些非常相似的东西。我可以给你我的HTML源代码但是我需要修改.NET代码以在公开发布之前删除公司特定的专有信息(并将其转换为C#)。

unfortunately for me to modify mine to your needs, it'd be almost up to one month before i'd be able to get around to it (due to a busy week before we take a 2 week vacation). but you can look at my HTML and see what you can reuse from it. from what i understand of your request, you'd be scratching about half of my JavaScript and its functionality. but if i were in your shoes, i'd appreciate this code to use as a starter and scrap what i didn't want and maybe add something. or you may just think my code is trash and not use it at all. ;P

不幸的是,我要根据你的需要修改我的,这几乎可以在我能够到达之前的一个月(由于我们休假前的一个忙碌的一周)。但您可以查看我的HTML并查看可以从中重用的内容。根据我对您的请求的理解,您将抓住我的一半JavaScript及其功能。但如果我在你的鞋子里,我会很感激这个代码用作启动器并废弃我不想要的东西并且可能添加一些东西。或者您可能只是认为我的代码是垃圾而根本不使用它。 ,P

so here's my HTML & JavaScript so you can run with it. It does handle the collapsing and expanding of nodes tho using ul and ol lists.

所以这是我的HTML和JavaScript,所以你可以运行它。它确实使用ul和ol列表来处理节点的折叠和扩展。

<!DOCTYPE HTML>
<html>
 <head><title>Oz-Med-Hist-VB</title>
  <meta charset='utf-8' />
  <meta http-equiv='X-UA-Compatible' content='IE=EmulateIE8' />
  <style type='text/css'>
    body { background: #CCC; margin-left: 1.5em; }
    li { list-style-type: none; text-indent: -38px; }

    .emptyGrayBox8 {
        background-color: #bbb; /* checked and unchecked background color */
        border: 1px solid #777; /* checked and unchecked border width & style & color */
        padding: 5.5px; display: inline-block; position: relative; margin: 0 3px; margin-top: 3px;
    }

    .checkInsideGrayBox:after {
        content: 'X';
        font-size: 13px;
        position: absolute;
        top: -2px;
        left: 1.5px;
        color: #555;
    }

    .checkInsideGrayBoxInsideLI:after {
        content: 'X';
        font-size: 13px;
        position: absolute;
        top: -2px;
        left: 39.5px;
        color: #555;
    }
  </style>
  <script>

    if (navigator.userAgent.indexOf('MSIE 8.') < 0)
    {
        document.write(
        '  <style>\n' +
        '   .checkInsideGrayBox               { left:  0.5px; }\n' +
        '   .checkInsideGrayBoxInsideLI:after { left: 38.5px; }\n' +
        '  </style>');
    }

var dta =
{   'checkedSubNodeCount--1'   : 0
,   'checkedSubNodeCount--19'  : 0
,   'checkedSubNodeCount--19-a': 0
,   'checkedSubNodeCount--19-b': 0
,   'checkedSubNodeCount--19-c': 0
,   'checkedSubNodeCount--22'  : 0
,   'checkedSubNodeCount--24'  : 0
,   'checkedSubNodeCount--25'  : 0
,   'checkedSubNodeCount--144' : 0
,   'checkedSubNodeCount--1728': 0
};

function chkBox_click(id) // this gets called when user clicks on checkbox or text to right of checkbox.
{ // when checkbox becomes checked, then associated group also becomes shown.
    var chkE = document.getElementById('chkBox--' + id); // CHecKBOX HTML element
    var grpE = document.getElementById('grpBox--' + id); // GRouPbox HTML element (UL element)
    var isChecked = chkE.checked;
    if (isChecked)
    {   // if user just checked the checkbox, and ...
        if (grpE != null) // if an associated (sub)group exists for this checkbox, ...
        { grpE.style.display = ''; } // then expand/show the group element.
    }
    setLabelHtm(id);
    var pid = getParentID(id); // Parent ID
    if (id == null) { return; } // if parent id doesn't exist then we're done here.

    // now 'pid' is parent ID of 'id'. for instance:
    // when id == '19-a'    then pid = '19'
    // when id == '19-a-1-' then pid = '19-a'

    var h = '';
    var maxLoopCount = 12; // infinite loop protection :P
    while (pid != null && --maxLoopCount >= 0)
    {
        chkE = document.getElementById('chkBox--' + pid); // CHecKBOX element *of parent*
        var pKey = 'checkedSubNodeCount--' + pid; // Key for this Parent ID
        if (isChecked) { ++dta[pKey]; } else { --dta[pKey]; }
        setLabelHtm(pid);
        if (h.length > 0) { h += '\n\n'; }
        h += 'id = ' + id + '   isChecked = ' + isChecked
        +   '\npid = ' + pid + '   chkE = ' + chkE
        +   '\ndta[\'' + pKey + '\'] = ' + dta[pKey];

        pid = getParentID(pid);
    }
//  alert(h);
} // function chkBox_click(id)

function chkBox_click8(id)
{
    var chkE = document.getElementById('chkBox--' + id); // CHecKBOX element
    var lblE = document.getElementById('chkLab--' + id); // CHecKbox LABel (HTML 'label' element for the checkbox)
    if (chkE == null || lblE == null) { return; }
    var isChecked = chkE.checked;
    var g = Number(chkE.tag);
    if (isChecked == false) { g = 3; chkE.tag = g; }
    if (isChecked == true ) { g = 2; chkE.tag = g; }
    alert(id + '\nisChecked = ' + isChecked + '\n.tag = ' + g);
} // function chkBox_click8(id)

function chkBox_clickIt(id)
{   var chkE = document.getElementById('chkBox--' + id); // CHecKBOX HTML element
    if (chkE != null) { chkE.click(); }
} // function chkBox_clickIt(id)

function getParentID(id)
{
    var pid = String(id); if (pid.length < 1) { return null; }
    if (pid[pid.length - 1] == '-') { pid = pid.substr(0, pid.length - 1); }
    var x = pid.lastIndexOf('-'); if (x < 0) { return null; }
    pid = pid.substr(0, x); // now pid is id of parent
    return pid;
} // function getParentID(id)

function hdrLab_click(id) // this will switch whether the associated group is hidden or shown
{// var chkE = document.getElementById('chkBox--' + id); // CHecKBOX HTML element
    var grpE = document.getElementById('grpBox--' + id); // GRouPBOX HTML element (UL element)
    if (grpE != null)
    {   if (grpE.style.display == '')
        {   grpE.style.display = 'none'; }
        else
        {   grpE.style.display = ''; }
    }
    setLabelHtm(id);
} // function hdrLab_click(id)

function setLabelHtm(id)
{   var grpE = document.getElementById('grpBox--' + id); // GRouPBOX HTML element (UL element)
    var lblE = document.getElementById('hdrLab--' + id);
    if (lblE == null) { return; }
    var h = null; var suffix = '.';
    if (grpE == null) { h = '<span style="visibility: hidden">+</span>'; }
    else
    {   var grpIsOpen = grpE.style.display == '';
        h = '<b>' + (grpIsOpen ? '−' : '+') + '</b>';
    }
    var s = String(id);
    if (s.length > 0 && s.substr(s.length - 1) == '-') { s = '&bull;'; suffix = ''; }
    else
    {
        var x = s.lastIndexOf('-') + 1;
        if (x > 0) { s = s.substr(x); }
    }
    h += '&nbsp;' + s + suffix;

    var cnt = dta['checkedSubNodeCount--' + id]; // CouNT of checked sub-nodes
    if (cnt != null) // if this node is a parent node
    {   var chkE = document.getElementById('chkBox--' + id); // CHecKBOX HTML element
        if (chkE != null) { chkE.style.display = (cnt > 0) ? 'none' : ''; }
        var hBeg = '<span class="emptyGrayBox8">';
        var hEnd = '</span>';
        if (cnt > 0) { h += hBeg + '<span class="checkInsideGrayBoxInsideLI"></span>' + hEnd; }
        else if (chkE == null) { h += hBeg + hEnd; }
    }
    lblE.innerHTML = h;
} // function setLabelHtm(id)

function init()
{   //alert('yes');
    hdrLab_click('1');
    hdrLab_click('1-a');
    hdrLab_click('19');
    hdrLab_click('19-a');
    hdrLab_click('19-a-1-');
    hdrLab_click('19-a-2-');
    hdrLab_click('19-b');
    hdrLab_click('19-b-1-');
    hdrLab_click('19-b-2-');
    hdrLab_click('19-b-3-');
    hdrLab_click('19-c');
    hdrLab_click('19-c-1-');
    hdrLab_click('19-c-2-');
    hdrLab_click('19-c-3-');
    hdrLab_click('22');
    hdrLab_click('22-a');
    hdrLab_click('22-b');
    hdrLab_click('23');
    hdrLab_click('24');
    hdrLab_click('24-a');
    hdrLab_click('24-b');
    hdrLab_click('24-c');
    hdrLab_click('25');
    hdrLab_click('25-a-');
    hdrLab_click('144');
    hdrLab_click('144-a');
    hdrLab_click('1728');
    hdrLab_click('1728-a');
//  alert(dta['checkedSubNodeCount--19-a']);
}
    window.onload = init;
  </script>
 </head>
 <body>
<ul style='list-style-type: none; margin-left: -1em'>
    <li><label id='hdrLab--1' onclick='hdrLab_click("1")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--1' onclick='hdrLab_click("1")')>&nbsp;Test for short number '1':</label></li>
    <ul id='grpBox--1'>
        <li><label id='hdrLab--1-a' onclick='hdrLab_click("1-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--1-a' onclick='chkBox_click("1-a");' /><label id='txtLab--1-a' onclick='chkBox_clickIt("1-a")')>Test for short number '1' subitem:</label></li>
    </ul>
    <li><label id='hdrLab--19' onclick='hdrLab_click("19")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--19' onclick='hdrLab_click("19")')>&nbsp;Which fruit do you prefer?</label></li>
    <ul id='grpBox--19'>
        <li><label id='hdrLab--19-a' onclick='hdrLab_click("19-a")' tabindex='0' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-a' onclick='chkBox_click("19-a");' /><label id='txtLab--19-a' onclick='chkBox_clickIt("19-a")')>Apples:</label></li>
        <ul id='grpBox--19-a'>
            <li><label id='hdrLab--19-a-1-' onclick='hdrLab_click("19-a-1-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-a-1-' onclick='chkBox_click("19-a-1-");' /><label id='txtLab--19-a-1-' onclick='chkBox_clickIt("19-a-1-")')>Red delicious</label></li>
            <li><label id='hdrLab--19-a-2-' onclick='hdrLab_click("19-a-2-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-a-2-' onclick='chkBox_click("19-a-2-");' /><label id='txtLab--19-a-2-' onclick='chkBox_clickIt("19-a-2-")')>Granny smith</label></li>
        </ul>
        <li><label id='hdrLab--19-b' onclick='hdrLab_click("19-b")' tabindex='0' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-b' onclick='chkBox_click("19-b");' /><label id='txtLab--19-b' onclick='chkBox_clickIt("19-b")')>Bananas:</label></li>
        <ul id='grpBox--19-b'>
            <li><label id='hdrLab--19-b-1-' onclick='hdrLab_click("19-b-1-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-b-1-' onclick='chkBox_click("19-b-1-");' /><label id='txtLab--19-b-1-' onclick='chkBox_clickIt("19-b-1-")')>Green</label></li>
            <li><label id='hdrLab--19-b-2-' onclick='hdrLab_click("19-b-2-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-b-2-' onclick='chkBox_click("19-b-2-");' /><label id='txtLab--19-b-2-' onclick='chkBox_clickIt("19-b-2-")')>Yellow (ripe but not too ripe)</label></li>
            <li><label id='hdrLab--19-b-3-' onclick='hdrLab_click("19-b-3-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-b-3-' onclick='chkBox_click("19-b-3-");' /><label id='txtLab--19-b-3-' onclick='chkBox_clickIt("19-b-3-")')>Brown (<i>very</i> ripe)</label></li>
        </ul>
        <li><label id='hdrLab--19-c' onclick='hdrLab_click("19-c")' tabindex='0' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-c' onclick='chkBox_click("19-c");' /><label id='txtLab--19-c' onclick='chkBox_clickIt("19-c")')>Juice</label></li>
        <ul id='grpBox--19-c'>
            <li><label id='hdrLab--19-c-1-' onclick='hdrLab_click("19-c-1-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-c-1-' onclick='chkBox_click("19-c-1-");' /><label id='txtLab--19-c-1-' onclick='chkBox_clickIt("19-c-1-")')>Orange juice</label></li>
            <li><label id='hdrLab--19-c-2-' onclick='hdrLab_click("19-c-2-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-c-2-' onclick='chkBox_click("19-c-2-");' /><label id='txtLab--19-c-2-' onclick='chkBox_clickIt("19-c-2-")')>Grape juice</label></li>
            <li><label id='hdrLab--19-c-3-' onclick='hdrLab_click("19-c-3-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--19-c-3-' onclick='chkBox_click("19-c-3-");' /><label id='txtLab--19-c-3-' onclick='chkBox_clickIt("19-c-3-")')>Tomato juice</label></li>
        </ul>
    </ul>
    <li><label id='hdrLab--22' onclick='hdrLab_click("22")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--22' onclick='hdrLab_click("22")')>&nbsp;Which juice do you prefer?</label></li>
    <ul id='grpBox--22'>
        <li><label id='hdrLab--22-a' onclick='hdrLab_click("22-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--22-a' onclick='chkBox_click("22-a");' /><label id='txtLab--22-a' onclick='chkBox_clickIt("22-a")')>Apple juice</label></li>
        <li><label id='hdrLab--22-b' onclick='hdrLab_click("22-b")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--22-b' onclick='chkBox_click("22-b");' /><label id='txtLab--22-b' onclick='chkBox_clickIt("22-b")')>Orange juice</label></li>
    </ul>
    <li><label id='hdrLab--23' onclick='hdrLab_click("23")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--23' onclick='chkBox_click("23");' /><label id='txtLab--23' onclick='chkBox_clickIt("23")')>Single checkmark question with no subnodes</label></li>
    <li><label id='hdrLab--24' onclick='hdrLab_click("24")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--24' onclick='hdrLab_click("24")')>&nbsp;Best OS?</label></li>
    <ul id='grpBox--24'>
        <li><label id='hdrLab--24-a' onclick='hdrLab_click("24-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--24-a' onclick='chkBox_click("24-a");' /><label id='txtLab--24-a' onclick='chkBox_clickIt("24-a")') style='color:green'>Android!</label></li>
        <li><label id='hdrLab--24-b' onclick='hdrLab_click("24-b")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--24-b' onclick='chkBox_click("24-b");' /><label id='txtLab--24-b' onclick='chkBox_clickIt("24-b")') style='color:brown'>Apple</label></li>
        <li><label id='hdrLab--24-c' onclick='hdrLab_click("24-c")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--24-c' onclick='chkBox_click("24-c");' /><label id='txtLab--24-c' onclick='chkBox_clickIt("24-c")')>Linux</label></li>
    </ul>
    <li><label id='hdrLab--25' onclick='hdrLab_click("25")' tabindex='0' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--25' onclick='chkBox_click("25");' /><label id='txtLab--25' onclick='chkBox_clickIt("25")')>Check question with subnode check too.</label></li>
    <ul id='grpBox--25'>
        <li><label id='hdrLab--25-a-' onclick='hdrLab_click("25-a-")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--25-a-' onclick='chkBox_click("25-a-");' /><label id='txtLab--25-a-' onclick='chkBox_clickIt("25-a-")')>Sub-node check question</label></li>
    </ul>
    <li><label id='hdrLab--144' onclick='hdrLab_click("144")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--144' onclick='hdrLab_click("144")')>&nbsp;Test for 3-digit number:</label></li>
    <ul id='grpBox--144'>
        <li><label id='hdrLab--144-a' onclick='hdrLab_click("144-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--144-a' onclick='chkBox_click("144-a");' /><label id='txtLab--144-a' onclick='chkBox_clickIt("144-a")')>Test for 3-digit number subitem:</label></li>
    </ul>
    <li><label id='hdrLab--1728' onclick='hdrLab_click("1728")' tabindex='0' onkeypress='this.click(); return false;'></label><label id='txtLab--1728' onclick='hdrLab_click("1728")')>&nbsp;Test for 4-digit number:</label></li>
    <ul id='grpBox--1728'>
        <li><label id='hdrLab--1728-a' onclick='hdrLab_click("1728-a")' onkeypress='this.click(); return false;'></label><input type='checkbox' id='chkBox--1728-a' onclick='chkBox_click("1728-a");' /><label id='txtLab--1728-a' onclick='chkBox_clickIt("1728-a")')>Test for 4-digit number subitem:</label></li>
    </ul>
</ul>
<div style='display: none'>
<script>
    var ua = navigator.userAgent;
    var ieVersion = null;
    var ix = 0;
    for (var v = 7; v <= 99; v++)
    {
        document.write('<!--[if IE ' + v + ']><hr />According to the conditional comment this is IE ' + v + '.<![endif]-->');
        if ((ix = ua.indexOf('MSIE ' + v + '.')) >= 0)
        {   ieVersion = '';
            ix += 5;
            while (ix < ua.length && '1234567890.'.indexOf(ua.charAt(ix)) >= 0)
            {   ieVersion += ua.charAt(ix++);
            }   document.write('<hr />According to JavaScript, this is IE ' + ieVersion);
        }
    }
    if (ieVersion == null)
    {   document.write('<hr />According to JavaScript, this IE version could not be determined.');
    }   document.write('<hr />navigator.userAgent = ' + navigator.userAgent);
</script><hr />
    <button id='webBtn1'>Fire an event</button>
    <button id='btnHi' onclick='window.external.sayHelloFromJavaScript("Hello from JavaScript!", 5);'>Say Hello</button>
</div>
 </body>
</html>

i also note that my project used a WebControl built for IE that needed to work as far back as WinXP. so i was limited in what i could use since it had to work for IE 7, but the nice thing is it works for way back to WinXP's latest working version of IE. i also care about using standards compliant code, so to my knowledge, my JavaScript also works for any standards-compliant browsers. i tested it in latest versions of Chrome and FF, and IE 10, IE 8 and IE 7.

我还注意到我的项目使用了为IE构建的WebControl,它需要像WinXP一样工作。因为我必须为IE 7工作,所以我可以使用的东西有限,但好的是它可以回到WinXP的最新工作版IE。我也关心使用符合标准的代码,因此据我所知,我的JavaScript也适用于任何符合标准的浏览器。我在最新版本的Chrome和FF以及IE 10,IE 8和IE 7中进行了测试。

there's still a good bit of code that's needed to generate the data into an HTML page, but this can solve half of your request.

将数据生成到HTML页面中仍然需要很多代码,但这可以解决一半的请求。

in my .NET code, i had to build those JavaScript and HTML lists, which i can give in a few weeks.

在我的.NET代码中,我必须构建那些JavaScript和HTML列表,我可以在几周内完成。

#3


0  

You can use Dictionary objects to populate hierarchical data. This code sample might be helpful:

您可以使用Dictionary对象填充分层数据。此代码示例可能会有所帮助:

Dictionary<string, Dictionary<string, List<string>>> dictPage = new Dictionary<string, Dictionary<string, List<string>>>();

foreach (DataRow row in dt.Rows)
{
    string sPageID = row["PageName"].ToString();
    string sVersionID = row["VersionName"].ToString();
    string sElementID = row["ElementName"].ToString();

    if (!dictPage.ContainsKey(sPageID))
        dictPage.Add(sPageID, new Dictionary<string, List<string>>());

    if (!dictPage[sPageID].ContainsKey(sVersionID))
        dictPage[sPageID].Add(sVersionID, new List<string>());

    dictPage[sPageID][sVersionID].Add(sElementID);
}

I have used DataTable in this sample code, but you can use the same with SqlDataReader.

我在此示例代码中使用了DataTable,但您可以将其与SqlDataReader一起使用。

To create HTML you can do something like this:

要创建HTML,您可以执行以下操作:

StringBuilder sbHtmlUL = new StringBuilder();

sbHtmlUL.Append("<ul>");

foreach (var page in dictPage)
{
    sbHtmlUL.Append("<li>");
    sbHtmlUL.Append(page.Key);

    sbHtmlUL.Append("<ul>");

    foreach (var version in page.Value)
    {
        sbHtmlUL.Append("<li>");
        sbHtmlUL.Append(version.Key);

        sbHtmlUL.Append("<ul>");

        foreach (var element in version.Value)
        {
            sbHtmlUL.Append("<li>");
            sbHtmlUL.Append(element);
            sbHtmlUL.Append("</li>");
        }

        sbHtmlUL.Append("</ul>");
        sbHtmlUL.Append("</li>");
    }

    sbHtmlUL.Append("</ul>");
    sbHtmlUL.Append("</li>");
}

sbHtmlUL.Append("</ul>");

In this process, you can also apply CSS classes, IDs or simple JavaScript to add a collapse/expand behavior.

在此过程中,您还可以应用CSS类,ID或简单JavaScript来添加折叠/展开行为。

Please note that I did not compare performance of this code with other approaches so I am not so sure about it, but it took 8ms in a test, on an average machine, to process 8000 records.

请注意,我没有将此代码的性能与其他方法进行比较,因此我对此不太确定,但在一台普通计算机上测试需要8毫秒才能处理8000条记录。