Add support for "nested" menu items

some menus were just getting too long, so this gives us a way to collapse
certain items, which user can expand as needed
This commit is contained in:
Lance Edgar 2021-02-01 11:57:12 -06:00
parent 329e75ee82
commit fe80028c07
3 changed files with 139 additions and 38 deletions

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar # Copyright © 2010-2021 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -33,6 +33,7 @@ from rattail.util import import_module_path
class MenuGroup(Object): class MenuGroup(Object):
title = None title = None
items = None items = None
is_menu = True
is_link = False is_link = False
@ -41,10 +42,19 @@ class MenuItem(Object):
url = None url = None
target = None target = None
is_link = True is_link = True
is_menu = False
is_sep = False
class MenuItemMenu(Object):
title = None
items = None
is_menu = True
is_sep = False is_sep = False
class MenuSeparator(object): class MenuSeparator(object):
is_menu = False
is_sep = True is_sep = True
@ -61,55 +71,108 @@ def make_simple_menus(request):
# collect "simple" menus definition, but must refine that somewhat to # collect "simple" menus definition, but must refine that somewhat to
# produce our final menus # produce our final menus
raw_menus = menus_module.simple_menus(request) raw_menus = menus_module.simple_menus(request)
mark_allowed(request, raw_menus)
final_menus = [] final_menus = []
for topitem in raw_menus: for topitem in raw_menus:
if topitem['allowed']:
if topitem.get('type') == 'link': if topitem.get('type') == 'link':
final_menus.append( final_menus.append(make_menu_entry(topitem))
MenuItem(title=topitem['title'],
url=topitem['url'],
target=topitem.get('target')))
else: # assuming 'menu' type else: # assuming 'menu' type
# figure out which ones the user has permission to access
allowed = []
for item in topitem['items']:
if item.get('type') == 'sep':
allowed.append(item)
if item.get('perm'):
if request.has_perm(item['perm']):
allowed.append(item)
else:
allowed.append(item)
if allowed:
# user must have access to something; construct items for the menu
menu_items = [] menu_items = []
for item in allowed: for item in topitem['items']:
if not item['allowed']:
continue
# separator # nested submenu
if item.get('type') == 'sep': if item.get('type') == 'menu':
submenu_items = []
for subitem in item['items']:
if subitem['allowed']:
submenu_items.append(make_menu_entry(subitem))
menu_items.append(MenuItemMenu(
title=item['title'],
items=submenu_items))
elif item.get('type') == 'sep':
# we only want to add a sep, *if* we already have some
# menu items (i.e. there is something to separate)
# *and* the last menu item is not a sep (avoid doubles)
if menu_items and not menu_items[-1].is_sep: if menu_items and not menu_items[-1].is_sep:
menu_items.append(MenuSeparator()) menu_items.append(make_menu_entry(item))
# menu item else: # standard menu item
else: menu_items.append(make_menu_entry(item))
menu_items.append(
MenuItem(title=item['title'],
url=item['url'],
target=item.get('target')))
# remove final separator if present # remove final separator if present
if menu_items and menu_items[-1].is_sep: if menu_items and menu_items[-1].is_sep:
menu_items.pop() menu_items.pop()
# only add if we wound up with something # only add if we wound up with something
assert menu_items
if menu_items: if menu_items:
final_menus.append( final_menus.append(MenuGroup(
MenuGroup(title=topitem['title'], items=menu_items)) title=topitem['title'],
items=menu_items))
return final_menus return final_menus
def make_menu_entry(item):
"""
Convert a simple menu entry dict, into a proper menu-related object, for
use in constructing final menu.
"""
# separator
if item.get('type') == 'sep':
return MenuSeparator()
# standard menu item
return MenuItem(
title=item['title'],
url=item['url'],
target=item.get('target'))
def is_allowed(request, item):
"""
Logic to determine if a given menu item is "allowed" for current user.
"""
perm = item.get('perm')
if perm:
return request.has_perm(perm)
return True
def mark_allowed(request, menus):
"""
Traverse the menu set, and mark each item as "allowed" (or not) based on
current user permissions.
"""
for topitem in menus:
if topitem.get('type', 'menu') == 'menu':
topitem['allowed'] = False
for item in topitem['items']:
if item.get('type') == 'menu':
for subitem in item['items']:
subitem['allowed'] = is_allowed(request, subitem)
item['allowed'] = False
for subitem in item['items']:
if subitem['allowed'] and subitem.get('type') != 'sep':
item['allowed'] = True
break
else:
item['allowed'] = is_allowed(request, item)
for item in topitem['items']:
if item['allowed'] and item.get('type') != 'sep':
topitem['allowed'] = True
break

View file

@ -46,6 +46,11 @@ header .level-left .global-title {
font-weight: bold; font-weight: bold;
} }
/* indent nested menu items a bit */
header .navbar-item.nested {
padding-left: 2.5rem;
}
header .level #current-context, header .level #current-context,
header .level-left #current-context { header .level-left #current-context {
font-size: 2em; font-size: 2em;

View file

@ -198,11 +198,28 @@
<div class="navbar-item has-dropdown is-hoverable"> <div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">${topitem.title}</a> <a class="navbar-link">${topitem.title}</a>
<div class="navbar-dropdown"> <div class="navbar-dropdown">
% for subitem in topitem.items: % for item in topitem.items:
% if item.is_menu:
<% item_hash = id(item) %>
<% toggle = 'menu_{}_shown'.format(item_hash) %>
<div>
<a class="navbar-link" @click.prevent="toggleNestedMenu('${item_hash}')">
${item.title}
</a>
</div>
% for subitem in item.items:
% if subitem.is_sep: % if subitem.is_sep:
<hr class="navbar-divider" v-show="${toggle}">
% else:
${h.link_to("{}".format(subitem.title), subitem.url, class_='navbar-item nested', target=subitem.target, **{'v-show': toggle})}
% endif
% endfor
% else:
% if item.is_sep:
<hr class="navbar-divider"> <hr class="navbar-divider">
% else: % else:
${h.link_to(subitem.title, subitem.url, class_='navbar-item', target=subitem.target)} ${h.link_to(item.title, item.url, class_='navbar-item', target=item.target)}
% endif
% endif % endif
% endfor % endfor
</div> </div>
@ -512,6 +529,11 @@
this.$refs.themePickerForm.submit() this.$refs.themePickerForm.submit()
}, },
% endif % endif
toggleNestedMenu(hash) {
const key = 'menu_' + hash + '_shown'
this[key] = !this[key]
},
}, },
} }
@ -520,6 +542,17 @@
feedbackMessage: "", feedbackMessage: "",
} }
## declare nested menu visibility toggle flags
% for topitem in menus:
% if topitem.is_menu:
% for item in topitem.items:
% if item.is_menu:
WholePageData.menu_${id(item)}_shown = false
% endif
% endfor
% endif
% endfor
</script> </script>
</%def> </%def>