Skip to content

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.