Add "global searchbox" for quicker access to main views

This commit is contained in:
Lance Edgar 2022-12-26 17:31:37 -06:00
parent b985124bef
commit dc90abcf09
4 changed files with 111 additions and 3 deletions

View file

@ -43,7 +43,7 @@ from tailbone.db import Session
from tailbone.config import (csrf_header_name, should_expose_websockets, from tailbone.config import (csrf_header_name, should_expose_websockets,
get_buefy_version, get_buefy_0_8) get_buefy_version, get_buefy_0_8)
from tailbone.menus import make_simple_menus from tailbone.menus import make_simple_menus
from tailbone.util import should_use_buefy from tailbone.util import should_use_buefy, get_global_search_options
def new_request(event): def new_request(event):
@ -179,6 +179,9 @@ def before_render(event):
css = request.rattail_config.get('tailbone', 'theme.falafel.buefy_css') css = request.rattail_config.get('tailbone', 'theme.falafel.buefy_css')
renderer_globals['buefy_css'] = css renderer_globals['buefy_css'] = css
# add global search data for quick access
renderer_globals['global_search_data'] = get_global_search_options(request)
# here we globally declare widths for grid filter pseudo-columns # here we globally declare widths for grid filter pseudo-columns
widths = request.rattail_config.get('tailbone', 'grids.filters.column_widths') widths = request.rattail_config.get('tailbone', 'grids.filters.column_widths')
if widths: if widths:

View file

@ -184,12 +184,27 @@
<nav class="navbar" role="navigation" aria-label="main navigation"> <nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="${url('home')}"> <a class="navbar-item" href="${url('home')}"
v-show="!globalSearchActive">
${base_meta.header_logo()} ${base_meta.header_logo()}
<div id="global-header-title"> <div id="global-header-title">
${base_meta.global_title()} ${base_meta.global_title()}
</div> </div>
</a> </a>
<div v-show="globalSearchActive"
class="navbar-item">
<b-autocomplete ref="globalSearchAutocomplete"
v-model="globalSearchTerm"
:data="globalSearchFilteredData"
field="label"
open-on-focus
keep-first
icon-pack="fas"
clearable
@keydown.native="globalSearchKeydown"
@select="globalSearchSelect">
</b-autocomplete>
</div>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false"> <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
@ -200,6 +215,13 @@
<div class="navbar-menu"> <div class="navbar-menu">
<div class="navbar-start"> <div class="navbar-start">
<div v-if="globalSearchData.length"
class="navbar-item">
<a @click.prevent="globalSearchInit()">
<b-icon pack="fas" icon="search"></b-icon>
</a>
</div>
% for topitem in menus: % for topitem in menus:
% if topitem['is_link']: % if topitem['is_link']:
${h.link_to(topitem['title'], topitem['url'], target=topitem['target'], class_='navbar-item')} ${h.link_to(topitem['title'], topitem['url'], target=topitem['target'], class_='navbar-item')}
@ -738,7 +760,29 @@
let WholePage = { let WholePage = {
template: '#whole-page-template', template: '#whole-page-template',
mixins: [FormPosterMixin], mixins: [FormPosterMixin],
computed: {}, computed: {
globalSearchFilteredData() {
if (!this.globalSearchTerm.length) {
return this.globalSearchData
}
return this.globalSearchData.filter((option) => {
return option.label.toLowerCase().indexOf(this.globalSearchTerm.toLowerCase()) >= 0
})
},
},
mounted() {
window.addEventListener('keydown', this.globalKey)
for (let hook of this.mountedHooks) {
hook(this)
}
},
beforeDestroy() {
window.removeEventListener('keydown', this.globalKey)
},
methods: { methods: {
changeContentTitle(newTitle) { changeContentTitle(newTitle) {
@ -757,6 +801,36 @@
}, },
% endif % endif
globalKey(event) {
// Ctrl+8 opens global search
if (event.target.tagName == 'BODY') {
if (event.ctrlKey && event.key == '8') {
this.globalSearchInit()
}
}
},
globalSearchInit() {
this.globalSearchTerm = ''
this.globalSearchActive = true
this.$nextTick(() => {
this.$refs.globalSearchAutocomplete.focus()
})
},
globalSearchKeydown(event) {
// ESC will dismiss searchbox
if (event.which == 27) {
this.globalSearchActive = false
}
},
globalSearchSelect(option) {
location.href = option.url
},
toggleNestedMenu(hash) { toggleNestedMenu(hash) {
const key = 'menu_' + hash + '_shown' const key = 'menu_' + hash + '_shown'
this[key] = !this[key] this[key] = !this[key]
@ -767,6 +841,12 @@
let WholePageData = { let WholePageData = {
contentTitleHTML: ${json.dumps(capture(self.content_title))|n}, contentTitleHTML: ${json.dumps(capture(self.content_title))|n},
feedbackMessage: "", feedbackMessage: "",
globalSearchActive: false,
globalSearchTerm: '',
globalSearchData: ${json.dumps(global_search_data)|n},
mountedHooks: [],
} }
## declare nested menu visibility toggle flags ## declare nested menu visibility toggle flags

View file

@ -81,6 +81,23 @@ def get_form_data(request):
return request.POST return request.POST
def get_global_search_options(request):
"""
Returns global search options for current request. Basically a
list of all "index views" minus the ones they aren't allowed to
access.
"""
options = []
pages = sorted(request.registry.settings['tailbone_index_pages'],
key=lambda page: page['label'])
for page in pages:
if not page['permission'] or request.has_perm(page['permission']):
option = dict(page)
option['url'] = request.route_url(page['route'])
options.append(option)
return options
def should_use_buefy(request): def should_use_buefy(request):
""" """
Returns a flag indicating whether or not the current theme supports (and Returns a flag indicating whether or not the current theme supports (and

View file

@ -1001,6 +1001,14 @@ class CustomerOrderView(MasterView):
def _order_defaults(cls, config): def _order_defaults(cls, config):
route_prefix = cls.get_route_prefix() route_prefix = cls.get_route_prefix()
url_prefix = cls.get_url_prefix() url_prefix = cls.get_url_prefix()
model_title = cls.get_model_title()
permission_prefix = cls.get_permission_prefix()
# add pseudo-index page for creating new custorder
# (makes it available when building menus etc.)
config.add_tailbone_index_page('{}.create'.format(route_prefix),
"New {}".format(model_title),
'{}.create'.format(permission_prefix))
# person autocomplete # person autocomplete
config.add_route('{}.person_autocomplete'.format(route_prefix), config.add_route('{}.person_autocomplete'.format(route_prefix),