-
Notifications
You must be signed in to change notification settings - Fork 118
Expand file tree
/
Copy pathautoComplete.js
More file actions
221 lines (190 loc) · 7.4 KB
/
autoComplete.js
File metadata and controls
221 lines (190 loc) · 7.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import 'jquery-ui/ui/widgets/autocomplete';
import getConstant from './constants';
import { isObject, isString, isArray } from './isType';
// Updates the ARIA help text that lets the user know how many suggestions
const updateAriaHelper = (autocomplete, suggestionCount) => {
if (isObject(autocomplete)) {
const helper = autocomplete.siblings('.autocomplete-help');
if (isObject(helper)) {
const text = getConstant('AUTOCOMPLETE_ARIA_HELPER');
helper.html(text.replace('%{n}', suggestionCount));
} else {
helper.html(getConstant('AUTOCOMPLETE_ARIA_HELPER_EMPTY'));
}
}
};
// Places the results into the crosswalk, updates the Aria helper and then
// extracts the 'name' from each result and returns it for consumption by
// the JQuery UI autocomplete widget
const processAjaxResults = (autocomplete, crosswalk, results) => {
let out = [];
if (isObject(autocomplete) && isObject(crosswalk) && isArray(results)) {
crosswalk.attr('value', JSON.stringify(results));
updateAriaHelper(autocomplete, results.length);
out = results.map((item) => item.name);
} else {
crosswalk.attr('value', JSON.stringify([]));
updateAriaHelper(autocomplete, 0);
}
return out;
};
// Extract the AJAX query arguments from the autocomplete
const queryArgs = (autocomplete, searchTerm) => {
const namespace = autocomplete.attr('data-namespace');
const attribute = autocomplete.attr('data-attribute');
return `{"${namespace}":{"${attribute}":"${searchTerm}"}}`;
};
// Displays/hides a 'Loading ...' message while waiting for an AJAX response
const toggleLoadingMessage = (context) => {
const selections = $(context);
const msg = getConstant('AUTOCOMPLETE_SEARCHING');
const loadingMessage = `<li class="loading-message ui-menu-item"><div class="ui-menu-item-wrapper" tabindex="-1">${msg}</div></li>`;
if (selections.length > 0) {
const message = selections.find('.loading-message');
const menu = selections.find('ul.ui-menu');
menu.show();
if (message.length > 0) {
message.remove();
} else {
menu.html(loadingMessage);
}
}
};
// Makes an AJAX request to the specified target
const search = (autocomplete, term, crosswalk, callback) => {
if (isObject(autocomplete) && isObject(crosswalk) && isString(term)) {
const url = autocomplete.attr('data-url');
const method = autocomplete.attr('data-method');
const data = JSON.parse(queryArgs(autocomplete, term));
if (isString(url) && term.length > 2) {
toggleLoadingMessage(autocomplete.siblings('div[id$="_ui-front"]'));
$.ajax({
url, method, data,
}).done((results) => {
callback(processAjaxResults(autocomplete, crosswalk, results));
}).fail(() => {
callback(processAjaxResults(autocomplete, crosswalk, []));
});
}
}
};
const toggleWarning = (autocomplete, displayIt) => {
const warning = autocomplete.siblings('.autocomplete-warning');
if (warning.length > 0) {
if (displayIt) {
warning.removeClass('hide').show();
} else {
warning.addClass('hide').hide();
}
}
};
// Looks up the value in the crosswalk
const findInCrosswalk = (selection, crosswalk) => {
// Default to the name only
let out = JSON.stringify({ name: selection });
// If the user selected an item and the crosswalk exists then try to
// find it in the crosswalk.
if (selection.length > 0 && crosswalk.length > 0) {
const json = JSON.parse(crosswalk.val());
const found = json.find((item) => item != null && item.name === selection);
// If the crosswalk was empty then out becomes undefined
out = (found === undefined ? out : JSON.stringify(found));
}
return out;
};
// Returns false if the selection is the default `{"name":"[value]"}`
const warnableSelection = (selection) => {
if (selection.length > 0) {
const json = Object.keys(JSON.parse(selection));
return (json.length <= 1 && json[0] === 'name');
}
return false;
};
// Updates the hidden id field with the contents from the crosswalk for the
// selected name
const handleSelection = (autocomplete, hidden, crosswalk, selection) => {
const out = findInCrosswalk(selection, crosswalk);
toggleWarning(autocomplete, warnableSelection(out));
// Set the ID and trigger the onChange event for any view specific
// JS to trigger events
hidden.val(out).trigger('change');
return true;
};
// Clear out the Sources and Crosswalk hidden fields for the given autocomplete
const scrubCrosswalkAndSource = (context) => {
if (isObject(context) && context.length > 0) {
const id = context.attr('id');
const crosswalk = context.siblings(`#${id.replace('_name', '_crosswalk')}`);
if (isObject(crosswalk) && crosswalk.length > 0) {
crosswalk.val('[]');
}
const sources = context.siblings(`#${id.replace('_name', '_sources')}`);
if (isObject(sources) && sources.length > 0) {
sources.val('[]');
}
}
};
// Removes all of the Sources and Crosswalk content before form submission
export const scrubOrgSelectionParamsOnSubmit = (formSelector) => {
const form = $(formSelector);
if (isObject(form) && form.length > 0) {
form.on('submit', () => {
form.find('.autocomplete').each((_idx, el) => {
scrubCrosswalkAndSource($(el));
});
});
}
};
export const initAutocomplete = (selector) => {
if (isString(selector)) {
const context = $(selector);
if (isObject(context) && context.length > 0) {
const id = context.attr('id');
const front = context.siblings('div[id$="_ui-front"]');
const crosswalk = context.siblings(`#${id.replace('_name', '_crosswalk')}`);
const hidden = context.siblings('.autocomplete-result');
toggleWarning(context, false);
// If the crosswalk is empty, make sure it is valid JSON
if (!crosswalk.val()) {
crosswalk.val(JSON.stringify([]));
}
// If a data-url was defined then this is an AJAX autocomplete
if (context.attr('data-url') && isObject(crosswalk)) {
// Setup the autocomplete and set it's source to the appropriate
context.autocomplete({
source: (req, resp) => search(context, req.term, crosswalk, resp),
select: (e, ui) => handleSelection(context, hidden, crosswalk, ui.item.label),
minLength: 3,
delay: 600,
appendTo: front,
});
} else {
const source = context.siblings(`#${id.replace('_name', '_sources')}`);
if (source) {
// Setup the autocomplete and set it's source to the appropriate
context.autocomplete({
source: JSON.parse(source.val()),
select: (e, ui) => handleSelection(context, hidden, crosswalk, ui.item.label),
minLength: 1,
delay: 300,
appendTo: front,
});
}
}
// Handle manual entry (instead of autocomplete selection)
context.on('keyup', (e) => {
const code = (e.keyCode || e.which);
// Only pay attention to key presses that would actually
// change the contents of the field
if ((code >= 48 && code <= 111) || (code >= 144 && code <= 222)
|| code === 8 || code === 9) {
handleSelection(context, hidden, crosswalk, context.val());
}
});
// Set the hidden id field to the value in the crosswalk
// or the default `{"name":"[value in textbox]"}`
hidden.val(findInCrosswalk(context.val(), crosswalk));
}
}
};
export default initAutocomplete;