getSelection().focusNode inside a specific id doesn't work - javascript

I have code to bold/unbold scripts via Window.getSelection()
I got it from this answer: Bold/unbold selected text using Window.getSelection()
It really works without problems .
But when I looked for another code to make the selection inside a div specified with id
I found this answer :
How to getSelection() within a specific div?
When I try to combine the first answer with the second answer
I found it doing bold and not unbold
My Code :
function addBold(){
/*
answer 1
https://stackoverflow.com/questions/63364212/bold-unbold-selected-text-using-window-getselection
answer 2
https://stackoverflow.com/questions/38758713/how-to-getselection-within-a-specific-div
*/
// answer 2
if(window.getSelection().baseNode.parentNode.id != "editor") return;
// end answer 2
// answer 1
const selection = window.getSelection().getRangeAt(0);
let selectedParent = selection.commonAncestorContainer.parentElement;
let mainParent = selectedParent;
if(selectedParent.closest("b"))
{
//Unbold
var text = document.createTextNode(selectedParent.textContent);
mainParent = selectedParent.parentElement;
mainParent.insertBefore(text, selectedParent);
mainParent.removeChild(selectedParent);
mainParent.normalize();
}
else
{
const span = document.createElement("b");
span.appendChild(selection.extractContents());
selection.insertNode(span);
mainParent.normalize();
}
//selection is set to body after clicking button for some reason
//https://stackoverflow.com/questions/3169786/clear-text-selection-with-javascript
if (window.getSelection) {
if (window.getSelection().empty) { // Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) { // Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) { // IE?
document.selection.empty();
}
// end answer 1
};
<div id="editor" contenteditable="true">
You are the programmers of the future
</div>
<button onclick="addBold()">Bold</button>
Like I said when you combined the two an
swers
Makes bold and ignores unbold
If you delete the first line of the validation condition,
if(window.getSelection().baseNode.parentNode.id
It will work successfully Bold/Unbold

that window.getSelection().baseNode.parentNode.id
When creating bold
The parent element of the text is obtained
and find it
Text
Therefore, he did not reach the main father element who kissed him
So replace the first line with this line and it works successfully
Replace
window.getSelection().baseNode.parentNode.id != "editor"
To
window.getSelection().focusNode.parentElement.closest("#editor").id != "editor"

Related

range.surroundContents in chrome extension is disabling right click

I'm building a chrome extension where selected text can have different highlighting styles applied to it. I used ranges to get this all to work, and I clone the range, put a span around it, and then delete the range and replace it with the cloned one. Everything seems fine except I've somehow managed to disable right clicking by triggering this behavior through the extension. I've narrowed it down the single line of range.surroundContents(span), but here's the full code section:
// Determines the selected text
document.onmouseup = function() {
var selection = document.getSelection();
selection = getSelectedText(color);
};
// Finds the text selected in the page, spans it, and gives it a class
function getSelectedText(inputColor) {
var span = document.createElement('span');
span.setAttribute('class', inputColor);
if(document.getSelection) {
var selection = document.getSelection();
if(selection.rangeCount == true) {
var range = selection.getRangeAt(0).cloneRange();
range.surroundContents(span);
selection.removeAllRanges();
selection.addRange(range);
}
}
}
Is there a way I can counter this? I've already tried using document.oncontextmenu = false directly following the problem line, but that's not bringing back right click. I also tried replacing it with newNode.appendChild(range.extractContents()); range.insertNode(newNode) as recommended by https://developer.mozilla.org/en-US/docs/Web/API/Range/surroundContents but then instead of highlighting text, it seems to just be removing it from the page.
#wOxxOm answered my question in a comment, but a setTimeout() is what worked. So for anyone else who might have a similar issue in the future:
// Finds the text selected in the page, spans it, and gives it a class
function getSelectedText(inputColor) {
var span = document.createElement('span');
span.setAttribute('class', inputColor);
if(document.getSelection) {
var selection = document.getSelection();
if(selection.rangeCount == true) {
var range = selection.getRangeAt(0).cloneRange();
setTimeout(function(){
range.surroundContents(span);
selection.removeAllRanges();
selection.addRange(range);
}, 100)
}
}
}

Wrap an html tag after clicking a toolbar button using js or jquery

I want to do something similar to what this website and wordpress does. When a user highlights text on the screen, then clicks a button on the toolbar it will wrap an html tag around the text. In jquery I would probably use the .wrap class but how would I detect if the user highlighted something.
For example, when the user writes Hello World then clicks on the bold button it will say <b>Hello World</b>
This mainly requires (1) accessing the selectionStart and selectionEnd properties of the input/textarea element and (2) replacing the substring of the value property across that range with the same text, but wrapped in the desired start and end tags. Also, I think it makes sense to reselect the replaced text, which requires a couple of calls to select() and setSelectionRange(). Also, if there's no selection (meaning start equals end) it's probably a good idea to do nothing at all.
window.selWrapBold = function(id) { selWrap(id,'<b>','</b>'); };
window.selWrapItalic = function(id) { selWrap(id,'<i>','</i>'); };
window.selWrap = function(id,startTag,endTag) {
let elem = document.getElementById(id);
let start = elem.selectionStart;
let end = elem.selectionEnd;
let sel = elem.value.substring(start,end);
if (sel==='') return;
let replace = startTag+sel+endTag;
elem.value = elem.value.substring(0,start)+replace+elem.value.substring(end);
elem.select();
elem.setSelectionRange(start,start+replace.length);
} // end selWrap()
<input type="button" value="bold" onclick="selWrapBold('ta1');"/>
<input type="button" value="italic" onclick="selWrapItalic('ta1');"/>
<br/>
<textarea id="ta1"></textarea>
Get the text of the html element which is wrapping the text, then add as html the text embedded in the <b> tag.
See jQuery DOM Manipulation for tutorials.
I used this question to get the selected text. And this question to
get the element with selected text in it. I combined them in a single function.
function updateHighlightedText() {
var text = "";
if (window.getSelection) {
text = window.getSelection().toString();
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
var node = $(window.getSelection().anchorNode.parentNode); //Get the selected node
node.html(node.text().replace(text, "<b>"+text+"</b>")); //Update the node
}

How to getSelection() within a specific div?

I have a contenteditable div (with id 'editor1') that allows users to input text. There is then a function that allows them to color any highlighted text. My js uses window.getSelection().getRangeAt(0), but the issue with this is that they can highlight words outside of the div and their color will change as well. So far; I've tried:
function red(){
{
var getText = document.getElementById("editor1").innerHTML;
var selection = getText.getSelection().getRangeAt(0);
var selectedText = selection.extractContents();
var span = document.createElement("span");
span.style.color = "red";
span.appendChild(selectedText);
selection.insertNode(span);
}
}
Fiddle: https://jsfiddle.net/xacqzhvq/
As you can see, if I highlight "this will become red as well", I can use the button to make that red too.
How can I only color the highlighted text only within the editor1 div?
You are able to get the node element from the selection using .baseNode. From there you can get the parent node and use that for comparison.
function red(){
// If it's not the element with an id of "foo" stop the function and return
if(window.getSelection().baseNode.parentNode.id != "foo") return;
...
// Highlight if it is our div.
}
In the example below I made the div have an id that you can check to make sure it's that element:
Demo
As #z0mBi3 noted, this will work the first time. But may not work for many highlights (if they happen to get cleared). The <span> elements inside the div create a hierarchy where the div is the parent elements of many span elements. The solution to this would be to take traverse up through the ancestors of the node until you find one with the id of "foo".
Luckily you can use jQuery to do that for you by using their .closest() method:
if($(window.getSelection().baseNode).closest("#foo").attr("id") != "foo") return;
Here is an answer with a native JS implemented method of .closest().
Are you looking for this,
//html
<body>
<p id='editor1'>asdf</p>
<button onclick='red()'>
RED
</button>
</body>
//JavaScript
window.red = function(){
//var getText = document.getElementById("editor1").innerHTML;
var selection = window.getSelection().getRangeAt(0);
var selectedText = selection.extractContents();
var span = document.createElement("span");
span.style.color = "red";
span.appendChild(selectedText);
selection.insertNode(span);
}
Plunker: https://plnkr.co/edit/FSFBADoh83Pp93z1JI3g?p=preview
Try This Code :
function addBold(){
if(window.getSelection().focusNode.parentElement.closest("#editor").id != "editor") return;
const selection = window.getSelection().getRangeAt(0);
let selectedParent = selection.commonAncestorContainer.parentElement;
let mainParent = selectedParent;
if(selectedParent.closest("b"))
{
//Unbold
var text = document.createTextNode(selectedParent.textContent);
mainParent = selectedParent.parentElement;
mainParent.insertBefore(text, selectedParent);
mainParent.removeChild(selectedParent);
mainParent.normalize();
}
else
{
const span = document.createElement("b");
span.appendChild(selection.extractContents());
selection.insertNode(span);
mainParent.normalize();
}
if (window.getSelection) {
if (window.getSelection().empty) { // Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) { // Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) { // IE?
document.selection.empty();
}
};
<div id="editor" contenteditable="true">
You are the programmers of the future
</div>
<button onclick="addBold()">Bold</button>
I got the code and added my edits from those following answers :
Bold/unbold selected text using Window.getSelection()
getSelection().focusNode inside a specific id doesn't work

How can set new content for selected item with javascript?

suppose to be we have a paragraph with this content " Hi , It's a new question in stackoverflow!"
and when we are selecting something in this paragraph , it's turn to be Red .for example we selected stackoverflow & then it turn to <span class="red">stackoverflow</span>.how can we do this with Javascript?
here is my codes :
var x = {};
x.getSelected = function() {
var t = '';
if (window.getSelection) {
t = window.getSelection();
} else if (document.getSelection) {
t = document.getSelection();
} else if (document.selection) {
t = document.selection.createRange().text;
}
return t;
}
$(document).ready(function() {
var selectedText;
$(document).bind("mouseup", function() {
selectedText = x.getSelected()
if (selectedText !=''){
alert(selectedText);
//Now I wanna set new content for selected item but not working
a=selectedText;
selectedText.html("<span class='red'>"+a+"</span>");
}
});
});
.red {
color : red;
}
<p>suppose to be we have a paragraph with this content " Hi , It's a new question in stackoverflow!" and when we are selecting something in this paragraph , it's turn to be Red .for example we selected stackoverflow & then it turn to .how can we do this with Javascript? </p>
...when we are selecting something in this paragraph , it's turn to be
Red...
You could have a stab at the styleWithCSS command of the editing API, execCommand that is.
However, before proceeding please note that:
This spec is incomplete and it is not expected that it will advance
beyond draft status. Authors should not use most of these features
directly, but instead use JavaScript editing libraries. The features
described in this document are not implemented consistently or fully
by user agents, and it is not expected that this will change in the
foreseeable future.... This spec is to meant to help implementations
in standardizing these existing features. It is predicted that in the
future both specs will be replaced by Content Editable Events and
Input Events....
Having clarified that, the following will work in most modern browsers viz. Edge, FireFox and Chrome that I could test in.
By default the foreColor command of execCommand wraps the selected text with a font tag, which is deprecated. So, you need to use the styleWithCSS command. Now this works with the editing API, which means that the element you are trying to work with, should have its contentEditable attribute set.
To work around this, you can temporarily set this attribute just before changing the color in the selected text fragment and then resetting the attribute once done.
Given your paragraph like this:
<p id="p">
Hi , It's a new question in StackOverflow!
</p>
When you select the word StackOverflow, the following code will result in this...
<p id="p">
Hi , It's a new question in <span style="color: rgb(255, 0, 0);">StackOverflow</span>!
</p>
... wrapping your selected text in a span with the style applied.
Fiddle: http://jsfiddle.net/abhitalks/j9w6dj7m/
Snippet:
p = document.getElementById('p');
p.addEventListener('mouseup', setColor);
function setColor() {
p.setAttribute('contentEditable', true);
document.execCommand('styleWithCSS', false, true);
document.execCommand('foreColor', false, "#f00");
p.setAttribute('contentEditable', false);
}
<p id="p" contentEditable="false">
Hi , It's a new question in stackoverflow!
</p>
Edit:
Now that you have added code (and what you have already tried) in your question, you could use the range selection to do what you are after.
Specifically, you will need to learn:
selection: https://developer.mozilla.org/en-US/docs/Web/API/Selection, this you have already done. Cheers!
range: https://developer.mozilla.org/en-US/docs/Web/API/Range/Range, because you will be dealing with ranges here
selection.getRangeAt(): https://developer.mozilla.org/en-US/docs/Web/API/Selection/getRangeAt, because you will need to extract the selected text as a range object
range.surroundContents(): https://developer.mozilla.org/en-US/docs/Web/API/range/surroundContents, because you will need to surround the selected text range with a span.
Putting it all together all you have to do is (explanation in code comments):
function setClass() {
var selection = x.getSelected(), range, // you have already done this
span = document.createElement("span"); // create a span element
span.classList.add('red'); // add the class to the span
if (selection != '') {
range = selection.getRangeAt(0); // get the range from selected text
range.surroundContents(span); // surround the range with span
}
}
Fiddle 2: http://jsfiddle.net/abhitalks/kn0u5frj/
Snippet 2:
var x = {},
p = document.getElementById('p');
p.addEventListener('mouseup', setClass);
function setClass() {
var selection = x.getSelected(), range,
span = document.createElement("span");
span.classList.add('red');
if (selection != '') {
range = selection.getRangeAt(0);
range.surroundContents(span);
}
}
x.getSelected = function() {
var t = '';
if (window.getSelection) {
t = window.getSelection();
} else if (document.getSelection) {
t = document.getSelection();
} else if (document.selection) {
t = document.selection.createRange().text;
}
return t;
}
.red { color: #f00; }
<p id="p">
Hi , It's a new question in stackoverflow!
</p>
You can use the getSelection() method
Below is the example:
Repeated Question:
How to get selected text in textarea?
You can use CSS with :: selection http://caniuse.com/#search=%3A%3Aselection
::selection {
background: #ffb7b7; /* WebKit/Blink Browsers */
}
::-moz-selection {
background: #ffb7b7; /* Gecko Browsers */
}
Or javascript with range

How to remove H1 formatting within ContentEditable (wysiwyg)

With the exception of using Undo, I don't think there's a way to remove h1 and h2 tags in content editable. The expected behavior is clicking the H1 button again should toggle it off, but it does not. There's also a "remove formatting" button, but it only works on items that are bold, italic, etc. Is there a way to do this through javascript?
Edit: Result must remove the opening and closing H1 tag, and not replace it with anything else.
Please see the simplified test case here:
http://jsfiddle.net/kthornbloom/GSnbb/1/
<div id="editor" contenteditable="true">
<h1>This is a heading one</h1>
How can I remove the header styling if I want to?
</div>
I decided to implement the approach I outlined in my comment to my other answer: traversing nodes within the selected range and removing particular nodes (in this case, based on tag name).
Here's the full demo. It won't work in IE <= 8 (which lacks DOM Range and Selection support) but will in everything other major current browser. One problem is that the selection isn't always preserved, but that isn't too hard to achieve.
http://jsfiddle.net/gF3sa/1/
This example includes modified range traversal code from elsewhere on SO.
function nextNode(node) {
if (node.hasChildNodes()) {
return node.firstChild;
} else {
while (node && !node.nextSibling) {
node = node.parentNode;
}
if (!node) {
return null;
}
return node.nextSibling;
}
}
function getRangeSelectedNodes(range, includePartiallySelectedContainers) {
var node = range.startContainer;
var endNode = range.endContainer;
var rangeNodes = [];
// Special case for a range that is contained within a single node
if (node == endNode) {
rangeNodes = [node];
} else {
// Iterate nodes until we hit the end container
while (node && node != endNode) {
rangeNodes.push( node = nextNode(node) );
}
// Add partially selected nodes at the start of the range
node = range.startContainer;
while (node && node != range.commonAncestorContainer) {
rangeNodes.unshift(node);
node = node.parentNode;
}
}
// Add ancestors of the range container, if required
if (includePartiallySelectedContainers) {
node = range.commonAncestorContainer;
while (node) {
rangeNodes.push(node);
node = node.parentNode;
}
}
return rangeNodes;
}
function getSelectedNodes() {
var nodes = [];
if (window.getSelection) {
var sel = window.getSelection();
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
nodes.push.apply(nodes, getRangeSelectedNodes(sel.getRangeAt(i), true));
}
}
return nodes;
}
function replaceWithOwnChildren(el) {
var parent = el.parentNode;
while (el.hasChildNodes()) {
parent.insertBefore(el.firstChild, el);
}
parent.removeChild(el);
}
function removeSelectedElements(tagNames) {
var tagNamesArray = tagNames.toLowerCase().split(",");
getSelectedNodes().forEach(function(node) {
if (node.nodeType == 1 &&
tagNamesArray.indexOf(node.tagName.toLowerCase()) > -1) {
// Remove the node and replace it with its children
replaceWithOwnChildren(node);
}
});
}
removeSelectedElements("h1,h2,h3,h4,h5,h6");
This may not exactly meet your needs, but you could do it by using the FormatBlock command and passing in "div" or "pre" as the final parameter:
document.execCommand('formatBlock', false, 'p');
Demo: http://jsfiddle.net/GSnbb/2/ [jsFiddle has been deleted]
EDIT: Yes, this doesn't answer the question as it is now. However, it pre-dates the edit to the question about not replacing the <h1> element and was a reasonable answer to the original question.
It is feasible with javascript, logic is the following:
get the selected text and its position (cf.
Get the Highlighted/Selected text
and
javascript - Getting selected text position)
remove all the <h1> and </h1> from the selected text
s = s.replace(/<h1>/g, '');
s = s.replace(/<\/h1>/g,'');
Insert the corrected text in place of the original one
I have drafted a solution based on your JSFiddle, but it requires some tweaking.
works: removing <h1> and </h1> from selected text on Gecko and webKit based browsers
not developed: IE support - cf. links in the jsfiddle, should not be difficult
broken:
replacement of incomplete selections (containing only one of <h1> and </h1>) - easy to fix
removal of <h1> when it is right at the beginning of the selected text - you will need to play around a bit more with selections and ranges to sort that out.
P.S. Have you considered using an existing text editor plugin instead of creating it by yourself ?

Categories