1 import {onChildEvent} from '../services/dom';
2 import {Component} from './component';
5 * @typedef EntitySelectorSearchOptions
6 * @property entityTypes string
7 * @property entityPermission string
8 * @property searchEndpoint string
14 export class EntitySelector extends Component {
19 this.input = this.$refs.input;
20 this.searchInput = this.$refs.search;
21 this.loading = this.$refs.loading;
22 this.resultsContainer = this.$refs.results;
24 this.searchOptions = {
25 entityTypes: this.$opts.entityTypes || 'page,book,chapter',
26 entityPermission: this.$opts.entityPermission || 'view',
27 searchEndpoint: this.$opts.searchEndpoint || '',
33 this.setupListeners();
36 if (this.searchOptions.searchEndpoint) {
42 * @param {EntitySelectorSearchOptions} options
44 configureSearchOptions(options) {
45 Object.assign(this.searchOptions, options);
50 this.elem.addEventListener('click', this.onClick.bind(this));
53 this.searchInput.addEventListener('input', () => {
54 lastSearch = Date.now();
57 if (Date.now() - lastSearch < 199) return;
58 this.searchEntities(this.searchInput.value);
62 this.searchInput.addEventListener('keydown', event => {
63 if (event.keyCode === 13) event.preventDefault();
66 // Keyboard navigation
67 onChildEvent(this.$el, '[data-entity-type]', 'keydown', event => {
68 if (event.ctrlKey && event.code === 'Enter') {
69 const form = this.$el.closest('form');
72 event.preventDefault();
77 if (event.code === 'ArrowDown') {
78 this.focusAdjacent(true);
80 if (event.code === 'ArrowUp') {
81 this.focusAdjacent(false);
85 this.searchInput.addEventListener('keydown', event => {
86 if (event.code === 'ArrowDown') {
87 this.focusAdjacent(true);
92 focusAdjacent(forward = true) {
93 const items = Array.from(this.resultsContainer.querySelectorAll('[data-entity-type]'));
94 const selectedIndex = items.indexOf(document.activeElement);
95 const newItem = items[selectedIndex + (forward ? 1 : -1)] || items[0];
102 this.searchInput.value = '';
108 this.searchInput.focus();
111 searchText(queryText) {
112 this.searchInput.value = queryText;
113 this.searchEntities(queryText);
117 this.loading.style.display = 'block';
118 this.resultsContainer.style.display = 'none';
122 this.loading.style.display = 'none';
123 this.resultsContainer.style.display = 'block';
127 if (!this.searchOptions.searchEndpoint) {
128 throw new Error('Search endpoint not set for entity-selector load');
131 window.$http.get(this.searchUrl()).then(resp => {
132 this.resultsContainer.innerHTML = resp.data;
138 const query = `types=${encodeURIComponent(this.searchOptions.entityTypes)}&permission=${encodeURIComponent(this.searchOptions.entityPermission)}`;
139 return `${this.searchOptions.searchEndpoint}?${query}`;
142 searchEntities(searchTerm) {
143 if (!this.searchOptions.searchEndpoint) {
144 throw new Error('Search endpoint not set for entity-selector load');
147 this.input.value = '';
148 const url = `${this.searchUrl()}&term=${encodeURIComponent(searchTerm)}`;
149 window.$http.get(url).then(resp => {
150 this.resultsContainer.innerHTML = resp.data;
156 const now = Date.now();
157 const answer = now - this.lastClick < 300;
158 this.lastClick = now;
163 const listItem = event.target.closest('[data-entity-type]');
165 event.preventDefault();
166 event.stopPropagation();
167 this.selectItem(listItem);
172 const isDblClick = this.isDoubleClick();
173 const type = item.getAttribute('data-entity-type');
174 const id = item.getAttribute('data-entity-id');
175 const isSelected = (!item.classList.contains('selected') || isDblClick);
178 this.input.value = isSelected ? `${type}:${id}` : '';
180 const link = item.getAttribute('href');
181 const name = item.querySelector('.entity-list-item-name').textContent;
182 const data = {id: Number(id), name, link};
185 item.classList.add('selected');
187 window.$events.emit('entity-select-change', null);
190 if (!isDblClick && !isSelected) return;
193 this.confirmSelection(data);
196 window.$events.emit('entity-select-change', data);
200 confirmSelection(data) {
201 window.$events.emit('entity-select-confirm', data);
205 const selected = this.elem.querySelectorAll('.selected');
206 for (const selectedElem of selected) {
207 selectedElem.classList.remove('selected', 'primary-background');