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

    0xD34F
    @0xD34F Куратор тега JavaScript
    Странно, но все работает, несмотря на ошибку:

    TypeError: Cannot read properties of null (reading 'insertAdjacentHTML').

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

    Какие есть способы победить ошибку:

    • Проверка наличия элемента перед тем как попытаться что-то в него добавить.

      Можно делать это явно:

      arr.forEach(n => {
        const el = document.getElementById(n.id);
        if (el) {
          el.innerHTML = `
            <h2>Id: ${n.id}</h2>
            <h3>${n.title}</h3>
            <p>${n.body}</p>
          `;
        }
      });

      Или не очень:

      for (const { id, title, body } of arr) {
        document.querySelector(`[id="${id}"]`)?.insertAdjacentHTML('beforeend', `
          <h2>Id: ${id}</h2>
          <h3>${title}</h3>
          <p>${body}</p>
        `);
      }

    • Создание элементов под все полученные объекты (в этом случае пусть section изначально будет пустым):

      document.querySelector('section').innerHTML = arr
        .map(n => `
          <div id="post-${n.id}">
            <h2>Id: ${n.id}</h2>
            <h3>${n.title}</h3>
            <p>${n.body}</p>
          </div>`)
        .join('');

    • Ограничение объёма получаемых данных.

      Если id элементов действительно имеют показанный вами вид (начинаются с 1 и идут по возрастанию), то просто запрашиваем столько объектов, сколько есть элементов:

      const limit = document.querySelector('section').children.length;
      const requestURL = `https://jsonplaceholder.typicode.com/posts?_limit=${limit}`;

      Или, можно запросить объекты с id как у элементов:

      const requestURL = 'https://jsonplaceholder.typicode.com/posts?' + Array
        .from(document.querySelector('section').children, n => `id=${n.id}`)
        .join('&');


    Какой из способов следует предпочесть? 2 + 3. Только, конечно, количество объектов или их id надо брать не из DOM.
    Ответ написан
  • Как сложить значения input'ов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Кого складываем:

    const elements = document.querySelectorAll('[name^=AR_AMOUNT]');

    Нормальный способ - перебираем коллекцию элементов (цикл в явном виде или с помощью методов массива), достаём значения, складываем:

    const sum = Array.prototype.reduce.call(
      elements,
      (acc, n) => acc + +n.value,
      0
    );
    
    // или
    
    let sum = 0;
    for (const { value } of elements) {
      sum += Number(value);
    }

    Ненормальный способ - рекурсия. Функция получает коллекцию элементов и индекс, если элемент с указанным индексом существует, возвращаем его значение плюс результат рекурсивного вызова с индексом, увеличенным на единицу; если элемента нет - возвращаем 0:

    const sum = (function sum(arr, i) {
      return arr[i] ? parseFloat(arr[i].value) + sum(arr, i + 1) : 0;
    })(elements, 0);

    Дикий способ - сами ничего считать не будем. Выдёргиваем из коллекции элементов значения; склеиваем их, используя в качестве разделителя символ +, в строку; строку отдаём в eval; всё, сумма получена (ну, почти, если исходная коллекция элементов была пуста, то строка тоже будет пустой, так что надо не забыть подставить 0 вместо возможного undefined, который является результатом выполнения пустой строки):

    const sum = eval(Array.from(elements, n => n.value).join('+')) ?? 0;
    Ответ написан
    Комментировать
  • Как через цикл вложить элементы DOM один в другой?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Перебираем массив от конца к началу:

    main.innerHTML = arr.reduceRight((content, tag) => `<${tag}>${content}</${tag}>`, '');
    
    // или
    
    main.append(arr.reduceRight((content, tag) => {
      const el = document.createElement(tag);
      el.append(content);
      return el;
    }, ''));

    И наоборот:

    main.insertAdjacentHTML('beforeend', (function next(i) {
      return arr[i] ? `<${arr[i]}>${next(-~i)}</${arr[i]}>` : '';
    })(0));
    
    // или
    
    arr.forEach(function(tag) {
      this[0].appendChild(document.createElement(tag));
      this[0] = this[0].lastChild;
    }, [ main ]);
    Ответ написан
    Комментировать
  • Как объединить несколько объектов в один?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const merge = (target, ...sources) =>
      sources.reduce((acc, n) => (
        Object.entries(n).forEach(([ k, v ]) =>
          acc[k] = v instanceof Object
            ? merge(acc[k] instanceof Object ? acc[k] : {}, v)
            : v
        ),
        acc
      ), target);
    
    
    const result = merge({}, ...arrayObj);
    Ответ написан
    3 комментария
  • Как сделать сортировку HTML объектов в зависимости от чисел внутри элемента?

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

    const sorted = (data, key) => Array
      .from(data, n => [ key(n), n ])
      .sort(([a], [b]) => a < b ? -1 : +(a > b))
      .map(n => n[1]);

    Как отсортировать содержимое DOM-элемента - да просто добавить ему это содержимое в отсортированном виде:

    const sortChildren = (el, key) =>
      el.append(...sorted(el.children, key));

    Направление сортировки будем указывать с помощью data-атрибута:

    <button data-order="-1">От большего к меньшему</button>
    <button data-order="+1">От меньшего к большему</button>

    Кнопкам назначаем общий обработчик клика, где сортируем содержимое .catalog-items (вес элемента - число внутри, умноженное на направление сортировки), кликнутой кнопке класс добавляем, у остальных убираем:

    const wrapper = document.querySelector('.catalog-items');
    const buttons = document.querySelectorAll('[data-order]');
    
    buttons.forEach(n => n.addEventListener('click', onClick));
    
    function onClick({ target: t }) {
      const order = +t.dataset.order;
      sortChildren(wrapper, el => parseInt(el.innerText) * order);
      buttons.forEach(n => n.classList.toggle('active', n === t));
    }
    Ответ написан
    3 комментария
  • Как выполнять вычисления с помощью функций (результат вызова предыдущей должен быть аргументом следующей)?

    0xD34F
    @0xD34F Куратор тега JavaScript
    "Числовые" функции проверяют, является ли переданное им значение функцией, если да - вызывают её со своим числом в качестве аргумента, нет - просто возвращают число.

    Функции операций принимают второе число, возвращают функцию, принимающую первое число и производящую над переданными числами операцию.

    const [ zero, one, two, three, four, five, six, seven, eight, nine ] = Array.from(
      { length: 10 },
      (_, i) => f => f instanceof Function ? f(i) : i
      // или
      // (_, i) => f => f?.(i) ?? i
    );
    
    const plus = a => b => b + a;
    const minus = a => b => b - a;
    const times = a => b => b * a;
    const dividedBy = a => b => b / a | 0;
    Ответ написан
    Комментировать
  • Как реализовать функцию, которая принимает любое неотрицательное целое число и возвращает его цифрами в порядке убывания?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const sortDigits = num => +[...`${num}`].sort((a, b) => b - a).join('');

    или

    const sortDigits = num => ''.concat.apply('', num.toString().split('').sort().reverse()) - 0;

    или

    const sortDigits = num => Number(Array
      .from('' + num)
      .reduce((acc, n) => (acc[n]++, acc), Array(10).fill(0))
      .reduceRight((acc, n, i) => acc + String(i).repeat(n), '')
    );

    или

    const sortDigits = num => Array
      .prototype
      .reduce
      .call(String(num), (acc, n) => ((acc[9 - n] ??= []).push(n | 0), acc), [])
      .flat()
      .reduce((acc, n) => acc * 10 + n, 0);
    Ответ написан
    1 комментарий
  • Как вывести значения false общим числовым количеством?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const statsMeta = [
      { title: 'отредактированные', count: item => +item.edited },
      { title: 'неотредактированные', count: item => +!item.edited },
      { title: 'всего', count: () => 1 },
    ];
    
    const statsData = allMessages.reduce((acc, n) => (
      statsMeta.forEach((m, i) => acc[i] += m.count(n)),
      acc
    ), Array(statsMeta.length).fill(0));
    
    console.log(statsMeta.map((n, i) => `${n.title}: ${statsData[i]}`).join('\n'));
    Ответ написан
    Комментировать
  • Как удалить все классы, начинающиеся с определённой подстроки?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Кого надо удалить: const classPrefix = 'modal--';

    Удаляем:

    Array.from(el.classList).forEach(n => el.classList.toggle(n, !!n.indexOf(classPrefix)));

    или

    el.classList.remove(...[...el.classList].filter(n => n.startsWith(classPrefix)));

    или

    for (let i = el.classList.length; i--;) {
      if (el.classList[i].search(classPrefix) === 0) {
        el.classList.remove(el.classList[i]);
      }
    }

    или

    el.classList.value = el.classList.value
      .split(' ')
      .filter(RegExp.prototype.test.bind(RegExp(`^(?!${classPrefix})`)))
      .join(' ');

    или

    el.className = el.className.replace(RegExp(`(^| )${classPrefix}\\S*`, 'g'), '').trim();
    Ответ написан
    9 комментариев
  • Как изменить имена ключей в массиве объектов исходя из другого массива объектов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const result = arr2.map(function(n) {
      return Object.fromEntries(this.map(m => [ m[1], n[m[0]] ]));
    }, arr1.flatMap(Object.entries));

    или

    const keys = Object.entries(Object.assign({}, ...arr1));
    const result = arr2.map(n => keys.reduce((acc, m) => (acc[m[1]] = n[m[0]], acc), {}));
    Ответ написан
    4 комментария
  • Как повторить каждый символ, чтобы количество повторений было равно его позиции в строке?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const xxx = str => Array
      .from(str, (n, i) => n.toUpperCase() + n.toLowerCase().repeat(i))
      .join('-');

    или

    const xxx = str => str
      .toUpperCase()
      .split('')
      .reduce((acc, n, i) => `${acc}${i ? '-' : ''}${n}${Array(-~i).join(n.toLowerCase())}`, '');

    или

    const xxx = str => str
      .toUpperCase()
      .replace(/(?<=.)./g, (m, i) => '-'.concat(m, ...Array(i).fill(m.toLowerCase())));
    Ответ написан
    Комментировать
  • Как заменить данные карты на звездочки?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const hidden = (str, count = 4) => '*'.repeat(count) + str.slice(-4);

    или

    const hidden = (str, count = 4) => str.replace(/.*?(?=.{0,4}$)/, Array(count + 1).join('*'));
    Ответ написан
    1 комментарий
  • Не правильно рассчитывает progress у кастомного range slider если указать min, как поправить?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Вычитайте нижнюю границу везде, где рассчитываете проценты. Например, вместо

    range.style.left = ((minPrice / rangeInput[0].max) * 100) + "%";

    должно быть

    range.style.left = ((minPrice - rangeInput[0].min) / (rangeInput[0].max - rangeInput[0].min) * 100) + '%';
    Ответ написан
    Комментировать
  • Как сложить radio и checkbox и вывести итоговой цифрой в форме на jquery?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Надо у формы слушать событие change:

    const form = document.querySelector('form');
    form.addEventListener('change', onChange);
    form.dispatchEvent(new Event('change'));

    Как может выглядеть обработчик:

    function onChange() {
      this.querySelector('.result').textContent =
        Array.prototype.reduce.call(
          this.querySelectorAll(':checked'),
          (acc, n) => acc + +n.getAttribute('rel'),
          0
        );
    }

    или

    function onChange({ currentTarget: ct }) {
      let sum = 0;
    
      for (const n of ct.elements) {
        sum += n.attributes.rel.value * n.checked;
      }
    
      ct.querySelector('.result').innerText = sum;
    }
    Ответ написан
    Комментировать
  • Как сделать чтобы при нажатии на кнопку открывался только один элемент, а не все?

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

    const blockSelector = 'селектор блоков';
    const buttonSelector = 'селектор кнопок';
    const activeClass = 'класс, показывающий блок';

    Класс надо переключать не у всех блоков сразу, а у того, который соответствует нажатой кнопке. Как определить, какой блок нужен? Можно просто по индексу. Нажали третью кнопку - нужен третий блок. Как узнать индекс кнопки? Например, он передаётся в коллбек метода forEach. Т.е., в список параметров внешнего forEach добавляем индекс, и вместо внутреннего forEach выполняем обращение к соответствующему элементу коллекции блоков:

    const blocks = document.querySelectorAll(blockSelector);
    const buttons = document.querySelectorAll(buttonSelector);
    
    buttons.forEach((n, i) => {
      n.addEventListener('click', () => {
        blocks[i].classList.toggle(activeClass);
      });
    });

    Кстати, создавать отдельные обработчики клика для каждой из кнопок не обязательно, можно сделать один общий. Как при этом получать индексы? - их можно прицепить прямо к кнопкам. Например, в виде data-атрибута:

    const onClick = function({ currentTarget: { dataset: { index } } }) {
      this[index].classList.toggle(activeClass);
    }.bind(document.querySelectorAll(blockSelector));
    
    document.querySelectorAll(buttonSelector).forEach((n, i) => {
      n.dataset.index = i;
      n.addEventListener('click', onClick);
    });

    Также есть вариант вычислять индекс при клике:

    const blocks = document.querySelectorAll(blockSelector);
    const buttons = document.querySelectorAll(buttonSelector);
    
    buttons.forEach(n => n.addEventListener('click', onClick));
    
    function onClick() {
      const index = Array.prototype.indexOf.call(buttons, this);
      blocks[index].classList.toggle(activeClass);
    }

    А вообще, если у каждой из пар кнопка-блок есть отдельный общий предок (const containerSelector = '...';), то индексы не нужны. Достаточно от нажатой кнопки подняться до общего предка кнопки и блока, и найти внутри блок (а можно даже и не искать, если добавлять класс контейнеру; конечно, стили придётся в этом случае немного изменить, вместо .блок.класс будет .контейнер.класс .блок):

    const toggleBlock = button => button
      ?.closest(containerSelector)
      ?.querySelector(blockSelector)
      .classList
      .toggle(activeClass);
    
    
    // можно добавить обработчик клика каждой кнопке индивидуально
    document.querySelectorAll(buttonSelector).forEach(function(n) {
      n.addEventListener('click', this);
    }, e => toggleBlock(e.currentTarget));
    
    // или один раз на всю страницу
    document.addEventListener('click', e => {
      toggleBlock(e.target.closest(buttonSelector));
    });
    Ответ написан
    Комментировать
  • Как используя рекурсию сконкатенировать только заглавные буквы в слово?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const getNestedItems = (data, test) =>
      data instanceof Object
        ? Object.values(data).flatMap(n => getNestedItems(n, test))
        : test(data) ? [ data ] : [];
    
    
    const result = getNestedItems(obj, x => /^[A-Z]+$/.test(x)).join('');
    Ответ написан
    4 комментария
  • Как правильно передать значение свойства в слайдер?

    0xD34F
    @0xD34F Куратор тега JavaScript
    if (this.frame < 0) {
        this.frame = this.slides.lenght-1;
        this.setBackground(this.slides[this.frame])
    }

    Если frame неотрицательный, то вызывать setBackground уже не надо?

    Что такое lenght? Я такого слова не знаю.
    И массивы не знают. Так что получаете undefined. А после вычитания единицы - NaN. Элемента с индексом NaN в массиве нет, так что в setBackground улетает undefined - его вы и видите в стилях.

    if (this.frame > this.slides.length) {

    По-вашему, у массива может быть элемент с индексом, равным длине?

    buttonLeft.addEventListener('click', slider.left());

    Функция и результата её вызова - не одно и то же.
    Ответ написан
    1 комментарий
  • Есть ли возможность получить доступ к группам, кроме метода exec в регулярных выражениях?

    0xD34F
    @0xD34F Куратор тега JavaScript
    for (const { groups } of str.matchAll(re)) {
      console.log(groups);
    }
    Ответ написан
    Комментировать
  • Как посчитать, сколько процентов занимают повторяющиеся данные в массиве?

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

    Array.from(
      arr.reduce((acc, n) => acc.set(n, -~acc.get(n)), new Map),
      n => [ n[0], (n[1] / arr.length * 100).toFixed(2) + '%' ]
    )
    Ответ написан
    1 комментарий
  • Как сделать формат данных в цикле?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const clone = (data, replacements) =>
      data instanceof Object
        ? data instanceof Array
          ? data.map(n => clone(replacements?.hasOwnProperty(n) ? replacements[n] : n, replacements))
          : Object.fromEntries(Object.entries(data).map(([ k, v ]) => [
              k,
              clone(replacements?.hasOwnProperty(k) ? replacements[k] : v, replacements)
            ]))
        : data;
    
    
    const products = arr.map(n => clone(obj.product[0], n));
    Ответ написан
    4 комментария