1 <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
3 <div id="search_select_outer" class="autocomplete">
4 <div id="warning_pane" style="background: #FFFFFF; color: #CC0000;">
5 {% if incompatible == "true" %}
6 <h3>Warning: Incompatible Configuration</h3>
7 <p>Please make a different selection, as the current config conflicts with the selected pod</p>
10 <div id="added_counter">
11 <p id="added_number">0</p>
12 <p id="addable_limit">/ {% if selectable_limit > -1 %} {{ selectable_limit }} {% else %} ∞ {% endif %}added</p>
19 <input id="user_field" name="ignore_this" class="form-control" autocomplete="off" type="text" placeholder="{{placeholder}}" value="" oninput="search(this.value)"
20 {% if disabled %} disabled {% endif %}
24 <input type="hidden" id="selector" name="{{ name }}" class="form-control" style="display: none;"
25 {% if disabled %} disabled {% endif %}
29 <div id="scroll_restrictor">
30 <ul id="drop_results"></ul>
47 flex-direction: column;
53 border: 1px solid #ccc;
59 list-style-type: none;
63 border: solid 1px #ddd;
76 background-color: #f6f6f6;
78 text-decoration: none;
81 text-overflow: ellipsis;
86 border-bottom: 1px solid #ddd;
90 border: 1px solid #ccc;
93 vertical-align: middle;
101 #drop_results li a:hover{
102 background-color: #ffffff;
108 text-overflow: ellipsis;
122 text-overflow: ellipsis;
129 #drop_results li a:hover .entry_tooltip {
141 display: inline-block;
142 list-style-type: none;
145 text-overflow: ellipsis;
150 text-overflow: ellipsis;
157 #added_number, #addable_limit {
163 <script type="text/javascript">
165 var show_from_noentry = {{show_from_noentry|yesno:"true,false"}}; // whether to show any results before user starts typing
166 var show_x_results = {{show_x_results|default:-1}}; // how many results to show at a time, -1 shows all results
167 var results_scrollable = {{results_scrollable|yesno:"true,false"}}; // whether list should be scrollable
168 var selectable_limit = {{selectable_limit|default:-1}}; // how many selections can be made, -1 allows infinitely many
169 var placeholder = "{{placeholder|default:"begin typing"}}"; // placeholder that goes in text box
172 var items = {{items|safe}} // items to add to trie. Type is a dictionary of dictionaries with structure:
176 "id": any, identifiable on backend
177 "small_name": string, displayed first (before separator), searchable (use for e.g. username)
178 "expanded_name": string, displayed second (after separator), searchable (use for e.g. email address)
179 "string": string, not displayed, still searchable
185 {{ selectable_limit }}: changes what number displays for field
186 {{ name }}: form identifiable name, relevant for backend
187 // when submitted, form will contain field data in post with name as the key
188 {{ placeholder }}: "greyed out" contents put into search field initially to guide user as to what they're searching for
189 {{ initial }}: in search_field_init(), marked safe, an array of id's each referring to an id from items
193 var expanded_name_trie = {}
194 expanded_name_trie.isComplete = false;
195 var small_name_trie = {}
196 small_name_trie.isComplete = false;
198 string_trie.isComplete = false;
200 var added_items = [];
204 if( show_from_noentry )
210 var textfield = document.getElementById("user_field");
211 var drop = document.getElementById("drop_results");
213 textfield.disabled = "True";
214 drop.style.display = "none";
216 var btns = document.getElementsByClassName("btn-remove");
217 for( var i = 0; i < btns.length; i++ )
219 btns[i].classList.add("disabled");
223 function search_field_init() {
224 build_all_tries(items);
226 var initial = {{ initial|safe }};
228 for( var i = 0; i < initial.length; i++)
230 select_item(String(initial[i]));
232 if(initial.length == 1)
234 search(items[initial[0]]["small_name"]);
235 document.getElementById("user_field").value = items[initial[0]]["small_name"];
239 function build_all_tries(dict)
247 function add_item(item)
250 add_to_tree(item['expanded_name'], id, expanded_name_trie);
251 add_to_tree(item['small_name'], id, small_name_trie);
252 add_to_tree(item['string'], id, string_trie);
255 function add_to_tree(str, id, trie)
260 if( !inner_trie[str.charAt(0)] )
263 inner_trie[str.charAt(0)] = new_trie;
267 new_trie = inner_trie[str.charAt(0)];
270 if( str.length == 1 )
272 new_trie.isComplete = true;
277 new_trie.ids.push(id);
279 inner_trie = new_trie;
280 str = str.substring(1);
284 function search(input)
286 if( input.length == 0 && !show_from_noentry){
290 else if( input.length == 0 && show_from_noentry)
292 dropdown(items); //show all items
297 var tr1 = getSubtree(input, expanded_name_trie);
299 var tr2 = getSubtree(input, small_name_trie);
301 var tr3 = getSubtree(input, string_trie);
303 var results = collate(trees);
308 function getSubtree(input, given_trie)
311 recursive function to return the trie accessed at input
314 if( input.length == 0 ){
319 var substr = input.substring(0, input.length - 1);
320 var last_char = input.charAt(input.length-1);
321 var subtrie = getSubtree(substr, given_trie);
322 if( !subtrie ) //substr not in the trie
326 var indexed_trie = subtrie[last_char];
331 function serialize(trie)
334 takes in a trie and returns a list of its item id's
339 return itemIDs; //empty, base case
341 for( var key in trie )
347 itemIDs = itemIDs.concat(serialize(trie[key]));
349 if ( trie.isComplete )
351 itemIDs.push(...trie.ids);
357 function collate(trees)
360 takes a list of tries
361 returns a list of ids of objects that are available
364 for( var i in trees )
366 var available_IDs = serialize(trees[i]);
367 for( var j=0; j<available_IDs.length; j++){
368 var itemID = available_IDs[j];
369 results[itemID] = items[itemID];
375 function generate_element_text(obj)
377 var content_strings = [obj['expanded_name'], obj['small_name'], obj['string']].filter(x => Boolean(x));
378 var result = content_strings.shift();
379 if( result == null || content_strings.length < 1) return result;
380 return result + " (" + content_strings.join(", ") + ")";
383 function dropdown(ids)
386 takes in a mapping of ids to objects in items
387 and displays them in the dropdown
389 var drop = document.getElementById("drop_results");
390 while(drop.firstChild)
392 drop.removeChild(drop.firstChild);
397 var result_entry = document.createElement("li");
398 var result_button = document.createElement("a");
400 var result_text = generate_element_text(obj);
401 result_button.appendChild(document.createTextNode(result_text));
402 result_button.setAttribute('onclick', 'select_item("' + obj['id'] + '")');
403 var tooltip = document.createElement("span");
404 var tooltiptext = document.createTextNode(result_text);
405 tooltip.appendChild(tooltiptext);
406 tooltip.setAttribute('class', 'entry_tooltip');
407 result_button.appendChild(tooltip);
408 result_entry.appendChild(result_button);
409 drop.appendChild(result_entry);
412 var scroll_restrictor = document.getElementById("scroll_restrictor");
414 if( !drop.firstChild )
416 scroll_restrictor.style.visibility = 'hidden';
420 scroll_restrictor.style.visibility = 'inherit';
424 function select_item(item_id)
427 var item = items[item_id]['id'];
428 if( (selectable_limit > -1 && added_items.length < selectable_limit) || selectable_limit < 0 )
430 if( added_items.indexOf(item) == -1 )
432 added_items.push(item);
435 update_selected_list();
436 // clear search bar contents
437 document.getElementById("user_field").value = "";
438 document.getElementById("user_field").focus();
442 function remove_item(item_ref)
444 item = Object.values(items)[item_ref];
445 var index = added_items.indexOf(item);
446 added_items.splice(index, 1);
448 update_selected_list()
449 document.getElementById("user_field").focus();
452 function update_selected_list()
454 document.getElementById("added_number").innerText = added_items.length;
455 selector = document.getElementById('selector');
456 selector.value = JSON.stringify(added_items);
457 added_list = document.getElementById('added_list');
459 while(selector.firstChild)
461 selector.removeChild(selector.firstChild);
463 while(added_list.firstChild)
465 added_list.removeChild(added_list.firstChild);
470 for( var key in added_items )
472 item_id = added_items[key];
473 item = items[item_id];
475 var element_entry_text = generate_element_text(item);
477 list_html += '<div class="list_entry">'
478 + '<p class="added_entry_text">'
481 + '<button onclick="remove_item('
482 + Object.values(items).indexOf(item)
483 + ')" class="btn-remove btn">remove</button>';
484 list_html += '</div>';
487 added_list.innerHTML = list_html;