Search code examples
javascriptjqueryhtmlnested-sortable

<li> being cloned with jquery sortable onDrop event


Attention: Don't confuse jQuery Sortable with jQuery UI Sortable

I'm using jQuery Sortable to console.log() every <li> of every <ul> on the page, but when i rearrange a <li>, the console display the element twice

This is how the list look like on the page:

html-list-issue Everything OK here!

This is how the list look like on the console:

html-console-issue Here lies the problem!

HTML code:

<div id="cmd_container">
        <button class="w3-btn w3-blue" onclick="inclib()">Comando de linha única</button>
        <button class="w3-btn w3-blue" onclick="conditional()">Comando de multiplas linhas</button> 
        <button class="w3-btn w3-light-grey" onclick="serialize()">Serializar lista</button>
</div>

<!-- The list -->
<p>Código padrão:</p>
<ul id="lista"></ul>

JS code:

$("#lista").sortable();

function serialize() {
    console.clear();
    $.each($("li"), function() {
        console.log($(this).text());
    });
}

// SINGLE LINE COMMAND (DEMO 1)
function inclib() {
    var biblioteca = prompt("Digite o nome da biblioteca:");
    if (biblioteca == "" || biblioteca == null) {
        alert("Digite o nome do comando!");
    } else {
        var html = "" +
            "<li>" + biblioteca + "</li>\n"
        $("#lista").append(html);
    }
}

// MULTIPLE LINES COMMAND (DEMO 2)
function conditional() {
    var condicional = prompt("Digite a expressão conditional:");
    if (condicional == "" || condicional == null) {
        alert("Digite o nome do comando!");
    } else {
        var html = "" +
            "<li>" +
                condicional + " {\n" +
                "<ul></ul>" +
                "\n}" +
            "</li>"
        $("#lista").append(html);
    }
}

PS: I've already included all the necessary libraries on the original code:

  • jQuery 3.3.1
  • jQuery Sortable

Solution

  • The problem is that you are trying to get all li items on the document, if you add two with inclib with text: Command 1 and Command 2 then you will have the following html:

    <ul id="lista">
      <li>Command 1<li>
      <li>Command 2</li>
    </ul>
    

    The you add another using conditional with text: Command 3 then you will have the following html:

    <ul id="lista">
      <li>Command 1<li>
      <li>Command 2</li>
      <li>Command 3 {
          <ul></li>
      }
      </li>
    </ul>
    

    If you move Command 1 or Command 2 inside Command 3, then you will have the following html:

    <ul id="lista">
      <li>Command 2</li>
      <li>Command 3 {
          <ul>
              <li>Command 1<li>
          </li>
      }
      </li>
    </ul>
    

    When you select all the li items on your document, using $("li"), you will get 3 items, but (in the case i described) the one with a nested ul will return all its content (including the content of the nested li) if you get its text using the jQuery .text() function, so you will get the following output:

    Command 2
    Command 3 {
        Command 1
    }
    Command 1
    

    So, if you want to get a text representation of the sortable ul, you should only get the first level li items, using the following selector: #lista > li.

    Check my working example. Hope it helps.

    function serialize() {
    			console.clear();
    			$.each($("#lista > li"), function() {
    				console.log($(this).text());
    			});
          var data = $('#lista').sortable("serialize").get();
            console.log(JSON.stringify(data, null, ' '));
    		}
    		
    		// SINGLE LINE COMMAND (DEMO 1)
    		function inclib() {
    			var biblioteca = prompt("Digite o nome da biblioteca:");
    			if (biblioteca == "" || biblioteca == null) {
    				alert("Digite o nome do comando!");
    			} else {
    				var html = "" +
    					"<li>" + biblioteca + "</li>\n"
    				$("#lista").append(html);
    			}
    			$('#lista').sortable();
    		}
    		
    		// MULTIPLE LINES COMMAND (DEMO 2)
    		function conditional() {
    			var condicional = prompt("Digite a expressão conditional:");
    			if (condicional == "" || condicional == null) {
    				alert("Digite o nome do comando!");
    			} else {
    				var html = "" +
    					"<li>" +
    					condicional + " {\n" +
    					"<ul></ul>" +
    					"\n}" +
    					"</li>"
    				$("#lista").append(html);
    			}
    			$('#lista').sortable();
    		}
    		
    		$(document).ready(function() {
    			$("#lista").sortable();
    		});
    <div id="cmd_container">
    		<button class="w3-btn w3-blue" onclick="inclib()">Comando de linha única</button>
    		<button class="w3-btn w3-blue" onclick="conditional()">Comando de multiplas linhas</button>
    		<button class="w3-btn w3-light-grey" onclick="serialize()">Serializar lista</button>
    	</div>
    	
    	
    	<p>Código padrão:</p>
    	<ul id="lista"></ul>
    	
    	<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    	<script src="https://johnny.github.io/jquery-sortable/js/jquery-sortable-min.js"></script>

    Edit According to the docs you should get a serialized version of the sortable using: $("#lista").sortable("serialize") (i added an example to the snippet).