Задать вопрос
  • Как собрать массив id на основе двух массивов?

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

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

    Могут ли значения во втором массиве повторяться? Если да - сколько раз надо добавлять соответствующие им 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
    Всегда ли искомый объект существует (т.е., существует ли для каждого value из первого массива аналогичное значение code во втором)? Если это не важно и вас устраивает возможное получение undefined, тогда

    const result = array1.map(n => array2.find(m => m.code === 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 = array1.map(function({ value: n }) {
      return this.get(n) ?? `объект с code="${n}" отсутствует`;
    }, new Map(array2.map(n => [ n.code, n ])));

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

    const result = array1.flatMap(function(n) {
      return this[n.value] ?? [];
    }, array2.reduce((acc, n) => ((acc[n.code] ??= []).push(n), acc), {}));

    Имеет ли значение взаимное расположение объектов в результирующем массиве? Если оно должно быть таким же, как и в array2, то его и надо перебирать при извлечении объектов, а не первый массив:

    const result = array2.filter(function(n) {
      return this.has(n.code);
    }, new Set(array1.map(n => n.value)));
    Ответ написан
    Комментировать
  • Метод addEventListener mouseover не работает над слайдами Swiper?

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

    e.target.dataset.followerStyle

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

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

    0xD34F
    @0xD34F Куратор тега JavaScript
    Делаем просто:

    const result = Object.values(arr.reduce((max, n) => (
      max[n.group] = +max[n.group]?.score > +n.score ? max[n.group] : n,
      max
    ), {}));

    Делаем сложно:

    function group(data, key, val = n => n) {
      const getKey = key instanceof Function ? key : n => n[key];
      const getVal = val instanceof Function ? val : n => n[val];
      const result = new Map;
    
      for (const n of data) {
        const k = getKey(n);
        result.set(k, result.get(k) ?? []).get(k).push(getVal(n));
      }
    
      return result;
    }
    
    function max(data, key = n => n) {
      const getVal = key instanceof Function ? key : n => n[key];
      let result = null;
    
      for (const n of data) {
        const val = getVal(n);
        result = result?.[1] >= val ? result : [ n, val ];
      }
    
      return result?.[0];
    }

    const result = Array.from(
      group(arr, 'group').values(),
      n => max(n, m => +m.score)
    );
    Ответ написан
    Комментировать
  • Почему у 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 комментарий
  • Как работать с обновлением в инпуте при сортировке элементов?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Не обновляйте данные, пока фокус не будет потерян. Типа, v-model.lazy.

    Или можете слушать событие input и дёргать метод focus у поля ввода, это заставит (ну, такое должно быть поведение по умолчанию) браузер выполнить прокрутку к элементу (если вдруг недостаточно, можно перед фокусом ещё и scrollIntoView вызвать). Типа так.
    Ответ написан
    1 комментарий
  • Почему во Vue3 не срабатывает computed, когда меняется ref?

    0xD34F
    @0xD34F Куратор тега Vue.js
    const current = ref(props.value);

    Так, ref, хорошо.

    emits("change", current);

    Этот ref отдаёте родителю. Странно, но допустим.

    А что в родителе?

    @change="changeCount"

    Слушаете change, окей.

    function changeCount(value) {
      count.value = value;
    }

    Но при этом зачем-то устанавливаете в качестве значения одного ref'а другой ref, полученный из дочернего компонента. Это как?

    Отправляйте из дочернего компонента число вместо ref'а:

    emits("change", current); ---> emits("change", current.value);
    Ответ написан
  • Как форматировать сегодняшнюю дату в 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);
    Ответ написан
    Комментировать
  • Как правильно выводить данные из дата атрибутов родителей в дочерние элементы в определенном порядке?

    0xD34F
    @0xD34F Куратор тега JavaScript
    document.addEventListener('click', updateCounter);
    document.addEventListener('input', updateCounter);
    
    document.querySelectorAll('.tariff__counter').forEach(n => {
      const max = n.dataset.seats;
      n.querySelectorAll('.tariff__counter-max').forEach(m => m.innerText = max);
      n.querySelectorAll('.amount').forEach(m => m.max = max);
      n.querySelectorAll('button').forEach(m => m.dataset.change = m.innerText === '+' ? 1 : -1);
    });
    
    function updateCounter({ target: t }) {
      const input = t.closest('.tariff__counter-block')?.querySelector('.amount');
      if (input) {
        const { min, max, value } = input;
        input.value = Math.max(min, Math.min(max, (value | 0) + (t.dataset.change | 0)));
      }
    }
    Ответ написан
    1 комментарий
  • Почему не отображаются данные на странице, а вылазит ошибка?

    0xD34F
    @0xD34F Куратор тега React
    Что, "posts.map is not a function"? Давайте взглянем, что такое posts:

    const [posts, setPosts] = useState([]);

    Изначально это массив. У массивов map есть. А раз воспользоваться map'ом всё-таки не удаётся, значит, где-то вместо массива в качестве значения posts устанавливается какой-то мусор. Смотрим, где вызывается setPosts:

    setPosts(fetchReq);

    Окей, а что такое fetchReq? Давайте узнаем:

    const fetchReq = fetch(`${fetchURL}/posts`).then(res => res.json());

    Надо же, это промис. Зачем вы промис кладёте туда, где нужен массив? У промисов метода map нет. Надо дождаться выполнения этого промиса и передавать в setPosts результат. Исправляем:

    fetch(`${fetchURL}/posts`)
      .then(r => r.json())
      .then(setPosts);
    Ответ написан
    Комментировать
  • Как сделать, чтобы при клике на кнопку появлялось поле ввода?

    0xD34F
    @0xD34F Куратор тега React
    const amounts = [ 5, 10, 15 ];
    const [ amount, setAmount ] = useState(null);
    const [ isCustomAmount, setIsCustomAmount ] = useState(false);

    {amounts.map(n => (
      <label>
        <input
          type="radio"
          disabled={isCustomAmount}
          checked={amount === n && !isCustomAmount}
          onChange={() => setAmount(n)}
        />
        ${n}
      </label>
    ))}
    <label>
      <input
        type="checkbox"
        checked={isCustomAmount}
        onChange={e => (setAmount(null), setIsCustomAmount(e.target.checked))}
      />
      Другая сумма
    </label>
    {isCustomAmount && <input value={amount} onChange={e => setAmount(+e.target.value)} />}
    <input type="hidden" name="amountusd" value={amount} />
    Ответ написан
    5 комментариев
  • Как правильно преобразовать массив объектов на основе другого массива?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const keys = [ 'id', 'secondId' ];
    const result = firstArray.map(n => secondArray.find(m => keys.every(k => n[k] === m[k])) ?? n);

    или

    const arrToTree = (arr, keys) =>
      arr.reduce((acc, n) => (
        keys.reduce((p, c, i) => p[n[c]] ??= i + 1 === keys.length ? n : {}, acc),
        acc
      ), {});
    
    const keys = [ 'id', 'secondId' ];
    const tree = arrToTree(secondArray, keys);
    const result = firstArray.map(n => keys.reduce((p, c) => p?.[n[c]], tree) ?? n);

    или

    const arrToTree = (arr, keys) =>
      arr.reduce((acc, n) => (
        keys(n).reduce(
          (p, c, i, a) => p.set(c, p.get(c) ?? (-~i === a.length ? n : new Map)).get(c),
          acc
        ),
        acc
      ), new Map);
    
    const keys = n => [ n.id, n.secondId ];
    const result = firstArray.map(function(n) {
      return keys(n).reduce((p, c) => p?.get(c), this) ?? n;
    }, arrToTree(secondArray, keys));
    Ответ написан
    1 комментарий