]> BookStack Code Mirror - bookstack/blob - resources/assets/js/components/breadcrumb-listing.js
Added keyboard navigation to breadcrumb dropdowns
[bookstack] / resources / assets / js / components / breadcrumb-listing.js
1
2
3 class BreadcrumbListing {
4
5     constructor(elem) {
6         this.elem = elem;
7         this.searchInput = elem.querySelector('input');
8         this.loadingElem = elem.querySelector('.loading-container');
9         this.entityListElem = elem.querySelector('.breadcrumb-listing-entity-list');
10         this.toggleElem = elem.querySelector('[dropdown-toggle]');
11
12         // this.loadingElem.style.display = 'none';
13         const entityDescriptor = elem.getAttribute('breadcrumb-listing').split(':');
14         this.entityType = entityDescriptor[0];
15         this.entityId = Number(entityDescriptor[1]);
16
17         this.toggleElem.addEventListener('click', this.onShow.bind(this));
18         this.searchInput.addEventListener('input', this.onSearch.bind(this));
19         this.elem.addEventListener('keydown', this.keyDown.bind(this));
20     }
21
22     keyDown(event) {
23         if (event.key === 'ArrowDown') {
24             this.listFocusChange(1);
25             event.preventDefault();
26         } else if  (event.key === 'ArrowUp') {
27             this.listFocusChange(-1);
28             event.preventDefault();
29         }
30     }
31
32     listFocusChange(indexChange = 1) {
33         const links = Array.from(this.entityListElem.querySelectorAll('a:not(.hidden)'));
34         const currentFocused = this.entityListElem.querySelector('a:focus');
35         const currentFocusedIndex = links.indexOf(currentFocused);
36         const defaultFocus = (indexChange > 0) ? links[0] : this.searchInput;
37         const nextElem = links[currentFocusedIndex + indexChange] || defaultFocus;
38         nextElem.focus();
39     }
40
41     onShow() {
42         this.loadEntityView();
43     }
44
45     onSearch() {
46         const input = this.searchInput.value.toLowerCase().trim();
47         const listItems = this.entityListElem.querySelectorAll('.entity-list-item');
48         for (let listItem of listItems) {
49             const match = !input || listItem.textContent.toLowerCase().includes(input);
50             listItem.style.display = match ? 'flex' : 'none';
51             listItem.classList.toggle('hidden', !match);
52         }
53     }
54
55     loadEntityView() {
56         this.toggleLoading(true);
57
58         const params = {
59             'entity_id': this.entityId,
60             'entity_type': this.entityType,
61         };
62
63         window.$http.get('/search/entity/siblings', {params}).then(resp => {
64             this.entityListElem.innerHTML = resp.data;
65         }).catch(err => {
66             console.error(err);
67         }).then(() => {
68             this.toggleLoading(false);
69             this.onSearch();
70         });
71     }
72
73     toggleLoading(show = false) {
74         this.loadingElem.style.display = show ? 'block' : 'none';
75     }
76
77 }
78
79 export default BreadcrumbListing;