1

I need some help to figure it out a solution with the focusout and click event conflict.

I'm doing a dropdownlist project, so we have two part of the dropdown:

  1. If click on the arrow button, this one opened a list. But, when you click on the arrow button this one should close the list.

  2. If click on text box, this one opened a list. But, when you click on text box this one should close the list.

The problem that I have, it is focusout event of the text box when you try to close the list pressing the arrow button. It is opening, closing and opening again.

So it should close at once when need it and when is focusout if the list is opened.

PS: the delete button doesn't has function yet.

What should I do?

"use strict";

// LISTA DE INICIALIZACIÓN DE VARIABLES

// Se obtiene los ID de los elementos principales.

const tagSys = document.getElementById('tag-sys');
const tagInput = document.getElementById('input-section');
const textInput = document.getElementById('text-input');
const arrowButton = document.getElementById('btn-arrow');
var displayToggle = false;

// Se crea un arreglo por default.

var data = [
    'The Shawshank Redemption',
    'The Godfather',
    'The Godfather: Part II',
    'The Dark Knight',
    '12 Angry Men',
    "Schindler's List",
    'Pulp Fiction',
    'The Lord of the Rings: The Return of the King',
    'The Good, the Bad and the Ugly',
    'Fight Club'
];

// LISTA DE EVENTOS ACONTINUACIÓN

/* 
    El siguiente addEventListener se aplica sobre textInput.
    Su función hace mostrar el listado de data segun las palabras
    ingresadas.
*/

textInput.addEventListener('keyup', function (e) {
    console.log('keyup');
    let hasData = DropdownFilter(e.target.value);
    DropdownDisplay(hasData);
});

/* 
    El siguiente addEventListener se aplica sobre textInput.
    Su función hace mostrar el listado de la data.
*/

textInput.addEventListener('click', function () {
    DisplayToggle();
});

/* 
    El siguiente addEventListener se aplica sobre textInput.
    Su función hace cerrar el listado de la data.
*/

textInput.addEventListener('focusout', function () {
    if (displayToggle == true) {
        let hasData = false;
        DropdownDisplay(hasData);
        displayToggle = false;
    }
});

/* 
    El siguiente addEventListener se aplica sobre arrowButton.
    Su función hace enviar el focus al textInput y mostrar
    el listado de la data.
*/

arrowButton.addEventListener('click', function () {
    textInput.focus();
    DisplayToggle();
});

// LISTA DE MÉTODOS ACONTINUACIÓN

function DisplayToggle() {
    if (displayToggle == false) {
        let hasData = DropdownList(data);
        DropdownDisplay(hasData);
        displayToggle = true;
    } else {
        let hasData = false;
        DropdownDisplay(hasData);
        displayToggle = false;
    }
}

/* 
    El siguiente método DropdrowFilter, recibe como parametro
    una palabra que se encarga de buscar dentro del arreglo,
    para luego enviar al metodo DropdownList.
*/

function DropdownFilter(params) {
    let dataFilter = data.filter(function (e) {
        return e.toLowerCase().includes(params.toLowerCase());
    });
    return DropdownList(dataFilter);
}

/* 
    El siguiente método DropdrowList, recibe como parametro
    una lista de datos que usara para mostrar.
*/

function DropdownList(dataList) {
    RemoveList();
    if (dataList.length != 0) {
        let objList = {
            div: document.createElement('div'),
            ul: document.createElement('ul'),
            li: ''
        };

        objList.div.setAttribute("id", "tag-list");
        objList.div.classList.add('tag-list');
        tagInput.appendChild(objList.div);

        objList.div.appendChild(objList.ul);

        dataList.forEach(element => {
            objList.li = document.createElement('li');
            objList.li.textContent = element;
            objList.ul.appendChild(objList.li);
        });
    } else {
        let objList = {
            div: document.createElement('div'),
            ul: document.createElement('ul'),
            li: document.createElement('li')
        };

        objList.div.setAttribute("id", "tag-list");
        objList.div.classList.add('tag-list');
        tagInput.appendChild(objList.div);

        objList.div.appendChild(objList.ul);

        dataList[0] = 'No options';
        objList.li.textContent = dataList[0];
        objList.ul.appendChild(objList.li);
    }

    return true; // Retornamos True porque ya retorna data para mostrar.
}

/* 
    El siguiente método RemoveList, cumple como función
    eliminar la etiqueta con ID 'tag-list'.
*/

function RemoveList() {
    if (document.getElementById('tag-list')) {
        let div = document.getElementById('tag-list');
        tagInput.removeChild(div);
    }
}

/* 
    El siguiente metodo DropdownDisplay, cumple como función
    agregar la clase 'tag-list-show' para que sea visible la data,
    y la posición de la flecha.
*/

