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

    0xD34F
    @0xD34F Куратор тега JavaScript
    const uniqueWithSum = arr =>
      arr.reduce((acc, n) => {
        const keys = n.slice(0, -1);
        const item = acc.find(m => m.length === n.length && keys.every((k, i) => k === m[i]));
        (item ?? (acc[acc.length] = [ ...keys, 0 ]))[keys.length] += n[keys.length];
        return acc;
      }, []);

    или

    const uniqueWithSum = (function(arr) {
      const indexTree = new Map;
      return arr.reduce((acc, [...keys]) => {
        const val = keys.pop();
        const indexes = keys.reduce((p, c) => p.set(c, p.get(c) ?? new Map).get(c), indexTree);
        const index = indexes.set(this, indexes.get(this) ?? ~-acc.push([ ...keys, 0 ])).get(this);
        acc[index][keys.length] += val;
        return acc;
      }, []);
    }).bind(Symbol());

    или

    const uniqueWithSum = arr =>
      [...arr.reduce((acc, n) => {
        const end = n.length - 1;
        const key = n.reduce((p, c, i) => i === end ? p : p.set(c, p.get(c) ?? new Map).get(c), acc[0]);
        acc[1].set(key, acc[1].get(key) ?? n.map((m, i) => i !== end && m)).get(key)[end] += n[end];
        return acc;
      }, [ new Map, new Map ])[1].values()];
    Ответ написан
    1 комментарий
  • Как заменить ключи во вложенных объектах?

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

    const replaceKeys = (value, replacer) =>
      value instanceof Object
        ? value instanceof Array
          ? value.map(n => replaceKeys(n, replacer))
          : Object.fromEntries(Object
              .entries(value)
              .map(n => [ replacer(n[0]), replaceKeys(n[1], replacer) ])
            )
        : value;
    
    
    const newObj = replaceKeys(obj, k => `${k}_upd`);

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

    function replaceKeys(value, replacer) {
      const stack = [];
      const clones = new Map;
      const getClone = val => val instanceof Object
        ? (clones.has(val) || stack.push([ val, clones.set(val, val.constructor()).get(val) ]),
           clones.get(val))
        : val;
    
      for (getClone(value); stack.length;) {
        const [ source, target ] = stack.pop();
        const isArray = Array.isArray(source);
        for (const k in source) if (Object.hasOwn(source, k)) {
          target[isArray ? k : replacer(k)] = getClone(source[k]);
        }
      }
    
      return getClone(value);
    }
    Ответ написан
    Комментировать
  • Как можно заменить цвет #a8a3a0 на пример на квадратик белого цвета?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const regex = /#[a-f\d]+;$/i;
    const replacement = '<span class="color" style="background: $&"></span>';
    
    document.querySelectorAll('селектор сами сообразите').forEach(n => {
      n.innerHTML = n.innerText.replace(regex, replacement);
    });
    Ответ написан
    2 комментария
  • Как разбить массив на подмассивы, отсортировать по рядам упорядоченных чисел?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Делаем просто, ровно то, что спрошено в вопросе:

    const result = arr.reduce((acc, n, i, a) => (
      n === a[i - 1] + 1 || acc.push([]),
      acc.at(-1).push(n),
      acc
    ), []);

    Делаем сложно, решаем задачу в более общем виде (группируем элементы не только массивов, а любых итерируемых объектов; условие создания новой группы отделяем от собственно кода группировки):

    function groupAdjacent(
      data,
      {
        key = n => n,
        newGroup = (c, p) => c !== p,
      } = {}
    ) {
      const result = [];
      const getVal = key instanceof Function ? key : n => n[key];
      let prev = null;
      let i = -1;
    
      for (const n of data) {
        const curr = getVal(n, ++i);
    
        if (!result.length || newGroup(curr, prev)) {
          result.push([]);
        }
    
        result.at(-1).push(n);
        prev = curr;
      }
    
      return result;
    }

    Как применять в вашем случае:

    const result = groupAdjacent(arr, { newGroup: (c, p) => c !== -~p });
    // или
    const result = groupAdjacent(arr, { key: (n, i) => n - i });

    Какие ещё возможны способы применения:

    groupAdjacent(arr, { key: 'name' })
    groupAdjacent(arr, { newGroup: n => n === 1 })
    groupAdjacent(arr).map(n => n.length > 1 ? n : n[0])
    groupAdjacent(arr, { newGroup: n => /^\d+\./.test(n) })
    Ответ написан
    5 комментариев
  • Почему модальное окно всплывает только при нажатие на первую кнопку?

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

    document.querySelector('.wrapper_product') ---> event.target
    Ответ написан
    4 комментария
  • Как скрывать текущий item(li) при открытие другого item(li)?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Если прямо отвечать на спрошенное, то...
    menu.addEventListener('click', ({ target: t }) => {
      if (t.tagName === 'SPAN') {
        const parent = t.parentNode;
        parent.classList.toggle('active');
        for (const n of menu.querySelectorAll('.active')) {
          if (n !== parent) {
            n.classList.toggle('active', n.contains(parent));
          }
        }
      }
    });

    Но вообще, давайте-ка напишем чуть более универсальное решение.

    Что если количество уровней вложенности не будет ограничено числом два? Пусть скрытые элементы показываются только когда активен родитель, а не любой предок, т.е.:

    - .menu_list_item.active .submenu {
    + .active > .submenu {

    - .submenu_list_item.active .product {
    + .active > .product {

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

    const container = document.querySelector('#menu');
    const itemSelector = 'li';
    const buttonSelector = `${itemSelector} span`;
    const activeClass = 'active';
    
    container.addEventListener('click', function(e) {
      const item = e.target.closest(buttonSelector)?.closest(itemSelector);
      if (item) {
        item.classList.toggle(activeClass);
        this.querySelectorAll(`.${activeClass}`).forEach(n => {
          if (n !== item) {
            n.classList.toggle(activeClass, n.contains(item));
          }
        });
      }
    });
    Ответ написан
    3 комментария
  • Как избавиться от lodash?

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

    Как видите, всё довольно просто.
    Ответ написан
    Комментировать
  • Как добавлять класс элементу рядом с активной radio кнопкой?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Если прямо отвечать на спрошенное, то...
    const items = document.querySelectorAll('.faq__input');
    const onChange = ({ target: t }) => t
      .closest('.faq__items')
      .querySelectorAll('.faq__question--top')
      .forEach(n => n.classList.toggle('active', n.nextElementSibling.checked));
    
    items.forEach(n => n.addEventListener('change', onChange));

    Но вообще, предлагаю назначать класс элементам, находящимся максимально высоко, самым дальним не общим предкам радиокнопок. Т.е., .faq__item вместо .faq__question--top. Если в будущем задумаете стилизовать внутри выбранного .faq__item элементы, находящиеся за пределами .faq__question--top, то не придётся переписывать js-код.

    Каким элементам надо переключать класс, что за радиокнопки находятся внутри, какой класс надо переключать, как класс переключать:

    const itemSelector = '.faq__item';
    const radioSelector = '.faq__input';
    const activeClass = 'active';
    const toggleActiveClass = radioGroupName => document
      .querySelectorAll(`${radioSelector}[name="${radioGroupName}"]`)
      .forEach(n => n.closest(itemSelector).classList.toggle(activeClass, n.checked));

    Можно назначить обработчик события каждой радиокнопке индивидуально:

    document.querySelectorAll(radioSelector).forEach(function(n) {
      n.addEventListener('change', this);
    }, e => toggleActiveClass(e.target.name));

    А можно назначить делегированный обработчик странице:

    document.addEventListener('change', ({ target: t }) => {
      if (t.matches(radioSelector)) {
        toggleActiveClass(t.name);
      }
    });
    Ответ написан
    2 комментария
  • Как собрать массив id на основе двух массивов?

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

    Может ли быть так, что какие-то из значений второго массива отсутствуют в первом? Если да - добавлять в результирующий массив какое-то дефолтное значение, или ничего не делать?

    Могут ли значения во втором массиве повторяться? Если да - сколько раз надо добавлять соответствующие им id в результирующий массив - один или согласно количеству повторений?

    Могут ли значений title в первом массиве быть неуникальны? Если да, какие из соответствующих им id помещать в результирующий массив - первые, последние или все, что есть?

    В общем, вариантов тут масса, выбирайте:

    computed: {
      ids() {
        return this.strings.map(n => this.objects.find(m => m.title === n)?.id);
      },
    
      // или
    
      ids() {
        const ids = this.objects.reduce((acc, n) => (acc[n.title] = n.id, acc), {});
        return this.strings.map(n => ids[n] ?? 'Получить id не удалось - объекта с указанным title нет');
      },
    
      // или
    
      idByTitle() {
        return Object.fromEntries(this.objects.map(n => [ n.title, n.id ]));
      },
      ids() {
        const ids = this.idByTitle;
        return this.strings.reduce((acc, n) => (
          Object.hasOwn(ids, n) && acc.push(ids[n]),
          acc
        ), []);
      },
    
      // или
    
      stringsSet() {
        return new Set(this.strings);
      },
      ids() {
        return this.objects.reduce((acc, n) => (
          this.stringsSet.has(n.title) && acc.push(n.id),
          acc
        ), []);
      },
    
      // или
    
      idsByTitle() {
        return this.objects.reduce((acc, n) => (
          (acc[n.title] ??= []).push(n.id),
          acc
        ), []);
      },
      ids() {
        return this.strings.flatMap(n => this.idsByTitle[n] ?? []);
      },
    },

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

    computed, к сожалению не подходит

    Подходит. Так во vue принято оформлять данные, зависящие от других данных. Что спросили - на то и ответил.

    Если вдруг у вас там vuex или pinia, то вместо computed надо поместить показанный код в getters, и в случае vuex также заменить this на обращение к параметрам - первый для стейта, второй для геттеров.

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

    0xD34F
    @0xD34F Куратор тега JavaScript
    const result = array2.filter(function(n) {
      return this.has(n.code);
    }, new Set(array1.map(n => n.value)));

    или

    const obj2 = Object.fromEntries(array2.map(n => [ n.code, n ]));
    const result = array1.reduce((acc, n) => ((n = obj2[n.value]) && acc.push(n), acc), []);

    или

    const result = array2.filter(n => array1.some(m => m.value === n.code));
    Ответ написан
    Комментировать
  • Метод addEventListener mouseover не работает над слайдами Swiper?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Работает.

    e.target.dataset.followerStyle

    Что такое e.target? Ну, когда вы наводите курсор на слайд с data-атрибутом. На тот слайд, который не видно за вложенной в него картинкой.

    Надо не сразу target хватать, а пытаться найти у него предка с нужным вам атрибутом.
    Ответ написан
    1 комментарий
  • Как вычленить максимальные значения из массива объектов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Object.values(arr.reduce((max, n) => (
      max[n.group] = max[n.group]?.score > n.score ? max[n.group] : n,
      max
    ), {}))
    Ответ написан
    Комментировать
  • Почему у Proxy object лишние элементы в массиве?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Как работает метод push, особое внимание на пятый и шестой пункты - устанавливается значение, устанавливается новая длина массива. Поскольку push вы взываете у proxy, а не у оригинального массива, то и при установке значения, и при установке новой длины срабатывает обработчик set.

    Как избавиться от такого поведения... Конечно, можно в обработчике set ничего не делать при попытке установить значение length. Правда, так вы тоже получите побочные эффекты, например - невозможность напрямую назначить длину массива; при выполнении pop'а, несмотря на то, что элемент будет удалён, длина массива не изменится и т.п.

    Так что стоит подумать - а действительно ли для решения стоящей перед вами задачи надо использовать proxy.
    Ответ написан
    Комментировать
  • Как создать массив объектов с определенными свойствами на основе другого массива объектов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    // значения withSeat вместо раскладывания в отдельные переменные собираете в объект
    const withSeat = {
      adult: 1,
      teenager: 1,
      babe: 0,
    };
    
    
    const passengers = cabins.flatMap(n => Object
      .entries(n)
      .flatMap(([ k, v ]) => Array.from(
        { length: v },
        () => ({ withSeat: withSeat[k] })
      ))
    );
    Ответ написан
    6 комментариев
  • Как перебрать массив с элементами и при отличии первой буквы элемента с первой буквой следующего элемента добавлять эту букву перед элементом?

    0xD34F
    @0xD34F Куратор тега JavaScript
    arr.reduce((acc, n, i, a) => (
      a[i - 1]?.name[0] !== n.name[0] && acc.push({ name: n.name[0] }),
      acc.push(n),
      acc
    ), [])

    или

    arr.flatMap((n, i, a) =>
      i && a[~-i].name[0] === n.name[0]
        ? n
        : [ { name: n.name[0] }, n ]
    )
    Ответ написан
    Комментировать
  • Как создать объект из массива объектов c одинаковыми ключами?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const obj = Object.fromEntries(Object
      .entries(arr
        .flatMap(Object.entries)
        .reduce((acc, n) => ((acc[n[0]] ??= new Set).add(n[1]), acc), {}))
      .map(([ k, v ]) => [ k, v.size === 1 ? [...v][0] : null ])
    );

    или

    const obj = arr.reduce((acc, n) => (
      Object.keys(n).forEach(k => {
        acc[k] = Object.hasOwn(acc, k) && n[k] !== acc[k] ? null : n[k];
      }),
      acc
    ), {});
    Ответ написан
    1 комментарий
  • Как форматировать сегодняшнюю дату в Today?

    0xD34F
    @0xD34F Куратор тега JavaScript
    formatDate(date) {
      const today = new Date();
      return [ 'getFullYear', 'getMonth', 'getDate' ].every(n => date[n]() === today[n]())
        ? 'Today'
        : flatpickr.formatDate(date, 'j M');
    },
    Ответ написан
    1 комментарий
  • Как добавить класс в javascript код группового выделения чек-боксов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const groupSelector = 'fieldset.js-tree-box';
    const mainSelector = 'legend input';
    const itemSelector = 'legend + span input';
    const activeClass = 'active';
    
    document.addEventListener('change', ({ target: t }) => {
      const group = t.closest(groupSelector);
      if (group) {
        const main = group.querySelector(mainSelector);
        const items = [...group.querySelectorAll(itemSelector)];
    
        if (main === t) {
          items.forEach(n => n.checked = t.checked);
        } else {
          const numChecked = items.reduce((acc, n) => acc + n.checked, 0);
          main.checked = numChecked === items.length;
          main.indeterminate = 0 < numChecked && numChecked < items.length;
        }
    
        group.classList.toggle(activeClass, main.checked);
      }
    });
    Ответ написан
    Комментировать
  • Как отсортировать элементы внутри массива?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const sorted = (data, key) => Array
      .from(data, n => [ n, key(n) ])
      .sort((a, b) => a[1] - b[1])
      .map(n => n[0]);
    
    
    document.querySelector('.result').append(...sorted(
      document.querySelector('.data').cloneNode(true).children,
      n => +n.innerText
    ));
    Ответ написан
    2 комментария
  • Как делегировать событие mouseenter?

    0xD34F
    @0xD34F Куратор тега JavaScript
    делегирование не работает

    Ну ещё бы оно работало - mouseenter не всплывает. Надо его ловить на стадии захвата.

    в консоли выдает "e.target.closest is not a function"

    Всё правильно - обработчик висит на документе, документ не является элементом, соответственно, никаких методов элемента у него нет.

    const hoverCellNum = hoverCell.getAttribute('data-tdnum');

    Во-первых, такого атрибута в показанной вами разметке ни у кого нет. Во-вторых, он и не нужен - у ячеек таблицы есть свойство cellIndex.

    Исправляем:

    document.addEventListener('mouseenter', ({ target: t }) => {
      if (t.matches?.('td._hight-light__cell')) {
        t.closest('tbody').querySelectorAll('.table-big__row').forEach(n => {
          n.cells[t.cellIndex].classList.add('_hover');
        });
      }
    }, true);
    
    document.addEventListener('mouseleave', e => {
      if (e.target.matches?.('td._hight-light__cell')) {
        document.querySelectorAll('._hover').forEach(n => n.classList.remove('_hover'));
      }
    }, true);
    Ответ написан
    Комментировать