How to avoid removing typed text from contenteditable <p> in jQuery

拟墨画扇 提交于 2019-12-10 16:02:00

问题


I'm using jQuery UI draggable component to add <span> to content editable <p>.

The expected output was, paragraph <p> should be editable and the draggable component should able to drag and drop to paragraph and also the content of the <p> should able to be editable. I have problems with my code. When I type something inside <p> and click the outside <p>. typed words removing from the paragraph.

My code as follows:

$(function() {
  function textWrapper(str, sp, btn) {
    if (sp == undefined) {
      sp = [0, 0];
    }
    var txt = "";
    if (btn) {
      txt = "<span class='w b'>" + str + "</span>";
    } else {
      txt = "<span class='w'>" + str + "</span>";
    }

    if (sp[0]) {
      txt = "&nbsp;" + txt;
    }

    if (sp[1]) {
      txt = txt + "&nbsp;";
    }

    return txt;
  }

  function chunkWords(p) {
    var words = p.split(" ");
    words[0] = textWrapper(words[0], [0, 1]);
    var i;
    for (i = 1; i < words.length; i++) {
      var re = /\[.+\]/;
      if (re.test(words[i])) {
        var b = makeTextBox(words[i].slice(1, -1));
        words[i] = "&nbsp;" + b.prop("outerHTML") + "&nbsp;";
      } else {
        if (words[0].indexOf(".")) {
          words[i] = textWrapper(words[i], [1, 0]);
        } else {
          words[i] = textWrapper(words[i], [1, 1]);
        }
      }
    }
    return words.join("");
  }

  function unChunkWords(tObj) {
    var words = [];
    $(".w", tObj).each(function(i, el) {
      console.log($(el).text(), $(el).attr("class"));
      if ($(el).hasClass("b")) {
        words.push("[" + $(el).text().trim() + "]");
      } else {
        words.push($(el).text().trim());
      }
    });
    return words.join(" ");
  }

  function makeBtn(tObj) {
    var btn = $("<span>", {
      class: "ui-icon ui-icon-close"
    }).appendTo(tObj);
  }

  function makeTextBox(txt) {
    var sp = $("<span>", {
      class: "w b"
    }).html(txt);
    makeBtn(sp);
    return sp;
  }

  function makeDropText(obj) {
    return obj.droppable({
      drop: function(e, ui) {
        var txt = ui.draggable.text();
        var newSpan = textWrapper(txt, [1, 0], 1);
        $(this).after(newSpan);
        makeBtn($(this).next("span.w"));
        makeDropText($(this).next("span.w"));
        $("span.w.ui-state-highlight").removeClass("ui-state-highlight");
      },
      over: function(e, ui) {
        $(this).add($(this).next("span.w")).addClass("ui-state-highlight");
      },
      out: function() {
        $(this).add($(this).next("span.w")).removeClass("ui-state-highlight");
      }
    });
  }

  $("p.given").html(chunkWords($("p.given").text()));

  $("p.given").on("click", ".b > .ui-icon", function() {
    $(this).parent().remove();
  });

  $("p.given").blur(function() {
    var w = unChunkWords($(this));
    console.log(w);
    $(this).html(chunkWords(w));
    makeDropText($("p.given span.w"));
  });

  $("span.given").draggable({
    helper: "clone",
    revert: "invalid"
  });

  makeDropText($("p.given span.w"));
});
p.given {
  display: flex;
  flex-wrap: wrap;
}

p.given span.w span.ui-icon {
  cursor: pointer;
}

div.blanks {
  display: inline-block;
  min-width: 50px;
  border-bottom: 2px solid #000000;
  color: #000000;
}

div.blanks.ui-droppable-active {
  min-height: 20px;
}

span.answers>b {
  border-bottom: 2px solid #000000;
}

span.given {
  margin: 5px;
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div class="row">
  <p class="given" contenteditable="true">Lorem Ipsum is simply dummy text of the printing and typesetting industry. [Lorem] Ipsum has been the industry's standard dummy text ever since the 1500s, Lorem Ipsum is simply dummy text of the printing and typesetting industry.</p>
</div>

<div class="divider"></div>
<div class="section">
  <section>
    <div class="card blue-grey ">
      <div class="card-content white-text">
        <div class="row">
          <div class="col s12">
            <span class="given btn-flat white-text red lighten-1" rel="1">the Santee, thDakota</span>
            <span class="given btn-flat white-text red lighten-1" rel="2">America</span>
            <span class="given btn-flat white-text red lighten-1" rel="3">Qatar</span>
            <span class="given btn-flat white-text red lighten-1" rel="4">Philippines</span>
          </div>
        </div>
      </div>
    </div>
  </section>
</div>

The problem occurs intermittently.


回答1:


Sometimes the user-entered text is removed again, and the cause is in the function unChunkWords:

This function iterates only over elements (with class "w"), but it does not iterate over the plain text nodes that may occur in-between those elements. And in a content-editable element, the user can indeed type text in areas between elements. And so this loop in unChunkWords will never visit such text, omitting it in the array it returns.

You can force it to happen by putting the cursor at the end of a word, before a space, then press the right arrow key. Either this moves the cursor to the start of the next word, or it does not move visibly (it just moved out of the span it was in). Either way, your cursor is now in the text node that separates the two words. Type something and click somewhere else. ... the anomaly happens.

There are many ways to circumvent this. One of them is to use the jQuery contents() method, which also collects text nodes. Change the following code:

$(".w", tObj).each(function(i, el) {
  if ($(el).hasClass("b")) {
    words.push("[" + $(el).text().trim() + "]");
  } else {
    words.push($(el).text().trim());
  }
});

...to this:

$(tObj).contents().each(function (i, el) {
  if (el.nodeType !== 3 && !$(el).is(".w")) return; // Only regard ".w" or text nodes
  if ($(el).hasClass("b")) {
    words.push("[" + $(el).text().trim() + "]");
  } else {
    words.push($(el).text().trim());
  }
});

Now the text you enter will not be omitted from words, even when you type it in text nodes that are direct children of the content-editable element.

Adding spaces

Your code is adding spaces with .join(" ") without verifying that the text fragments are really separated by white space in the content of the p element. So, I would just grab all content, including spacing and just concatenate that. That way you will have the word separations exactly as they are in the p element.

So then your function would be:

  function unChunkWords(tObj) {
    var words = "";
    $(tObj).contents().each(function (i, el) {
      if ($(el).hasClass("b")) {
        words += "[" + $(el).text() + "]";
      } else {
        words += $(el).text();
      }
    });
    return words.replace(/\s+/g, " ").trim();
  }

Disclaimer: I have only looked at this particular problem, pointing out why you have this particular behaviour, and how to fix it. This does not mean that now your code will work correctly in all its intended functionality, which would go beyond the scope of the question.



来源:https://stackoverflow.com/questions/58527619/how-to-avoid-removing-typed-text-from-contenteditable-p-in-jquery

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!