1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

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

Discussion in 'XenForo Development Discussions' started by TheBigK, Jul 1, 2015.

  1. TheBigK

    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="$forumsvalue="$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.png


    Would really appreciate some help fixing my loop.
     
  2. Chris D

    Chris D XenForo Developer Staff Member

    There is a forum picker in this add-on:

    Add Skimlinks Code | XenForo Community

    That makes use of optgroups. The mark up might be a little bit more complicated because it's for an option, but it should give you some idea of the logic required.
     
    TheBigK likes this.
  3. TheBigK

    TheBigK Well-Known Member

    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.
     
  4. James

    James Well-Known Member

    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
     
    TheBigK likes this.
  5. TheBigK

    TheBigK Well-Known Member

    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! :)
     
  6. James

    James Well-Known Member

    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!
     
  7. Chris D

    Chris D XenForo Developer Staff Member

    Heh, I had forgotten I had done it like that :)

    But, yeah, it should be fine.
     
  8. TheBigK

    TheBigK Well-Known Member

    Just for the sake of learning, is there a way to code this loop and have the <optgroup> closed properly?
     
  9. James

    James Well-Known Member

    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
     
  10. Lawrence

    Lawrence Well-Known Member

    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>
     
    TheBigK likes this.
  11. TheBigK

    TheBigK Well-Known Member

    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.
     
  12. Lawrence

    Lawrence Well-Known Member

    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>
    The quick navigation menu would be a good example for generating a list of nodes based on permissions, :)
     
    James likes this.
  13. TheBigK

    TheBigK Well-Known Member

    @Lawrence - I think it should <xen:else /> instead of </xen:if> for the first IF condition. The code above repeats the category name.
     
    Lawrence likes this.
  14. Lawrence

    Lawrence Well-Known Member

    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.
     
  15. Lawrence

    Lawrence Well-Known Member

    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>
     

Share This Page