413 lines
15 KiB
TypeScript
Executable File
413 lines
15 KiB
TypeScript
Executable File
import { addSubscription } from '../../utils/subscription.utils';
|
|
import { navigate } from '../../router';
|
|
import Services from '../../services/service';
|
|
|
|
// Initialize function, create initial tokens with itens that are already selected by the user
|
|
export async function init() {
|
|
const element = document.querySelector('select') as HTMLSelectElement;
|
|
// Create div that wroaps all the elements inside (select, elements selected, search div) to put select inside
|
|
const wrapper = document.createElement('div');
|
|
if (wrapper) addSubscription(wrapper, 'click', clickOnWrapper);
|
|
wrapper.classList.add('multi-select-component');
|
|
wrapper.classList.add('input-field');
|
|
|
|
// Create elements of search
|
|
const search_div = document.createElement('div');
|
|
search_div.classList.add('search-container');
|
|
const input = document.createElement('input');
|
|
input.classList.add('selected-input');
|
|
input.setAttribute('autocomplete', 'off');
|
|
input.setAttribute('tabindex', '0');
|
|
if (input) {
|
|
addSubscription(input, 'keyup', inputChange);
|
|
addSubscription(input, 'keydown', deletePressed);
|
|
addSubscription(input, 'click', openOptions);
|
|
}
|
|
|
|
const dropdown_icon = document.createElement('a');
|
|
dropdown_icon.classList.add('dropdown-icon');
|
|
|
|
if (dropdown_icon) addSubscription(dropdown_icon, 'click', clickDropdown);
|
|
const autocomplete_list = document.createElement('ul');
|
|
autocomplete_list.classList.add('autocomplete-list');
|
|
search_div.appendChild(input);
|
|
search_div.appendChild(autocomplete_list);
|
|
search_div.appendChild(dropdown_icon);
|
|
|
|
const processes = await getProcesses();
|
|
for (let process of processes) {
|
|
const processName = process[1].title;
|
|
const opt = new Option(processName);
|
|
opt.value = processName;
|
|
element.add(opt);
|
|
}
|
|
// set the wrapper as child (instead of the element)
|
|
element.parentNode?.replaceChild(wrapper, element);
|
|
// set element as child of wrapper
|
|
wrapper.appendChild(element);
|
|
wrapper.appendChild(search_div);
|
|
|
|
addPlaceholder(wrapper);
|
|
|
|
// const select = document.querySelector(".select-field");
|
|
// for (const process of processeList) {
|
|
// const option = document.createElement("option");
|
|
// option.setAttribute("value", process.name);
|
|
// option.innerText = process.name;
|
|
|
|
// select.appendChild(option);
|
|
// }
|
|
}
|
|
|
|
function removePlaceholder(wrapper: HTMLElement) {
|
|
const input_search = wrapper.querySelector('.selected-input');
|
|
input_search?.removeAttribute('placeholder');
|
|
}
|
|
|
|
function addPlaceholder(wrapper: HTMLElement) {
|
|
const input_search = wrapper.querySelector('.selected-input');
|
|
const tokens = wrapper.querySelectorAll('.selected-wrapper');
|
|
if (!tokens.length && !(document.activeElement === input_search)) input_search?.setAttribute('placeholder', '---------');
|
|
}
|
|
|
|
// Listener of user search
|
|
function inputChange(e: Event) {
|
|
const target = e.target as HTMLInputElement;
|
|
const wrapper = target?.parentNode?.parentNode;
|
|
const select = wrapper?.querySelector('select') as HTMLSelectElement;
|
|
const dropdown = wrapper?.querySelector('.dropdown-icon');
|
|
|
|
const input_val = target?.value;
|
|
|
|
if (input_val) {
|
|
dropdown?.classList.add('active');
|
|
populateAutocompleteList(select, input_val.trim());
|
|
} else {
|
|
dropdown?.classList.remove('active');
|
|
const event = new Event('click');
|
|
dropdown?.dispatchEvent(event);
|
|
}
|
|
}
|
|
|
|
// Listen for clicks on the wrapper, if click happens focus on the input
|
|
function clickOnWrapper(e: Event) {
|
|
const wrapper = e.target as HTMLElement;
|
|
if (wrapper.tagName == 'DIV') {
|
|
const input_search = wrapper.querySelector('.selected-input');
|
|
const dropdown = wrapper.querySelector('.dropdown-icon');
|
|
if (!dropdown?.classList.contains('active')) {
|
|
const event = new Event('click');
|
|
dropdown?.dispatchEvent(event);
|
|
}
|
|
(input_search as HTMLInputElement)?.focus();
|
|
removePlaceholder(wrapper);
|
|
}
|
|
}
|
|
|
|
function openOptions(e: Event) {
|
|
const input_search = e.target as HTMLElement;
|
|
const wrapper = input_search?.parentElement?.parentElement;
|
|
const dropdown = wrapper?.querySelector('.dropdown-icon');
|
|
if (!dropdown?.classList.contains('active')) {
|
|
const event = new Event('click');
|
|
dropdown?.dispatchEvent(event);
|
|
}
|
|
e.stopPropagation();
|
|
}
|
|
|
|
// Function that create a token inside of a wrapper with the given value
|
|
function createToken(wrapper: HTMLElement, value: any) {
|
|
const search = wrapper.querySelector('.search-container');
|
|
const inputInderline = document.querySelector('.selected-processes');
|
|
// Create token wrapper
|
|
const token = document.createElement('div');
|
|
token.classList.add('selected-wrapper');
|
|
const token_span = document.createElement('span');
|
|
token_span.classList.add('selected-label');
|
|
token_span.innerText = value;
|
|
const close = document.createElement('a');
|
|
close.classList.add('selected-close');
|
|
close.setAttribute('tabindex', '-1');
|
|
close.setAttribute('data-option', value);
|
|
close.setAttribute('data-hits', '0');
|
|
close.innerText = 'x';
|
|
if (close) addSubscription(close, 'click', removeToken);
|
|
token.appendChild(token_span);
|
|
token.appendChild(close);
|
|
inputInderline?.appendChild(token);
|
|
}
|
|
|
|
// Listen for clicks in the dropdown option
|
|
function clickDropdown(e: Event) {
|
|
const dropdown = e.target as HTMLElement;
|
|
const wrapper = dropdown?.parentNode?.parentNode;
|
|
const input_search = wrapper?.querySelector('.selected-input') as HTMLInputElement;
|
|
const select = wrapper?.querySelector('select') as HTMLSelectElement;
|
|
dropdown.classList.toggle('active');
|
|
|
|
if (dropdown.classList.contains('active')) {
|
|
removePlaceholder(wrapper as HTMLElement);
|
|
input_search?.focus();
|
|
|
|
if (!input_search?.value) {
|
|
populateAutocompleteList(select, '', true);
|
|
} else {
|
|
populateAutocompleteList(select, input_search.value);
|
|
}
|
|
} else {
|
|
clearAutocompleteList(select);
|
|
addPlaceholder(wrapper as HTMLElement);
|
|
}
|
|
}
|
|
|
|
// Clears the results of the autocomplete list
|
|
function clearAutocompleteList(select: HTMLSelectElement) {
|
|
const wrapper = select.parentNode;
|
|
|
|
const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
|
|
if (autocomplete_list) autocomplete_list.innerHTML = '';
|
|
}
|
|
|
|
// Populate the autocomplete list following a given query from the user
|
|
function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) {
|
|
const { autocomplete_options } = getOptions(select);
|
|
console.log('🚀 ~ populateAutocompleteList ~ autocomplete_options:', autocomplete_options);
|
|
|
|
let options_to_show;
|
|
|
|
if (dropdown) options_to_show = autocomplete_options;
|
|
else options_to_show = autocomplete(query, autocomplete_options);
|
|
|
|
const wrapper = select.parentNode;
|
|
const input_search = wrapper?.querySelector('.search-container');
|
|
const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
|
|
if (autocomplete_list) autocomplete_list.innerHTML = '';
|
|
const result_size = options_to_show.length;
|
|
|
|
if (result_size == 1) {
|
|
const li = document.createElement('li');
|
|
li.innerText = options_to_show[0];
|
|
li.setAttribute('data-value', options_to_show[0]);
|
|
if (li) addSubscription(li, 'click', selectOption);
|
|
autocomplete_list?.appendChild(li);
|
|
if (query.length == options_to_show[0].length) {
|
|
const event = new Event('click');
|
|
li.dispatchEvent(event);
|
|
}
|
|
} else if (result_size > 1) {
|
|
for (let i = 0; i < result_size; i++) {
|
|
const li = document.createElement('li');
|
|
li.innerText = options_to_show[i];
|
|
li.setAttribute('data-value', options_to_show[i]);
|
|
if (li) addSubscription(li, 'click', selectOption);
|
|
autocomplete_list?.appendChild(li);
|
|
}
|
|
} else {
|
|
const li = document.createElement('li');
|
|
li.classList.add('not-cursor');
|
|
li.innerText = 'No options found';
|
|
autocomplete_list?.appendChild(li);
|
|
}
|
|
}
|
|
|
|
// Listener to autocomplete results when clicked set the selected property in the select option
|
|
function selectOption(e: any) {
|
|
console.log('🚀 ~ selectOption ~ e:', e);
|
|
const wrapper = e.target.parentNode.parentNode.parentNode;
|
|
const input_search = wrapper.querySelector('.selected-input');
|
|
const option = wrapper.querySelector(`select option[value="${e.target.dataset.value}"]`);
|
|
|
|
console.log('🚀 ~ selectOption ~ option:', option);
|
|
option.setAttribute('selected', '');
|
|
createToken(wrapper, e.target.dataset.value);
|
|
if (input_search.value) {
|
|
input_search.value = '';
|
|
}
|
|
|
|
showSelectedProcess(e.target.dataset.value);
|
|
|
|
input_search.focus();
|
|
|
|
e.target.remove();
|
|
const autocomplete_list = wrapper.querySelector('.autocomplete-list');
|
|
|
|
if (!autocomplete_list.children.length) {
|
|
const li = document.createElement('li');
|
|
li.classList.add('not-cursor');
|
|
li.innerText = 'No options found';
|
|
autocomplete_list.appendChild(li);
|
|
}
|
|
|
|
const event = new Event('keyup');
|
|
input_search.dispatchEvent(event);
|
|
e.stopPropagation();
|
|
}
|
|
|
|
// function that returns a list with the autcomplete list of matches
|
|
function autocomplete(query: string, options: any) {
|
|
// No query passed, just return entire list
|
|
if (!query) {
|
|
return options;
|
|
}
|
|
let options_return = [];
|
|
|
|
for (let i = 0; i < options.length; i++) {
|
|
if (query.toLowerCase() === options[i].slice(0, query.length).toLowerCase()) {
|
|
options_return.push(options[i]);
|
|
}
|
|
}
|
|
return options_return;
|
|
}
|
|
|
|
// Returns the options that are selected by the user and the ones that are not
|
|
function getOptions(select: HTMLSelectElement) {
|
|
// Select all the options available
|
|
const all_options = Array.from(select.querySelectorAll('option')).map((el) => el.value);
|
|
|
|
// Get the options that are selected from the user
|
|
const options_selected = Array.from(select.querySelectorAll('option:checked')).map((el: any) => el.value);
|
|
|
|
// Create an autocomplete options array with the options that are not selected by the user
|
|
const autocomplete_options: any[] = [];
|
|
all_options.forEach((option) => {
|
|
if (!options_selected.includes(option)) {
|
|
autocomplete_options.push(option);
|
|
}
|
|
});
|
|
|
|
autocomplete_options.sort();
|
|
|
|
return {
|
|
options_selected,
|
|
autocomplete_options,
|
|
};
|
|
}
|
|
|
|
// Listener for when the user wants to remove a given token.
|
|
function removeToken(e: Event) {
|
|
// Get the value to remove
|
|
const target = e.target as HTMLSelectElement;
|
|
const value_to_remove = target.dataset.option;
|
|
const wrapper = target.parentNode?.parentNode?.parentNode;
|
|
const input_search = wrapper?.querySelector('.selected-input');
|
|
const dropdown = wrapper?.querySelector('.dropdown-icon');
|
|
// Get the options in the select to be unselected
|
|
const option_to_unselect = wrapper?.querySelector(`select option[value="${value_to_remove}"]`);
|
|
option_to_unselect?.removeAttribute('selected');
|
|
// Remove token attribute
|
|
(target.parentNode as any)?.remove();
|
|
dropdown?.classList.remove('active');
|
|
const process = document.querySelector('#' + target.dataset.option);
|
|
process?.remove();
|
|
}
|
|
|
|
// Listen for 2 sequence of hits on the delete key, if this happens delete the last token if exist
|
|
function deletePressed(e: Event) {
|
|
const input_search = e.target as HTMLInputElement;
|
|
const wrapper = input_search?.parentNode?.parentNode;
|
|
const key = (e as KeyboardEvent).keyCode || (e as KeyboardEvent).charCode;
|
|
const tokens = wrapper?.querySelectorAll('.selected-wrapper');
|
|
|
|
if (tokens?.length) {
|
|
const last_token_x = tokens[tokens.length - 1].querySelector('a');
|
|
let hits = +(last_token_x?.dataset?.hits || 0);
|
|
|
|
if (key == 8 || key == 46) {
|
|
if (!input_search.value) {
|
|
if (hits > 1) {
|
|
// Trigger delete event
|
|
const event = new Event('click');
|
|
last_token_x?.dispatchEvent(event);
|
|
} else {
|
|
if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '2';
|
|
}
|
|
}
|
|
} else {
|
|
if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '0';
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Dismiss on outside click
|
|
addSubscription(document, 'click', () => {
|
|
// get select that has the options available
|
|
const select = document.querySelectorAll('[data-multi-select-plugin]');
|
|
for (let i = 0; i < select.length; i++) {
|
|
if (event) {
|
|
var isClickInside = select[i].parentElement?.parentElement?.contains(event.target as Node);
|
|
|
|
if (!isClickInside) {
|
|
const wrapper = select[i].parentElement?.parentElement;
|
|
const dropdown = wrapper?.querySelector('.dropdown-icon');
|
|
const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
|
|
//the click was outside the specifiedElement, do something
|
|
dropdown?.classList.remove('active');
|
|
if (autocomplete_list) autocomplete_list.innerHTML = '';
|
|
addPlaceholder(wrapper as HTMLElement);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
async function showSelectedProcess(elem: MouseEvent) {
|
|
if (elem) {
|
|
const cardContent = document.querySelector('.process-card-content');
|
|
|
|
const processes = await getProcesses();
|
|
const process = processes.find((process: any) => process[1].title === elem);
|
|
if (process) {
|
|
const processDiv = document.createElement('div');
|
|
processDiv.className = 'process';
|
|
processDiv.id = process[0];
|
|
const titleDiv = document.createElement('div');
|
|
titleDiv.className = 'process-title';
|
|
titleDiv.innerHTML = `${process[1].title} : ${process[1].description}`;
|
|
processDiv.appendChild(titleDiv);
|
|
for (const zone of process[1].zones) {
|
|
const zoneElement = document.createElement('div');
|
|
zoneElement.className = 'process-element';
|
|
const zoneId = process[1].title + '-' + zone.id;
|
|
zoneElement.setAttribute('zone-id', zoneId);
|
|
zoneElement.setAttribute('process-title', process[1].title);
|
|
zoneElement.setAttribute('process-id', `${process[0]}_${zone.id}`);
|
|
zoneElement.innerHTML = `${zone.title}: ${zone.description}`;
|
|
addSubscription(zoneElement, 'click', select);
|
|
processDiv.appendChild(zoneElement);
|
|
}
|
|
if (cardContent) cardContent.appendChild(processDiv);
|
|
}
|
|
}
|
|
}
|
|
|
|
function select(event: Event) {
|
|
const target = event.target as HTMLElement;
|
|
const oldSelectedProcess = document.querySelector('.selected-process-zone');
|
|
oldSelectedProcess?.classList.remove('selected-process-zone');
|
|
if (target) {
|
|
target.classList.add('selected-process-zone');
|
|
}
|
|
const name = target.getAttribute('zone-id');
|
|
console.log('🚀 ~ select ~ name:', name);
|
|
}
|
|
|
|
function goToProcessPage() {
|
|
const target = document.querySelector('.selected-process-zone');
|
|
console.log('🚀 ~ goToProcessPage ~ event:', target);
|
|
if (target) {
|
|
const process = target?.getAttribute('process-id');
|
|
|
|
console.log('=======================> going to process page', process);
|
|
navigate('process-element/' + process);
|
|
}
|
|
}
|
|
|
|
(window as any).goToProcessPage = goToProcessPage;
|
|
|
|
async function getProcesses(): Promise<any[]> {
|
|
const service = await Services.getInstance();
|
|
const processes = await service.getProcesses();
|
|
|
|
return processes;
|
|
}
|