Как избежать удаления набранного текста из contenteditable <p>в jQuery - PullRequest
4 голосов
/ 23 октября 2019

Я использую перетаскиваемый компонент jQuery UI, чтобы добавить <span> к редактируемому контенту <p>.

Ожидаемый результат был, абзац <p> должен быть редактируемым, а перетаскиваемый компонент должен иметь возможность перетаскивать ипереход к абзацу, а также содержимое <p> должно быть доступно для редактирования. У меня проблемы с моим кодом. Когда я набираю что-то внутри <p> и нажимаю снаружи <p>. удаление напечатанных слов из абзаца.

Мой код следующий:

$(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>

Проблема возникает с перебоями.

1 Ответ

3 голосов
/ 25 октября 2019

Иногда введенный пользователем текст снова удаляется, и причина кроется в функции unChunkWords:

Эта функция выполняет итерацию только над элементами (с классом "w"),но он не перебирает узлы простого текста, которые могут возникать между этими элементами. А в элементе, редактируемом содержимым, пользователь действительно может печатать текст в областях между элементами. И поэтому этот цикл в unChunkWords никогда не будет посещать такой текст, опуская его в массиве, который он возвращает.

Вы можете заставить это произойти, поместив курсор в конец слова, перед пробелом, затемнажмите клавишу со стрелкой вправо. Либо это перемещает курсор к началу следующего слова, либо он не перемещается визуально (он просто переместился из span, в котором он находился). В любом случае, ваш курсор теперь находится в текстовом узле, который разделяет два слова. Введите что-нибудь и нажмите где-нибудь еще. ... случается аномалия.

Есть много способов обойти это. Одним из них является использование метода jQuery contents(), который также собирает текстовые узлы. Измените следующий код:

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

... на этот:

$(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());
  }
});

Теперь введенный вами текст не будет опущен из words, даже если вы введете его в текстузлы, которые являются прямыми потомками редактируемого содержимого элемента.

Добавление пробелов

Ваш код добавляет пробелы с помощью .join(" ") без проверки того, что фрагменты текста действительно разделены пробелом в содержимомэлемента p. Итак, я бы просто взял весь контент, включая интервалы, и просто объединял это. Таким образом, вы будете иметь разделение слов в точности так, как они есть в элементе p.

Итак, ваша функция будет:

  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();
  }

Отказ от ответственности: IЯ только посмотрел на эту конкретную проблему, указав, почему у вас такое конкретное поведение и как ее исправить. Это не означает, что теперь ваш код будет работать корректно во всех его предполагаемых функциональных возможностях, что выходит за рамки вопроса.

...