Сохранение таймаута внутри элемента - PullRequest
1 голос
/ 14 мая 2019

Я создал <select multiple> с раскрывающимся списком (если вас интересует полный код: Codepen или фрагмент в нижней части) с вспомогательной функцией (упрощенная версия ниже), обрабатывающей событие blur ( потеря фокуса ввода или щелчок где-то еще):

function blur(element) {
    clearTimeout(eval(element.dataset.timer));
    element.dataset.timer = setTimeout(() => {
        if (activeElement !== el)
            el.style.display = "none";
    }, 200).toString();
}

Создает тайм-аут и сохраняет его в свойстве dataset-свойство элемента. Поскольку это свойство принимает только строки, оно преобразуется в toString() и обратно в eval() при очистке предыдущего таймера. Это НЕ является необходимым в том смысле, что оно прекрасно работает, если не выполнять преобразование и оценку следующим образом:

function blur(element) {
    clearTimeout(element.dataset.timer);
    element.dataset.timer = setTimeout(() => {
        if (activeElement !== el)
            el.style.display = "none";
    }, 200);
}

В любом случае это похоже на взлом, и я также знаю, что eval() не следует использовать, поэтому мне интересно, как мне вместо этого решить эту проблему. Я уже показал свой код CodeReview , конкретно упомянув функцию, но никто не жаловался на это. Это не может быть правильным способом сделать это, не так ли?

Вот фрагмент полного кода, если это необходимо:

convertSelect("001", "Options");

function convertSelect(el_id, name) {
    let el = document.getElementById(el_id),
        opts = Array.from(el.options);

    let input_el = document.createElement('input');
    input_el.setAttribute('id', el_id + '_input');
    input_el.setAttribute('type', 'text');
    input_el.setAttribute('autocomplete', 'off');
    input_el.setAttribute('readonly', 'readonly');
    input_el.setAttribute('style', `width:${el.offsetWidth}px`);
    input_el.addEventListener('focus', () => document.getElementById(el_id + '_span').style.display = "");
    input_el.addEventListener('blur', () => blur(el_id));
    el.parentNode.insertBefore(input_el, el.nextSibling);

    let span_el = document.createElement('span');
    span_el.setAttribute('id', el_id + '_span');
    span_el.setAttribute('style', `min-width:${(input_el.offsetWidth + 50)}px;margin-top:${input_el.offsetHeight}px;margin-left:-${input_el.offsetWidth}px;position:absolute;border:1px solid grey;display:none;z-index:9999;text-align:left;background:white;max-height:130px;overflow-y:auto;overflow-x:hidden;`);
    span_el.addEventListener('mouseout', () => blur(el_id));
    span_el.addEventListener('click', () => document.getElementById(el_id + '_input').focus());
    input_el.parentNode.insertBefore(span_el, input_el.nextSibling);

    opts.forEach(opt => {
        let i = opts.indexOf(opt);

        let temp_label = document.createElement('label');
        temp_label.setAttribute('for', el_id + '_' + i);

        let temp_input = document.createElement('input');
        temp_input.setAttribute('style', 'width:auto;');
        temp_input.setAttribute('type', 'checkbox');
        temp_input.setAttribute('id', el_id + '_' + i);
        temp_input.checked = opt.selected;
        temp_input.disabled = opt.disabled || el.disabled;
        temp_input.addEventListener('change', () => check(el_id, name));

        temp_label.appendChild(temp_input);
        temp_label.appendChild(document.createTextNode(opt.textContent));
        span_el.appendChild(temp_label);
    });
    el.style.display = 'none';
    check(el_id, name);
}


function blur(el_id) {
    let el = document.getElementById(el_id);
    clearTimeout(el.dataset.timer);
    el.dataset.timer = setTimeout(() => {
        if (document.activeElement.id !== el_id + '_input' && document.activeElement.id !== el_id + '_span')
            document.getElementById(el_id + '_span').style.display = "none";
    }, 200);
}

function check(el_id, name) {
    let el = document.getElementById(el_id),
        opts = Array.from(el.options),
        select_qty = 0,
        select_name;

    opts.forEach(opt => {
        let i = opts.indexOf(opt),
            checkbox = document.getElementById(`${el_id}_${i}`);

        el.options[i].selected = checkbox.checked;
        if (checkbox.checked) {
            select_name = checkbox.parentElement.childNodes[1].textContent;
            select_qty++;
        }
        document.getElementById(`${el_id}_input`).value = select_qty < 1 ? '' : (select_qty > 1 ? `${select_qty} ${name}` : select_name);
    });

    el.dispatchEvent(new Event('change', { 'bubbles': true }));
}
label {
  display: block;
}

input[type="text"]:hover {
  cursor: default;
}
<select id="001" multiple>
  <option value="2">Option Two</option>
  <option value="4">Option Four</option>
  <option value="6">Option Six</option>
  <option value="8" disabled>Disabled Option</option>
</select>

Ответы [ 2 ]

2 голосов
/ 14 мая 2019

Если вы пришли из статически скомпилированного языка, иногда JavaScripts освобождает упаковку / распаковку типов, что может показаться чуждым.Однако в вашем примере не должно быть проблем.

Однако существуют другие способы хранения данных в элементе, и если ваши данные усложняются, хранение в атрибуте данных может оказаться не самой лучшей идеей..

Один из вариантов - это просто расширение вашей идеи атрибута данных.Вы можете использовать JSON для сериализации ваших данных:

element.dataset.options = JSON.stringify({timerId: setTimeout(....)});

Один из недостатков вышеизложенного состоит в том, что типы, которые вы можете хранить, ограничены тем, что JSON может сериализовать, но такие вещи, как строки / целые числа / массивы и т. Д. Работают хорошо.

Элементы в JavaScript такие же, как и любой другой объект.Таким образом, теоретически вы можете просто хранить как свойство:

element._mytimerid = setTimeout(...`, 

При таком подходе вы должны быть осторожны с тем, что вы назвали свойством.

Закрытия являются популярным выбором:

function setupBlur() {
  var timerId = null;
  element.onBlur = function () {
    clearTimeout(timerId);
    timerId = setTimeout(.....
  }
}

setupBlur();

Вы также можете использовать вышеупомянутое внутри IIFE, чтобы сохранить вызов setupBlur:

(function () { 
  var timerId = null;
  element.onBlur = function () {
  clearTimeout(timerId);
  timerId = setTimeout(.....
}());

Более современный способ хранения данных в элементе - использование WeakMap:

const timerIds = new WeakMap();
.....
const timerId = timerIds.get(element);
clearTimeout(timerId);
timerIds.set(element, setTimer(.....));
0 голосов
/ 14 мая 2019

Можете ли вы создать глобальный объект таймера, в котором вы храните тайм-аут (ы) с последовательными ключами и сохраняете только ключ в наборе данных?Вроде как:

var timersObj = {};
//store timeout in object
timersObj["timerIndex_0"] = setTimeout(() => {
        if (activeElement !== el)
            el.style.display = "none";
    }, 200)
element.dataset.timer = "timerIndex_0";
//clear timeout by key
clearTimeout(timersObj[element.dataset.timer])
...