Home > front end >  Convert hierarchical dictionary to HTML nested dropdown
Convert hierarchical dictionary to HTML nested dropdown

Time:01-13

I have a list of dicts like this, but we may have more depth and so more id and pids ...

WIDGETS = [
    {"id": 1, "pid": 0, "url": "/upload"},
    {"id": 2, "pid": 0, "url": "/entry"},
    {"id": 3, "pid": 0, "url": "/report"},
    {"id": 4, "pid": 3, "url": "/reppremium"},
    {"id": 5, "pid": 4, "url": "/reppremiumsum"},
    {"id": 6, "pid": 4, "url": "/reppremiumfull"},
    {"id": 7, "pid": 3, "url": "/repcommission"},
    {"id": 8, "pid": 7, "url": "/repcommissionsum"},
    {"id": 9, "pid": 7, "url": "/repcommissionfull"},
    {"id": 10, "pid": 3, "url": "/repportions"},
    {"id": 11, "pid": 10, "url": "/repportionssum"},
    {"id": 12, "pid": 10, "url": "/repportionsfull"},
    {"id": 13, "pid": 0, "url": "/adduser"},
    {"id": 14, "pid": 0, "url": "/exportdb"},
    {"id": 15, "pid": 0, "url": "/importdb"},
]

and i want to convert it to a HTML nested / multilevel dropdown like this:

main menu -> /upload
             /report -> /reppremium    ->  /reppremiumsum
                                       ->   /reppremiumfull

                     -> /repcommission ->  /repcommissionsum
                                       ->  /repcommissionfull


                     -> /repportions   ->  /repportionssum
                                       ->  /repportionsfull
             /adduser
             /exportdb
             /importdb

i have tried some codes , but it doesnt work properly , as i know it needs a recursive function...

def get_widgets(widgets,text='',pid=0,text_m=''):
    childs = get_childs(pid,widgets)
    for child in childs:
 
        pidn = child['id']
        n = get_childs(pidn,widgets)
        print(n,'for id',pidn)


        if len(n) != 0:
            text  = f'''
            <li >
            <a  href="{ child['url'] }" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                { child['url'] }
            </a>
            <ul >
            '''
            pid_new = child['id']
            get_widgets(widgets,text,pid_new)
            text  = '</ul></li>'
            print(text)
            text_m  = text
        else:
            #print(n,'n')
            text  = f'''
            <li >
                <a  href="{ child['url'] }">{ child['url'] }</a>
            </li>
            ''' 
            text_m  = text
    


        text = ''



 
    return tex

i expect the code to show an html multilevel dropdown ... but populated by WIDGETS...

CodePudding user response:

As you've guessed this should be done recursively and there are several approaches to solve it.

Here is the complete code:

WIDGETS = [
    {"id": 1, "pid": 0, "url": "/upload"},
    {"id": 2, "pid": 0, "url": "/entry"},
    {"id": 3, "pid": 0, "url": "/report"},
    {"id": 4, "pid": 3, "url": "/reppremium"},
    {"id": 5, "pid": 4, "url": "/reppremiumsum"},
    {"id": 6, "pid": 4, "url": "/reppremiumfull"},
    {"id": 7, "pid": 3, "url": "/repcommission"},
    {"id": 8, "pid": 7, "url": "/repcommissionsum"},
    {"id": 9, "pid": 7, "url": "/repcommissionfull"},
    {"id": 10, "pid": 3, "url": "/repportions"},
    {"id": 11, "pid": 10, "url": "/repportionssum"},
    {"id": 12, "pid": 10, "url": "/repportionsfull"},
    {"id": 13, "pid": 0, "url": "/adduser"},
    {"id": 14, "pid": 0, "url": "/exportdb"},
    {"id": 15, "pid": 0, "url": "/importdb"},
]


def build_HTML(d, text=""):
    for (id_, url), children_list in d.items():
        text  = f"<li>{url}"

        if children_list:
            text  = "<ul>"
            for dd in children_list:
                text  = build_HTML(dd, "")
            text  = "</ul>"

        text  = "</li>"

    return text


def set_recursive(obj, d):
    for (id_, url), children_list in d.items():
        if obj["pid"] == id_:
            children_list.append({(obj["id"], obj["url"]): []})
            return
        else:
            for item in children_list:
                set_recursive(obj, item)



root = {(0, "/main-menu"): []}
while WIDGETS:
    d = WIDGETS[0]
    set_recursive(d, root)
    del WIDGETS[0]

print(f"<ul>{build_HTML(root)}</ul>")

output:

<ul><li>/main-menu<ul><li>/upload</li><li>/entry</li><li>/report<ul><li>/reppremium<ul><li>/reppremiumsum</li><li>/reppremiumfull</li></ul></li><li>/repcommission<ul><li>/repcommissionsum</li><li>/repcommissionfull</li></ul></li><li>/repportions<ul><li>/repportionssum</li><li>/repportionsfull</li></ul></li></ul></li><li>/adduser</li><li>/exportdb</li><li>/importdb</li></ul></li></ul>

Explanation:

In set_recursive() function, we basically create another data structure in form of:

{(0, '/main-menu'): [{(1, '/upload'): []},
                     {(2, '/entry'): []},
                     {(3, '/report'): [{(4, '/reppremium'): [{(5, '/reppremiumsum'): []},
                                                             {(6, '/reppremiumfull'): []}]},
                                       {(7, '/repcommission'): [{(8, '/repcommissionsum'): []},
                                                                {(9, '/repcommissionfull'): []}]},
                                       {(10, '/repportions'): [{(11, '/repportionssum'): []},
                                                               {(12, '/repportionsfull'): []}]}]},
                     {(13, '/adduser'): []},
                     {(14, '/exportdb'): []},
                     {(15, '/importdb'): []}]}

This is now a dictionary of elements which keeps track of every sub-elements. The way it works is that for every element, it recursively checks the data structure to see if it can find its "parent". If it finds, it belongs to that list. After we handle that we just delete it from the records.

In build_HTML() function you create your string in its most compressed form(browser's parser doesn't care).

The main point in this function is that you need to open <ul> before entering to the children_list and close it after that.

Note: This solution works properly because your data is sorted in the way that for appending every child, it's parent is already appended.

If you render that string:

<!DOCTYPE html>
<html>

<head>
</head>


<body>

    <ul><li>/main-menu<ul><li>/upload</li><li>/entry</li><li>/report<ul><li>/reppremium<ul><li>/reppremiumsum</li><li>/reppremiumfull</li></ul></li><li>/repcommission<ul><li>/repcommissionsum</li><li>/repcommissionfull</li></ul></li><li>/repportions<ul><li>/repportionssum</li><li>/repportionsfull</li></ul></li></ul></li><li>/adduser</li><li>/exportdb</li><li>/importdb</li></ul></li></ul>

</body>

The beautified version of that string is:

<ul>
    <li>/main-menu
        <ul>
            <li>/upload</li>
            <li>/entry</li>
            <li>/report<ul>
                    <li>/reppremium
                        <ul>
                            <li>/reppremiumsum</li>
                            <li>/reppremiumfull</li>
                        </ul>
                    </li>
                    <li>/repcommission
                        <ul>
                            <li>/repcommissionsum</li>
                            <li>/repcommissionfull</li>
                        </ul>
                    </li>
                    <li>/repportions
                        <ul>
                            <li>/repportionssum</li>
                            <li>/repportionsfull</li>
                        </ul>
                    </li>
                </ul>
            </li>
            <li>/adduser</li>
            <li>/exportdb</li>
            <li>/importdb</li>
        </ul>
    </li>
</ul>
  • Related