• Как сделать так, чтобы по потери фокуса input'ом измененный текст попадал в соответствующий li?

    0xD34F
    @0xD34F Куратор тега React
    Надо запоминать индекс кликнутого li.

    const defaultEdit = {
      index: -1,
      value: '',
    };
    
    function App() {
      const [ notes, setNotes ] = useState([...'12345']);
      const [ edit, setEdit ] = useState(defaultEdit);
    
      const onClick = ({ currentTarget: { dataset: { index } } }) =>
        setEdit({ index: +index, value: notes[index] });
    
      const onChange = ({ target: { value } }) =>
        setEdit(edit => ({ ...edit, value }));
    
      const onBlur = () => {
        setNotes(notes => notes.map((n, i) => i === edit.index ? edit.value : n));
        setEdit(defaultEdit);
      };
    
      return (
        <div>
          <ul>{notes.map((n, i) => (
            <li data-index={i} onClick={onClick}>
              {n}
            </li>))}
          </ul>
          <input
            value={edit.value}
            onChange={onChange}
            onBlur={onBlur}
          />
        </div>
      );
    }
    Ответ написан
  • Как переписать код перемещения блока мышью с чистого js на vue?

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

    const coords = reactive([ 50, 50 ]);
    
    const circleStyles = computed(() => ({
      left: `${coords[0]}px`,
      top: `${coords[1]}px`,
    }));
    
    const updateCoords = e => (coords[0] += e.movementX, coords[1] += e.movementY);
    const updateCoordsOn = () => document.addEventListener('mousemove', updateCoords);
    const updateCoordsOff = () => document.removeEventListener('mousemove', updateCoords);

    <div
      class="circle"
      :style="circleStyles"
      @mousedown="updateCoordsOn"
      @mouseup="updateCoordsOff"
    ></div>
    Ответ написан
    Комментировать
  • Почему не работает двусторонняя привязка с родительским компонентом во vue2?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Потому что возможность указывать аргумент для v-model появилась в третьей версии vue.

    Вместо v-model используйте sync.
    Ответ написан
    3 комментария
  • Как построить древовидный массив по уровню?

    0xD34F
    @0xD34F
    function createTree($data, $params = []) {
      extract($params + [
        'levelKey' => 'level',
        'childrenKey' => 'children',
      ]);
    
      $root = [];
    
      foreach ($data as $n) {
        $arr = &$root;
    
        for (
          $level = $data[0][$levelKey];
          $level++ < $n[$levelKey];
          $arr = &$arr[count($arr) - 1][$childrenKey]
        ) ;
    
        $arr[] = $n + [ $childrenKey => [] ];
      }
    
      return $root;
    }
    
    
    $tree = createTree($arMenu, [ 'levelKey' => 'LEVEL' ]);

    или

    function createTree($data, $params = []) {
      $levelKey = $params['levelKey'] ?? 'level';
      $childrenKey = $params['childrenKey'] ?? 'children';
    
      $root = [];
      $stack = [ [ $data[0][$levelKey], &$root ] ];
    
      foreach ($data as $n) {
        $end = end($stack);
        $level = $n[$levelKey];
    
        if ($level > $end[0]) {
          $stack[] = [ $level, &$end[1][count($end[1]) - 1][$childrenKey] ];
        } else while ($level < end($stack)[0]) {
          array_pop($stack);
        }
    
        end($stack)[1][] = array_merge($n, [ $childrenKey => [] ]);
      }
    
      return $root;
    }
    Ответ написан
    Комментировать
  • Как в reducerе обратиться к другому reducerу?

    0xD34F
    @0xD34F
    Что имеет сказать на этот счёт документация:

    The functions passed to the reducers parameter can be accessed through the caseReducers return field.

    Так что

    playlistSlice.caseReducers.playAudio(state, { payload: state.id });
    Ответ написан
    3 комментария
  • Как сделать развертывание аккордеона плавным?

    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)));
    });
    Ответ написан
    Комментировать
  • Как подключить и использовать внешний JS скрипт Choices.js в Vue.js компонент?

    0xD34F
    @0xD34F Куратор тега Vue.js
    <div v-once>
      <select ref="select" v-model="region"></select>
    </div>

    mounted() {
      const choices = new Choices(this.$refs.select);
      this.$watch(
        'regions',
        val => choices.setChoices(val, 'value', 'name', true),
        { immediate: true }
      );
      this.$on('hook:beforeDestroy', () => choices.destroy());
    },
    Ответ написан
  • Как перемещать элемент по клику на кнопки?

    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 Куратор тега Vue.js
    Назначайте класс в зависимости от значения свойства, которое будет устанавливаться в обработчиках onload и onerror. Например.
    Ответ написан
    1 комментарий
  • Как подгрузить данные из объекта?

    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 (let i = 0; i < arr.length; i++) {
      const n = arr[i];
      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 комментарий