Задать вопрос
Ответы пользователя по тегу JavaScript
  • Как сделать сортировку по двум параметрам?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Сортируем существующий массив:

    arr.sort((a, b) => a.surname.localeCompare(b.surname) || a.name.localeCompare(b.name));

    Собираем отсортированный новый:

    const sorted = (arr, keys) => arr
      .map(n => [ n ].concat(keys(n)))
      .sort((a, b) => {
        let diff = 0;
        a.find((n, i) => diff = i && ((n < b[i]) ? -1 : +(n > b[i])));
        return diff;
      })
      .map(n => n[0]);
    
    const sortedArr = sorted(arr, n => [ n.surname.toLowerCase(), n.name.toLowerCase() ]);
    Ответ написан
    Комментировать
  • Почему заполняется только один select?

    0xD34F
    @0xD34F Куратор тега JavaScript
    selects[0].appendChild(option)
    selects[1].appendChild(option)

    Как один элемент может одновременно находиться внутри двух других элементов? Очевидно же, что никак:

    Если данный дочерний элемент является ссылкой на существующий узел в документе, то функция appendChild() перемещает его из текущей позиции в новую позицию

    Это должны быть два разных option'а. Можете во втором случае добавлять копию:

    selects[1].appendChild(option.cloneNode(true));

    Но вообще, вариант так себе. Появится ещё один select - побежите копипастить строку с добавлением option'а? Надо сделать цикл по select'ам. Например, так:

    selects.forEach(function(n) {
      n.append(...this.map(m => new Option(m[1].label, m[0])));
    }, Object.entries(metrics.convertRules));

    Или, можно собирать html вместо создания отдельных элементов:

    const optionsHTML = Object
      .entries(metrics.convertRules)
      .map(n => `<option value="${n[0]}">${n[1].label}</option>`)
      .join('');
    
    selects.forEach(n => n.innerHTML = optionsHTML);
    Ответ написан
  • Как заполнить произведение для каждой ячейки?

    0xD34F
    @0xD34F Куратор тега JavaScript
    62b0f00f35c0f358713719.png

    "Добавить"? Понятно, почему ничего не получается - формы добавляются динамически, так что обработчики событий вы пытаетесь назначать элементам, которых ещё нет. Применим делегирование - обработчик добавляем один раз, выше форм, проверяем целевой элемент, если это оказался интересующий нас input, то получаем из него элемент формы (поднимаемся или просто по ссылке, если ваша форма действительно является формой), из элемента формы получаем input'ы (ищем или опять же, если у вас там <form>, то доступна коллекция ссылок) и элемент, в который надо записать результат вычислений.

    document.addEventListener('input', e => {
      if (e.target.classList.contains('form-control')) {
        const form = e.target.closest('тут селектор формы');
        form.querySelector('span').innerText = Array.prototype.reduce.call(
          form.querySelectorAll('input.form-control'),
          (acc, n) => acc * (+n.value || 0),
          1
        );
      }
    });
    
    // или
    
    document.addEventListener('input', ({ target: t }) => {
      if (t.matches('.form-control')) {
        t.form.querySelector('span').textContent = Array
          .from(t.form.elements)
          .reduce((acc, n) => acc * (n.value | 0), 1);
      }
    });
    Ответ написан
    Комментировать
  • Как правильно распарсить такой массив со строками?

    0xD34F
    @0xD34F Куратор тега JavaScript
    arr.reduce((acc, n) => (
      n = n.match(/(\S+) = (.*)/),
      n && (acc[n[1]] = n[2]),
      acc
    ), {})

    или

    Object.fromEntries(arr
      .map(n => n.split(': ').pop().split(' = '))
      .filter(n => n.length === 2)
    )
    Ответ написан
    2 комментария
  • Как реализовать несколько независимых степперов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Как опознать кнопки +/-, как обновить значение:

    const plusSelector = '.count-plus';
    const minusSelector = '.count-minus';
    
    function updateCount(el, change) {
      const counterEl = el.closest('.count-wrapper').querySelector('.counter');
      counterEl.innerText = Math.max(0, +counterEl.innerText + change);
    }

    Слушать клики можно непосредственно на кнопках:

    function setClickHandler(selector, change) {
      document.querySelectorAll(selector).forEach(function(n) {
        n.addEventListener('click', this);
      }, e => updateCount(e.target, change));
    }
    
    setClickHandler(plusSelector, 1);
    setClickHandler(minusSelector, -1);

    Или на каком-нибудь их общем предке:

    document.addEventListener('click', ({ target: t }) => {
      const change = +t.matches(plusSelector) || -t.matches(minusSelector);
      if (change) {
        updateCount(t, change);
      }
    });
    Ответ написан
    Комментировать
  • Почему функция ничего не возвращает?

    0xD34F
    @0xD34F Куратор тега JavaScript
    return arr.forEach((fn) => {

    Понятно.

    Вот дураки вроде вас: раз дурак, два дурак, три дурак, четыре, пять.
    Ответ написан
    1 комментарий
  • Как с помощью JQUERY перебрать и сложить значение нескольких select?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Функция суммирования:

    function sum(data, key = n => n) {
      const getVal = key instanceof Function ? key : n => n[key];
      let result = 0;
    
      for (const n of data) {
        result += +getVal(n) || 0;
      }
    
      return result;
    }

    Элементы могут быть представлены как jquery-объект, NodeList или HTMLCollection (ну и разумеется, обычный массив, если что, тоже будет обсчитан как надо):

    const $form = $('form').on('change', 'select', () => {
      $('#result').val(sum($form.find('select'), n => $(n).val()));
    });

    const input = document.querySelector('#result');
    const selects = document.querySelectorAll('form select');
    const onChange = () => input.value = sum(selects, 'value');
    selects.forEach(n => n.addEventListener('change', onChange));

    document.querySelector('form').addEventListener('change', function(e) {
      if (e.target.tagName === 'SELECT') {
        document.getElementById('result').value = sum(
          this.getElementsByTagName('select'),
          n => n.selectedOptions[0].getAttribute('value')
        );
      }
    });

    document.forms[0].onchange = ({ target: t }) => {
      if (t.matches('select')) {
        t.form.result.value = sum(
          t.form.querySelectorAll('option:checked:not(:disabled)'),
          n => n.attributes.value.value
        );
      }
    };
    Ответ написан
    1 комментарий
  • Как убрать checked с input radio при клике на него?

    0xD34F
    @0xD34F Куратор тега JavaScript
    А что за задача-то решается? Нужен гибрид радиокнопки и чекбокса?

    Вариант раз, радиокнопки:

    let checked = null;
    
    $('input').click(function() {
      checked = checked === this.value ? null : this.value;
      this.checked = !!checked;
    });
    
    // или
    
    document.querySelectorAll('input').forEach(function(n) {
      n.addEventListener('click', this);
    }, function({ target: t }) {
      t.checked = !!(this[0] = this[0] === t ? null : t);
    }.bind([]));

    Вариант два, с заменой радиокнопок чекбоксами:

    const $checkboxes = $('input').change(function() {
      if (this.checked) {
        $checkboxes.not(this).prop('checked', false);
      }
    });
    
    // или
    
    const checkboxes = document.querySelectorAll('input');
    const onChange = e => checkboxes.forEach(n => n.checked &&= n === e.target);
    checkboxes.forEach(n => n.addEventListener('change', onChange));

    Вариант три - вместо того, чтобы изменять значение свойства checked вручную, добавьте ещё одну радиокнопку, которая будет обозначать отсутствие выбора.
    Ответ написан
    1 комментарий
  • Как передать в аргументы функции карирования старые аргумент + новые?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const sum5 = (...args) =>
      args.length > 4
        ? args.slice(0, 5).reduce((acc, n) => acc + n, 0)
        : sum5.bind(null, ...args);
    //  или
    //  : (...args2) => sum5(...args, ...args2);
    Ответ написан
    1 комментарий
  • Как сделать, чтобы при выборе значения в одном select'е оно пропадало в других?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const selects = [...document.querySelectorAll('select')];
    const onChange = () =>
      selects.forEach(function({ value, options: [...n] }) {
        n.forEach(m => m.hidden = this(m.value) && value !== m.value);
      }, Set.prototype.has.bind(new Set(selects.map(n => n.value))));
    
    selects.forEach(n => n.addEventListener('change', onChange));
    Ответ написан
    1 комментарий
  • Как "развернуть" таблицу, превратив строки в столбцы?

    0xD34F
    @0xD34F Куратор тега JavaScript
    function transposeTable(table) {
      const headerCol = table.rows[0]?.cells[1]?.tagName === 'TH';
      const content = Array.from(
        table.rows,
        tr => Array.from(tr.cells, td => td.innerHTML)
      );
    
      table.innerHTML = content[0]?.map((n, i) => `
        <tr>${content.map((m, j) => (j = (headerCol ? j : i) ? 'td' : 'th', `
          <${j}>${m[i]}</${j}>`)).join('')}
        </tr>
      `).join('') ?? '';
    }

    или

    function transposeTable(table) {
      const cells = Array.prototype.reduce.call(table.rows, (acc, tr) => (
        Array.prototype.forEach.call(tr.cells, (n, i) => (acc[i] ??= []).push(n)),
        acc
      ), []);
    
      table.replaceChildren();
      cells.forEach(n => table.insertRow().append(...n));
    }

    https://jsfiddle.net/q4wy9s1o/
    Ответ написан
    Комментировать
  • Как проверить, что набор карт образует покерную комбинацию "стрит"?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Сделать вспомогательный объект, где для нечисловых карт будут указываться их числовые эквиваленты. Массив карт превратить в массив соответствующих им чисел (карты используется как ключ для извлечения значения из объекта, если ничего не найдено, то карта приводится к числу); отсортировать массив; убедиться, что разность соседних значений всегда составляет единицу.

    const weights = {
      j: 11,
      q: 12,
      k: 13,
      a: 14,
    };
    
    const isStraight = hand => hand
      .map(n => weights[n] ?? +n)
      .sort((a, b) => a - b)
      .every((n, i, a) => !i || (n - a[i - 1] === 1));
    Ответ написан
    Комментировать
  • Как убрать disabled при повторном нажатии на чекбокс?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Не надо отслеживать никаких "повторных" "нажатий".
    Достаточно поставить состояние кнопки в зависимость от состояния чекбокса:

    чекбокс.addEventListener('change', e => кнопка.disabled = !e.target.checked);
    Ответ написан
    1 комментарий
  • Как отобразить блок "Нет элементов"?

    0xD34F
    @0xD34F Куратор тега JavaScript
    $('.elem_block').on('click', '.dell', function() {
      $(this).closest('.block').addClass('fx_none');
      $('.no-res').toggleClass('fx_none', !!$('.block:not(.fx_none)').length);
    });

    или

    document.addEventListener('click', e => {
      const btn = e.target.closest('.dell');
      if (btn) {
        btn.closest('.block').classList.add('fx_none');
        document.querySelector('.no-res').classList.toggle(
          'fx_none',
          !!document.querySelector('.block:not(.fx_none)')
        );
      }
    });

    UPD. Наконец-то дождались. Теперь можно сделать так:

    • Класс fx_none вырезаем
    • Элементам .no-res и .elem_block добавляем общую обёртку, какой-нибудь <div class="container">
    • По кликам на .dell вместо добавления класса, скрывающего элементы, удаляем их по-настоящему:

      document.querySelectorAll('.dell').forEach(function(n) {
        n.addEventListener('click', this);
      }, e => e.target.closest('.block').remove());
    • Прячем .no-res, если в .elem_block что-то есть:

      .container:has(.elem_block *) .no-res {
        display: none;
      }

      Если же удаление элементов не вариант, то прятать .no-res надо в том случае, если существует .block без класса, которые его скрывает:

      .container:has(.block:not(.fx_none)) .no-res {
        display: none;
      }


    Ответ написан
    Комментировать
  • Как переделать код для множества segmented control?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const controls = document.querySelectorAll('.segmented-control');
    controls.forEach(n => n.addEventListener('change', updatePillPosition));
    window.addEventListener('resize', () => controls.forEach(n => updatePillPosition.call(n)));
    
    function updatePillPosition() {
      const inputs = [...this.querySelectorAll('.option input')];
      const x = this.offsetWidth / inputs.length * inputs.findIndex(n => n.checked);
      this.querySelector('.selection').style.transform = `translateX(${x}px)`;
    }
    Ответ написан
    Комментировать
  • Как показать определённое количество блоков по клику?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const SHOW_ON_LOAD = 3;
    const SHOW_MORE = 2;
    
    const items = [...document.querySelectorAll('.box-list__item')];
    const button = document.querySelector('.show-more');
    
    showItems(SHOW_ON_LOAD);
    button.addEventListener('click', () => showItems(SHOW_MORE));
    
    function showItems(count) {
      items.splice(0, count).forEach(n => n.classList.add('ui-box-active'));
      button.classList.toggle('ui-button-hidden', !items.length);
    }
    Ответ написан
    1 комментарий
  • Когда применять arr.reduce?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Когда сочтёте нужным. Типа, подумали, и решили - здесь reduce нужен. Или не нужен. Да, подумали. Для этого у вас есть особый инструмент - голова называется.
    Ответ написан
    Комментировать
  • Как превратить данные input в массив js?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const data = Array.prototype.reduce.call(
      document.querySelectorAll('input'),
      (acc, n) => {
        const keys = n.name.match(/\w+/g);
        const key = keys.pop();
        keys.reduce((p, c) => p[c] ??= {}, acc)[key] = n.value;
        return acc;
      },
      {}
    );
    Ответ написан
    Комментировать
  • Как сделать кастомный выпадающий список?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Вместо назначения индивидуальных обработчиков клика элементам списка (которых в момент назначения ещё нет, openItems оказывается пуст, так что обработчики никому не назначаются) сделайте один делегированный, надо

    openItems.forEach(function (formItem) {
      formItem.addEventListener('click', function () {
        openBtn.innerText = this.innerText;
        openList.classList.remove('open');
        hiddenInput.value = this.dataset.value;
      });
    });

    заменить на

    openList.addEventListener('click', function(e) {
      const item = e.target.closest('li.form-item');
      if (item) {
        openBtn.innerText = item.innerText;
        openList.classList.remove('open');
        hiddenInput.value = item.dataset.value;
      }
    });
    Ответ написан
    1 комментарий