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:
parent
329e75ee82
commit
fe80028c07
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue