SortableJS 没有在 localStorage 中保存我排序的待办事项列表

SortableJS isn't saving my sorted todo list in localStorage

我有一个使用 localStorage 和 SortableJS 的待办事项项目。我遇到问题,当我对待办事项列表进行排序时,它不会更新 localStorage。有人可以帮我想办法保存排序列表吗?代码在下面,但最好访问代码段下的代码笔 link。

const clear = document.querySelector(".clear");
const dateElement = document.getElementById("date");
const list = document.getElementById("list");
const input = document.getElementById("input");

// Class names
const CHECK = "fa-check-circle";
const UNCHECK = "fa-circle-thin";
const LINE_THROUGH = "lineThrough";

// Variables
let LIST, id;

// Get item from localStorage
let data = localStorage.getItem("TODO");

// Check if data is not empty
if (data) {
  LIST = JSON.parse(data);
  id = LIST.length;
  loadList(LIST);
} else {
  LIST = [];
  id = 0;
}

// Load items to the user's interface
function loadList(array) {
  array.forEach(function(item) {
    addToDo(item.name, item.id, item.done, item.trash);
  });
}

// Clear the localStorage
clear.addEventListener("click", function() {
  localStorage.clear();
  location.reload();
})

// Show today's date
const options = {
  weekday: "long",
  month: "short",
  day: "numeric"
};
const today = new Date();

dateElement.innerHTML = today.toLocaleDateString("en-US", options);

// Add to do function
function addToDo(toDo, id, done, trash) {

  if (trash) {
    return;
  }

  const DONE = done ? CHECK : UNCHECK;
  const LINE = done ? LINE_THROUGH : "";

  const item = `<li class="item">
                  <i class="fa ${DONE}" job="complete" id="${id}"></i>
                  <p class="text ${LINE}">${toDo}</p>
                  <i class="fa fa-trash-o de" job="delete" id="${id}"></i>
                </li>
                `;
  const position = "beforeend";

  list.insertAdjacentHTML(position, item);
}

// Add an item to the list when the user cick the enter key
document.addEventListener("keyup", function(event) {
  if (event.keyCode == 13) {
    const toDo = input.value;

    // If the input isn't empty
    if (toDo) {
      addToDo(toDo);

      LIST.push({
        name: toDo,
        id: id,
        done: false,
        trash: false
      });
      // Add item to localStorage
      localStorage.setItem("TODO", JSON.stringify(LIST));

      id++;
    }
    input.value = ""
  }
});


// complete to do
function completeToDo(element) {
  element.classList.toggle(CHECK);
  element.classList.toggle(UNCHECK);
  element.parentNode.querySelector(".text").classList.toggle(LINE_THROUGH);

  LIST[element.id].done = LIST[element.id].done ? false : true;
}

// Remove to do
function removeToDo(element) {
  element.parentNode.parentNode.removeChild(element.parentNode);

  LIST[element.id].trash = true;
  // Add item to localStorage
  localStorage.setItem("TODO", JSON.stringify(LIST));
}

// Target the items created dynamically
list.addEventListener("click", function(event) {
  const element = event.target;
  const elementJob = element.attributes.job.value;

  if (elementJob == "complete") {
    completeToDo(element);
  } else if (elementJob == "delete") {
    removeToDo(element);
  }
  // Add item to localStorage
  localStorage.setItem("TODO", JSON.stringify(LIST));
});


// For sorting the list
Sortable.create(list, {
  animation: 100,
  group: 'list-1',
  draggable: '#list li',
  handle: '#list li',
  sort: true,
  filter: '.sortable-disabled',
  chosenClass: 'active'
});
/* ------------ youtube.com/CodeExplained ------------ */

body {
  padding: 0;
  margin: 0;
  background-color: rgba(0, 0, 0, 0.1);
  font-family: 'Titillium Web', sans-serif;
}


/* ------------ container ------------ */

.container {
  padding: 10px;
  width: 380px;
  margin: 0 auto;
}


/* ------------ header ------------ */

.header {
  width: 380px;
  height: 200px;
  background-image: url('');
  background-size: 100% 200%;
  background-repeat: no-repeat;
  border-radius: 15px 15px 0 0;
  position: relative;
}

.clear {
  width: 30px;
  height: 30px;
  position: absolute;
  right: 20px;
  top: 20px;
}

.clear i {
  font-size: 30px;
  color: #FFF;
}

.clear i:hover {
  cursor: pointer;
  text-shadow: 1px 3px 5px #000;
  transform: rotate(45deg);
}

#date {
  position: absolute;
  bottom: 10px;
  left: 10px;
  color: #FFF;
  font-size: 25px;
  font-family: 'Titillium Web', sans-serif;
}


/* ------------ content ------------ */

.content {
  width: 380px;
  height: 350px;
  max-height: 350px;
  background-color: #FFF;
  overflow: auto;
}

.content::-webkit-scrollbar {
  display: none;
}

.content ul {
  padding: 0;
  margin: 0;
}

