Javascript-форум (https://javascript.ru/forum/)
-   Библиотеки/Тулкиты/Фреймворки (https://javascript.ru/forum/library-toolkit-framework/)
-   -   VUE 2 сортировка компонентов (https://javascript.ru/forum/library-toolkit-framework/84654-vue-2-sortirovka-komponentov.html)

ANAGAMA 09.11.2022 19:12

VUE 2 сортировка компонентов
 
Итак страница имеет
<div id="app">
<sort>
   <line uid="1"> бла бла бла — много html идет в  слот </line>
   <line uid="2">  бла бла бла — много html идет в  слот </line>
   <line uid="3"> бла бла бла — много html идет в  слот </line>
</sort>
</div>


В компоненте line кнопочка - наверх (вниз) по кнопочке улетает событие.
Родитель (sort) ловит событие и меняет местами компоненты

Вся эта колобаха генерится движком сайта. Вытаскивать все данные в Vue и там их сортить совсем не хочется.

Aetae 09.11.2022 22:05

Это противоречит сути vue так-то. Именно отдельно данный и надо слать, не вижу проблем заменить этот мусор на json.

Но если оень хочется, то придётся ручками писать render функцию, ктоторая будет сортировать ноды упавшие в слот к <sort>

ANAGAMA 09.11.2022 23:29

Цитата:

Сообщение от Aetae (Сообщение 548836)
Это противоречит сути vue так-то. <sort>

Спасибо. В общем да, но все в этой жизни чему то противоречит.

Но локоть сильно близко. И вроде все дети есть ... и можно было бы их местами поменять...

Или забить на эту задачу (и сделать смену на сервере в общем то админка) или приклеить туда jQuery и на нем махнуть местами два слоя...

Aetae 09.11.2022 23:38

Кстати ты не можешь называть компонент "line" (если конечно не пошаманишь с конфигом Vue), т.к. такой элемент есть среди стандартных.

voraa 10.11.2022 08:10

Цитата:

Сообщение от ANAGAMA
или приклеить туда jQuery и на нем

А без jquery никак?
Поставить один элемент перед другим - одна строка.
elem1.before (elem2) // elem2 поставить перед elem1

Aetae 10.11.2022 09:31

Можно вообще это всё обойти: использовать для sort - display:flex (row\coluum что надо) и проставлять детям order не трогая реальной разметки.)

Aetae 10.11.2022 11:58

С render функцией это можно сделать примерно так:
<div id="app">
<v-sort>
   <v-line uid="1">1 бла бла бла — много html идет в слот </v-line>
   <v-line uid="2">2 бла бла бла — много html идет в слот </v-line>   
   <v-line uid="3">3 бла бла бла — много html идет в слот </v-line>
</v-sort>
</div>

<script src="https://unpkg.com/vue@2"></script>
<script>
function addListener(options, type, listener) {
  if (!options || !type || !listener)
    return;
  
  if (!options.listeners) 
    options.listeners = {};
  
  if (!options.listeners[type]) 
    options.listeners[type] = listener;
  else if (Array.isArray(options.listeners[type])) 
    options.listeners[type].push(listener);
  else 
    options.listeners[type] = [options.listeners[type], listener];
  
  return options;
}

Vue.component('v-sort', {
  data() {
    return {
      keyCounter: 0,
      events: ['up', 'down'],
      children: []
    }
  },
  render(h) {
    return h('div', this.children.map(
      ({vNode, key}) => h('div', { key }, [vNode])
    ));
  },
  created() {
    this.children = this.getChildren();
  },
  methods: {
    getNewKey() {
      return this.keyCounter++;
    },
    getChildren() {
      const vNodes = this.$slots.default?.filter(vNode => vNode.componentOptions);
      
      if (!vNodes?.length) 
        return [];

      return vNodes.map(vNode => {
        const key = this.getNewKey();
        
        this.events.forEach(event => addListener(
          vNode.componentOptions, 
          event, 
          (...args) => this[event](key, ...args)
        ));
        
        return {
          key,
          vNode
        };
      });
    },
    findIndexByKey(key) {
      return this.children.findIndex(child => key === child.key);
    },
    up(key) {
      const index = this.findIndexByKey(key);
      if (index < 1) return;
      this.children.splice(index - 1, 2, this.children[index], this.children[index - 1]);
    },
    down(key) {
      const index = this.findIndexByKey(key);
      if (index === -1 || index > this.children.length - 2) return;
      this.children.splice(index, 2, this.children[index + 1], this.children[index]); 
    }
  }
})

Vue.component('v-line', {
  template: `<p>
    <button @click="$emit('up')">up</button>
    <slot/>
    <button @click="$emit('down')">down</button>
  </p>`
});

new Vue().$mount('#app')
</script>


Конечно гораздо лучше было бы если бы не нужно было слушать события из подкомпонентов, а просто рисовать кнопки прямо из компонента sort.)

ANAGAMA 10.11.2022 18:35

Нереально круто!
В общем посмотрел я на твою колобаху и понял, мне проще переписать в стандартной модели ВУ. А вообще нереально круто.

рони 10.11.2022 19:20

Aetae,
если я каким-то хитрым способом изменю порядок в this.children, что нужно сделать чтоб новые изменения вступили в силу?

voraa 10.11.2022 19:40

Вот чем мне не нравятся все фрейворки, так это тем, что простейшие вещи (почесать левое ухо) надо делать хрен знает как (правой ногой через спину)

Aetae 10.11.2022 20:37

рони, изменения массива this.children сами по себе запускают ререндер, этож vue. Если ты имеешь ввиду изменения в содержимом v-slot, то в задаче этого не стояло, но в принципе можно по beforeUpdate добавить алгоритм сопоставления позиций убирая отсутствующие и добавляя новые в конец, например.

voraa, есть множество способов сделать эту задачу на Vue красиво и аккуратно, просто делать её надо изначально на vue.
Выглядеть будет это например так:
<div id="app">
   <v-line v-for="content in data" @up="up" @down="down">{{content}}</v-line>
</div>

Без всякого шаманства.

Извращения нужны для извращённых случаев, которые идут в разрез с механизмом использования инструмента.

Vue - работает с данными, которые элегантно мапит в разметку, а в задаче мы ему скармливаем готовую разметку и просим с ней разобраться. То что он вообще может что-то сделать с этим - это уже показатель.

То что ты тут видишь - это advanced техники в основном для узких специфических задач авторов плагинов\библиотек. Так что не надо оскорблять божественный Vue.)

