Re: [mezzanine-users] multi-level bootstrap dropdown?

1,251 views
Skip to first unread message

Stephen McDonald

unread,
Oct 9, 2012, 3:44:17 PM10/9/12
to mezzani...@googlegroups.com
Hi Tim, it's a great question.

The current menu template for the top-level nav with dropdowns (mezzanine/pages/templates/pages/menu/dropdown.html) only support a single level of dropdowns. I believe the current version of Bootstrap used is 2.0.something, so we'd first need to upgrade to the latest Bootstrap version, and then upgrade the dropdown.html template to support multi-level dropdowns.

If you'd like to work on this it'd be a welcome addition. Keep in mind that for it to be merged in, we'd need to ensure the upgrade works across all the various templates, from the different page types in Mezzanine (forms, galleries, etc) all the way to Cartridge (the ecommerce app for Mezzanine). I'd be happy to assist in that audit if you want to kick off the initial work.

Otherwise it'll happen eventually at some point. I've created an open issue for the upgrade here: https://github.com/stephenmcd/mezzanine/issues/428



On Wed, Oct 10, 2012 at 5:10 AM, Tim Tate <pote...@gmail.com> wrote:
Hi,
I'm a total noob at this, so please forgive if this is a stupid question.
I have two levels of pages in my current site, "Our Team/Executive Team/Joe Smith, Jane Doe, etc", and have all the "Joe Smith"-level pages checked to show in the top menu.
But the menu only shows the single level in a dropdown - so I can only navigate to "Executive Team".
I know Bootstrap as of 2.1.1 supports multi-level dropdowns, (ex: http://jsfiddle.net/2Smgv/934/ ), how can I incorporate this into mezzanine's existing dropdown menus?

Or is there maybe just a setting I missed that already does this?

Thanks,
Tim



--
Stephen McDonald
http://jupo.org

Tim Tate

unread,
Oct 9, 2012, 6:58:31 PM10/9/12
to mezzani...@googlegroups.com, st...@jupo.org
I'd be happy to get it started, but I'm new to django and mezzanine, how would I create the loop that pulls the second-level menu items from level 1 items?

Stephen McDonald

unread,
Oct 9, 2012, 7:02:33 PM10/9/12
to Tim Tate, mezzani...@googlegroups.com
Have a look at the dropdown menu template:


and one of the other templates that recursively shows all levels, the tree menu is a good example:


Also have a read of the docs on how the menus work in general:


TLDR is that the dropdown menu template explicitly only shows the first two levels.

Brian Schott

unread,
Oct 10, 2012, 10:52:40 AM10/10/12
to mezzani...@googlegroups.com, st...@jupo.org
Tim,

The key line is:
        {% if page.has_children %}{% page_menu page %}{% endif %}
That recursively calls the template again for the next level.

I think the if branch_level ==1 changes to branch_level > 0, then need to add submenu logic to that test case.  Somebody clever might be able to merge the whole thing into a single recursive call and merge the two top-level if statements.  Right now, it's a bit of a hack.

Checkout the submenus on dropdowns example:

I'm not sure if this will work unless you update bootstrap to 2.1.  

Brian

---
{% load i18n pages_tags %}
{% spaceless %}
{% if page_branch_in_menu %}

{% if branch_level == 0 %}
<ul class="nav nav-collapse pull-left">
    {% for page in page_branch %}
    {% if page.in_menu %}
    {% if page.login_required and request.user.is_authenticated or not page.login_required %}
    <li class="dropdown{% if page.is_current_or_ascendant %} active{% endif %}"
        id="dropdown-menu-{{ page.html_id }}">
        <a href="{{ page.get_absolute_url }}">
            {{ page.title }}
            {% if page.has_children %}<span class="caret"></span>{% endif %}
        </a>
        {% if page.has_children %}{% page_menu page %}{% endif %}
    </li>
    <li class="divider-vertical"></li>
    {% endif %}
    {% endif %}
    {% endfor %}
</ul>
{% endif %}

{% if branch_level == 1 %}
<ul class="dropdown-menu">
    {% for page in page_branch %}
    {% if page.in_menu %}
    {% if page.login_required and request.user.is_authenticated or not page.login_required %}
    <li{% if page.is_current_or_ascendant %} class="active"{% endif %}
        id="dropdown-menu-{{ page.html_id }}">
        <a href="{{ page.get_absolute_url }}">{{ page.title }}</a>
    </li>
    {% endif %}
    {% endif %}
    {% endfor %}
</ul>
{% endif %}

{% endif %}
{% endspaceless %}



-------------------------------------------------
Brian Schott, CTO
Nimbis Services, Inc.


Tim Tate

unread,
Oct 10, 2012, 2:04:30 PM10/10/12
to mezzani...@googlegroups.com, st...@jupo.org
Thanks Stephen and Brian,
I think I'm headed in the right direction. I'm missing something though, probably just not coding right for Bootstrap. When I hover over the top-level menu item, all menu levels show, including the submenus, which of course shouldn't be showing until I hover over their parents.
Yes, I'm using Bootstrap 2.1.2, as when I got started I didn't realize I maybe shouldn't :)
(so far no problems there btw)

{% if branch_level > 0 %}
<ul class="dropdown-menu">
    {% for page in page_branch %}
    {% if page.in_menu %}
    {% if page.login_required and request.user.is_authenticated or not page.login_required %}
    <li{% if page.is_current_or_ascendant %} class="active {% if page.has_children %} dropdown-submenu{% endif %}"{% else %}
            {% if page.has_children %}class="dropdown-submenu"{% endif %}
            {% endif %}
        id="dropdown-menu-{{ page.html_id }}">
        <a href="{{ page.get_absolute_url }}">{{ page.title }}</a>
        {% if page.has_children %}{% page_menu page %}{% endif %}
    </li>
    {% endif %}
    {% endif %}
    {% endfor %}
</ul>
{% endif %}


On Tuesday, October 9, 2012 12:44:39 PM UTC-7, Stephen McDonald wrote:

Tim Tate

unread,
Oct 10, 2012, 2:24:11 PM10/10/12
to mezzani...@googlegroups.com, st...@jupo.org
Scratch that, I'm on 2.1.1. While the Bootstrap homepage says 2.1.2 is out, the link to that is currently 404, and the github repo has 2.1.1 as current.
Still, the menu HTML output looks right to me.

Tim Tate

unread,
Oct 10, 2012, 3:09:21 PM10/10/12
to mezzani...@googlegroups.com, st...@jupo.org
OK I think I've narrowed it down to this line in mezzanine.css in the navigation section:

ul.nav li.dropdown:hover ul.dropdown-menu {display:block;}

I'm not sure where to go from there though.

Brian Schott

unread,
Oct 10, 2012, 3:30:17 PM10/10/12
to mezzani...@googlegroups.com, st...@jupo.org
Yeah, I'm not sure half of what is in mezzanine.css should be there because it breaks standard bootswatch templates.  What happens if you take that out?  

This fiddle seems to dynamically change the classes for the submenus, might be worth a look.

-------------------------------------------------
Brian Schott, CTO
Nimbis Services, Inc.



Tim Tate

unread,
Oct 10, 2012, 3:58:33 PM10/10/12
to mezzani...@googlegroups.com, st...@jupo.org
I think I've got it working to my satisfaction at least.

So for 1, remove that line in mezzanine.css.
And the final trick was to add data-toggle="dropdown" class="dropdown-toggle active" to the <a> tag in the top-level nav.

The only downside to this, is that top-level link no longer goes anywhere, it just toggles the dropdown. In my case, that's fine, I didn't want to navigate to that page anyway. But it might be nice to figure out a way to get it to respond to a hover instead of a click.

Here's the dropdown.html code:

{% load i18n pages_tags %}
{% spaceless %}
{% if page_branch_in_menu %}

{% if branch_level == 0 %}
<ul class="nav nav-collapse pull-left">
    {% for page in page_branch %}
    {% if page.is_primary and forloop.first %}
    <li class="{% if on_home %} active{% endif %}" id="dropdown-menu-{{ page.html_id }}">
        <a href="{% url home %}">{% trans "Home" %}</a>
    </li>
    <li class="divider-vertical"></li>
    {% endif %}
    {% if page.in_menu %}
    {% if page.login_required and request.user.is_authenticated or not page.login_required %}
    <li class="dropdown{% if page.is_current_or_ascendant %} active{% endif %}"
        id="dropdown-menu-{{ page.html_id }}">
        <a href="{{ page.get_absolute_url }}"{% if page.has_children %}data-toggle="dropdown" class="dropdown-toggle active"{% endif %}>
            {{ page.title }}
            {% if page.has_children %}<span class="caret"></span>{% endif %}
        </a>
        {% if page.has_children %}{% page_menu page %}{% endif %}
    </li>
    <li class="divider-vertical"></li>
    {% endif %}
    {% endif %}
    {% endfor %}
</ul>
{% endif %}

{% if branch_level > 0 %}
<ul class="dropdown-menu"{% if branch_level == 1 %} role="menu"{% endif %}>
    {% for page in page_branch %}
    {% if page.in_menu %}
    {% if page.login_required and request.user.is_authenticated or not page.login_required %}
    <li{% if page.is_current_or_ascendant %} class="active {% if page.has_children %} dropdown-submenu{% endif %}"{% else %}
            {% if page.has_children %}class="dropdown-submenu"{% endif %}
            {% endif %}
        id="dropdown-menu-{{ page.html_id }}">
        <a href="{{ page.get_absolute_url }}">{{ page.title }}</a>
        {% if page.has_children %}{% page_menu page %}{% endif %}
    </li>
    {% endif %}
    {% endif %}
    {% endfor %}
</ul>
{% endif %}

{% endif %}
{% endspaceless %}

Brian Schott

unread,
Oct 10, 2012, 4:09:09 PM10/10/12
to mezzani...@googlegroups.com, pote...@gmail.com
Nice!  I will try it out.  I think that requires some js fiddling if you want drop-down menus on hover and a link on click. 

    jQuery('.submenu').hover(function ({
        jQuery(this).children('ul').removeClass('submenu-hide').addClass('submenu-show');
    }function ({
        jQuery(this).children('ul').removeClass('.submenu-show').addClass('submenu-hide');
    }).find("a:first").append(" &raquo; ");


-------------------------------------------------
Brian Schott, CTO
Nimbis Services, Inc.



Tim Tate

unread,
Oct 10, 2012, 4:11:03 PM10/10/12
to mezzani...@googlegroups.com, st...@jupo.org
This stackexchange thread helped solve the hover issue:

$('ul.nav li.dropdown').hover(function() {
    $(this).closest('.dropdown-menu').stop(true, true).show();
    $(this).addClass('open'); 
} , function() { 
    $(this).closest('.dropdown-menu').stop(true, true).hide(); 
    $(this).removeClass('open'); 
});

Combined with my previous post, it's looking pretty good.
You can even remove the data-toggle stuff from the top-level link, and it works like a link again.

Thanks a lot for pointing me in the right direction, guys!

Stephen McDonald

unread,
Oct 10, 2012, 4:17:40 PM10/10/12
to Tim Tate, mezzani...@googlegroups.com
And thanks for working this out :-) Like I mentioned it would be a welcome addition, so if you can set up a pull request once you're happy with everything, that would be great.

Brian Schott

unread,
Oct 10, 2012, 4:31:18 PM10/10/12
to mezzani...@googlegroups.com, Tim Tate
+1!

One thing I noticed is that I copy-pasted our private version of the existing mezzanine dropdown menu.  We added a test to filter out login-required pages from the menu unless you are actually logged in.  That might not be the desired behavior for everybody.  It is certainly a change from the default behavior.  This is the line (and the corresponding {% endif %}.

    {% if page.login_required and request.user.is_authenticated or not page.login_required %}

Worthwhile discussing.

Brian


The original is here:

{% load i18n pages_tags %}
{% spaceless %}
{% if page_branch_in_menu %}

{% if branch_level == 0 %}
<ul class="nav pull-right">
    {% for page in page_branch %}
    {% if page.is_primary and forloop.first %}
    <li class="{% if on_home %} active{% endif %}" id="dropdown-menu-{{ page.html_id }}">
        <a href="{% url home %}">{% trans "Home" %}</a>
    </li>
    <li class="divider-vertical"></li>
    {% endif %}
    {% if page.in_menu %}
    <li class="dropdown{% if page.is_current_or_ascendant %} active{% endif %}"
        id="dropdown-menu-{{ page.html_id }}">
        <a href="{{ page.get_absolute_url }}">
            {{ page.title }}
            {% if page.has_children %}<span class="caret"></span>{% endif %}
        </a>
        {% if page.has_children %}{% page_menu page %}{% endif %}
    </li>
    <li class="divider-vertical"></li>
    {% endif %}
    {% endfor %}
</ul>
{% endif %}

{% if branch_level == 1 %}
<ul class="dropdown-menu">
    {% for page in page_branch %}
    {% if page.in_menu %}
    <li{% if page.is_current_or_ascendant %} class="active"{% endif %}
        id="dropdown-menu-{{ page.html_id }}">
        <a href="{{ page.get_absolute_url }}">{{ page.title }}</a>
    </li>
    {% endif %}
    {% endfor %}
</ul>
{% endif %}

{% endif %}
{% endspaceless %}
-------------------------------------------------
Brian Schott, CTO
Nimbis Services, Inc.



Tim Tate

unread,
Oct 10, 2012, 4:41:30 PM10/10/12
to mezzani...@googlegroups.com, Tim Tate
I didn't see that, thanks Brian. I don't need that functionality now, but it's certainly worth noting for later :)

Stephen, I have a github account but I've never really used it for anything. Is there a tutorial for doing what you're asking?

Eduardo Rivas

unread,
Oct 21, 2012, 8:04:19 PM10/21/12
to mezzani...@googlegroups.com, st...@jupo.org
Tim, I will try to walk you all the way through making the pull request. I assume you are comfortable using git in your machine (via the command line).
  1. Fork Stephen's repo in Github. Go to the link and click the "Fork" button on the top of the page. This will create an exact copy of everything that is in it right now. Remember that, since the version of Mezzanine you will be working is not exactly 1.2.4 (the current stable version), but somewhat rather different (more up to date). Hopefully this will not be an obstacle for the work you've already done.
  2. Now you have your own repo on Github with the development branch of Mezzanine. The repo on your account (not Stephen's) is what git calls the "origin". 
  3. What you want to do next is get origin on your computer.
    1. mkdir ~/github (creates a new folder named "github")
    2. cd ~/github (sets your working directory to your newly created folder)
    3. git init (creates a new empty local repo)
    4. git clone https://github.com/your-user-name/mezzanine.git (clones your github repo [origin] to your local machine. You will see git downloading all the files to your computer.)
  4. git checkout -b bootstrap-upgrade (creates a new branch called 'bootstrap-upgrade'. You will perform all your modifications in this branch)
  5. At this stage you will have to setup your environment to run from the development version of Mezzanine. I assume you can use virtualenvs or whatever method works the best for you. If you don't know how to do this, please let me now.
  6. Make all the desired modifications to the project. Once you're done, commit all your changes to your local branch (bootstrap-upgrade).
  7. We will now push the branch you've been working on to origin: git push origin bootstrap-upgrade
  8. Git will upload all the changes you've made to your repo in Github. You can now visit your repo in Github and look for this newly pushed branch. It has to be there to continue.
  9. Now, while still in your repo, click "Pull request". You are telling Github you will like to submit the work you've been doing to the repo you forked from (Stephen's repo). Fill the form with these parameters: base repo: stephenmcd/mezzanine base branch: master head repo: your-user/mezzanine head branch: bootstrap-upgrade
  10. Continue with the pull request, you will see all the commits you've made while working on this, the diff files, etc. Fill in any additional details and submit the pull request. Try to be descriptive and thorough.
  11. If everything is ok, Stephen will merge the changes you've made to the main repo, and voila! Your modifications are now part of the master branch of Mezzanine. 
I hope that helps. I leave some additional links that can be useful:
It's worth noting that I'm fairly new to git also, and that is how I've done things so far. Any of the veterans is more than welcome to correct or add up on this little guide.

Stephen McDonald

unread,
Oct 21, 2012, 8:17:57 PM10/21/12
to mezzani...@googlegroups.com
Thanks Ed what an awesome response.

Sorry Tim I must have missed your last message in this thread - no doubt my reply wouldn't have been as thorough as Ed's :-)

Eduardo Rivas

unread,
Oct 21, 2012, 8:22:33 PM10/21/12
to mezzani...@googlegroups.com, st...@jupo.org
Thanks Stephen! It's always good to be helpful :-)
Reply all
Reply to author
Forward
0 new messages