I'm using jQuery UI draggable component to add to content editable
.This code works to find and I have a little issue. The problem is I cannot drag and drop draggable component as first word, How can I solve this? Please help me to solve this.
Note : the current functionlity cannot be break.
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 = " " + txt;
}
if (sp[1]) {
txt = txt + " ";
}
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] = " " + b.prop("outerHTML") + " ";
} 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 = "";
$(tObj).contents().each(function (i, el) {
if ($(el).hasClass("b")) {
words += "[" + $(el).text() + "]";
} else {
words += $(el).text();
}
});
return words.replace(/\s+/g, " ").trim();
}
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>
You could do the following:
While the dragging is ongoing:
This visual indication will thus indicate whether the word will be inserted after the first word (2 are highlighted) or before it (1 is highlighted).
When the user drops the element:
So, you need two changes in your code to make this work:
Add the following code for implementing the first part:
document.addEventListener("mousemove", function() {
var $draggable = $(".ui-draggable-dragging");
if (!$draggable.length) return; // nothing is being dragged
var $highlighted = $(".ui-state-highlight");
if (!$highlighted.length || $($highlighted).index() > 0) return; // first word is not highlighted
// Get center x coordinate of the item that is being dragged
var dragX = $draggable.offset().left + $draggable.width() / 2;
// Get center x coordinate of the first word in the paragraph
var firstX = $highlighted.offset().left + $highlighted.width() / 2;
// If draggable is more on the left side of the first word, then only the first word should be highlighted
if ((dragX < firstX) === ($highlighted.length < 2)) return; // Situation is as it should be
// Toggle the highlight on the second word of the paragraph
$highlighted.first().next("span.w").toggleClass("ui-state-highlight");
});
Modify the drop
handler for implementing the second part:
drop: function(e, ui) {
var txt = ui.draggable.text();
// Use proper jQuery to create a new span element
var newSpan = $("<span>").addClass('w b').text(txt);
// Determine if the element is being dropped on the first word, and only that one
if (!$(".ui-state-highlight").last().index()) {
$(this).before(newSpan, " "); // ...then prepend
} else {
$(this).after(" ", newSpan); // normal case
}
makeBtn(newSpan);
makeDropText(newSpan);
$("span.w.ui-state-highlight").removeClass("ui-state-highlight");
},
chunkWords
For some reason you had hard-coded in chunkWords
that the first word (at index 0) could not be a dropped item -- it is always interpreted as plain text (marked as bold). This is a bug in your current code also, and you can reproduce it as follows:
You wrote that existing behaviour should not break, but from comments I understand you did not expect this behaviour.
Another issue is the following condition in that function:
if (words[0].indexOf(".")) {
...think about that: why would a point in the first word influence the spacing of another word in your phrase? Surely you intended to do:
if (words[i].indexOf(".")) {
Anyway, I would suggest to not use textWrapper
at all, and add the spaces in your final join
. Not before.
Fixing those two issues, that chunkWords
should finally look like this:
function chunkWords(p) {
var words = p.split(" "), b;
for (var i = 0; i < words.length; i++) {
if (/\[.+\]/.test(words[i])) {
b = makeTextBox(words[i].slice(1, -1));
} else {
b = $("<span>").addClass("w").text(words[i]);
}
// do not pad the value with " " at this moment:
words[i] = b.prop("outerHTML");
}
return words.join(" "); // add the spaces here
}
As with these changes textWrapper
is no longer used, you can remove that function.
See the applied changes in this fiddle