• Как подгрузить данные из объекта?

    0xD34F
    @0xD34F Куратор тега React
    Объект менять не нужно.

    Нужно.

    Так будет лучше, просто поверьте:

    const SERIES = [
      {
        comment: '1 сезон',
        folder: [
          { comment: '1 серия' },
          { comment: '2 серия' },
        ],
      },
      {
        comment: '2 сезон',
        folder: [
          { comment: '1 серия' },
          { comment: '2 серия' },
          { comment: '3 серия' },
        ],
      },
    ];
    
    const ItemsList = ({ header, items, active, onChange }) => (
      <div>
        <h3>{header}</h3>
        <ul className="items">
          {items.map(({ comment }, i) => (
            <li
              className={`item ${active === i ? 'active' : ''}`}
              onClick={() => onChange?.(i !== active ? i : -1)}
            >
              {comment}
            </li>
          ))}
        </ul>
      </div>
    );
    
    function App() {
      const [ iSeason, setActiveSeason ] = useState(-1);
      const [ iEpisode, setActiveEpisode ] = useState(-1);
      const season = SERIES[iSeason];
      const episode = season?.folder[iEpisode];
    
      useEffect(() => {
        setActiveEpisode(-1);
      }, [ iSeason ]);
    
      return (
        <div>
          <ItemsList
            header="сезоны"
            items={SERIES}
            active={iSeason}
            onChange={setActiveSeason}
          />
          {season && <ItemsList
            header="серии"
            active={iEpisode}
            onChange={setActiveEpisode}
            items={season.folder}
          />}
          {episode && <div>Выбрали: {season.comment}, {episode.comment}</div>}
        </div>
      );
    }
    Ответ написан
  • Как отнимать единицу при клике на неверный вариант в квизе?

    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 Куратор тега Яндекс.Карты
    Сгруппируйте данные по координатам:

    const grouped = productData.reduce((acc, n) => (
      (acc[n.coordinates] ??= []).push(n),
      acc
    ), {});

    Соответственно, когда будете собирать строку для ballonContent метки, вместо одного объекта придётся пробежать по массиву объектов:

    for (const [ coord, data ] of Object.entries(grouped)) {
      const placemark = new ymaps.Placemark(
        coord.split(',').map(parseFloat),
        {
          balloonContent: data
            .map(n => `
              <div>
                ${n.address}
                <br>
                <a href="${n.productURL}">Подробнее</a>
              </div>`)
            .join(''),
        },
        {
          preset: 'islands#blueDotIcon',
          maxWidth: 300,
        }
      );
    
      map.geoObjects.add(placemark);
    }

    Или, воспользуйтесь кластеризатором:

    const placemarks = productData.map((n, i) => new ymaps.Placemark(
      n.coordinates.split(',').map(parseFloat),
      {
        balloonContent: `${n.address}<br><a href="${n.productURL}">Подробнее</a>`,
        clusterCaption: `Адрес №${i + 1}`,
      },
      {
        preset: 'islands#blueDotIcon',
        maxWidth: 300,
      }
    ));
    
    const clusterer = new ymaps.Clusterer({
      clusterDisableClickZoom: true,
    });
    
    clusterer.add(placemarks);
    map.geoObjects.add(clusterer);
    Ответ написан
    1 комментарий
  • Как выполнить несколько замен в строке так, следующие замены не перетирали результат предыдущих?

    0xD34F
    @0xD34F
    Вместо того, чтобы перебирать "алфавит" и заменять символы по одному, перебирайте "зашифрованный" текст и подставляйте вместо текущего символа соответствующий ему из "алфавита":

    decoded = ''.join(data_crypt.get(n, n) for n in text)
    print(decoded)
    Ответ написан
    3 комментария
  • Как сделать, чтобы количество активных чекбоксов не опускалось ниже определённого?

    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 комментария
  • Как получить индекс элемента с определенным классом на jQuery?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Пытаюсь так:

    let currentIndex = $(".js-practice_button.current").index();

    Но значение всегда 0, у какой бы кнопки класс current не присутствовал.

    Потому что метод index по умолчанию определяет индекс элемента среди соседей, а так как у каждой кнопки есть отдельный родитель... Ну да, получаете то, что получаете.

    Можно вместо индекса кнопки определять индекс родителя:

    const index = $('.js-practice_button.current').closest('li').index();

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

    const index = $('.js-practice_button.current').index('.js-practice_button');

    А вообще, к чёрту jquery. Есть варианты и на чистом js:

    const index = Array.prototype.findIndex.call(
      document.querySelectorAll('.js-practice_button'),
      n => n.classList.contains('current')
    );
    
    // или
    
    const el = document.querySelector('.js-practice_button.current')?.parentNode;
    const index = el ? [...el.parentNode.children].indexOf(el) : -1;
    
    // или
    
    let index = -1;
    for (
      let el = document.querySelector('.js-practice_button.current')?.closest('li');
      el;
      el = el.previousElementSibling, index++
    ) ;
    Ответ написан
    Комментировать
  • Как удалить из массива числа с повторяющимися цифрами?

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

    const noRepeatingDigits = num => !/(\d).*\1/.test(num);
    
    // или
    
    const noRepeatingDigits = num => -~Math.log10(num) === new Set(`${num}`).size;
    
    // или
    
    const noRepeatingDigits = num => [...'' + num].every((n, i, a) => i === a.indexOf(n));
    
    // или
    
    const noRepeatingDigits = num => String(num)
      .split('')
      .reduce((acc, n) => (acc[n]++, acc), Array(10).fill(0))
      .every(n => n < 2);
    
    // или
    
    const noRepeatingDigits = num =>
      !''.match.call(num, /./g).some(function(n) {
        return this[n] = Object.hasOwn(this, n);
      }, {});
    
    // или
    
    const noRepeatingDigits = num =>
      !Array.from(num.toString()).sort().find((n, i, a) => n === a[i + 1]);

    Теперь массив. Можно удалить ненужное из существующего:

    arr.splice(0, arr.length, ...arr.filter(noRepeatingDigits));
    
    // или
    
    let numDeleted = 0;
    
    for (const [ i, n ] of arr.entries()) {
      arr[i - numDeleted] = n;
      numDeleted += !noRepeatingDigits(n);
    }
    
    arr.length -= numDeleted;
    
    // или
    
    for (let i = arr.length; i--;) {
      if (!noRepeatingDigits(arr[i])) {
        arr.splice(i, 1);
      }
    }

    Или собрать новый:

    const newArr = arr.filter(noRepeatingDigits);
    
    // или
    
    const newArr = [];
    
    for (const n of arr) {
      if (noRepeatingDigits(n)) {
        newArr.push(n);
      }
    }
    
    // или
    
    const newArr = [];
    
    for (let i = 0; i < arr.length; i++) {
      if (noRepeatingDigits(arr[i])) {
        newArr[newArr.length] = arr[i];
      }
    }
    
    // или
    
    const newArr = (function xxx(arr, i = 0) {
      return i < arr.length
        ? (noRepeatingDigits(arr[i]) ? [ arr[i] ] : []).concat(xxx(arr, i + 1))
        : [];
    })(arr);
    Ответ написан
    1 комментарий
  • Как сделать равномерное слияние многомерного массива?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Кого надо объединить:

    const arrs = [
      [ 1, 2, 3, 4 ],
      [ 5, 6, 7, 8 ],
      [ 9, 10, 11 ],
    ];

    Объединяем:

    const result = [];
    const max = Math.max(...arrs.map(n => n.length));
    const index = Array(arrs.length).fill(0);
    
    for (let i = 0; i < max; i++) {
      for (let j = 0; j < index.length; j++) {
        if (index[j] < arrs[j].length) {
          result[result.length] = arrs[j][index[j]++];
        }
      }
    }

    или

    const result = arrs
      .reduce((acc, arr) => (
        arr.forEach((n, i) => (acc[i] ??= []).push(n)),
        acc
      ), [])
      .flat();
    Ответ написан
    Комментировать
  • Как отфильтровать элементы li по объекту?

    0xD34F
    @0xD34F Куратор тега JavaScript
    const li = Array.prototype.filter.call(
      document.querySelector('ul').children,
      function(n) {
        return this.every(([ k, v ]) => v === n.querySelector(`.${k}`).innerText);
      },
      Object.entries(items)
    );

    или

    const li = [];
    
    COLLECT_LI:
    for (const n of document.querySelectorAll('li')) {
      for (const k in items) {
        if (Object.hasOwn(items, k) && items[k] !== n.querySelector('.' + k).textContent) {
          continue COLLECT_LI;
        }
      }
    
      li.push(n);
    }
    Ответ написан
    4 комментария
  • Почему при обновлении состояния не рендерится компонент?

    0xD34F
    @0xD34F Куратор тега React
    Не выдумывайте, всё рендерится.

    Почему видимых изменений нет? Потому что обновляете одно состояние, а рендер выполняете на основе другого, items в App и items в ElementsList - это разные массивы. Не надо никакого useState в ElementList, выполняйте рендер на основе prop'а; метод onDelete перенесите в App и тоже передавайте в ElementList через props.
    Ответ написан
    1 комментарий
  • Миграция на VUE 3, не работает роутер?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Для vue 3 предназначена четвёртая версия роутера, а не третья:

    - <script src="https://unpkg.com/vue-router@3/dist/vue-router.js"></script>
    + <script src="https://unpkg.com/vue-router@4/dist/vue-router.global.js"></script>

    А вот это надо вырезать:

    import VueRouter from 'vue-router'
    Ответ написан
  • Как получить данные маркеров, которые находятся в области видимости карты (vue-yandex-map)?

    0xD34F
    @0xD34F Куратор тега Яндекс.Карты
    Подписаться на события инициализации карты и изменения её границ. Получить границы видимой области карты. Перебрать массив данных, на основе которых создаются маркеры, проверяя, как координаты маркеров соотносятся с границами видимой области. Как-то так:

    <yandex-map
      ref="map"
      @map-was-initialized="onBoundsChange"
      @boundschange="onBoundsChange"
      ...
    >
      <ymap-marker
        v-for="n in markersData"
        ...
      >

    methods: {
      onBoundsChange() {
        const bounds = this.$refs.map.myMap.getBounds();
        this.markersData.forEach(n => {
          if (
            bounds[0][0] < n.coords[0] && n.coords[0] < bounds[1][0] &&
            bounds[0][1] < n.coords[1] && n.coords[1] < bounds[1][1]
          ) {
            // ...
          }
        });
      },
      ...
    Ответ написан
    Комментировать
  • Как объединись значения из двух массивов в один?

    0xD34F
    @0xD34F
    $extract = fn($keys, $item) => array_combine($keys, array_map(fn($k) => $item[$k], $keys));
    
    $grouped = [];
    $productKeys = [ 'product_id', 'sku', 'quantity' ];
    $orderKeys = array_diff(array_keys($arr[0] ?? []), $productKeys);
    
    foreach ($arr as $n) {
      $id = $n['order_id'];
      $grouped[$id] ??= $extract($orderKeys, $n);
      $grouped[$id]['products'][] = $extract($productKeys, $n);
    }
    Ответ написан
  • Почему не получается передать пользовательское событие родительскому компоненту?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Всё получается, всё передаётся.

    метод testemit компонента question не отрабатывает

    А должен? Нет, не должен - вы его нигде не вызываете. Как и не используете в качестве обработчика событий - вижу, что $emit('testemit') есть, а вот @testemit="testemit" отсутствует.
    Ответ написан
  • Как искать по своим вопросам или ответам?

    0xD34F
    @0xD34F
    Поиск тут сильно так себе, так что искать лучше через гугл. Лично я использую запрос следующего вида:

    site:qna.habr.com <имя-пользователя> <тег> <чего ищем>

    Отобрать вопросы/ответы - подписываетесь на интересующие вас теги, заходите в профиль, раздел "подписки", подраздел "теги", появится список тегов - рядом с каждым будут ссылки на списки соответствующих вопросов и ответов (конечно, если таковые у вас есть). Но, разумеется, можно и напрямую пройти, если знать, как нужный url выглядит. Вот ваш laravel, например.
    Ответ написан
    3 комментария
  • Как удалять теги из массива, которые уже не используются?

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

    tags = Array.from(new Set(data.flatMap(n => n.tags)));

    Если массив по какой-то причине заменять нельзя, тогда удалите все элементы из существующего и запишите новые:

    tags.splice(0, tags.length, ...new Set(data.flatMap(n => n.tags)));
    Ответ написан
    Комментировать
  • Как очищать все поля формы поиска, кроме одного?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Сделать observable объект, содержащий ссылку на экземпляр компонента, в котором последний раз осуществлялся пользовательский ввод. Установить наблюдение за этой ссылкой, и если значение не совпадает с текущим экземпляром - сбрасывать значение input'а:

    const lastEdited = Vue.observable({ instance: null });

    props: [ 'value' ],
    methods: {
      onInput(e) {
        this.$emit('input', e.target.value);
        lastEdited.instance = this;
      },
    },
    created() {
      this.$watch(() => lastEdited.instance, val => {
        if (val && val !== this) {
          this.$emit('input', '');
        }
      });
    },
    beforeDestroy() {
      if (lastEdited.instance === this) {
        lastEdited.instance = null;
      }
    },

    <input :value="value" @input="onInput">

    https://jsfiddle.net/jLw096zm/

    Или можно шину событий применить. Если случилось изменение input'а пользователем, кидать событие, в качестве аргумента передавать ссылку на экземпляр компонента, в обработчике сравнивать значение аргумента и this, если не совпали - очищать input:

    const eventBus = new Vue();

    props: [ 'value' ],
    methods: {
      onInput(e) {
        this.$emit('input', e.target.value);
        eventBus.$emit('clear-other-inputs', this);
      },
    },
    created() {
      const clear = e => e !== this && this.$emit('input', '');
      eventBus.$on('clear-other-inputs', clear);
      this.$on('hook:beforeDestroy', () => eventBus.$off('clear-other-inputs', clear));
    },

    <input :value="value" @input="onInput">

    https://jsfiddle.net/jLw096zm/1/
    Ответ написан
    Комментировать