Вход

Просмотр полной версии : VUE 2 сортировка компонентов


ANAGAMA
09.11.2022, 19:12
Итак страница имеет

<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
Это противоречит сути vue так-то. <sort>

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

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

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

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

voraa
10.11.2022, 08:10
или приклеить туда 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
изменения массива 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
Вот чем мне не нравятся все фрейворки, так это тем, что простейшие вещи (почесать левое ухо) надо делать хрен знает как (правой ногой через спину)

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

ANAGAMA
12.11.2022, 12:04
<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
А со страницы как ему данные отправить? Какойнить приличный массивчик.



Можно в коде сформировать свои данные в виде 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>