Updtd entity-selector for keyboard nav and new component system

For #2064
This commit is contained in:
Dan Brown 2021-02-12 22:10:37 +00:00
parent 6a4b020dd8
commit f36e6d9917
4 changed files with 79 additions and 27 deletions

View file

@ -1,22 +1,32 @@
import {onChildEvent} from "../services/dom";
/**
* Entity Selector
* @extends {Component}
*/
class EntitySelector {
constructor(elem) {
this.elem = elem;
setup() {
this.elem = this.$el;
this.entityTypes = this.$opts.entityTypes || 'page,book,chapter';
this.entityPermission = this.$opts.entityPermission || 'view';
this.input = this.$refs.input;
this.searchInput = this.$refs.search;
this.loading = this.$refs.loading;
this.resultsContainer = this.$refs.results;
this.addButton = this.$refs.add;
this.search = '';
this.lastClick = 0;
this.selectedItemData = null;
const entityTypes = elem.hasAttribute('entity-types') ? elem.getAttribute('entity-types') : 'page,book,chapter';
const entityPermission = elem.hasAttribute('entity-permission') ? elem.getAttribute('entity-permission') : 'view';
this.searchUrl = window.baseUrl(`/ajax/search/entities?types=${encodeURIComponent(entityTypes)}&permission=${encodeURIComponent(entityPermission)}`);
this.input = elem.querySelector('[entity-selector-input]');
this.searchInput = elem.querySelector('[entity-selector-search]');
this.loading = elem.querySelector('[entity-selector-loading]');
this.resultsContainer = elem.querySelector('[entity-selector-results]');
this.addButton = elem.querySelector('[entity-selector-add-button]');
this.setupListeners();
this.showLoading();
this.initialLoad();
}
setupListeners() {
this.elem.addEventListener('click', this.onClick.bind(this));
let lastSearch = 0;
@ -42,8 +52,39 @@ class EntitySelector {
});
}
this.showLoading();
this.initialLoad();
// Keyboard navigation
onChildEvent(this.$el, '[data-entity-type]', 'keydown', (e, el) => {
if (e.ctrlKey && e.code === 'Enter') {
const form = this.$el.closest('form');
if (form) {
form.submit();
e.preventDefault();
return;
}
}
if (e.code === 'ArrowDown') {
this.focusAdjacent(true);
}
if (e.code === 'ArrowUp') {
this.focusAdjacent(false);
}
});
this.searchInput.addEventListener('keydown', e => {
if (e.code === 'ArrowDown') {
this.focusAdjacent(true);
}
})
}
focusAdjacent(forward = true) {
const items = Array.from(this.resultsContainer.querySelectorAll('[data-entity-type]'));
const selectedIndex = items.indexOf(document.activeElement);
const newItem = items[selectedIndex+ (forward ? 1 : -1)] || items[0];
if (newItem) {
newItem.focus();
}
}
showLoading() {
@ -57,15 +98,19 @@ class EntitySelector {
}
initialLoad() {
window.$http.get(this.searchUrl).then(resp => {
window.$http.get(this.searchUrl()).then(resp => {
this.resultsContainer.innerHTML = resp.data;
this.hideLoading();
})
}
searchUrl() {
return `/ajax/search/entities?types=${encodeURIComponent(this.entityTypes)}&permission=${encodeURIComponent(this.entityPermission)}`;
}
searchEntities(searchTerm) {
this.input.value = '';
let url = `${this.searchUrl}&term=${encodeURIComponent(searchTerm)}`;
const url = `${this.searchUrl()}&term=${encodeURIComponent(searchTerm)}`;
window.$http.get(url).then(resp => {
this.resultsContainer.innerHTML = resp.data;
this.hideLoading();
@ -73,8 +118,8 @@ class EntitySelector {
}
isDoubleClick() {
let now = Date.now();
let answer = now - this.lastClick < 300;
const now = Date.now();
const answer = now - this.lastClick < 300;
this.lastClick = now;
return answer;
}
@ -123,8 +168,8 @@ class EntitySelector {
}
unselectAll() {
let selected = this.elem.querySelectorAll('.selected');
for (let selectedElem of selected) {
const selected = this.elem.querySelectorAll('.selected');
for (const selectedElem of selected) {
selectedElem.classList.remove('selected', 'primary-background');
}
this.selectedItemData = null;

View file

@ -193,8 +193,12 @@ $btt-size: 40px;
.entity-list-item p {
margin-bottom: 0;
}
.entity-list-item:focus {
outline: 2px dotted var(--color-primary);
outline-offset: -4px;
}
.entity-list-item.selected {
background-color: rgba(0, 0, 0, 0.05) !important;
@include lightDark(background-color, rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
.loading {
height: 400px;

View file

@ -1,12 +1,15 @@
<div class="form-group entity-selector-container">
<div entity-selector class="entity-selector {{$selectorSize ?? ''}}" entity-types="{{ $entityTypes ?? 'book,chapter,page' }}" entity-permission="{{ $entityPermission ?? 'view' }}">
<input type="hidden" entity-selector-input name="{{$name}}" value="">
<input type="text" placeholder="{{ trans('common.search') }}" entity-selector-search>
<div class="text-center loading" entity-selector-loading>@include('partials.loading-icon')</div>
<div entity-selector-results></div>
<div component="entity-selector"
class="entity-selector {{$selectorSize ?? ''}}"
option:entity-selector:entity-types="{{ $entityTypes ?? 'book,chapter,page' }}"
option:entity-selector:entity-permission="{{ $entityPermission ?? 'view' }}">
<input refs="entity-selector@input" type="hidden" name="{{$name}}" value="">
<input type="text" placeholder="{{ trans('common.search') }}" @if($autofocus ?? false) autofocus @endif refs="entity-selector@search">
<div class="text-center loading" refs="entity-selector@loading">@include('partials.loading-icon')</div>
<div refs="entity-selector@results"></div>
@if($showAdd ?? false)
<div class="entity-selector-add">
<button entity-selector-add-button type="button"
<button refs="entity-selector@add" type="button"
class="button outline">@icon('add'){{ trans('common.add') }}</button>
</div>
@endif

View file

@ -23,7 +23,7 @@
{!! csrf_field() !!}
<input type="hidden" name="_method" value="PUT">
@include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create'])
@include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create', 'autofocus' => true])
<div class="form-group text-right">
<a href="{{ $page->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>