Equivalent of selectionStart, selectionEnd, and focus() for contenteditable spans and divs - javascript

I am trying to set a contenteditable span so that it will on the onkeyup event refill the span with formatted text.
However, when I try this the cursor disappears and I can't type anymore. This is because the formatting program, instead of modifying the text already in the span, erases it all and then writes the formatted text in its place.
It is important (I think it is) to note that the element does not appear to be losing focus. Also, because I would like for this project to remain fairly "secret" until it's release, I would rather not give away the source code right now.

This is probably due to you using the innerHTML to set the formatted text.
Instead use childNodes collection to access the content and replace textNodes with formatted html element. This avoids setting innerHTML and avoids loosing focus.

Related

Why can't I insertAdjecentHTML into a textarea?

I can't successfully use
document.querySelector('textarea').insertAdjacentHTML('beforeend', '<span style="color:red">Danger</span>')
<textarea>TEXT</textarea>
in my code. Chrome inserts the text escaped, FF doesn't show the node. Is there any documentation about this behavior?
Update: Beside the type on the function name it doesn't work (which is expected) because <textarea> only supports text content as specified in the HTML5 Standard (see "Content model: Text").
The method insertAdjacentHTML is inherited from HTMLElement -> Element.
Sidenote: Chrome and Firefox behave different if your first create an element and then append it (append but don't show) instead of showing the html as text.
The child nodes of a textarea:
Can only be text nodes. Element descendants are forbidden.
Represent the default value of the textarea, not the current value. Manipulating it is not guaranteed to update the current value.
If you want to change the value of a textarea, use the value property.
document.querySelector('textarea').value += '<span style="color:red">Danger</span>';
If you want a formatted and editable control, then don't use a textarea. See making content editable.

Remove direct parent node of selection (<b>)

I would like to remove and add the <b> tag to a selected text just by clicking on it.
I can already enclose selected text in the <b> tag via:
if (window.getSelection)
{
selection = getSelection();
var newNode = document.createElement("b");
range.surroundContents(newNode);
}
Now that time I would like to have a method to remove the <b> tag I added beforehand, but the commonly shown method of using range.commonAncestorContainer would give me lowest parent of the <div> type.
As a part of debugging I simply modified the code I used to add the <b> tag (to make sure I had a tag, I also first added it) to remove the parent node.
if (window.getSelection) {
selection = getSelection();
var newNode = document.createElement("b");
range.surroundContents(newNode);
node = range.commonAncestorContainer;
node.remove();
}
This will remove the parent <div> of the newly added <b> tag, instead of only the <b> tag itself.
For reference, here is the HTML:
<div contenteditable="true" id="input" class="inputblock" onclick="getClick()" onkeypress="changeNodeType(event)" data-text="Please paste your text here."></div>
The <b> tag will be created within text pasted into the shown <div>.
The fact that it also removes the content can be ignored. I'll find a way to work with that.
I unfortunately don't have as much time as I'd like to spend to write out a full code solution, but this is how I've solved this in the past:
Normalize your range. This is the hardest part. IE, edge, and different browsers behave differently so you will need to handle a lot of special edge cases. For example if the range is right before an empty text node, IE will do wonky things
For your the start and endcontainer, if they are text nodes, do nothing
Otherwise, you need to use a NodeIterator to do an in-order traversal and walk to the next node for the start container, and the previous node for the end container. This solves the case where, when you select something like <b>hello</b>, the selection can either be |<b>hello</b>| or <b>|hello|</b>. By normalizing and walking down the tree, you will end up with a - mostly equivalent - selection of <b>|hello|</b> in both cases. It's hard to explain this without pictures, so I would encourage you to draw out the DOM tree and do a walk to the next innermost child of the start container and the previous innermost child of the end container.
Once you have nestled your way down, you can start to walk UP the tree to find a bold tag. This is simply node.contains('b');
Do that for both the start and end node, then see if you're pointing to the same bold tag, and that the text content is the same as your selection.
If so, you have found your bold tag to nuke.
It's a non-trivial amount of code, and it requires a bunch of testing due really wonky edge cases with setting the selection and range in browsers. I have fought contenteditable for a long time :-) and I wish you the best of luck

How to avoid breaking block level HTML elements from partial selection?

The problem in a nutshell is: given a wysiwyg editor (CKEditor) you want to make a plugin doing text transformation - select a piece of text and manipulate the text in it (eg uppercase). Example:
this is paragraph one
this is paragraph two
If bold represents your selection the result would be
this is paragraph ONE
THIS is paragraph two
This issue here is the selection will be a complete HTML fragment, even when a selection is no containing the full tag. The selected HTML is:
<p>one</p> <p>this</p>
Notice the first and last <p> tags. When you do your dom traverse in the selection html, apply the text transformation and replace the html it will use those partial tags, so your result become:
this is paragraph
ONE
THIS
is paragraph two
I checked if it's possible to "merge" the first and last partial tags with their dom parents, however the selection object is isolated, it doesn't have siblings or parents from it's original context.
Also tried to find an option to retrieve the selection without these auto-fixed tags, but no luck.
On the CKEditor documentation they mention a walker object - however that automatically expands from the selection to the full enclosing tag, which means the selection is only used as a minimum boundary.
Also because the selection object is isolated, it's not possible to just change the dom node text values there - the original dom fragment needs to be replaced (at least in case of CKEditor).
I tried not to stick with the CKEditor api as much as possible, however at this point I don't see any alternatives either. Is this is really a hard problem or I'm missing something?
One solution is to use the browser engine to mark the selected area with a tag (afaik that's a native operation). This is the same as you make your selection bold or italic - however here it's gonna be a temporary wrapper. Then you walk the DOM and replace the content in the temporary tags - and finally remove the tag (keeping the content).
This makes sure you can apply your transformation on the exact selection and by removing the tag you won't break the original DOM. Steps in a nutshell:
apply tag on selection (use the browser or wysiwyg api)
find all temp tags:
recursively walk the children of the tag
if tag is text node then apply transformation
otherwise recursive walk
collect tag's left sibling text node + tag's html + right sibling text node
replace tag's parent html with the previous compilation (=remove temp tag)
Not too elegant however works. This is inspired by #Andrew_Mast's idea about wrapping in a span.
I would loop through all of the word(s) and for each set of words inside a different tag, surround it with <span style="text-transform: uppercase;"> and </span>

Massive replacing in a contentEditable DIV

I've been searching about this a few days, but everything I found is with a selected text. I need to "bold" all the words within a contendEditable DIV that matches with "test".
I've been messing around getting the innerText from the DIV and change those words with the same with bold, but the cursor gets lost and when inerting again the changes with innerHTML, the cursor goes at the beginning. So I started searching doing those changes directly on the DIV, and the only thing I found its the execCommand, but at far as I know, you must select text first, and do the change, one by one. I would like to read all the DIV and make changes on the fly, is this posible? Is there a way to combine execCommand with regex?
Please, give me some light! :)
If you want to bold the words inside a contenteditable div, that is equivalent to placing <strong> tags around the given words.
So, I believe your problem could be solved with first defining a RegExp, with a 'g' flag for global
var boldText = "test"
var testRegEx = new RegExp(boldText, "g")
and then, where $div is the JQuery object representing your collection of divs (or the one) whose contents you want to modify:
$div.html($div.html().replace(textRegEx,"<strong>" + boldText + "</strong>"))

jQuery.text() doesn't work with textarea elements

Any reason why jQuery('textarea').text() always returns default value instead of current text when the text area actually has some text and jQuery('textarea')[0].value does return the text?
Take a look at the simple example to see the problem.
Entering a value in an input element (textarea being one of them) doesn't change the markup. text() only grabs the text content of the markup. You should use val() instead:
jQuery('textarea').val()
The jquery way to get the text would be:
jQuery('textarea').val();

Categories