Displaying categories and forums using <optgroup> - Can't figure out the loop logic!

TheBigK

Well-known member
I'm scratching my head over this for long time now and there doesn't seem to be a straightforward solution. My $viewParams pass on $forums array; which has the rows fetched from xf_node table. In my template, I want to show a drop-down that lists the categories and forums as follows -

Category 1
--Forum1
--Forum2
Category2
--Forum3
--Forum4

I'm planning to use <optgroup> to display the output; but the loop I'm trying to write in my template isn't very straightforward.

My best attempt so far is this: -

PHP:
<select name="node_id" class="textCtrl">
                <xen:foreach loop="$forums" value="$forum">
                    <xen:if is="{$forum.node_type_id} == 'Category'">
                        <optgroup label="{$forum.title}">
                    </xen:if>
                        <option value="{$forum.node_id}">{$forum.title}</option>
                    </optgroup>
                </xen:foreach>
            </select>

But that obviously is wrong because it shows the output as follows -

Screen Shot 2015-07-01 at 3.23.29 pm.webp


Would really appreciate some help fixing my loop.
 
Thanks @Chris D . I referred to the above addon and found this code relevant -

PHP:
<xen:foreach loop="$preparedOption.options" value="$option">
                <xen:if is="{$option.type} == 'Category'">
                    <optgroup label="{$option.title}">
                <xen:else />
                    <option value="{$option.node_id}" {xen:selected "{$option.selected}"}>{$option.title}</option>
                </xen:if>
            </xen:foreach>

Now that looks very straightforward. Why does it not close the <optgroup>? All my trouble was from this: </optgroup> . I also searched for similar examples and found this: Select Dropdown PHP foreach loop with 'optgroup' options - Stack Overflow , which I found difficult to understand.
 
Thanks @Chris D . I referred to the above addon and found this code relevant -

PHP:
<xen:foreach loop="$preparedOption.options" value="$option">
                <xen:if is="{$option.type} == 'Category'">
                    <optgroup label="{$option.title}">
                <xen:else />
                    <option value="{$option.node_id}" {xen:selected "{$option.selected}"}>{$option.title}</option>
                </xen:if>
            </xen:foreach>

Now that looks very straightforward. Why does it not close the <optgroup>? All my trouble was from this: </optgroup> . I also searched for similar examples and found this: Select Dropdown PHP foreach loop with 'optgroup' options - Stack Overflow , which I found difficult to understand.
Most web browsers would close it after the last option value as a fallback.

You could utilise the hascontent parameter inside a <xen:if> and to close the optgroup yourself if content exists. Though, given it is node IDs and they're guaranteed to exist, you could just close it after the loop.

xen:if hascontent | XenForo Community
 
Thanks, James. I wasn't aware that I could do it without closing the <optgroup>. It's something new I learned today. Got the results I wanted. Thanks! :)
 
It isn't by any means "semantic" and most web page analysers (pagespeed etc) will probably flag it as not being proper, but as long as it works!
 
Just for the sake of learning, is there a way to code this loop and have the <optgroup> closed properly?
 
