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
Related
I'm trying to make a simple text editor so users can be able to bold/unbold selected text. I want to use Window.getSelection() not Document.execCommand(). It does exactly what I want but when you bold any text, you can't unbold it. I want it in a way that I can bold and unbold any selected text. I tried several things but no success.
function addBold(){
const selection = window.getSelection().getRangeAt(0);
const selectedText = selection.extractContents();
const span = document.createElement("span");
span.classList.toggle("bold-span");
span.appendChild(selectedText);
selection.insertNode(span);
};
.bold-span {font-weight: bold;}
<p contentEditable>Bold anything here and unbold it</p>
<button onclick="addBold()">Bold</button>
This is close to what you want but groups words together so an unselect will remove from whole word. I have not been able to complete this as I have to go, but should be a good starting point.
function addBold(){
const selection = window.getSelection().getRangeAt(0);
let selectedParent = selection.commonAncestorContainer.parentElement;
//console.log(parent.classList.contains("bold-span"))
//console.log(parent)
let mainParent = selectedParent;
if(selectedParent.classList.contains("bold-span"))
{
var text = document.createTextNode(selectedParent.textContent);
mainParent = selectedParent.parentElement;
mainParent.insertBefore(text, selectedParent);
mainParent.removeChild(selectedParent);
mainParent.normalize();
}
else
{
const span = document.createElement("span");
span.classList.toggle("bold-span");
span.appendChild(selection.extractContents());
//selection.surroundContents(span);
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();
}
};
.bold-span {font-weight: bold;}
<p contentEditable>Bold anything here and unbold it</p>
<button onclick="addBold()">Bold</button>
var span = '';
jQuery(function($) {
$('.embolden').click(function(){
var highlight = window.getSelection();
if(highlight != ""){
span = '<span class="bold">' + highlight + '</span>';
}else{
highlight = span;
span = $('span.bold').html();
}
var text = $('.textEditor').html();
$('.textEditor').html(text.replace(highlight, span));
});
});
You could define a function like this where the name of your class is "embolden"
I'm having problems running an action on more than one line.
In practice I do not get the desired effect and <p> tags are completely emptied of their content. This is the simple code I use, but it only works on a line:
Try to look at code in action HERE .
Follow the instructions in the example to understand the real problem.
function press() {
if (document.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount < 1) {
return;
}
var r = window.getSelection().getRangeAt(0);
var selectedText = r.toString(),
content = r.extractContents();
span = document.createElement('span'),
t = document.createTextNode(selectedText);
selectedText = '';
span.style.color = 'green';
span.appendChild(t);
r.insertNode(span);
window.getSelection().removeAllRanges();
}
}
<p>Select only me for first: i don't have problems</p>
<p>Also select me at the same time of first paragraph, now i have problems..</p>
<button onclick="press()">Press after selecting the text</button>
How can I add <span> tags around selected text within an element?
For example, if somebody highlights "John", I would like to add span tags around it.
HTML
<p>My name is Jimmy John, and I hate sandwiches. My name is still Jimmy John.</p>
JS
function getSelectedText() {
t = (document.all) ? document.selection.createRange().text : document.getSelection();
return t;
}
$('p').mouseup(function(){
var selection = getSelectedText();
var selection_text = selection.toString();
console.log(selection);
console.log(selection_text);
// How do I add a span around the selected text?
});
http://jsfiddle.net/2w35p/
There is a identical question here: jQuery select text and add span to it in an paragraph, but it uses outdated jquery methods (e.g. live), and the accepted answer has a bug.
I have a solution. Get the Range of the selecion and deleteContent of it, then insert a new span in it .
$('body').mouseup(function(){
var selection = getSelectedText();
var selection_text = selection.toString();
// How do I add a span around the selected text?
var span = document.createElement('SPAN');
span.textContent = selection_text;
var range = selection.getRangeAt(0);
range.deleteContents();
range.insertNode(span);
});
You can see the DEMO here
UPDATE
Absolutly, the selection will be delete at the same time. So you can add the selection range with js code if you want.
You can simply do like this.
$('body').mouseup(function(){
var span = document.createElement("span");
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents(span);
sel.removeAllRanges();
sel.addRange(range);
}
}
});
Fiddle
Reference Wrapping a selected text node with span
You can try this:
$('body').mouseup(function(){
var selection = getSelectedText();
var innerHTML = $('p').html();
var selectionWithSpan = '<span>'+selection+'</span>';
innerHTML = innerHTML.replace(selection,selectionWithSpan);
$('p').html(innerHTML);
});
and In your fiddle you are again opening a new <p> instead of a closing </p>. Update that please.
THIS WORKS (mostly*)!! (technically, it does what you want, but it needs HALP!)
JSFiddle
This adds <span ...> and </span> correctly, even if there are multiple instances of the selection in your element and you only care about the instance that's selected!
It works perfectly the first time if you include my commented line. It's after that when things get funky.
I can add the span tags, but I'm having a hard time replacing the plaintext with html. Maybe you can figure it out? We're almost there!! This uses nodes from getSelection. Nodes can be hard to work with though.
document.getElementById('d').addEventListener('mouseup',function(e){
var s = window.getSelection();
var n = s.anchorNode; //DOM node
var o = s.anchorOffset; //index of start selection in the node
var f = s.focusOffset; //index of end selection in the node
n.textContent = n.textContent.substring(0,o)+'<span style="color:red;">'
+n.textContent.substring(o,f)+'</span>'
+n.textContent.substring(f,n.textContent.length);
//adds the span tag
// document.getElementById('d').innerHTML = n.textContent;
// this line messes stuff up because of the difference
// between a node's textContent and it's innerHTML.
});
My end goal is for the user to be able to :
select text from a paragraph
wrap the text in a span
place an action button / div at the end of the selection that they could click to take further action
Here is my code so far:
function Discussion(){
var $this = this;
$(document).bind("mouseup",function(){
$this.selectText($this);
});
}
Discussion.prototype.selectText = function($this){
var selection = $this.getSelectedText();
if(selection.length > 3){
console.log(selection);
var spn = '<span class="selected">' + selection + '</span>';
$(this).html($(this).html().replace(selection, spn));
//ERROR here; it says that it can't replace() on undefined $(this).html()
}
}
Discussion.prototype.getSelectedText = function(){
if(window.getSelection){
return window.getSelection().toString();
}
else if(document.getSelection){
return document.getSelection();
}
else if(document.selection){
return document.selection.createRange().text;
}
}
As you might expect, so far I can get the text of the selection with window.getSelection().toString(). If I remove the .toString(), I get a Selection object. With this I can use window.getSelection().anchorNode.parentNode to get most of the information I need.
I also see that Selection.anchorOffset and Selection.extentOffset will give me the range of characters I've selected. Is there a way I can use this information to place the span?
I guess my questions would be:
Why isn't this code wrapping the selection in divs?
Will this fail with multiple instances of the same text?
After wrapping the selection with a span or inline-block div or something, will I be able to get (and use) its position for positioning an additional button / div?
Phew, thanks for your time!
Edit: I added a JS fiddle here : http://jsfiddle.net/nn62G/
I'm going to post my js fiddle here with the solution for posterity. Thanks for all the help, everyone!
http://jsfiddle.net/prodikl/mP8KT/
function Discussion(){
var $this = this;
$(document).bind("mouseup",function(){
$this.selectText($this);
});
}
Discussion.prototype.selectText = function($this){
$("mark").contents().unwrap();
$("mark").remove();
var selection = $this.getSelection();
var range = selection.getRangeAt(0);
var cssclass = $(selection.anchorNode.parentNode).attr("class");
if(selection.toString().length > 2){
$this.startPoint = selection.anchorOffset;
$this.endPoint = selection.extentOffset;
var newNode = document.createElement("mark");
range.surroundContents(newNode);
$("mark").attr('title', ' ');
$("mark").tooltip({
content : "<a class='content' href='http://www.google.com' target='_blank'>Here's a link.</a>"
});
$("mark").on('mouseleave',function(event){
event.stopImmediatePropagation();
}).tooltip("open");
}
}
Discussion.prototype.getSelection = function(){
if(window.getSelection){
return window.getSelection();
}
else if(document.getSelection){
return document.getSelection();
}
else if(document.selection){
return document.selection.createRange().text;
}
}
var discussion = new Discussion();
The value returned by getSelectedText is a selection not an element in the DOM. That is why your cal to the html function is failing, selection has no such property.
You can do what you want as follows, set the part of the document you want to process as contentEditable for example a <p> or <div>. When you select within that area then you can get the HTML element using document.activeElement. If you select outside a contentEdiatble area then activeElement returns the body element. The activeElement you CAN process :
document.activeElement.innerHTML =
document.activeElement.innerHTML.replace (selection, spn);
However, if you make parts of your document editable other things happen which you may not want. More seriously, if the selected text occurs multiple times you cannot determine which one has actually been selected so this may not be a solution to your problem.
Updated fiddle here
Ok well, a possible solution could be to find the element that contains the current selection and replace it's html something like:
// this get's you all the elements (including the top parent) that contain the selection
var existsInElements = $('*:contains("' + selection + '")');
// the exact element match is in the last one
var exactElement = existsInElements[existsInElements.length - 1];
Here is a fiddle that works.
I am using the highlighter module available in Rangy, and it work great in creating a highlight for the text that is selected.
In terms of changes to the html, the selected text is replaced by a span tag like the following for example:
the selected text is <span class="highlight">replaced by a span tag</span> like the
What I want to do is get a reference to the span element once it has been created so I can do some other stuff with it. How can this be done?
Please note there may be other spans with or without the highlight tag elsewhere, so these cannot be used to find it.
The important part of the code I have to create the highlight for the selected text is:
var highlighter = null;
var cssApplier = null;
rangy.init();
cssApplier = rangy.createCssClassApplier("highlight", { normalize: true });
highlighter = rangy.createHighlighter(document, "TextRange");
highlighter.addClassApplier(cssApplier);
var selection = rangy.getSelection();
highlighter.highlightSelection("highlight", selection);
I was waiting for #TimDown to update his answer with working code. But as he hasn't done that then I will post some myself (which is based on his answer).
The following function will return an array of highlight elements that have been creating, assuming the selection is still valid:
function GetAllCreatedElements(selection) {
var nodes = selection.getRangeAt(0).getNodes(false, function (el) {
return el.parentNode && el.parentNode.className == "highlight";
});
var spans = [];
for (var i = 0; i < nodes.length; i++) {
spans.push(nodes[i].parentNode);
}
return spans;
}
There is no guarantee that only one <span> element will be created: if the selection crosses element boundaries, several spans could be created.
Anyway, since the selection is preserved, you could use the getNodes() method of the selection range to get the spans:
var spans = selection.getRangeAt(0).getNodes([1], function(el) {
return el.tagName == "SPAN" && el.className == "highlight";
});