P.S. Хотя вот тот же Angular - там да, много ереси.

рони 10.11.2022 21:27

Цитата:

Сообщение от Aetae
изменения массива this.children сами по себе запускают ререндер,

мои изменения не входят в список
pop: ƒ mutator()
push: ƒ mutator()
reverse: ƒ mutator()
shift: ƒ mutator()
sort: ƒ mutator()
splice: ƒ mutator()
unshift: ƒ mutator()
что делает mutator я не осилил.
мне бы в ручную рендер запустить, я синтаксиса не знаю.
могу конечно достичь нужного запуском без аргументов this.children.splice();
но наверняка есть что-то типа this.render();

рони 10.11.2022 22:00

Aetae,
так сработало this.$forceUpdate();
может есть ещё варианты для такого this.children[0] = {}; или в Vue так не делают?

Aetae 10.11.2022 22:34

рони,
Vue.set(this.children, 0, {});
// или
this.$set(this.children, 0, {});

В vue 3 с этим вопрос решён(используется Proxy), там можно напрямую, но тут мы говорили о Vue 2.)

Собсно к реактивному отлову изменений массива по прямому присвоению до прокси было только два варианта:
1. В vue 2 отказались от этого в пользу splice и Vue.set, описав в документации, зато массив - всё ещё тот же самый массив, работают все методы массива, проверки на массив, сравнение === и.т.д.
2. В mobx сделали объект - обёртку над массивом, реактивно работает присвоение по номеру, но массив уже не массив и по === не сравнить, в функции ожидающие массив с гарантией не передашь и т.д. Требуется явно вызывать .toArray.

рони 10.11.2022 22:47

Aetae,
:write: :thanks:

ANAGAMA 11.11.2022 17:51

Цитата:

Сообщение от voraa (Сообщение 548851)
Вот чем мне не нравятся все фрейворки, так это тем, что простейшие вещи (почесать левое ухо) надо делать хрен знает как (правой ногой через спину)

ААААААА........... порвало. Да, есть такой момент. Вроде вот оно, вот ну, рядом ЖЕЖ.. а нет...... :)))

ANAGAMA 12.11.2022 12:04

Цитата:

Сообщение от Aetae (Сообщение 548853)
<div id="app">
   <v-line v-for="content in data" @up="up" @down="down">{{content}}</v-line>
</div>

Все так. Вопрос. Откуда ВУ. возьмет {{content}}?

Во всех руководствах этот вопрос стеснительно обходится. Дескать,возьмите фетч или аксиос, и загрузите данные и будет вам щастье.

А со страницы как ему данные отправить? Какойнить приличный массивчик.

Эдак 100 записей, каждая из которых маленькая картинка и с десяток полей?

А никак.

Можно упаковать эти данные пярмо в компоненты, но потом их не перетасуешь......

Aetae 12.11.2022 14:19

ANAGAMA, что значит "никак", лол.
Весь современный веб так и работает. API на сервере отдаёт массив вида
[{
  img: 'http://...',
  title: 'text',
  content: 'text'
}, {...}]
а фронт это дело рисует.

micscr 14.11.2022 10:12

Цитата:

Сообщение от ANAGAMA (Сообщение 548885)
А со страницы как ему данные отправить? Какойнить приличный массивчик.

Можно в коде сформировать свои данные в виде js, и потом передать их vue

<div id="app">
	<ul>
	    <li v-for="el in myarr">{{el.name}} - <span v-html="el.content"></span></li>
	</ul>
	
</div>



</body>
<script>
const myarr = [{name:'one', content:'<strong>one</strong>'}, {name:'two', content:'<strong>two</strong>'}, {name:'three', content:'<strong>three</strong>'}];

const vm = new Vue({
	el: "#app",
	data: {
		myarr 
		
	},


});

</script>


Часовой пояс GMT +3, время: 19:06.