Add "global searchbox" for quicker access to main views
This commit is contained in:
parent
b985124bef
commit
dc90abcf09
|
@ -43,7 +43,7 @@ from tailbone.db import Session
|
|||
from tailbone.config import (csrf_header_name, should_expose_websockets,
|
||||
get_buefy_version, get_buefy_0_8)
|
||||
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):
|
||||
|
@ -179,6 +179,9 @@ def before_render(event):
|
|||
css = request.rattail_config.get('tailbone', 'theme.falafel.buefy_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
|
||||
widths = request.rattail_config.get('tailbone', 'grids.filters.column_widths')
|
||||
if widths:
|
||||
|
|
|
@ -184,12 +184,27 @@
|
|||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
|
||||
<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()}
|
||||
<div id="global-header-title">
|
||||
${base_meta.global_title()}
|
||||
</div>
|
||||
</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">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
|
@ -200,6 +215,13 @@
|
|||
<div class="navbar-menu">
|
||||
<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:
|
||||
% if topitem['is_link']:
|
||||
${h.link_to(topitem['title'], topitem['url'], target=topitem['target'], class_='navbar-item')}
|
||||
|
@ -738,7 +760,29 @@
|
|||
let WholePage = {
|
||||
template: '#whole-page-template',
|
||||
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: {
|
||||
|
||||
changeContentTitle(newTitle) {
|
||||
|
@ -757,6 +801,36 @@
|
|||
},
|
||||
% 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) {
|
||||
const key = 'menu_' + hash + '_shown'
|
||||
this[key] = !this[key]
|
||||
|
@ -767,6 +841,12 @@
|
|||
let WholePageData = {
|
||||
contentTitleHTML: ${json.dumps(capture(self.content_title))|n},
|
||||
feedbackMessage: "",
|
||||
|
||||
globalSearchActive: false,
|
||||
globalSearchTerm: '',
|
||||
globalSearchData: ${json.dumps(global_search_data)|n},
|
||||
|
||||
mountedHooks: [],
|
||||
}
|
||||
|
||||
## declare nested menu visibility toggle flags
|
||||
|
|
|
@ -81,6 +81,23 @@ def get_form_data(request):
|
|||
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):
|
||||
"""
|
||||
Returns a flag indicating whether or not the current theme supports (and
|
||||
|
|
|
@ -1001,6 +1001,14 @@ class CustomerOrderView(MasterView):
|
|||
def _order_defaults(cls, config):
|
||||
route_prefix = cls.get_route_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
|
||||
config.add_route('{}.person_autocomplete'.format(route_prefix),
|
||||
|
|
Loading…
Reference in a new issue