Just for the sake of learning, is there a way to code this loop and have the <optgroup> closed properly?
Close it after the loop manually (i.e. insert an </optgroup>) or, if you want to be "proper" then you can use XenForo contentcheck and check if the loop actually has any values (which it will always but if it's for learning purposes...)
See Mike's example here: xen:if hascontent
 
Here is an example of how I generated a forum list for selection, complete with indentation for child forums:

PHP:
        $viewParams = array(
            'roster' => $roster,
            'userGroups' => $userGroups,
            'categories' => $this->_getRosterCategoryModel()->getCategoriesForSelection(),
            'nodes' => $this->_getNodeModel()->getAllNodes()
        );
        return $this->responseView('Icewind_Rosters_ViewAdmin_Roster_Edit', 'iwd_roster_edit', $viewParams);

Code:
            <ul>       
                <li>
                <select name="forum_ids" class="textCtrl" size="8" multiple="true">
                    <option value="" selected="{!$roster.forum_ids}">{xen:phrase iwd_roster_no_forums}</option>
                    <xen:foreach loop="$nodes" value="$node">
                        <option value="{$node.node_id}"
                            {xen:if "{$node.node_type_id} != 'Forum'", 'disabled="disabled"'}
                            {xen:if "{$roster.forum_ids} AND in_array({$node.node_id}, {$roster.forum_ids})", 'selected="selected"'}>
                            {xen:string repeat, '&nbsp; &nbsp; ', $node.depth}{$node.title}
                        </option>
                    </xen:foreach>
                </select>
                </li>
            </ul>
 
Thanks, @James . Could you please help me set a focus on a specific forum? How can I include that in my loop?

Ideally, I'd like the drop-down to list only the forums user has permissions to post threads in.
 
HTML:
<select name="node_id" class="textCtrl">
                <xen:foreach loop="$forums" value="$forum">
                    <xen:if is="{$forum.node_type_id} == 'Category'">
                        <optgroup label="{$forum.title}">
                    </xen:if>
                        <option value="{$forum.node_id}">{$forum.title}</option>
                    </optgroup>
                </xen:foreach>
            </select>

The code above is wrong, as it is missing another category check to properly close the optgroup, it should be this:
HTML:
<select name="node_id" class="textCtrl">
                <xen:foreach loop="$forums" value="$forum">
                    <xen:if is="{$forum.node_type_id} == 'Category'">
                        <optgroup label="{$forum.title}">
                    </xen:if>
                        <option value="{$forum.node_id}">{$forum.title}</option>
                    <xen:if is="{$forum.node_type_id} == 'Category'">
                        </optgroup>
                    </xen:if>
                </xen:foreach>
            </select>

Thanks, @James
Ideally, I'd like the drop-down to list only the forums user has permissions to post threads in.

The quick navigation menu would be a good example for generating a list of nodes based on permissions, :)
 
@Lawrence - I think it should <xen:else /> instead of </xen:if> for the first IF condition. The code above repeats the category name.

After looking at that code, adding the else wont work either, as the optgroup will just be opened and then immediate closed. IIRC that may be the reason I use the example from my roster add-on to generate the list of forums (it's been a while, and I really can't remember why I chose that way).

Anyways to get this to work properly:
Code:
<select name="node_id" class="textCtrl">
                <xen:foreach loop="$forums" value="$forum">
                    <xen:if is="{$forum.node_type_id} == 'Category'">
                        <optgroup label="{$forum.title}">
                    </xen:if>
                        <option value="{$forum.node_id}">{$forum.title}</option>
                    <xen:if is="{$forum.node_type_id} == 'Category'">
                        </optgroup>
                    </xen:if>
                </xen:foreach>
            </select>
some extra code would have to be added in to check for the next category so we can close the previous optgroup and open a new one. I'll see what I can come up with.
 
Not tested, but this *should* work, :)

HTML:
<select name="node_id" class="textCtrl">
    <xen:set var="$closeGroup">0</xen:set>
    <xen:foreach loop="$forums" value="$forum">
        <xen:if is="{$forum.node_type_id} == 'Category' && !{$closeGroup}">
            <optgroup label="{$forum.title}">
            <xen:set var="$closeGroup">1</xen:set>
        <xen:elseif is="{$forum.node_type_id} == 'Category' && {$closeGroup}" />
            </optgroup>
            <xen:set var="$closeGroup">0</xen:set>
        </xen:else />
            <option value="{$forum.node_id}">{$forum.title}</option>
        </xen:if>
    </xen:foreach>
    <xen:comment>Now close the last optgroup (or only optgroup, and if there was a category) after the foreach completes</xen:comment>
    <xen:if is="{$closeGroup}">
        </optgroup>
    </xen:if>
</select>
 
Top Bottom