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,
|
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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in a new issue