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

    0xD34F
    @0xD34F Куратор тега JavaScript
    function parseDate(str) {
      const months = Array.from(
        { length: 12 },
        (_, i) => new Date(0, i).toLocaleString('ru-RU', { month: 'long' })
      );
    
      const [ , month, day, year, hour, minute ] = str.match(/(\S+) (\d+), (\d+) (\d+):(\d+)/);
    
      return +new Date(
        year,
        months.indexOf(month.toLowerCase()),
        day,
        hour,
        minute
      );
    }
    Ответ написан
    1 комментарий
  • Как сделать такой фильтр опций на js или jq?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Собираем селектор на основе состояния чекбоксов (выставлен - класс должен присутствовать, не выставлен - помещаем класс внутрь :not), проверяем фильтруемые элементы на соответствие селектору:

    document.querySelector('.options').addEventListener('change', e => {
      const [ not, has ] = Array.prototype.reduce.call(
        e.currentTarget.querySelectorAll('input'),
        (acc, n) => (acc[+n.checked].push(`.${n.value}`), acc),
        [ [], [] ]
      );
    
      const selector = `${has.join('')}:not(${not.join(',')})`;
    
      for (const n of document.querySelector('.box').children) {
        n.classList.toggle('hidden', !n.matches(selector));
      }
    });

    Или, при обработке элемента перебираем чекбоксы и проверяем, что их состояния соответствуют наличию классов:

    const checkboxes = [...document.querySelectorAll('.options input')];
    const items = [...document.querySelector('.box').children];
    
    checkboxes.forEach(n => n.addEventListener('change', onChange));
    
    function onChange() {
      items.forEach(({ classList: cl }) => {
        cl.toggle('hidden', checkboxes.some(n => n.checked !== cl.contains(n.value)));
      });
    }
    Ответ написан
  • Как разбить многостроковый текст на массив с помощью регулярного выражения?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Понимаю что можно сделать проще с помощью split, но хочу научиться именно с помощью регулярного выражения

    Одно другому не мешает:

    str.split(/\n(?=#EXTINF)/)
    Ответ написан
    Комментировать
  • Как кликать на вложенные элементы так, чтобы не кликался родительский элемент?

    0xD34F
    @0xD34F Куратор тега JavaScript
    .dropdown.show {
      display: block;
    }

    const accordionItemSelector = '.data-accordion--summary-container';
    const contentSelector = '.dropdown';
    const activeClass = 'show';
    
    document.body.addEventListener('click', ({ target: t }) => {
      if (t.matches(accordionItemSelector)) {
        document.querySelectorAll(contentSelector).forEach(n => {
          if (!n.contains(t) && !t.contains(n)) {
            n.classList.remove(activeClass);
          };
        });
    
        t.querySelector(contentSelector).classList.toggle(activeClass);
      }
    });
    Ответ написан
    Комментировать
  • Как из query string получить объект следующего вида?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Просто исправить существующий код:

    - acc[key] = [value];
    + (acc[key] ??= []).push(value);

    Но можно его ещё и сократить:

    const convertQueryStringToObject = str => Array
      .from(new URLSearchParams(str))
      .reduce((acc, n) => ((acc[n[0]] ??= []).push(n[1]), acc), {});

    Или неоправданно усложнить и изуродовать:

    const convertQueryStringToObject = str => Array
      .from(str.matchAll(/([^&]+)=([^&]+)/g))
      .reduce((acc, [ , k, v ]) => (
        Object.hasOwn(acc, k) || (acc[k] = []),
        acc[k][acc[k].length] = v,
        acc
      ), {});
    Ответ написан
    1 комментарий
  • Как высчитать уровень исходя из массива?

    0xD34F
    @0xD34F Куратор тега JavaScript
    работает, но кажется, что достаточно криво

    Да, криво - всегда перебирается весь массив.

    Можно идти от конца к началу до тех пор, пока не встретится подходящий элемент:

    let lvl = 0;
    
    for (let i = LVLS.length; i--; ) {
      if (LVLS[i].exp <= EXP) {
        lvl = LVLS[i].lv;
        break;
      }
    }

    переделать это во что-то более красивое

    const lvl = LVLS.findLast(n => n.exp <= EXP)?.lv ?? 0;

    А вообще, учитывая, что уровни представлены натуральными числами без пропусков, отдельные свойства под их значения не нужны, можно искать индекс:

    const LVLS = [ 10, 25, 45, 70, 100 ];
    
    const lvl = 1 + LVLS.findLastIndex(n => n <= EXP);
    Ответ написан
    Комментировать
  • Как отобразить данные по тегам из json на странице?

    0xD34F
    @0xD34F Куратор тега JavaScript
    <button data-country="">Показать всё</button>
    <button data-country="France">Франция</button>
    <button data-country="Germany">Германия</button>
    <button data-country="England">Англия</button>

    const catalogEl = document.querySelector('#catalog-box');
    let catalogArr = [];
    
    const buttons = document.querySelectorAll('button[data-country]');
    buttons.forEach(n => n.addEventListener('click', onClick));
    
    function onClick({ target: t }) {
      buttons.forEach(n => n.classList.toggle('active', n === t));
      renderCatalog(t.dataset.country);
    }
    
    function renderCatalog(country) {
      const toRender = country
        ? catalogArr.filter(n => n.tag === country)
        : catalogArr;
    
      catalogEl.innerHTML = toRender
        .map(n => `
          <div class="catalog__card" id="${n.id}">
            <img src="${n.img}" alt="${n.alt}" class="catalog__card_img">
            <p class="catalog__card_author">${n.author}</p>
            <p class="catalog__card_name">${n.name}</p>
            <p class="catalog__card_note">${n.note}</p>
            <p class="catalog__card_price">${n.price}</p>
            <button class="catalog__card_btn">${n.btn}</button>
          </div>`)
        .join('');
    }
    
    fetch('catalogBox.json')
      .then(r => r.json())
      .then(r => {
        catalogArr = r;
        buttons[0].click();
      });
    Ответ написан
    Комментировать
  • Как сформировать массив объектов исходя из 2-х других массивов объектов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Сначала из первого массива сделаем объект, где значениями будут его элементы, а ключами - значения их, элементов, свойства name:

    const obj1 = Object.fromEntries(arr1.map(n => [ n.name, n ]));

    Затем можно собрать новый массив:

    const newArr2 = arr2.map(n => ({ ...obj1[n.name], ...n }));

    Или обновить существующий:

    arr2.forEach(n => {
      const obj = obj1[n.name];
      if (obj) {
        n.id = obj.id;
      }
    });

    UPD. Вынесено из комментариев:

    можно ли исключить объекты у которых не изменился value? То есть что бы этих объектов не было в итоговом массиве.

    const newArr2 = arr2.reduce((acc, n) => (
      obj1[n.name]?.value !== n.value && acc.push({ ...obj1[n.name], ...n }),
      acc
    ), []);
    Ответ написан
    5 комментариев
  • Как ограничить количество отмеченных чекбоксов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Отключаем чекбоксы, изменение состояния которых нежелательно:

    function restrictChecked({
      container,
      selector = 'input[type="checkbox"]',
      min = 0,
      max = Infinity,
      enableOnCancel = true,
    }) {
      const checkboxes = [...container.querySelectorAll(selector)];
      const onChange = () => {
        const countChecked = checkboxes.reduce((acc, n) => acc + n.checked, 0);
        const minReached = countChecked <= min;
        const maxReached = countChecked >= max;
        checkboxes.forEach(n => n.disabled = minReached && n.checked || maxReached && !n.checked);
      };
    
      checkboxes.forEach(n => n.addEventListener('change', onChange));
      return () => checkboxes.forEach(n => {
        n.disabled &&= !enableOnCancel;
        n.removeEventListener('change', onChange);
      });
    }

    Или, откатываем нежелательные изменения:

    function restrictChecked({
      container,
      selector = 'input[type="checkbox"]',
      min = 0,
      max = Infinity,
    }) {
      function onChange({ target: t }) {
        if (t.matches(selector)) {
          const countChecked = this.querySelectorAll(`${selector}:checked`).length;
          t.checked ||= countChecked <  min;
          t.checked &&= countChecked <= max;
        }
      }
    
      container.addEventListener('change', onChange);
      return () => container.removeEventListener('change', onChange);
    }
    Ответ написан
    Комментировать
  • Как с помощью reduce и рекурсии получить уровни вложенности для всех вложенных объектов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Функции добавить параметр - глубину вложенности.

    Проверяем, чем является значение, если не объект, то возвращаем как есть. Иначе собираем новый объект, в который записываем текущую глубину вложенности и свойства исходного объекта, обработав их значения функцией - делаем рекурсивный вызов с глубиной вложенности, увеличенной на единицу:

    const addDepth = (val, depth = 0) =>
      val instanceof Object
        ? Object.entries(val).reduce((acc, n) => (
            acc[n[0]] = addDepth(n[1], depth + 1),
            acc
          ), { depth })
        : val;
    Ответ написан
    Комментировать
  • Как конвертировать объект в строку из примера?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Рекурсия есть:

    const toString = (val, keys = []) =>
      val instanceof Object
        ? Object.entries(val).map(([ k, v ]) => {
            keys.push(k);
            const result = toString(v, keys);
            keys.pop();
            return result;
          }).join('&')
        : `${keys.join('.')}=${val}`;

    Рекурсии нет:

    function toString(val) {
      const result = [];
    
      for (const stack = [ [ val, [] ] ]; stack.length; ) {
        const [ n, keys ] = stack.pop();
        if (n instanceof Object) {
          stack.push(...Object.entries(n).map(([ k, v ]) => [ v, [ ...keys, k ] ]).reverse());
        } else {
          result.push(`${keys.join('.')}=${n}`);
        }
      }
    
      return result.join('&');
    }
    Ответ написан
    1 комментарий
  • Как можно переводить минут/секунд/часы в число если получаю не в формате числа?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const getSeconds = str =>
      [
        [ 'час', 60 * 60 ],
        [ 'минут', 60 ],
        [ 'секунд', 1 ],
      ].reduce((acc, n) => {
        return acc + (str.match(RegExp(`\\d+(?=\\s+${n[0]})`)) ?? 0) * n[1];
      }, 0);
    
    
    getSeconds('2 часа 22 секунды') // 7222
    getSeconds('99 минут') // 5940
    getSeconds('1 час 1 минута 1 секунда') // 3661
    getSeconds('666 секунд') // 666
    Ответ написан
    2 комментария
  • Как сделать развертывание аккордеона плавным?

    0xD34F
    @0xD34F Куратор тега CSS
    .panel {
      padding: 0 15px;
      transition: all 0.4s;
      height: 0;
      overflow: hidden;
    }
    
    .faq-item.active .panel {
      padding: 15px 15px 20px;
    }
    
    .faq-item.active .cross {
      transform: rotate(45deg);
    }

    const containerSelector = '.faq-list';
    const itemSelector = '.faq-item';
    const headerSelector = '.accordion';
    const contentSelector = '.panel';
    const activeClass = 'active';
    const toggle = item => item
      ?.closest(containerSelector)
      ?.querySelectorAll(itemSelector).forEach(n => {
        const state = n === item && !n.classList.contains(activeClass);
        const content = n.querySelector(contentSelector);
        n.classList.toggle(activeClass, state);
        content.style.height = `${state ? content.scrollHeight : 0}px`;
      });

    document.addEventListener('click', e => {
      toggle(e.target.closest(headerSelector)?.closest(itemSelector));
    });
    
    // или
    
    document.querySelectorAll(headerSelector).forEach(n => {
      n.addEventListener('click', toggle.bind(null, n.closest(itemSelector)));
    });
    Ответ написан
    Комментировать
  • Как перемещать элемент по клику на кнопки?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Вместо выдёргивания координат из стилей лучше будет сделать массив с координатами, обновлять этот массив, и уже на его основе задавать translate элементу. А чтобы не делать отдельные обработчики клика всем кнопкам, можно записать им в data-атрибут информацию о том, на сколько какие координаты должна изменить данная кнопка.

    <button data-steps="1,0">right</button>
    <button data-steps="-1,0">left</button>
    <button data-steps="0,1">down</button>
    <button data-steps="0,-1">up</button>
    <button data-steps="1,1">right down</button>
    <button data-steps="0,-2">double up</button>

    const coord = [ 0, 0 ];
    const stepSize = 30;
    
    const box = document.querySelector('#box');
    const buttons = document.querySelectorAll('[data-steps]');
    
    buttons.forEach(n => n.addEventListener('click', onClick));
    
    function onClick() {
      this.dataset.steps.split(',').forEach((n, i) => coord[i] += n * stepSize);
      box.style.transform = `translate(${coord.map(n => `${n}px`).join(',')})`;
    }
    Ответ написан
    Комментировать
  • Как отнимать единицу при клике на неверный вариант в квизе?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Не надо ничего отнимать, что-то изменилось - посчитали всё с нуля:

    const questionEl = document.querySelector('ul');
    const resultsEl = document.querySelector('p span');
    
    questionEl.addEventListener('change', showResults);
    
    function showResults() {
      resultsEl.innerText = Array.prototype.reduce.call(
        questionEl.querySelectorAll('input[type="radio"]:checked'),
        (acc, n) => acc + +n.value,
        0
      );
    }

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

    0xD34F
    @0xD34F Куратор тега JavaScript
    Рекурсия есть:

    const getNestedData = (data, test) => Object
      .values(data instanceof Object ? data : {})
      .reduce((acc, n) => (
        acc.push(...getNestedData(n, test)),
        acc
      ), test(data) ? [ data ] : []);
    
    
    const result = getNestedData(arr, n => n?.workControl?.includes?.('intermediate'));

    Рекурсии нет:

    function getNestedData(data, test) {
      const result = [];
    
      for (const stack = [ data ]; stack.length; ) {
        const n = stack.pop();
    
        if (n instanceof Object) {
          stack.push(...Object.values(n).reverse());
        }
    
        if (test(n)) {
          result.push(n);
        }
      }
    
      return result;
    }
    
    // или
    
    function getNestedData(data, test) {
      const result = [];
      const stack = [];
    
      for (let i = 0, arr = [ data ]; i < arr.length || stack.length; i++) {
        if (i === arr.length) {
          [ i, arr ] = stack.pop();
        } else {
          if (test(arr[i])) {
            result.push(arr[i]);
          }
    
          if (arr[i] instanceof Object) {
            stack.push([ i, arr ]);
            [ i, arr ] = [ -1, Object.values(arr[i]) ];
          }
        }
      }
    
      return result;
    }
    Ответ написан
    5 комментариев
  • Как сделать всегда активным один чекбокс из множества?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Где чекбоксы находятся, что за чекбоксы, каковы ограничения на количество отмеченных, как посчитать количество отмеченных:

    const container = document.querySelector('селектор общего предка чекбоксов');
    const checkboxSelector = 'селектор чекбоксов';
    const minChecked = 1;
    const maxChecked = Infinity;
    const countChecked = checkboxes =>
      Array.prototype.reduce.call(checkboxes, (acc, n) => acc + n.checked, 0);

    Если количество отмеченных чекбоксов меньше или равно минимально допустимому - блокируйте их, если количество отмеченных чекбоксов больше или равно максимально допустимому - блокируйте те, что не отмечены:

    const checkboxes = container.querySelectorAll(checkboxSelector);
    const onChange = () => {
      const count = countChecked(checkboxes);
      const minReached = count <= minChecked;
      const maxReached = count >= maxChecked;
      checkboxes.forEach(n => n.disabled = minReached && n.checked || maxReached && !n.checked);
    };
    
    checkboxes.forEach(n => n.addEventListener('change', onChange));

    Или, выставляйте снятый чекбокс обратно, если количество отмеченных упало ниже минимума и снимайте выставленный, если количество отмеченных превысило максимум:

    container.addEventListener('change', function({ target: t }) {
      if (t.matches(checkboxSelector)) {
        const count = countChecked(this.querySelectorAll(checkboxSelector));
        t.checked ||= count < minChecked;
        t.checked &&= count <= maxChecked;
      }
    });
    Ответ написан
    3 комментария
  • Почему итератор нужно делать итерируемым?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Посмотрите на результат выполнения следующего кода с [Symbol.iterator]() { return this; } и без:

    const iter = new Range(1, 5)[Symbol.iterator]();
    console.log(iter.next().value);
    console.log(iter.next().value);
    console.log([...iter]);
    Ответ написан
    2 комментария