.item {
  width: 380px;
  height: 45px;
  min-height: 45px;
  position: relative;
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  list-style: none;
  padding: 0;
  margin: 0;
}

.item i.co {
  position: absolute;
  font-size: 25px;
  padding-left: 5px;
  left: 15px;
  top: 10px;
}

.item i.co:hover {
  cursor: pointer;
}

.fa-check-circle {
  color: #6eb200;
}

.item p.text {
  position: absolute;
  padding: 0;
  margin: 0;
  font-size: 20px;
  left: 50px;
  top: 5px;
  background-color: #FFF;
  max-width: 285px;
}

.lineThrough {
  text-decoration: line-through;
  color: #ccc;
}

.item i.de {
  position: absolute;
  font-size: 25px;
  right: 15px;
  top: 10px;
}

.item i.de:hover {
  color: #af0000;
  cursor: pointer;
}


/* ------------ add item ------------ */

.add-to-do {
  position: relative;
  width: 360px;
  height: 40px;
  background-color: #FFF;
  padding: 10px;
  border-top: 1px solid rgba(0, 0, 0, 0.1);
}

.add-to-do i {
  position: absolute;
  font-size: 40px;
  color: #4162f6;
}

.add-to-do input {
  position: absolute;
  left: 50px;
  height: 35px;
  width: 310px;
  background-color: transparent;
  border: none;
  font-size: 20px;
  padding-left: 10px;
}

.add-to-do input::-webkit-input-placeholder {
  /* Chrome/Opera/Safari */
  color: #4162f6;
  font-family: 'Titillium Web', sans-serif;
  font-size: 20px;
}

.add-to-do input::-moz-placeholder {
  /* Firefox 19+ */
  color: #4162f6;
  font-family: 'Titillium Web', sans-serif;
  font-size: 20px;
}

.add-to-do input:-ms-input-placeholder {
  /* IE 10+ */
  color: #4162f6;
  font-family: 'Titillium Web', sans-serif;
  font-size: 20px;
}

.add-to-do input:-moz-placeholder {
  /* Firefox 18- */
  color: #4162f6;
  font-family: 'Titillium Web', sans-serif;
  font-size: 20px;
}
<script src="https://kit.fontawesome.com/ed2e310181.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/RubaXa/Sortable/Sortable.min.js"></script>
<div class="container">
  <div class="header">
    <div class="clear">
      <i class="fa fa-refresh"></i>
    </div>
    <div id="date"></div>
  </div>
  <div class="content">
    <ul id="list">
      <!-- <li class="item">
                  
                  <i class="fa fa-circle-thin co" job="complete" id="0"></i>
                  <p class="text"></p>
                  <i class="fa fa-trash-o" job="delete" id="1"></i>
                  
                </li> -->
    </ul>
  </div>
  <div class="add-to-do">
    <i class="fa fa-plus-circle"></i>
    <input type="text" id="input" placeholder="Add a to-do">
  </div>
</div>

请访问我的代码笔以获取工作项目。 尝试添加 2 个或更多待办事项然后排序,刷新时希望保留排序列表。

https://codepen.io/Foxseiz/pen/ZEGadWZ

您需要使用 onSort 回调。 示例代码:

const clear = document.querySelector(".clear");
const dateElement = document.getElementById("date");
const list = document.getElementById("list");
const input = document.getElementById("input");

// Class names
const CHECK = "fa-check-circle";
const UNCHECK = "fa-circle-thin";
const LINE_THROUGH = "lineThrough";

// Variables
let LIST, id;

// Get item from localStorage
let data = localStorage.getItem("TODO");

// Check if data is not empty
if(data) {
  LIST = JSON.parse(data);
  id = LIST.length;
  loadList(LIST);
}else{
  LIST =[];
  id = 0;
}

// Load items to the user's interface
function loadList(array) {
  array.forEach(function(item){
      addToDo(item.name, item.id, item.done, item.trash);
  });
}

// Clear the localStorage
clear.addEventListener("click", function() {
  localStorage.clear();
  location.reload();
})

// Show today's date
const options = {weekday : "long", month : "short", day : "numeric"};
const today = new Date();

dateElement.innerHTML = today.toLocaleDateString("en-US", options);

// Add to do function
function addToDo(toDo, id, done, trash) {

  if(trash) { return; }

  const DONE = done ? CHECK : UNCHECK;
  const LINE = done ? LINE_THROUGH : "";

  const item = `<li class="item">
                  <i class="fa ${DONE}" job="complete" id="${id}"></i>
                  <p class="text ${LINE}">${toDo}</p>
                  <i class="fa fa-trash-o de" job="delete" id="${id}"></i>
                </li>
                `;
  const position = "beforeend";

  list.insertAdjacentHTML(position, item);
}

// Add an item to the list when the user cick the enter key
document.addEventListener("keyup", function(event) {
  if(event.keyCode == 13) {
    const toDo = input.value;

    // If the input isn't empty
    if(toDo) {
      addToDo(toDo);

      LIST.push({
        name : toDo,
        id : id,
        done : false,
        trash : false
      });
      // Add item to localStorage
      localStorage.setItem("TODO", JSON.stringify(LIST));

      id++;
    }
    input.value = ""
  }
});


