Commit 77df0cbd authored by Iker Hurtado's avatar Iker Hurtado
Browse files

New multiselect autocomplete textfield (AutocompleteMultiselectTextfield.js)...

New multiselect autocomplete textfield (AutocompleteMultiselectTextfield.js) that shows the items selected (without dropping the component dropdown)
(unfinished)
parent 5ff199f4
Pipeline #93413 skipped with stage
......@@ -522,11 +522,7 @@ div.title span.unfolded::before{
}*/
.textfield-filter{
padding: 6px;
border: 1px solid #DDD;
width: 200px;
}
.material-name-autocomplete-textfield, .material-name-autocomplete-dropdown{
......@@ -535,20 +531,20 @@ div.title span.unfolded::before{
/* Autocomplete component */
/* Autocomplete components */
.AutocompleteTextField{
display: inline-block;
}
.AutocompleteTextField-dropdown{
.AutocompleteTextField-dropdown, .AutocompleteMultiselectTextfield-dropdown{
font-size: 0.9em;
position: absolute;
z-index: 99;
box-shadow: 1px 1px 4px gray;
}
.AutocompleteTextField-dropdown > div {
.AutocompleteTextField-dropdown > div, .AutocompleteMultiselectTextfield-dropdown > div {
padding: 2px 10px 2px 10px;
cursor: pointer;
background-color: #DDD;
......@@ -559,6 +555,32 @@ div.title span.unfolded::before{
border-color: #E56400 !important;
}
/*
.AutocompleteMultiselectTextfield{
}
*/
.AutocompleteMultiselectTextfield-selected-box{
padding: 3px;
}
.selectedItemLabel{
display: inline-block;
/*border: 1px solid gray;*/
box-shadow: 1px 1px 2px #BBB;
border-radius: 4px;
padding: 1px 5px;
cursor: pointer;
}
.structure_type-autocomplete-multiselect-textfield input[type="text"]{
padding: 6px;
border: 1px solid #DDD;
width: 200px;
}
/* To remove
.autocomplete-em {
......
......@@ -26,255 +26,218 @@
class AutocompleteMultiselectTextfield {
constructor(name = "") {
this.classPostfix = name;
this.element = document.createElement('input');
this.element.type = 'text';
this.element.className = 'autocomplete-multiselectlist-' + this.classPostfix;
this.currentFocus = -1;
this.selectListener = undefined;
/* the items member variable keeps track of the selected options
by storing a (sorted) list of all possible values and their selection state.
A list entry looks like {"value": "A1", "selected": false}
*/
this.items = [];
}
replaceElement(oldElement) {
oldElement.parentElement.replaceChild(this.element, oldElement);
}
setSelectListener(listener) {
this.selectListener = listener;
}
autocomplete(allAcValues) {
/* the autocomplete function takes an array of possible autocomplete values.
in the following we will use 'ac' as abbrevation for 'autocomplete'
*/
/* store possible values and initialize them as not selected */
for (var i = 0; i < allAcValues.length; i++) {
this.items.push({value: allAcValues[i],
selected: false}
);
}
constructor(id = "", placeholder = '', allowEmptyInput = false) {
this.id = id;
this.element = document.createElement('div');
//this.element.style.display = 'inline';
this.element.className = `AutocompleteMultiselectTextfield ${id}-autocomplete-multiselect-textfield`;
this.element.innerHTML = `
<input type="text" placeholder="${placeholder}" />
<div class="AutocompleteMultiselectTextfield-selected-box"></div>
<div class="AutocompleteMultiselectTextfield-dropdown ${this.id}-autocomplete-multiselect-dropdown"></div>
`;
this.input = this.element.querySelector('input');
this.selectedItemsBox = this.element.querySelector('.AutocompleteMultiselectTextfield-selected-box');
this.listContainer = this.element.querySelector('.AutocompleteMultiselectTextfield-dropdown');
this.selectListener;
// state
this.valueList; // List of autocomplete (possible) values
this.selectedValues = new Set();
this.allowEmptyInput = allowEmptyInput;
// event management
//
// react to clicking into the textfield
this.input.addEventListener("click", (e) => {
this._processInput();
e.stopPropagation();
});
/* process input when someone writes in the text field:*/
this.element.addEventListener("input", (e) => {
this._processInput(allAcValues);
this.input.addEventListener("input", (e) => {
this._processInput();
});
/* react to keyboard navigation */
this.element.addEventListener("keydown", (e) => {
/*
// react to keyboard navigation
this.input.addEventListener("keydown", (e) => {
if (e.keyCode == 40) { // arrow DOWN
this._setActive(this.currentFocus + 1);
if (this._getActiveListItem())
this._setActiveListItem(this._getActiveListItem().nextSibling);
} else if (e.keyCode == 38) { // arrow UP
this._setActive(this.currentFocus - 1);
} else if (e.keyCode == 27) { // ESC key
this.element.value = '';
this._closeAllLists();
if (this._getActiveListItem())
this._setActiveListItem(this._getActiveListItem().previousSibling);
}
});
/* react to enter key */
this.element.addEventListener("keypress", (e) => {
// react to enter key
this.input.addEventListener("keypress", e => {
if (e.keyCode == 13) { // ENTER
/* simulate a click on the "active" item:*/
this._clickActive();
// simulate a click on the "active" item
this._clickOnActiveItem();//_clickActive();
}
});
/* react to klicking into the textfield */
this.element.addEventListener("click", (e) => {
this._processInput();
e.stopPropagation();
// close lists when someone clicks in the document
document.addEventListener("click", e => {
console.log('close lists when someone clicks in the document')
this._cleanList();// this._closeAllLists();
});
It doesn't worki properly because it closes the dropdown when you click on it
*/
// close lists when someone clicks in the document:*/
document.addEventListener("click", (e) => {
this.element.value = '';
this._closeAllLists();
this.selectedItemsBox.addEventListener("click", e => {
let itemLabel = event.target.closest('span'); // (1)
this._toggleValue(itemLabel.dataset.value);//.textContent);
});
}
getSelected() {
let values = [];
for (let item of this.items) {
if (item.selected) {
values.push(item.value);
}
}
return values;
}
_processInput() {
let currentInput = this.element.value;
/*close any already open lists of autocompleted values*/
this._closeAllLists();
/*create a DIV element that will contain the items (values):*/
let listContainer = document.createElement("DIV");
listContainer.setAttribute("id", "autocomplete-list");
listContainer.classList.add("autocomplete-items");
listContainer.classList.add("autocomplete-items-"+ this.classPostfix);
/*append the DIV element as a child of the autocomplete container:*/
this.element.parentNode.appendChild(listContainer);
/* keyboard interaction */
listContainer.setAttribute("tabindex", "0");
listContainer.addEventListener("keydown", (e) => {
if (e.keyCode == 40) { // arrow DOWN
this._setActive(this.currentFocus + 1);
e.preventDefault();
} else if (e.keyCode == 38) { // arrow UP
this._setActive(this.currentFocus - 1);
e.preventDefault();
} else if (e.keyCode == 27) { // ESC key
this.element.value = '';
this._closeAllLists();
}
this.listContainer.addEventListener("click", e => {
let listItem = event.target.closest('div'); // (1)
this._toggleItem(listItem);//.textContent);
});
listContainer.addEventListener("keypress", (e) => {
if (e.keyCode == 13) { // ENTER
/* simulate a click on the "active" item:*/
this._clickActive();
}
this.listContainer.addEventListener("mouseover", e => {
let listItem = event.target.closest('div'); // (1)
this._setActiveListItem(listItem);
});
/* show all items matching the input text */
let acItemIndex = 0;
for (let item of this.items) {
/*check if the item contains the same letters as the text field value:*/
let acValue = item.value;
let acSelected = item.selected;
let pos = 0;
if (currentInput) {
pos = acValue.toUpperCase().search(currentInput.toUpperCase());
}
/* if there is no input text given, pos = 0 and thus an item is generated */
if (pos >= 0){
let listItem = this._generateListItem(acValue,
acSelected,
currentInput,
acItemIndex)
listContainer.appendChild(listItem);
/* check if a valid option was completely entered.
if so, set the focus to the element corresponding to the input */
if (acValue.toUpperCase() === currentInput.toUpperCase()) {
this._setActive(acItemIndex);
getValues(){
return [];//this.input.value;
}
acItemIndex++;
resetValue(){
this.input.value = '';
}
disable(bool){
this.input.disabled = bool;
}
setAutocompleteList(valueList){
this.valueList = valueList;
}
_generateListItem(acText, selected, inputText, itemIndex) {
/*create a DIV element for each matching element:*/
let listItem = document.createElement("div");
let itemCheckbox = document.createElement("input");
itemCheckbox.type = "checkbox";
itemCheckbox.checked = selected;
/* TODO: check why catching this event is necessary */
itemCheckbox.addEventListener("click", e => {
listItem.click();
e.stopPropagation();
});
listItem.appendChild(itemCheckbox);
/*make the matching letters bold:*/
if (inputText && inputText != ""){
let pos = acText.toUpperCase().search(inputText.toUpperCase());
listItem.appendChild(document.createTextNode(acText.substr(0, pos)));
let emText = document.createElement("span");
emText.className = "autocomplete-em";
emText.innerHTML = acText.substr(pos, inputText.length);
/* TODO: check why catching this event is necessary */
emText.addEventListener("click", e => {
listItem.click();
e.stopPropagation();
});
listItem.appendChild(emText);
listItem.appendChild(document.createTextNode(acText.substr(pos + inputText.length)));
} else {
listItem.appendChild(document.createTextNode(acText));
setSelectListener(listener) {
this.selectListener = listener;
}
/* clicking on the AS list item puts selects the corresponding name for searching */
listItem.addEventListener("click", (e) => {
let checkbox = e.target.getElementsByTagName("input")[0];
this._toggleSelect(acText, checkbox);
e.stopPropagation();
});
/* hovering puts the focus on the related list item */
listItem.addEventListener("mouseover", (e) => {
this._setActive(itemIndex);
_processInput() {
const currentInput = this.input.value;
// close any already open lists of autocompleted values
this._cleanList();//this._closeAllLists();
// in case of an empty input field
if (!this.allowEmptyInput && !currentInput) {
return false;
}
// for each autocomplete value
let counter = 0;
const matchingValues = this.valueList.filter( value => {
const matching = value.toUpperCase().includes(currentInput.toUpperCase());
if (matching) counter++;
return counter <= 15 && matching;
});
//console.log('matchingValues', matchingValues)
this.listContainer.innerHTML = '';
matchingValues.forEach( value => {
const listItem = generateListItem(value, currentInput, this.selectedValues.has(value));
this.listContainer.append(listItem);
// check if a valid option was completely entered. if so, set the focus to the element corresponding to the input
if (value.toUpperCase() === currentInput.toUpperCase())
this._setActiveListItem(listItem);
function generateListItem(value, inputText, present) {
const listItem = document.createElement("div");
let innerHTML = `<input type="checkbox" data-value="${value}" ${present ? 'checked' : ''}>`;
if (inputText){
const pos = value.toUpperCase().indexOf(inputText.toUpperCase()); // console.log('pos', pos)
innerHTML +=
`${value.substring(0, pos)}<strong>${value.substring(pos, pos+inputText.length)}</strong>${value.substring(pos + inputText.length)}`;
}else
innerHTML += value;
listItem.innerHTML = innerHTML;
return listItem;
}
// _setText(value) {
// /*insert the value for the autocomplete text field:*/
// this.element.value = value;
// /* notify listener */
// if (this.selectListener) {
// this.selectListener();
// }
//
// /*close the list of autocompleted values,
// (or any other open lists of autocompleted values)*/
// this._closeAllLists();
// }
_toggleSelect(value, checkbox) {
let newSelected;
for (let item of this.items) {
if (item.value == value) {
newSelected = !item.selected;
item.selected = newSelected;
break;
}
});
}
checkbox.checked = newSelected;
_toggleValue(value) {
const listItem = this.listContainer.querySelector('input[data-value="'+value+'"]').parentElement;
this._toggleItem(listItem);
}
_setActive(index) {
let listItems = document.getElementById("autocomplete-list")
.getElementsByTagName("div");
/* remove the active status from all list items */
Array.from(listItems).forEach(item => {
item.classList.remove("autocomplete-active");
});
/* ensure to stay in the list
out of boundary indices are mapped to the closest border */
let newFocus = Math.max(0, index);
newFocus = Math.min(newFocus, listItems.length-1);
this.currentFocus = newFocus;
_toggleItem(listItem) {
const value = listItem.textContent;
const present = this.selectedValues.has(value);
listItem.querySelector('input').checked = !present;
if (present){
this.selectedValues.delete(value);
this.selectedItemsBox.querySelector('span[data-value="'+value+'"]').remove();
}else{
this.selectedValues.add(value);
this.selectedItemsBox.append(createSelectedItemLabel(value));
}
//console.log('_addValue: this.selectedValues', this.selectedValues)
if (this.selectListener) this.selectListener();
/* mark the active status by a style class */
listItems[newFocus].classList.add("autocomplete-active");
function createSelectedItemLabel(value){
const label = document.createElement('span');
label.className = 'selectedItemLabel';
label.dataset.value = value;
label.innerHTML = `${value} ❌`;
return label;
}
}
_clickActive() {
if (this.currentFocus > -1) {
let listItems = document.getElementById("autocomplete-list")
.getElementsByTagName("div");
listItems[this.currentFocus].click();
_getActiveListItem(){
return this.listContainer.querySelector('.autocomplete-active');
}
_setActiveListItem(element){
const currentActiveItem = this.listContainer.querySelector('.autocomplete-active');
if (currentActiveItem) currentActiveItem.classList.remove('autocomplete-active');
element.classList.add('autocomplete-active');
}
_closeAllLists() {
/*close all autocomplete lists in the document */
let allAcLists = document.getElementsByClassName("autocomplete-items");
for (let acList of allAcLists) {
acList.parentNode.removeChild(acList);
_clickOnActiveItem() {
const activeItem = this.listContainer.querySelector('.autocomplete-active');
if (activeItem) activeItem.click();
}
this.currentFocus = -1;
_cleanList() {
this.listContainer.innerHTML = '';
}
}
......
......@@ -31,8 +31,8 @@ class AutocompleteTextfield {
this.element.className = `AutocompleteTextField ${id}-autocomplete-textfield`;
this.element.innerHTML = `
<input type="text" placeholder="${placeholder}" /> <!-- class="autocomplete-textfield-${this.id}" /> -->
<div class="AutocompleteTextField-dropdown ${this.id}-autocomplete-dropdown"></div> <!-- autocomplete-items- -->
<input type="text" placeholder="${placeholder}" />
<div class="AutocompleteTextField-dropdown ${this.id}-autocomplete-dropdown"></div>
`;
this.input = this.element.querySelector('input');
......@@ -145,14 +145,14 @@ class AutocompleteTextfield {
if (value.toUpperCase() === currentInput.toUpperCase())
this._setActiveListItem(listItem);
function generateListItem(value, inputText, itemIndex) {
function generateListItem(value, inputText) {
const listItem = document.createElement("div");
if (inputText){
const pos = value.toUpperCase().indexOf(inputText.toUpperCase()); // console.log('pos', pos)
listItem.innerHTML +=
`${value.substring(0, pos)}<strong>${value.substring(pos, pos+inputText.length)}</strong>${value.substring(pos + inputText.length)}`;
}else
listItem.innerHTML = acText;
listItem.innerHTML = value;
return listItem;
}
......
......@@ -282,20 +282,18 @@ class AutocompleteField{
</div>`;
this.autocomplete = new AutocompleteMultiselectTextfield(id);
this.autocomplete.element.placeholder = "Search and select options";
this.autocomplete.element.classList.add('textfield-filter');
this.autocomplete = new AutocompleteMultiselectTextfield(id, 'Search and select options', true);
this.element.append(this.autocomplete.element)
let r1 = util.serverReq(util.getSuggestionURL(this.fieldId), (e) => {
let names = JSON.parse(r1.response)[this.fieldId];
this.autocomplete.autocomplete(names);
this.autocomplete.setAutocompleteList(names);
});
}
getValues(){
const values = this.autocomplete.getSelected();
const values = this.autocomplete.getValues();//getSelected();
return ( values.length === 0 ? null : { fieldId: this.fieldId, value: values } );
}
......
......@@ -34,7 +34,6 @@ class MaterialNameBox{
`;
this.materialNameField = new AutocompleteTextfield('material-name', 'Start typing a material name');
this.materialNameField.element.querySelector('input').classList.add('textfield-composition'); // ** Pending to change
this.element.prepend(this.materialNameField.element);
this.button = this.element.querySelector('.adding-name-btn');
......
......@@ -830,9 +830,8 @@ class FormulaBox{
this.element.className = 'FormulaBox';// this.element.setAttribute("id",'formula-box');
this.element.innerHTML=
`
<input type="text" class="textfield-composition"
placeholder="Add formula to the search query above" >
<button class="adding-formula-btn" disabled>Add to query</button>
<input type="text" placeholder="Add formula to the search query above">
<button class="adding-formula-btn" disabled> Add to query </button>
`;
this.formulaTextField = this.element.querySelector('input');
this.formulaButton = this.element.querySelector('.adding-formula-btn');
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment