• Как получить уровни вложенности для всех вложенных объектов?

    0xD34F
    @0xD34F Куратор тега JavaScript
    Функции добавить параметр - глубину вложенности.

    Проверяем, чем является значение, если не объект, то возвращаем как есть. Иначе собираем новый объект, в который записываем текущую глубину вложенности и свойства исходного объекта, обработав их значения функцией - делаем рекурсивный вызов с глубиной вложенности, увеличенной на единицу:

    const addDepth = (val, depth = 0) =>
      val instanceof Object
        ? Object.entries(val).reduce((acc, n) => (
            acc[n[0]] = addDepth(n[1], depth + 1),
            acc
          ), { depth })
        : val;
    Ответ написан
    Комментировать
  • Как конвертировать объект в строку из примера?

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

    const toString = (val, keys = []) =>
      val instanceof Object
        ? Object.entries(val).map(([ k, v ]) => {
            keys.push(k);
            const result = toString(v, keys);
            keys.pop();
            return result;
          }).join('&')
        : `${keys.join('.')}=${val}`;

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

    function toString(val) {
      const result = [];
    
      for (const stack = [ [ val, [] ] ]; stack.length;) {
        const [ n, keys ] = stack.pop();
        if (n instanceof Object) {
          stack.push(...Object.entries(n).map(([ k, v ]) => [ v, [ ...keys, k ] ]).reverse());
        } else {
          result.push(`${keys.join('.')}=${n}`);
        }
      }
    
      return result.join('&');
    }
    Ответ написан
    1 комментарий
  • Как адаптировать итеративный алгоритм обхода бинарного дерева к обходу сильноветвящегося дерева?

    0xD34F
    @0xD34F
    на любом языке

    Ну как скажете:

    копирование
    суммирование
    поиск значений, удовлетворяющих какому-либо условию - одного или всех, что есть
    Ответ написан
    Комментировать
  • Не могу удалить элемент input из списка - всегда удаляется последний?

    0xD34F
    @0xD34F Куратор тега Vue.js
    v-for="(address, i) in form.addresses_to" :key="i"

    всегда удаляется последний

    Конечно последний. Индекс в качестве ключа - хуже, чем ничего. Индекс ключом быть не может, думаете, что ключи есть, а на самом деле их нет. А если ключей нет, то

    Vue использует алгоритм, который минимизирует перемещение элементов

    Т.е., было в массиве N значений, на основе которых создано N элементов. Одно значение из массива выкидывается, значит, и элементов должно остаться N - 1. Какой удалить проще всего? - последний, не надо будет перемещать элементы, расположенные за ним.

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

    0xD34F
    @0xD34F Куратор тега Vue.js
    Метаданные формы - массив объектов, содержащих имя поля, имя компонента, с которым будет взаимодействовать пользователь, объект параметров для компонента:

    const formMeta = ref([
      {
        name: '...',
        component: '...',
        props: { ... },
      },
      ...
    ]);

    Данные формы - объект, в качестве ключей будут выступать значения свойств name элементов formMeta. Изначально можно сделать пустым: const formData = ref({});. Можно явно задать начальные значения:

    const formData = ref({
      имяПоля1: значение1,
      имяПоля2: значение2,
      ...
    });

    Или есть вариант доставать ключи и дефолтные значения из метаданных:

    const formData = ref(Object.fromEntries(formMeta.value.map(n => [
      n.name,
      n.defaultValue ?? null,
    ])));

    На основе formMeta создаётся форма, через v-model свойства formData связываются с экземплярами компонентов:

    <form>
      <div v-for="n in formMeta">
        <component
          :is="components[n.component]"
          v-model="formData[n.name]"
          v-bind="n.props"
        />
      </div>
    </form>

    Вот как-то так.
    Ответ написан
    1 комментарий
  • Что не так с рекурсией?

    0xD34F
    @0xD34F
    Не возвращаете результат рекурсивного вызова - где return перед gen_nums(stop_n, number)?

    А вообще, какого хрена последнее число, в отличие от остальных, не выводится самой функцией? Давайте избавимся от этой шизофрении:

    def gen_nums(max_num, num):
      if num <= max_num:
        print(num)
        gen_nums(max_num, num + 1)
    
    # или
    
    def gen_nums(num):
      if num >= 1:
        gen_nums(num - 1)
        print(num)
    Ответ написан
  • Как строку с часами, минутами и секундами преобразовать в число?

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

    const units = [
      [ 'де?н', 24 * 60 * 60 ],
      [  'час',      60 * 60 ],
      [  'мин',           60 ],
      [  'сек',            1 ],
    ].map(n => [ RegExp(`\\d+(?=\\s+${n[0]})`), n[1] ]);
    
    const getSeconds = str =>
      units.reduce((acc, n) => acc + n[0].exec(str) * n[1], 0);

    getSeconds('2 часа 22 секунды') // 7222
    getSeconds('99 минут') // 5940
    getSeconds('1 час 1 минута 1 секунда') // 3661
    getSeconds('1 день 23 часа 59 минут 60 секунд') // 172800
    getSeconds('2 дня') // 172800
    getSeconds('546 секунд и ещё 2 минуты') // 666

    Если вдруг возможно повторение единиц измерения внутри одной строки, то поиск с помощью регулярных выражений должен быть глобальным - RegExp(`\\d+(?=\\s+${n[0]})`, 'g'), а функция подсчёта секунд примет следующий вид:

    const getSeconds = str =>
      units.reduce((seconds, [ reg, multiplier ]) => {
        return [...str.matchAll(reg)].reduce((acc, n) => acc + n * multiplier, seconds);
      }, 0);
    
    // или
    
    const getSeconds = str => units
      .flatMap(n => (str.match(n[0]) ?? []).map(m => m * n[1]))
      .reduce((acc, n) => acc + n, 0);
    
    // или
    
    const getSeconds = str => eval(units
      .map(n => `${n[1]} * (${str.match(n[0])?.join('+') ?? 0})`)
      .join('+')
    );

    getSeconds('1 секунда плюс 3 секунды плюс 5 секунд') // 9
    getSeconds('21 день, 7 дней, да ещё 3 дня - всего секунд в мае месяце будет') // 2678400
    Ответ написан
    2 комментария
  • Как вывести сложную таблицу во Vue.js?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Если количество уровней вложенности невелико и известно заранее, то... Для обработки каждого уровня вложенности используется отдельный <template v-for, ячейки с rowspan'ами создаются в зависимости от равенства индексов элементов вложенных массивов нулю, значения rowspan'ов - длины (суммы длин) вложенных массивов. Например:

    const rowspan = item => item.backlinks.reduce((acc, n) => acc + n.recipients.length, 0);

    <tbody>
      <template v-for="item in data">
        <template v-for="(backlink, iBacklink) in item.backlinks">
          <template v-for="(recipient, iRecipient) in backlink.recipients">
            <tr>
              <template v-if="!iBacklink && !iRecipient">
                <td :rowspan="rowspan(item)">{{ item.name }}</td>
                <td :rowspan="rowspan(item)">{{ item['domain score'] }}</td>
              </template>
              <template v-if="!iRecipient">
                <td :rowspan="backlink.recipients.length">{{ backlink.donor }}</td>
                <td :rowspan="backlink.recipients.length">{{ backlink['page score'] }}</td>
              </template>
              <td>{{ recipient.url }}</td>
              <td>{{ recipient.image }}</td>
            </tr>
          </template>
        </template>
      </template>
    </tbody>

    Более универсальное решение - собрать из вложенных данных плоские, дополнив их значениями rowspan. Понадобится массив ключей и рекурсивная функция, принимающая массив данных, массив ключей, и индекс, начиная с которого надо обрабатывать ключи. Бежим по массиву данных, вложенный цикл по ключам - если значение, соответствующее текущему ключу, не является массивом, то оно помещается в массив данных строки, в противном случае следует рекурсивный вызов, после которого элементам массива данных строки добавляется значение rowspan, равное размеру результата рекурсивного вызова:

    const keys = ref([
      'name', 'domain score', 'backlinks',
      'donor', 'page score', 'recipients', 
      'url', 'image',
    ]);

    function createTableData(arr, keys, iKey = 0) {
      return arr.flatMap(n => {
        const row = [];
        const innerRows = [];
    
        for (let i = iKey; i < keys.length; i++) {
          const val = n[keys[i]];
          if (Array.isArray(val)) {
            innerRows.push(...createTableData(val, keys, i + 1));
            row.forEach(cell => cell.rowspan = innerRows.length);
            row.push(...innerRows.shift());
            break;
          } else {
            row.push({ text: val });
          }
        }
    
        return [ row, ...innerRows ];
      });
    }

    Преобразование вложенных данных в плоские можно оформить в виде вычисляемого свойства:

    const tableData = computed(() => {
      return createTableData(props.data, props.keys);
    });

    Ну а построить таблицу на основе плоского массива - дело тривиальное:

    <tbody>
      <tr v-for="row in tableData">
        <td
          v-for="cell in row"
          v-text="cell.text"
          :rowspan="cell.rowspan"
        ></td>
      </tr>
    </tbody>
    Ответ написан
    Комментировать
  • Как вывести списком данные?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Рекурсивный компонент. Проверяем, является ли переданное значение объектом. Если нет - отображаем его. Если да - бежим по его свойствам, под каждое создаём по экземпляру компонента. Например, так.
    Ответ написан
    Комментировать
  • Как сделать так, чтобы по потери фокуса 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 = ref([ 50, 50 ]);
    
    const circleStyles = computed(() => ({
      left: `${coords.value[0]}px`,
      top: `${coords.value[1]}px`,
    }));
    
    const updateCoords = e => coords.value = [ e.pageX, e.pageY ];
    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(',')})`;
    }
    Ответ написан
    Комментировать