sortableJS
My problem
11/03/25 - I'm currently working on a to-do list web app. It's just going to be a list of labeled checkboxes - a mini version of the actual to-do list assignment I'm supposed to be doing. The feature I was excited to implement was dragging list items to sort. sortableJS seemed like the perfect solution. All I had to do was attach it to the task-list div, right?
Well, it only works as expected if the contents of the div does not change. I want the list to persist even after refreshing or closing the browser.
sortableJS has a built in way to save to local storage. You can add a 'store' option when creating a new instance.
Sortable.create(list, {
animation: 150,
group: task-list-order,
store: {
/**
* Get the order of elements. Called once during initialization.
* @param {Sortable} sortable
* @returns {Array}
*/
get: function (sortable) {
let order = localStorage.getItem(sortable.options.group.name);
return order ? order.split('|') : [];
},
/**
* Save the order of elements. Called onEnd (when the item is dropped).
* @param {Sortable} sortable
*/
set: function (sortable) {
let order = sortable.toArray();
localStorage.setItem(sortable.options.group.name, order.join('|'));
}
Notice the toArray()? It's weird, right? What's being put in the array?
Turns out, when toArray() is invoked, a data-id is generated for each task-list child with the _generateId(el)function.
* Generate id
* @param {HTMLElement} el
* @returns {String}
* @private
*/
function _generateId(el) {
var str = el.tagName + el.className + el.src + el.href + el.textContent,
i = str.length,
sum = 0;
while (i--) {
sum += str.charCodeAt(i);
}
return sum.toString(36);
}
It looks something like this in Local Storage
"task-list-order": 2vt|3bc|2rt
The data-idis generated from the contents of the child (see var str = el.tagName + el.className + el.src + el.href + el.textContent). This local storage string only updates when the item is dropped.
So, if I were to add a new child or edit the text content of an existing child, I would need to drag and drop that child in order for local storage to update, which is ridiculous. If I don't update local storage, after refreshing the page, the children that are unaccounted for will be placed at the top of the task-list div.
Recreate data-id (failed solution #1)
I tried to fix this by putting the _generateId(el) function in my taskStorage object (used to update local storage). I invoked it in my storeTask() and editStoredTask() public functions.
It worked well when adding new children to the task-list div (using storeTask()).
However, after the child's text content changed and I invoke editStoredTask(), I can't seem to generate the correct data-id. When I drag it, the data-id generated by sortableJS would be completely different. Some other part of the child div must be different when I change the text content compared to when I drag and drop it. Maybe the "draggable" attribute? I'm not sure if I even make sense anymore.
part of the editStoredTask()function -
let currentTaskList = JSON.parse(localStorage.getItem(this.storageKey));
//get key of old value
//this.tasklabel is the current checkbox label element
let keyOfOldLabel = this._getKeybyValue(currentTaskList, this.taskLabel.textContent);
let newId = this._generateId(this.taskLabel)
//--updating sortable list in storage--
let sortableString = localStorage.getItem(sortableKey);
//position in task
this.currentIndex = this._getCurrentIndex(currentTaskList, keyOfOldLabel)
let sortableArr = Array.from(this._splitSortableString(sortableString));
sortableArr.splice(this.currentIndex, 1, newId);
//only update sortable storage if it exists
if (localStorage.getItem(sortableKey)){
localStorage.setItem(sortableKey, sortableArr.join("|"))}
Synthetic DragEvent (failed solution #2)
As a last ditch effort, I tried to make a synthetic DragEvent. Maybe if I fake a drag, it might trick sortableJS to update the local storage. It didn't work.
I tried this based on this MouseEvent example.
function simulateDrag(targetElement) {
// Create a synthetic DragEvent
let evt = new MouseEvent("drag");
// Send the event to the checkbox element
targetElement.dispatchEvent(evt);
}
and this..
function simulateDrag(targetElement) {
// Create a synthetic DragEvent
let dragStart = new MouseEvent("dragstart");
let dragEnd = new MouseEvent("dragend");
// Send the event to the checkbox element
targetElement.dispatchEvent(dragStart);
targetElement.dispatchEvent(dragStart);
}
and even this
function simulateDrag(targetElement) {
// Create a synthetic DragEvent
targetElement.setAttribute("draggable", true);
targetElement.setAttribute("draggable", false);
}
...I don't know why I thought that last one would work.
Giving up (for now)
Well, I decided to just forgo the sortableJS altogether for this project. It's not part of the assignment anyway. I thought it would be a neat qol addition to my app. I thought it would be easy to implement.
I'll return to sortableJS when I'm a smarter person.
Despite this failure, I got some valuable experience reading through and navigating someone else's code. I understood some of it, but most of them where a mystery.