Home > Blockchain >  jQuery UI Sortable Refresh Error - How to Refresh Dynamically Added Sortable Elements?
jQuery UI Sortable Refresh Error - How to Refresh Dynamically Added Sortable Elements?

Time:10-28

sortable example

In the gif :

  1. Page loads initializing my sortable code (below)
  2. Add a section button (irrelevant to this question)
  3. Add a lesson to that section (dynamic, element didn't exist when page loaded, Important!)
  4. That lesson is not sortable because it was dynamically created
  5. In console, I redundantly paste my entire sortable initializer
  6. The lesson becomes sortable (as desired)

My question:

When a dynamic element is created, how do I correctly "refresh" or give the new elements all the properties and events (options?) that my initialized sortable items have.

Simplest HTML pseudocode of my GIF:

<div>
  <ul class="sortable-sections">
    <li class="section">
      <ul class="sortable-lessons">
        <li>Item 1</li>
      </ul>
    </li>
  </ul>
</div>

Is there a way to say (with jQuery), "any item loaded on page load or dynamically created with class sortable-lessons is a sortable item".?

My Sortable code:

$('.sortable-lessons').sortable({
        connectWith: ".sortable-lessons",
        placeholder: 'placeholder',
        items: "li:not(.unsortable)",
        forcePlaceholderSize: true,
        start: function( event, ui ) {
            $(ui.item).addClass("yellow");
        },
        stop: function(event, ui) {
            $(ui.item).removeClass("yellow");
        }
    });

My effort:

I thought maybe after my function adds the new lesson, I could just call:

$( ".sortable-lessons" ).sortable( "refresh" );

But this throws an error and I have no idea why.

Error:

jquery.min.js:2 Uncaught Error: cannot call methods on sortable prior to initialization;
  attempted to call method 'refresh'
    at Function.error (jquery.min.js:2)
    at HTMLUListElement.<anonymous> (jquery-ui.min.js:6)
    at Function.each (jquery.min.js:2)
    at S.fn.init.each (jquery.min.js:2)
    at S.fn.init.t.fn.<computed> [as sortable] (jquery-ui.min.js:6)
    at <anonymous>:1:26

Literally the documentation on refresh seems to indicate this is exactly what the ("refresh") method is for:

refresh()

Refresh the sortable items. Triggers the reloading of all sortable items, causing new items to be recognized. This method does not accept any arguments. Code examples: Invoke the refresh method:

$( ".selector" ).sortable( "refresh" );

A simple-stupid solution I found ( Refresh list after adding an item with jQuery UI sortable plugin ) mentions just executing the entire sortable initializer again. Surely this is the wrong way to do this. I want to do this the correct way.

Others call destroy on the original, then rebuild it again. Again, doesn't seem like the right way.

EDIT / UPDATE Reproducible Example

let sectionId = 1;
let lessonId = 1;

$('.sortable-sections').sortable();
$('.sortable-lessons').sortable({
  connectWith: ".sortable-lessons",
  items: "li",
});

function addSection(id) {
  const item = ''  
    '<li  id="'   id   '">'  
      '<span>section '   id   ' <button >Add lesson</button></span>'  
      '<ul ></ul>'  
    '</li>';
  $('.sortable-sections').append(item);
}

$('body').on('click', '#add-section', function () {
  addSection(sectionId);
  sectionId =1;
});

function addLesson(sectionId, lessonId) {
  const section = $('#'   sectionId);
  const item = '<li ><div >lesson '   lessonId   '</div></li>';
  section.find('ul.sortable-lessons').append(item);
  $('.sortable-lessons').sortable('refresh');
}

$('body').on('click', '.add-lesson', function () {
  const section_id = $(this).closest('.section').attr('id');
  addLesson(section_id, lessonId);
  lessonId =1;
});
.border {
  border: 1px solid black;
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <div>
    <button id="add-section">Add Section</button>
    <ul class="sortable-sections">
    </ul>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
</body>
</html>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

But that example is starting from scratch when NO sections or lessons exist.

Here's an example when SOME sections and lessons exist.

  1. Add a new section
  2. Try to add a new lesson in that section

let sectionId = 7;
let lessonId = 12;

$('.sortable-sections').sortable();
$('.sortable-lessons').sortable({
  connectWith: ".sortable-lessons",
  items: "li",
});

function initializeSortableLessons() {
  $('.sortable-lessons').sortable({
    connectWith: ".sortable-lessons",
    items: "li",
  });
}


function addSection(id) {
  const item = ''  
    '<li  id="'   id   '">'  
      '<span>section '   id   ' <button >Add lesson</button></span>'  
      '<ul ></ul>'  
    '</li>';
  $('.sortable-sections').append(item);
}

$('body').on('click', '#add-section', function () {
  addSection(sectionId);
  sectionId =1;
});

function addLesson(sectionId, lessonId) {
  const section = $('#'   sectionId);
  const item = '<li ><div style="border: 1px solid black;">lesson '   lessonId   '</div></li>';
  section.find('ul.sortable-lessons').append(item);
  $('.sortable-lessons').sortable('refresh');
}

$('body').on('click', '.add-lesson', function () {
  const section_id = $(this).closest('.section').attr('id');
  addLesson(section_id, lessonId);
  lessonId =1;
});
.border {
        border: 1px solid black;
    }
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

  <div>
    <button id="add-section">Add Section</button>
    <ul class="sortable-sections ui-sortable">
      <li class="section" id="1"><span>section 1 <button class="add-lesson">Add lesson</button></span><ul class="sortable-lessons"></ul></li><li class="section" id="2"><span>section 2 <button class="add-lesson">Add lesson</button></span><ul class="sortable-lessons"><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li></ul></li><li class="section" id="3"><span>section 3 <button class="add-lesson">Add lesson</button></span><ul class="sortable-lessons"></ul></li><li class="section" id="4"><span>section 4 <button class="add-lesson">Add lesson</button></span><ul class="sortable-lessons"><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li></ul></li><li class="section" id="5" style=""><span>section 5 <button class="add-lesson">Add lesson</button></span><ul class="sortable-lessons"><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li></ul></li><li class="section" id="6" style=""><span>section 6 <button class="add-lesson">Add lesson</button></span><ul class="sortable-lessons"><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li><li class="lesson"><div style="border: 1px solid black;">lesson 1</div></li></ul></li></ul>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
</body>
</html>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

Consider the following. You must initialize the new <ul> as Sortable. That way when you add the <li> and call refresh it is already initialized as a Sortable.

$(function() {

  function addLesson(event) {
    var section = $(this).closest("li");
    var item = $("<li>", {
      class: "lesson"
    });
    $("<div>").css("border", "1px solid black").html("Lesson "   ($(".lesson").length   1)).appendTo(item);
    $('ul.sortable-lessons', section).append(item).sortable('refresh');
  }

  function addSection(props, target) {
    var item = $("<li>", props);
    $("<span>").html("Section "   props.id   " ").appendTo(item);
    $("<button>", {
      class: "add-lesson"
    }).html("Add Lesson").appendTo($("span", item));
    $("<ul>", {
      class: "sortable-lessons"
    }).appendTo(item).sortable({
      connectWith: ".sortable-lessons"
    });
    if (target === undefined) {
      item.appendTo($(".sortable-sections"));
      $(".sortable-sections").sortable("refresh");
    } else {
      item.appendTo(target);
      if ($(target).hasClass("ui-sortable")) {
        $(target).sortable("refresh");
      }
    }
  }

  $('.sortable-sections').sortable();
  $('.sortable-lessons').sortable({
    connectWith: ".sortable-lessons"
  });

  $('body').on('click', '#add-section', function() {
    addSection({
      id: $(".section").length   1
    });
  });

  $('body').on('click', '.add-lesson', addLesson);
});
.border {
  border: 1px solid black;
}
<div>
  <button id="add-section">Add Section</button>
  <ul class="sortable-sections ui-sortable">
    <li class="section" id="1"><span>section 1 <button class="add-lesson">Add lesson</button></span>
      <ul class="sortable-lessons"></ul>
    </li>
    <li class="section" id="2"><span>section 2 <button class="add-lesson">Add lesson</button></span>
      <ul class="sortable-lessons">
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
      </ul>
    </li>
    <li class="section" id="3"><span>section 3 <button class="add-lesson">Add lesson</button></span>
      <ul class="sortable-lessons"></ul>
    </li>
    <li class="section" id="4"><span>section 4 <button class="add-lesson">Add lesson</button></span>
      <ul class="sortable-lessons">
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
      </ul>
    </li>
    <li class="section" id="5" style=""><span>section 5 <button class="add-lesson">Add lesson</button></span>
      <ul class="sortable-lessons">
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
      </ul>
    </li>
    <li class="section" id="6" style=""><span>section 6 <button class="add-lesson">Add lesson</button></span>
      <ul class="sortable-lessons">
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
        <li class="lesson">
          <div style="border: 1px solid black;">lesson 1</div>
        </li>
      </ul>
    </li>
  </ul>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related