Commit e35871eb authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Merge branch 'enc-search' into enc-search-logic

parents 66bf2d5b 08e45aff
......@@ -522,36 +522,26 @@ div.title span.unfolded::before{
}*/
.textfield-filter{
padding: 6px;
border: 1px solid #DDD;
width: 200px;
}
.material-name-autocomplete-textfield, .material-name-autocomplete-dropdown{
width: 450px;
}
/* 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;
border: 1px solid transparent;
}
......@@ -559,6 +549,48 @@ div.title span.unfolded::before{
border-color: #E56400 !important;
}
.AutocompleteMultiselectTextfield-selected-box{
padding: 3px;
}
.selectedItemLabel{
display: inline-block;
border-left: 1px solid #E56400;border-right: 1px solid #E56400;
color: #E56400;
/*box-shadow: 1px 1px 2px #BBB;*/
border-radius: 3px;
padding: 1px 4px;
margin-right: 8px; margin-top: 3px;
cursor: pointer;
}
/* Material name autocomplete */
.material-name-autocomplete-textfield{
width: 450px;
}
.material-name-autocomplete-dropdown{
background-color: #DDD;
box-shadow: 1px 1px 4px gray;
}
/* Structure type autocomplete */
.structure_type-autocomplete-multiselect-textfield input[type="text"]{
padding: 6px;
border: 1px solid #DDD;
width: 200px;
}
.structure_type-autocomplete-multiselect-dropdown{
width: 210px;
background-color: white;
box-shadow: 0 1px 2px #E56400;
}
/* To remove
.autocomplete-em {
......
......@@ -26,255 +26,199 @@
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 = true) {
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
// When clicking on the textfield the dropdown shows up
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);
// the dropdown list is updated as the user writes on the textfield
this.input.addEventListener("input", (e) => {
this._processInput();
});
/* react to keyboard navigation */
this.element.addEventListener("keydown", (e) => {
if (e.keyCode == 40) { // arrow DOWN
this._setActive(this.currentFocus + 1);
} else if (e.keyCode == 38) { // arrow UP
this._setActive(this.currentFocus - 1);
} else if (e.keyCode == 27) { // ESC key
this.element.value = '';
this._closeAllLists();
}
// The dropdown is hiden when the mouse leaves the component area
this.element.addEventListener("mouseleave", e => {
this._cleanList();
});
/* react to enter key */
this.element.addEventListener("keypress", (e) => {
if (e.keyCode == 13) { // ENTER
/* simulate a click on the "active" item:*/
this._clickActive();
}
// Handle the item removal on the selected items box
this.selectedItemsBox.addEventListener("click", e => {
let itemLabel = event.target.closest('span');
this._removeSelectedValue(itemLabel.dataset.value);
//itemLabel.dispatchEvent(new Event('change'));
});
/* react to klicking into the textfield */
this.element.addEventListener("click", (e) => {
this._processInput();
e.stopPropagation();
// Handle the item selection on the dropdown
this.listContainer.addEventListener("click", e => {
let listItem = event.target.closest('div');
this._toggleItem(listItem);
});
// close lists when someone clicks in the document:*/
document.addEventListener("click", (e) => {
this.element.value = '';
this._closeAllLists();
this.listContainer.addEventListener("mouseover", e => {
let listItem = event.target.closest('div');
this._setActiveListItem(listItem);
});
}
getSelected() {
let values = [];
for (let item of this.items) {
if (item.selected) {
values.push(item.value);
}
}
return values;
getValues(){
return Array.from(this.selectedValues);
}
_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();
}
});
listContainer.addEventListener("keypress", (e) => {
if (e.keyCode == 13) { // ENTER
/* simulate a click on the "active" item:*/
this._clickActive();
}
});
/* 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);
}
acItemIndex++;
}
}
/*resetValue(){
this.input.value = '';
}*/
disable(bool){
this.input.disabled = bool;
}
_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));
setAutocompleteList(valueList){
this.valueList = valueList;
}
setSelectListener(listener) {
this.selectListener = listener;
}
_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;
}
/* 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();
// 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;
}
/* hovering puts the focus on the related list item */
listItem.addEventListener("mouseover", (e) => {
this._setActive(itemIndex);
});
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;
}
_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;
_removeSelectedValue(value) {
/* mark the active status by a style class */
listItems[newFocus].classList.add("autocomplete-active");
this.selectedValues.delete(value);
this.selectedItemsBox.querySelector('span[data-value="'+value+'"]').remove();
// check if the dropdown is unfolded and uncheck the checkbox if so
const itemCheckbox = this.listContainer.querySelector('input[data-value="'+value+'"]');
if (itemCheckbox) itemCheckbox.checked = false;
}
_clickActive() {
if (this.currentFocus > -1) {
let listItems = document.getElementById("autocomplete-list")
.getElementsByTagName("div");
listItems[this.currentFocus].click();
_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(value);
function createSelectedItemLabel(value){
const label = document.createElement('span');
label.className = 'selectedItemLabel';
label.dataset.value = value;
label.innerHTML = `${value} ❌`;
return label;
}
}
_closeAllLists() {
/*close all autocomplete lists in the document */
let allAcLists = document.getElementsByClassName("autocomplete-items");
for (let acList of allAcLists) {
acList.parentNode.removeChild(acList);
_setActiveListItem(element){
const currentActiveItem = this.listContainer.querySelector('.autocomplete-active');
if (currentActiveItem) currentActiveItem.classList.remove('autocomplete-active');
element.classList.add('autocomplete-active');
}
/* Not being used for now
_getActiveListItem(){
return this.listContainer.querySelector('.autocomplete-active');
}
this.currentFocus = -1;
_clickOnActiveItem() {
const activeItem = this.listContainer.querySelector('.autocomplete-active');
if (activeItem) activeItem.click();
}*/
_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;
}
......
......@@ -137,8 +137,7 @@ class FilterPanel {
filterMap.set(values.fieldId, values.value)
}
});
console.log('FilterPanel getValues:', filterMap);
//console.log('FilterPanel getValues:', filterMap);
return filterMap;
}
......@@ -282,20 +281,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');
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 } );
}
......
......@@ -68,10 +68,11 @@ class MaterialList {
this._render();
}
/*
invalidateSearch(){
this._hide();
}*/
this.visible = false;
this._render();
}
initSearch(optimadeQuery){
......
......@@ -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);