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