Search code examples
coldfusionmenucfml

Build dynamic navigation with list headers (coldfusion & cfscript)


For a project im building a navigation. The table is like this

SELECT TOP 1000 
    [id]
    ,[title]
    ,[action]
    ,[listOrder]
    ,[fk_parentId]
FROM [portfolio].[dbo].[menu]

Where Fk_parentId refers to a id... to build up a menu with levels. Listorder contains a number

Now i want my navigation to output like this

<ul class="nav nav-list">
       <li class="nav-header active">List header</li>
       <li class="active"><a href="#">Home</a></li>
       <li><a href="#">Library</a></li>
       <li><a href="#">Applications</a></li>
       <li class="nav-header">Another list header</li>
       <li><a href="#">Profile</a></li>
       <li><a href="#">Settings</a></li>
       <li class="divider"></li>
       <li><a href="#">Help</a></li>
</ul>

so the nav headers must be detected as a nav header and menu items as child. For now i have this code

public void function main(struct rc) {

    queryService = new query(); 
    queryService.setDatasource("portfolio"); 
    result = queryService.execute(sql="SELECT * FROM menu ORDER by listOrder"); 


    // result
    GetMenuData = result.getResult(); 


    // Loopen over result
    writeOutput("<ul class='nav nav-list>'");
    for (i = 1; i LTE GetMenuData.RecordCount; i = (i + 1))
    {

        // Output
        WriteOutput(
        "<li><a href='"& GetMenuData[ "action" ][ i ] & "'>" & GetMenuData[ "title" ][ i ] & "</a></li>"
        );

    }
    writeOutput("</ul>'");

}

this results:

    <ul class='nav nav-list>'
        <li><a href='alk.profile'>PROFILE</a></li>
        <li><a href=''>List header</a></li>
        <li><a href='main.'>home</a></li>
        <li><a href=''>Another List header</a></li>
        <li><a href='alh.settings'>settings</a></li>
        <li><a href='main.library'>librarY</a></li>
        <li><a href='help.main'>Help</a></li>
        <li><a href='main.applications'>applications</a></li>
    </ul>'

How can I add class header to a "header" <li> like listheader, another list header?

How can i dynamicly add the divider between settings and help?

title                   action              listOrder   fk_parentId
Another List header     NULL                20          NULL
PROFILE                 alk.profile         5           539BB1A4-5AB5-4059-93AD-17DD8EABAF60
Help                    help.main           40          NULL
settings                alh.settings        20          539BB1A4-5AB5-4059-93AD-17DD8EABAF60
applications            main.applications   50          C5EFAE69-FD2A-4B35-A613-B8D429091A8F
List header             NULL                10          NULL
home                    main.               20          C5EFAE69-FD2A-4B35-A613-B8D429091A8F
librarY                 main.library        30          C5EFAE69-FD2A-4B35-A613-B8D429091A8F

Solution

  • I can't see how you specify active from what you posted, but the following at least would result in the function returning the list with header classes.

    public string function main(struct rc) {
    
        // Set up the return string
        var strReturn = '<ul class="nav nav-list">';
    
        // Set up the query
        var queryService = new Query(
            datasource='portfolio'
        ); 
    
        // Execute and get result, specifying field names
        var GetMenuData = queryService.execute(sql='
            SELECT 
                id,
                action,
                title,
                fk_parentId
            FROM menu
            ORDER by listOrder ASC
        ').getResult();
    
        // Loop over result
        for (var i = 1; i <= GetMenuData.RecordCount; i++) {
    
            // For this result, what classes are needed?
            var strClasses = '';
    
            // Header class
            if (
                Len(GetMenuData['fk_parentId'][ i ]) == 0 // Null / len 0 parent == header
            ) {
                strClasses = ListAppend(strClasses,'nav-header',' ');
            }
    
            // Add in some logic here for 'active' later on probably a good idea?
            // strClasses = ListAppend(strClasses,'active',' ') if id == active id? May need adjustment to query for parent
    
            if (
                Len(strClasses) > 0
            ) {
                strClasses  = ' class="'&strClasses&'"';
            }
    
            // Output list item
            strReturn &= '<li'&strClasses&'>';
    
                // Add href if needed
                if (
                    Len(GetMenuData['action'][ i ]) > 0
                ) {
                    strReturn &= '<a href="'&GetMenuData['action'][ i ]&'">';
                }
    
                strReturn &= GetMenuData['title'][ i ];
    
                if (
                    Len(GetMenuData['action'][ i ]) > 0
                ) {
                    strReturn &= '</a>';
                }
    
            // Close off the list item
            strReturn &= '</li>';
        }
    
        // End the return string
        strReturn &= '</ul>';
    
        // And return it
        return strReturn;
    }
    

    A few notes on the changes

    • Changed function to have return type "string" instead of "void" and modified it so it returns a string instead of directly writing. This in general allows more control over where precisely the list would be output.
    • Added scoping for all variables invoked within the function (using 'var' keyword). Please note that the below example will only work in CF9/railo. If using a previous version then the var definitions need to be at top of the function, if unsure just ask
    • Left a space to indicate where you would add in logic for flagging "active"
    • Used i++ in place of i = (i + 1) (see What is the difference between ++i and i++? for some info for what that does)
    • Used strReturn &= ... to add to string, functionally equivilant to strReturn = strReturn & ....