suggestion to order loaded tags
Closed this issue · 7 comments
the drag&dropping side is easy using SortableJS is easy (i updated the demo.html for that :) )
the issue is that i'm using a select under the hood. the items there are not sorted regardless of the order when you picked or entered the tags
in order to do this, i would need to sort the options in the select to match whatever has been entered and reflect any drag and drop as well. Sounds doable but I don't have the use for that at the moment but if you want to make a PR you are welcome :-)
actually... i found a solution you can simply sort the options based on in the order of the tags after drag and drop
function sortSelect(elem, sort) {
var tmp = [];
var selectedValue = elem[elem.selectedIndex].value;
for (var i = 0; i < elem.options.length; i++) {
tmp.push(elem.options[i]);
}
tmp.sort(function (a, b) {
if (sort.indexOf(a.value) === -1) {
return tmp.length;
}
return sort.indexOf(a.value) - sort.indexOf(b.value);
});
while (elem.options.length > 0) {
elem.options[0] = null;
}
for (var i = 0; i < tmp.length; i++) {
elem.options[i] = tmp[i];
}
}
const validationTags = document.querySelector("#validationTags");
const example1 = validationTags.parentElement.querySelector("div.form-control").querySelector("div");
new Sortable(example1, {
animation: 150,
filter: "input",
draggable: "span",
ghostClass: "blue-background-class",
onEnd: function (evt) {
var itemEl = evt.item; // dragged HTMLElement
var oldIndex = evt.oldIndex; // element's old index within old parent
var newIndex = evt.newIndex; // element's new index within new parent
// Sort options according to tag orders
if (oldIndex != newIndex) {
const indexes = Array.from(validationTags.parentElement.querySelectorAll("span.badge")).map((badge) => {
return (badge.dataset && badge.dataset.value) || -1;
});
sortSelect(validationTags, indexes);
}
},
});
You're a genius, if that's what I was needing.
I'm testing it and if there are two tags it makes the change fine and saves it fine in the database.
In the case that it has more labels, it is giving me the following error:
Uncaught TypeError: Cannot read properties of undefined (reading 'value')
at edit:200:36
at Array.reduce ()
at Bt.onEnd (edit:199:101)
at W (Sortable.min.js:2:11012)
at U (Sortable.min.js:2:11592)
at Bt._onDrop (Sortable.min.js:2:28869)
It is in this line
return badge.dataset.value || -1;
I will continue testing
thank you.
@DanielKimmich mhh did you see I made an update to the script ? i don't get the issue with my code in demo.html
It is correct, I have tried "demo.html" and I copied part of that code and it works for me in my application and it works.
So I will continue analyzing because it does not work for me. I tell you that I am using the "Laravel Collective" library and I created my own form.
{{--Form::texttags('name', 'options', 'values', 'attributes') --}}
@php
$clase = $attributes['class'] ?? 'form-select';
$new = $attributes['data-allow-new'] ?? 'false';
$badge = $attributes['badgeStyle'] ?? 'primary';
if (old($name) !== null){
$values = old($name);
}
$selected = $values ? (implode(',',$values)) : '';
@endphp
<select
class="{!!$clase!!}"
id="{{ $name }}"
name="{{ $name.'[]' }}"
multiple
data-full-width="true"
data-allow-clear="true"
data-allow-new="{!!$new!!}"
data-badge-style="{!!$badge!!}"
data-suggestions-threshold="0"
data-selected="{!!$selected!!}"
>
@empty($options)
@empty($values)
<option value="">Escriba las etiquetas...</option><!-- you need at least one option with the placeholder -->
@else
<option value="" selected="selected" disabled hidden >Escriba las etiquetas...</option><!-- you need at least one option with the placeholder -->
@foreach ($values as $value)
<option value="{{$value}}">{{ $value }}</option>
@endforeach
@endempty
@else
<option value="" selected="selected" disabled hidden >Seleccione las etiquetas...</option><!-- you need at least one option with the placeholder -->
@foreach ($options as $value)
<option value="{{$value}}">{{ $value }}</option>
@endforeach
@endempty
</select>
<script src="{{ asset('packages/sortablejs/Sortable.js') }}" type="module"></script>
<script type="module">
import Tags from "{{ asset('packages/bootstrap5-tags/tags.js') }}"
Tags.init("#{!!$name!!}");
function sortSelect(elem, sort) {
var tmp = [];
var selectedValue = elem[elem.selectedIndex].value;
for (var i = 0; i < elem.options.length; i++) {
tmp.push(elem.options[i]);
}
tmp.sort(function (a, b) {
return sort.indexOf(b.value) - sort.indexOf(a.value);
});
while (elem.options.length > 0) {
elem.options[0] = null;
}
for (var i = 0; i < tmp.length; i++) {
elem.options[i] = tmp[i];
}
}
const sortingTags = document.querySelector("#{!!$name!!}");
const example1 = sortingTags.parentElement.querySelector("div.form-control").querySelector("div");
new Sortable(example1, {
animation: 150,
filter: "input",
draggable: "span",
ghostClass: "blue-background-class",
onEnd: function (evt) {
var itemEl = evt.item; // dragged HTMLElement
var oldIndex = evt.oldIndex; // element's old index within old parent
var newIndex = evt.newIndex; // element's new index within new parent
// Sort options according to tag orders
if (oldIndex != newIndex) {
const indexes = Array.from(sortingTags.parentElement.querySelectorAll("span.badge")).reduce((badge) => {
// console.log (badge.dataset);
return badge.dataset.value || -1;
});
// console.log (indexes);
sortSelect(sortingTags, indexes);
}
},
});
</script>
Thank you very much for taking care and helping me.
Regards,
i don't see anything wrong on the first look so unfortunately that's as much support I can provide as part of creator of this library :-)
i do freelance work as well if needed ;-)
your error means that there is no data attribute => cannot call value of undefined.
simply make a console log of badge console.log(badge) and check what you got there that has no data attribute. then it's a matter of tweaking your queryselector or reduce tag or simply make a forEach loop instead of a reduce tag to have better control
Thanks again, now it's working for me.
Make a few changes, replace the name of the elements "document.querySelector('#{!!$name!!}')", not using a variable in the script.
const example1 = document.querySelector('#{!!$name!!}').parentElement.querySelector('div.form-control').querySelector('div');
new Sortable(example1, {
animation: 150,
filter: 'input',
draggable: 'span',
ghostClass: "blue-background-class",
onEnd: function (evt) {
var itemEl = evt.item; // dragged HTMLElement
var oldIndex = evt.oldIndex; // element's old index within old parent
var newIndex = evt.newIndex; // element's new index within new parent
// Sort options according to tag orders
if (oldIndex != newIndex) {
const indexes = Array.from(document.querySelector('#{!!$name!!}').parentElement.querySelectorAll("span.badge")).map((badge) => {
// console.log (badge.dataset);
return (badge.dataset && badge.dataset.value) || -1;
});
// console.log (indexes);
sortSelect(document.querySelector('#{!!$name!!}'), indexes);
}
},
});
For my part I consider closed
Regards,