Skip to main content
September 1, 2009
Answered

Displaying Nested Sets

  • September 1, 2009
  • 2 replies
  • 2293 views

Hello~

I am just starting to use the nested set model for storing hierarchical data in a database, and while I understand the concept, I am not at all sure how to actually display the data in, say, an unordered list! Right now, I am using the following query to retrieve my list:

SELECT
        node.pageName
    FROM
        admin_nav AS node,
        admin_nav AS parent
    WHERE
        node.lft BETWEEN parent.lft AND parent.rght
        AND parent.pageName = 'Assessment'
    ORDER BY
        node.lft

It outputs correctly if I cfdump it, but I am not sure how to create a loop that would put the <ul> and <li> tags in the correct places. I have searched for some kind of tutorial about this, but no luck! Any help would be appreciated. Thanks!

KC

    This topic has been closed for replies.
    Correct answer Adam Cameron.

    Whenever I have needed to do an output like that of an arbitary depth I found a recursive function to be the easiest way to acomplish it.

    You put your <cfoutput....> logic into a <cffunction...> tag.  Pass in the query and start looping over the top loop.  Then inside that loop test to see if that node has child nodes, if so call the same function to output that level.

    I don't really have a code sample I can lay my fingers on at the moment, but if this question is still open in a few hours I may have time to cook something up.

    HTH

    Ian


    Firstly to Ian: the whole idea behind using nested sets for hierarchies is so that one doesn't need to use recursion.  So falling back on recursion - whilst it'll work - kind of defeats the purpose of using nested sets.

    Now, to the problem.

    I think the OP is pretty much there, except for the handling of when to open and when to close the <ul/> blocks.

    I am presuming you have got your DEPTH values correct for this.  If not, we can come back to that.

    As you scan down your recordset (ordered by LEFT), if the DEPTH increases (from the previous record), you need to start a new UL, so instead of just rendering a <li/> (where that's the whole <li>label here, etc</li>), you need to rendered a <li><ul><li/>.  If the depth stays the same: just a <li/>.  If the depth decreases, you need to close as many <ul> tags as there is difference between the current and previous depth (because you could be at the end of more than one "generations", eg: it could be the youngest child of this generation of the youngest child of the preceding generation).  So that's </ul></li> for each level.

    I think using <ul> for this sort of thing is correct, but one could also use <div> tags, and this is easier.

    The query to generate the correct nesting is along these lines:

    select left as nodeOrder, 'OPEN' as nodeType, label, [other columns as necessary]

    from tbl_set

    union

    select right as nodeOrder, 'CLOSE' as nodeType, '' as label, [blanks for other columns]

    from tbl_set

    order by nodeOrder

    (you'll probably need a WHERE statement to suit your requirements here too).

    This gets the opening and closing nodes in the right order, as well as the correct number of closing nodes in the right sequence.  This is great for generating XML, and I suppose from there one could even use an XSLT to convert that to a <ul/> hierarchy.

    But at the very least, one can now simply loop over that record set and output a <div> for each OPEN and a </div> for each CLOSE, and you'll end up with a well-marked-up hierarchy.  It's just a matter of doing the CSS to display it nicely from there.

    Again, <ul> tags are the better sort of mark-up here, semantically.

    HTH.

    --

    Adam

    2 replies

    Inspiring
    September 1, 2009

    IIRC the records you would want to group share the same "parent" values. Look into whether you can use cfoutput "group" to produce the desired results

    September 1, 2009

    I have used the cfoutput group function previously, but I guess I am not seeing how it would help in this situation. Do you have an example? Thanks!

    Inspiring
    September 1, 2009

    I am not sure why my response posted twice, but correction.  I meant to say depth not parent (wrong model ;-)

    semi star gazer wrote:

    I have used the cfoutput group function previously, but I guess I am not seeing how it would help in this situation. Do you have an example? Thanks!

    Order the query results by "depth, left".   Then grouping by "depth".  It will work for a simple hierarchy. You will have to see whether or not the logic will fit with your display elements.

    <cfoutput query="getNav" group="depth">
        <cfoutput>
           #repeatString("--", depth)# #pageName#<br>
        </cfoutput>
    </cfoutput>

    Inspiring
    September 1, 2009

    IIRC the records you would want to group share the same "parent" values. Look into whether you can use cfoutput "group" to produce the desired results

    September 1, 2009

    I have update my query to retrieve the "depth" of each item in the list.

    <cfquery name="getNav" datasource="#application.db#">
        SELECT
            node.pageName,
            (COUNT(parent.pageName) - 1) AS depth
        FROM
            admin_nav AS node,
            admin_nav AS parent
        WHERE
            node.lft BETWEEN parent.lft AND parent.rght
        GROUP BY
            node.pageName,
            node.lft
        ORDER BY
            node.lft
    </cfquery>

    Then, I tried using something like this to generate my list:

    <cfoutput query="getNav">
        <cfloop index="i" to="#depth#" from="0">
            <ul>
                <li>
        </cfloop>
            #pageName#
        <cfloop index="i" to="#depth#" from="0">
                </li>
            </ul>
        </cfloop>
    </cfoutput>

    It actually kind of works, but it generates a lot of extra <ul> and <li> tags in the source code. I think I am probably kind of close to the solution I need, but I can't figure it out!