]> BookStack Code Mirror - bookstack/blob - resources/js/vues/components/autosuggest.js
Started migrating tag manager JS to HTML-first component
[bookstack] / resources / js / vues / components / autosuggest.js
1
2 const template = `
3     <div>
4         <input :value="value" :autosuggest-type="type" ref="input"
5             :placeholder="placeholder"
6             :name="name"
7             type="text"
8             @input="inputUpdate($event.target.value)"
9             @focus="inputUpdate($event.target.value)"
10             @blur="inputBlur"
11             @keydown="inputKeydown"
12             :aria-label="placeholder"
13             autocomplete="off"
14         />
15         <ul class="suggestion-box" v-if="showSuggestions">
16             <li v-for="(suggestion, i) in suggestions"
17                 @click="selectSuggestion(suggestion)"
18                 :class="{active: (i === active)}">{{suggestion}}</li>
19         </ul>
20     </div>
21 `;
22
23 function data() {
24     return {
25         suggestions: [],
26         showSuggestions: false,
27         active: 0,
28     };
29 }
30
31 const ajaxCache = {};
32
33 const props = ['url', 'type', 'value', 'placeholder', 'name'];
34
35 function getNameInputVal(valInput) {
36     let parentRow = valInput.parentNode.parentNode;
37     let nameInput = parentRow.querySelector('[autosuggest-type="name"]');
38     return (nameInput === null) ? '' : nameInput.value;
39 }
40
41 const methods = {
42
43     inputUpdate(inputValue) {
44         this.$emit('input', inputValue);
45         let params = {};
46
47         if (this.type === 'value') {
48             let nameVal = getNameInputVal(this.$el);
49             if (nameVal !== "") params.name = nameVal;
50         }
51
52         this.getSuggestions(inputValue.slice(0, 3), params).then(suggestions => {
53             if (inputValue.length === 0) {
54                 this.displaySuggestions(suggestions.slice(0, 6));
55                 return;
56             }
57             // Filter to suggestions containing searched term
58             suggestions = suggestions.filter(item => {
59                 return item.toLowerCase().indexOf(inputValue.toLowerCase()) !== -1;
60             }).slice(0, 4);
61             this.displaySuggestions(suggestions);
62         });
63     },
64
65     inputBlur() {
66         setTimeout(() => {
67             this.$emit('blur');
68             this.showSuggestions = false;
69         }, 100);
70     },
71
72     inputKeydown(event) {
73         if (event.key === 'Enter') event.preventDefault();
74         if (!this.showSuggestions) return;
75
76         // Down arrow
77         if (event.key === 'ArrowDown') {
78             this.active = (this.active === this.suggestions.length - 1) ? 0 : this.active+1;
79         }
80         // Up Arrow
81         else if (event.key === 'ArrowUp') {
82             this.active = (this.active === 0) ? this.suggestions.length - 1 : this.active-1;
83         }
84         // Enter key
85         else if ((event.key === 'Enter') && !event.shiftKey) {
86             this.selectSuggestion(this.suggestions[this.active]);
87         }
88         // Escape key
89         else if (event.key === 'Escape') {
90             this.showSuggestions = false;
91         }
92     },
93
94     displaySuggestions(suggestions) {
95         if (suggestions.length === 0) {
96             this.suggestions = [];
97             this.showSuggestions = false;
98             return;
99         }
100
101         this.suggestions = suggestions;
102         this.showSuggestions = true;
103         this.active = 0;
104     },
105
106     selectSuggestion(suggestion) {
107         this.$refs.input.value = suggestion;
108         this.$refs.input.focus();
109         this.$emit('input', suggestion);
110         this.showSuggestions = false;
111     },
112
113     /**
114      * Get suggestions from BookStack. Store and use local cache if already searched.
115      * @param {String} input
116      * @param {Object} params
117      */
118     getSuggestions(input, params) {
119         params.search = input;
120         const cacheKey = `${this.url}:${JSON.stringify(params)}`;
121
122         if (typeof ajaxCache[cacheKey] !== "undefined") {
123             return Promise.resolve(ajaxCache[cacheKey]);
124         }
125
126         return this.$http.get(this.url, params).then(resp => {
127             ajaxCache[cacheKey] = resp.data;
128             return resp.data;
129         });
130     }
131
132 };
133
134 export default {template, data, props, methods};