I have a contenteditable div
, and I want to split a node around a selection. Using execCommand()
, I can toggle "bold" on or off for a selection, so if I have:
<b>ABCDEFGHI</b>
and select DEF
, toggling "bold" gives me
<b>ABC</b>DEF<b>GHI</b>
where the <b>
node has been split into two <b>
nodes with a text node in between.
I want to be able to do the same with other elements not supported by execCommand()
, for example <bdi>
. In other words, if I start with
<bdi>ABCDEFGHI</bdi>
and select DEF
, I want to end up with
<bdi>ABC</bdi>DEF<bdi>GHI</bdi>
I can test if the selection is contained in a surrounding <bdi>
tag using range.commonAncestorContainer()
and if not wrap the range in a <bdi>
tag. However, what I want is the opposite: if there is an enclosing <bdi>
node, I want to split it into (a) a well-formed <bdi>
node before the selection, (b) a well-formed selection with no enclosing <bdi>
, and (c) another well-formed <bdi>
node after the selection, and then reassemble them. How can I do this?
EDIT: it seems that everyone believes I am trying to wrap a selection, but I’m not. Sergey’s response below shows how to wrap some plain text, but I want something else.
Trying for a minimal reproducible example, consider the following:
<html>
<head></head>
<body>
<b>This text is <i>marked as
bold
with</i> some italic text too.</b>
</body>
</html>
Now what I want is to UNMARK the text "bold" so that the final result is:
<html>
<head></head>
<body>
<b>This text is <i>marked as</i></b>
<i>bold</i>
<b><i>with</i> some italic text too.</b>
</body>
</html>
Note that the text includes <i>
…</i>
, which must also be split. This is trivially easy with execCommand()
, but I can’t figure out how to do it without execCommand()
(and hence do it for tags like <bdi>
as well). I’m looking for a vanilla JS solutsion, not jQuery or Rangy please.
2
Answers
You never even need
document.execCommand()
. This function is deprecated, by the way.Please consider this code sample:
This code snipped is greatly simplified, to keep it short. It wraps a fragment in bold
<b></b>
on the key gestureCtrl+Y
. I made the modified text bigger, by CSS stylingb
to make the effect more visible.The implementation is not complete, for the sake of simplicity. It does not work correctly across elements. To modify the DOM in the second line, where I have
<bdi>45678</bdi>
, you can select the text only inside the fragment45678
(colored, for the clearer demo), or only outside it. Also, the operation doesn’t work correctly if your selection combines both<p>
elements. I did not care about those cases just to keep this demo as simple as possible.You may want to refine it by processing all selection ranges and all nodes inside the selection, not just one text-only node in just one range, as in my example.
Added:
As the inquirer wasn’t satisfied with this simplified sample, I added a different, a bit more complicated one. It splits the inline element in two.
This more complicated code also needs further development. It does work as it is, but for any practical purpose, you really need to classify the types of
parent
and apply different processing for different types. For example, I demonstrate splitting<p>
into two separate paragraphs, but the code adds another<span>
, and this is required only for an inline element. What to do, depends on your purpose. Besides, if you repeat the same operations with different overlapping ranges, it will complicate and mess up HTML structure. Ideally, you need to analyze this structure and simplify it, and this is not so simple. I would suggest that the entire idea of your requirements is not the best design, so you better think of something more robust. But this is up to you.Usage
In the example below, select a tag using
<select id="tags">
:<mark>
✳<b>
<i>
<u>
<q>
<code>
✳ In addition, there’s a color picker
<input id="color">
that can allow you to assign different background color for each<mark>
.Next, select text within
<fieldset id="edit">
while holding down the ctrl key.To remove any tag just click the text while holding the alt key down.
If a selection intersects with a tag they will be split, but if a selection is within a tag completely it will be a nested tag. See Fig. 1 and Fig. 2.
Fig. 1 – Intersected tags will split.
Fig. 2 – A tag completely within another will be nested.
Details are commented in the example.
Example