Reputation: 7004
I am trying to figure out how twitter does the usergroup and hashtag implementation using a contenteditable
element. When the user enters @
or #
the text with those special characters is wrapped with a link.
In my example, I can wrap the text with a link, but there is a bug that I have not yet resolved. When the text with @
is wrapped with link, the cursor is moved to the start of the <div>
.
I don't know what I am missing, so I need your help please! And if someone can explain me why this is happening, it will be welcomed.
Upvotes: 2
Views: 754
Reputation: 16989
Welcome to the painful world of contenteditable
! I've discovered issues like this present themselves constantly as the solution becomes more complex- especially if you concern yourself with achieving consistent cross-browser behavior.
I see you query for the markup you wish to insert in your <div>
with the value found on valueOfQuery
. Since you are just calling .html()
and injecting this over top your previous markup, your cursor has no idea where to place itself at this point.
You'll need to explore and become familiar with the Range API, and in this example, we are saving our current selection before calling .html()
then restoring it afterwards using these two functions - saveSelection
and restoreSelection
var saveSelection, restoreSelection;
if (window.getSelection && document.createRange) {
saveSelection = function(containerEl) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
};
restoreSelection = function(containerEl, savedSel) {
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
} else if (document.selection) {
saveSelection = function(containerEl) {
var selectedTextRange = document.selection.createRange();
var preSelectionTextRange = document.body.createTextRange();
preSelectionTextRange.moveToElementText(containerEl);
preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
var start = preSelectionTextRange.text.length;
return {
start: start,
end: start + selectedTextRange.text.length
}
};
restoreSelection = function(containerEl, savedSel) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(containerEl);
textRange.collapse(true);
textRange.moveEnd("character", savedSel.end);
textRange.moveStart("character", savedSel.start);
textRange.select();
};
}
We then need to modify our current implementation within keyup
as follows
var selection = saveSelection($('#result')[0]); // save cursor pos
$('#result').html(valueOfQuery);
restoreSelection($('#result')[0], selection); // restore cursor pos
JSFiddle Link - working example
Upvotes: 2