function DropdownDisplay(params) {
    let div = document.getElementById('tag-list');
    if (params == true) {
        div.classList.add('tag-list-show');
        arrowButton.classList.replace('fa-chevron-down', 'fa-chevron-up');
    } else {
        div.classList.remove('tag-list-show');
        arrowButton.classList.replace('fa-chevron-up', 'fa-chevron-down');
    }
}
body {
    margin: 0;
    background-color: #f39c12;
    font-family: 'Poppins', sans-serif;
}

main {
    align-items: center;
    display: flex;
    height: 100vh;
    justify-content: center;
}

.tag-sys {
    border: 1px solid #000000;
    border-radius: 2px;
    cursor: text;
    display: flex;
    font-size: 13px;
    max-width: 200px;
    min-height: 20px;
    position: relative;
}

.input-section {
    display: flex;
    flex-wrap: wrap;
    width: 80%;
}

.text-input {
    border: none;
    border-right: 1px solid #000000;
    border-bottom-left-radius: 2px;
    border-top-left-radius: 2px;
    font-size: 1em;
    flex: 1;
    margin: 0;
    min-width: 20px;
    padding: 0;
    padding-left: 2px;
}

.text-input::placeholder {
    color: #bdc3c7;
}

.text-input:focus {
    outline: none;
}

.control-section {
    align-items: center;
    background-color: #bdc3c7;
    display: flex;
    justify-content: space-around;
    width: 20%;
}

.control-section-icon {
    align-items: center;
    border-radius: 3px;
    cursor: pointer;
    display: flex;
    height: 16px;
    justify-content: center;
    width: 16px;
}

.control-section-icon i {
    font-size: 14px;
}

.tag-list {
    background-color: #ffffff;
    border: 1px solid #000000;
    border-radius: 2px;
    position: absolute;
    font-size: 14px;
    height: 100px;
    overflow-y: scroll;
    left: -1px;
    top: 22px;
    width: 100%;
    display: none;
}

.tag-list-show {
    display: block !important;
}

.tag-list ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
}

.tag-list ul li {
    padding: 5px;
}

.tag-list ul li:hover {
    cursor: pointer;
    background-color: #bdc3c7;
}
<link rel="stylesheet" type="text/css" href="style.css">
    <link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"> 
    <script src="https://kit.fontawesome.com/1001c03ba9.js" crossorigin="anonymous"></script>
    <main>
        <div id="tag-sys" class="tag-sys">
            <div id="input-section" class="input-section">
                <input id="text-input" class="text-input" type="text">
            </div>
            <div class="control-section">
                <span class="control-section-icon">
                    <i id="btn-delete" class="fas fa-times"></i>
                </span>
                <span class="control-section-icon">
                    <i id="btn-arrow" class="fas fa-chevron-down"></i>
                </span>
            </div>
        </div>
    </main>
    <script src="app.js"></script>
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
yasserpulido
  • 198
  • 1
  • 2
  • 11

1 Answers1

1

This is because focusout fires before click. Use mousedown instead

vrnvorona
  • 397
  • 3
  • 8
  • I tried that and It didn't results. I moved `click` event first than `focusout` and I got the same result. I changed `click` for `mousedown` and nothing. – yasserpulido Aug 05 '20 at 14:05
  • Ok, i investigated further. It happens because you have `.focus` on click/mousdown. So, with clicks you trigger `focusout->click` and because of that you hide-show list very quickly. That is initial problem. But first easy solution is pointless because with mousedown you get `mousedown->focusin->focusout`. Mousedown triggers function which focuses on textfield, and mouseup after mousedown removes focus and triggers hide for dropdown. –  vrnvorona Aug 05 '20 at 16:10
  • Yes, I saw it, it is a kinda cycle between focusout and click. What do you recommend me? I thought about another solution, do not focus inputText when click on arrowButton, but that solution gives me another problem, how to close the list if I can't use focusout on arrowButton. – yasserpulido Aug 06 '20 at 02:26
  • 1
    I suggest to try use other way to close menu on outside, like `click` for document if target is not text field. https://stackoverflow.com/questions/6463486/jquery-drop-down-menu-closing-by-clicking-outside like here, but selected answer is not ideal since it uses stopPropagation which may be problematic in some cases. Though if for you it works perfectly it's ok. –  vrnvorona Aug 06 '20 at 03:47
  • That link, helped me to find something new for me, it's `Bubbling`. Also, I used the `addEveneListener` on `document`, but it was happening the same problem, so I used `stopPropagation` on the others arrowButton and inputButton `addEventListener` and it is working that I expect. – yasserpulido Aug 07 '20 at 13:43