// complete to do
function completeToDo(element) {
  element.classList.toggle(CHECK);
  element.classList.toggle(UNCHECK);
  element.parentNode.querySelector(".text").classList.toggle(LINE_THROUGH);

  LIST[element.id].done = LIST[element.id].done ? false : true;
}

// Remove to do
function removeToDo(element) {
  element.parentNode.parentNode.removeChild(element.parentNode);

  LIST[element.id].trash = true;
  // Add item to localStorage
  localStorage.setItem("TODO", JSON.stringify(LIST));
}

// Target the items created dynamically
list.addEventListener("click", function(event) {
  const element = event.target;
  const elementJob = element.attributes.job.value;

  if(elementJob == "complete") {
    completeToDo(element);
  }else if(elementJob == "delete"){
    removeToDo(element);
  }
  // Add item to localStorage
  localStorage.setItem("TODO", JSON.stringify(LIST));
});

function swapArrayElements(arr, indexA, indexB) {
  var temp = arr[indexA];
  arr[indexA] = arr[indexB];
  arr[indexB] = temp;
};

function orderList(oldIndex, newIndex) {
  swapArrayElements(LIST, oldIndex, newIndex)

  localStorage.setItem("TODO", JSON.stringify(LIST));
}


// For sorting the list
Sortable.create(list, {
    animation: 100,
    group: 'list-1',
    draggable: '#list li',
    handle: '#list li',
    sort: true,
    filter: '.sortable-disabled',
    chosenClass: 'active',
    onSort: function (/**Event*/evt) {
      orderList(evt.oldIndex, evt.newIndex);
    },
});

当你打电话时

Sortable.create(list, {
  animation: 100,
  group: 'list-1',
  draggable: '#list li',
  handle: '#list li',
  sort: true,
  filter: '.sortable-disabled',
  chosenClass: 'active'
});

实际上有一个 store 选项可以添加。像这样:

Sortable.create(list, {
    store: {
        //Get the order of elements. Called once during initialization.
        // @param   {Sortable}  sortable
        // @returns {Array}
        get: function (sortable) {
            var order = localStorage.getItem(sortable.options.group.name);
            return order ? order.split('|') : [];
        },
        // Save the order of elements.
        // @param {Sortable}  sortable
        set: function (sortable) {
            var order = sortable.toArray();
            localStorage.setItem(sortable.options.group.name, order.join('|'));
        }
    },
    ...rest of your options    
});

还有 Sortable.create returns 列表的 "Sortable" 对象,因此根据上面的代码构建,您现在可以访问 Sortable 对象

var mySortable = Sortable.create(list, {...your options});

现在您可以在任何事件之后调用 mySortable.Save(),您的 store 的设置函数将被调用。例如,将 mysortable.Save() 放入 document.addEventListener("keyup") 函数

Sortable.create(list, {
        group: "TODO2",
        options: {
          animation: 100,
          draggable: "#list li",
          handle: "#list li",
          sort: true,
          filter: ".sortable-disabled",
          chosenClass: "active"
        },
        store: {
          /**
           * Get the order of elements. Called once during initialization.
           * @param   {Sortable}  sortable
           * @returns {Array}
           */
          get: function(sortable) {
            var 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) {
            var order = sortable.toArray();
            localStorage.setItem(sortable.options.group.name, order.join("|"));
          }
        }
      });

这对你的情况有用

对于您的 Sortable.create 选项,您可以执行以下操作:

// For sorting the list
Sortable.create(list, {
            animation: 100,
            group: 'list-1',
            draggable: '#list li',
            handle: '#list li',
            sort: true,
            filter: '.sortable-disabled',
            chosenClass: 'active',
            onSort: function(e) {
                var items = e.to.children;
                var result = [];
                for (var i = 0; i < items.length; i++) {
                    result.push(items[i].id);
                }
                var lsBefore = JSON.parse(localStorage.getItem("TODO"));
                var lsAfter = [];
                for (var i = 0; i < result.length; i++) {
                    var found = false;
                    for (var j = 0; j < lsBefore.length && !found; j++) {
                        if (lsBefore[j].id == result[i]) {
                            lsAfter.push(lsBefore[j]);
                            lsBefore.splice(j, 1);
                            found = true;
                        }
                    }
                }
                localStorage.setItem("TODO", JSON.stringify(lsAfter));
                console.log(result);
                console.log(lsBefore);
                console.log(lsAfter);
            }

lsAfter 是您可以在本地存储中 store/update 重新排序的对象集。

我的解决方案还要求您的 const 项看起来像这样(我将 id 属性添加到 <li> 元素:

  const item = `<li class="item" id="${id}">
                  <i class="fa ${DONE}" job="complete" id="${id}"></i>
                  <p class="text ${LINE}">${toDo}</p>
                  <i class="fa fa-trash-o de" job="delete" id="${id}"></i>
                </li>
                `;