I have this function I am calling from rich text editor to add the content in the selection.
The problem is, it is not working properly, if I pass with html tags like <center>some</center>
Instead of making the content center, it outputs the HTML also text
function addTextAtCursorPostion(text) {
var sel, range, html;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(text));
}
}
This document.createTextNode takes only text. Is there a way i can process text and html?
or do we have any dummy/empty node that can place html inside?
Sample Content of my Rich Text Editor innerHTML taken using Firebug
<div contenteditable="true" id="editor">
<font face="Comic Sans MS">Go ahead</font>…
<a href="www.stackoverflow.com"><b><i>Welcome User</i>
**ADD HTML FORMATTED STRING HERE OR POSITION AT CURSOR**
</b></a><br>
</div>
If i wrap my htmlText with span/div/p i may get UI alignment issue
I do not think you can render HTML using a text node. You'll need to create an element and use innerHTML to insert the code.
var el = document.createElement('span');
el.innerHTML = text;
Not tested, but I'm thinking you could insert the HTML string into a custom HTML Element (which bring no inherited styles, other than those added globally) and then insert that into the range.
function addTextAtCursorPostion(text) {
var sel, range, html;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createElement("range").innerHTML=text);
}
}
Related
I'm making a web application to test regular expressions. I have an input where I enter the regexp and a contenteditable pre element where I enter the text where the matches are found and highlighted.
Example: asuming the regexp is ab, if the user types abcab in the pre element, both regexp and text are sent to an api I implemented which returns
<span style='background-color: lightgreen'>ab</span>c<span style='background-color: lightgreen'>ab</span>
and this string is set as the innerHTML of the pre element
This operation is made each time the user edites the content of the pre element (keyup event to be exact). The problem I have (and I hope you can solve) is that each time the innterHTML is set, the caret is placed at the beginning, and I want it to be placed right after the last character input by de user. Any suggestions on how to know where the caret is placed and how to place it in a desired position?
Thanks.
UPDATE For better understanding...A clear case:
Regexp is ab and in the contenteditable element we have:
<span style='background-color: lightgreen'>ab</span>c<span style='background-color: lightgreen'>ab</span>
Then I type a c between the first a and the first b, so now we have:
acbc<span style='background-color: lightgreen'>ab</span>
At this moment the caret has returned to the beginning of the contenteditable element, and it should be placed right after the c I typed. That's what I want to achieve, hope now it's more clear.
UPDATE2
function refreshInnerHtml() {
document.getElementById('textInput').innerHTML = "<span style='background-color: lightgreen'>ab</span>c<span style='background-color: lightgreen'>ab</span>";
}
<pre contenteditable onkeyup="refreshInnerHtml()" id="textInput" style="border: 1px solid black;" ></pre>
With some help from these functions from here ->
Add element before/after text selection
I've created something I think your after.
I basically place some temporary tags into the html where the current cursor is. I then render the new HTML, I then replace the tags with the span with a data-cpos attribute. Using this I then re-select the cursor.
var insertHtmlBeforeSelection, insertHtmlAfterSelection;
(function() {
function createInserter(isBefore) {
return function(html) {
var sel, range, node;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = window.getSelection().getRangeAt(0);
range.collapse(isBefore);
// Range.createContextualFragment() would be useful here but is
// non-standard and not supported in all browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
}
} else if (document.selection && document.selection.createRange) {
// IE < 9
range = document.selection.createRange();
range.collapse(isBefore);
range.pasteHTML(html);
}
}
}
insertHtmlBeforeSelection = createInserter(true);
insertHtmlAfterSelection = createInserter(false);
})();
function refreshInnerHtml() {
var
tag_start = '⇷', //lets use some unicode chars unlikely to ever use..
tag_end = '⇸',
sel = document.getSelection(),
input = document.getElementById('textInput');
//remove old data-cpos
[].forEach.call(
input.querySelectorAll('[data-cpos]'),
function(e) { e.remove() });
//insert the tags at current cursor position
insertHtmlBeforeSelection(tag_start);
insertHtmlAfterSelection(tag_end);
//now do our replace
let html = input.innerText.replace(/(ab)/g, '<span style="background-color: lightgreen">$1</span>');
input.innerHTML = html.replace(tag_start,'<span data-cpos>').replace(tag_end,'</span>');
//now put cursor back
var e = input.querySelector('[data-cpos]');
if (e) {
var range = document.createRange();
range.setStart(e, 0);
range.setEnd(e, 0);
sel.removeAllRanges();
sel.addRange(range);
}
}
refreshInnerHtml();
Type some text below, with the letters 'ab' somewhere within it. <br>
<pre contenteditable onkeyup="refreshInnerHtml()" id="textInput" style="border: 1px solid black;" >It's about time.. above and beyond</pre>
<html>
<head>
<script>
function test(inp){
document.getElementById(inp).value = document.getElementById(inp).value;
}
</script>
</head>
<body>
<input id="search" type="text" value="mycurrtext" size="30"
onfocus="test(this.id);" onclick="test(this.id);" name="search"/>
</body>
</html>
I quickly made this and it places the cursor at the end of the string in the input box. The onclick is for when the user manually clicks on the input and the onfocus is for when the user tabs on the input.
<input id="search" type="text" value="mycurrtext" size="30"
onfocus="test(this.id);" onclick="test(this.id);" name="search"/>
function test(inp){
document.getElementById(inp).value = document.getElementById(inp).value;
}
Here to make selection easy, I've added another span tag with nothing in it, and given it a data-end attribute to make it easy to select.
I then simply create a range from this and use window.getSelection addRange to apply it.
Update: modified to place caret after the first ab
var e = document.querySelector('[data-end]');
var range = document.createRange();
range.setStart(e, 0);
range.setEnd(e, 0);
document.querySelector('[contenteditable]').focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
<div contenteditable="true">
<span style='background-color: lightgreen'>ab</span><span data-end></span>c<span style='background-color: lightgreen'>ab</span>
</div>
I am using Medium.js editor script which helps with contenteditable divs.
But I also want to make adding images better and more like Medium.js itself does.
Currently, I am using this function to insert a node at the current cursor point:
function insertImageAtCursor(text) {
var sel, range, html;
sel = window.getSelection();
range = sel.getRangeAt(0);
range.deleteContents();
var descriptionNode = document.createElement("img");
descriptionNode.className = "img";
descriptionNode.src = text;
range.insertNode(descriptionNode);
range.setStartAfter(descriptionNode);
sel.removeAllRanges();
sel.addRange(range);
}
This works, however I am facing styling issues where I am currently in a paragraph and typing, then insert an image I will end up with content like:
<p>
this is a paragraph and I have inserted a image
<img src="insertimage.png"/>
</p>
Really, I should be using the figure element and it would insert the image outside of the current paragraph. This way when I add an image, it wouldn't be added in the current p element, but instead would be added after and as a figure element. I cannot simply append the image, as I want users to be able to be editing a blog post and be able to insert an image at any point.
Any help on this? There is a plugin already:
But the dependencies required are more than I wish to use and I already have image upload etc working. Just need to get the images being inserted better.
you can split the <p> in two <p> tags and add the <img> between this <p> tags.
Here is a sample code. It's not bug free :).
http://codepen.io/anon/pen/RaOpbr
function insertImageAtCursor(text) {
var sel, range, html;
sel = window.getSelection();
range = sel.getRangeAt(0);
var para = getParagraph(range.startContainer);
range.deleteContents();
range.setEndAfter(para);
var newPara = document.createElement("p");
newPara.innerHTML = range.toString();
range.deleteContents();
para.parentElement.appendChild(newPara);
para.parentElement.insertBefore(para, newPara);
var descriptionNode = document.createElement("img");
descriptionNode.className = "img";
descriptionNode.src = text;
para.parentElement.insertBefore(descriptionNode, newPara);
range.setStart(newPara, 0);
sel.removeAllRanges();
sel.addRange(range);
}
function getParagraph(node) {
var para = node;
while (para.parentElement !== null && (para.tagName == null || para.tagName.toLowerCase() !== 'p')) {
para = para.parentElement;
}
return para;
}
My current solution is:
Get selected html (include text and html tag), namely: selText
highlightText = <span>selText</span>
Find selText in innerHTML of the body or document (or the element which the mouse dragged in)
Replace with highlightText
But if the document is: a a a a a a and user selects the last a. My function will highlight the first or all a.
Any suggestion?
Thank you.
i think your question is duplicated, anyway i just searched the internet and found this article.
Below the final code to achieve what you ask
function highlightSelection() {
var selection;
//Get the selected stuff
if(window.getSelection)
selection = window.getSelection();
else if(typeof document.selection!="undefined")
selection = document.selection;
//Get a the selected content, in a range object
var range = selection.getRangeAt(0);
//If the range spans some text, and inside a tag, set its css class.
if(range && !selection.isCollapsed)
{
if(selection.anchorNode.parentNode == selection.focusNode.parentNode)
{
var span = document.createElement('span');
span.className = 'highlight-green';
range.surroundContents(span);
}
}
}
I also found this library rangy that is an helper you can use to select text but only works with jquery so i prefer the first vanilla-js solution.
var el = $("<span></span>");
el.text(rangy.getSelection().getRangeAt(0).toString());
rangy.getSelection().getRangeAt(0).deleteContents();
rangy.getSelection().getRangeAt(0).insertNode(el.get(0));
rangy.getSelection().getRangeAt(0).getSelection().setSingleRange(range);
On Range and User Selection
You have to select range using Document.createRange that return a Range object before you can use Range.surroundContents(), you could create a range this way.
var range = document.createRange();
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
In practice you follow this guide to understand range and selection tecniques.
The most important point is contained in this code
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
else if (document.selection) { // should come last; Opera!
userSelection = document.selection.createRange();
}
After this you can use
userSelection.surroundContents()
I have a contentEditable div where I want to insert HTML tags (a simple span element).
Is there a cross browser solution that allows me to insert those tags over my div selection or cursor position. If something else is selected on the page (not in the div), I want to append the tag to the end of the div.
Thanks
Here is a kickstart
// get the selection range (or cursor position)
var range = window.getSelection().getRangeAt(0);
// create a span
var newElement = document.createElement('span');
newElement.id = 'myId';
newElement.innerHTML = 'Hello World!';
// if the range is in #myDiv ;)
if(range.startContainer.parentNode.id==='myDiv') {
// delete whatever is on the range
range.deleteContents();
// place your span
range.insertNode(newElement);
}
I don't have IE but works fine on firefox, chrome and safari. Maybe you want to play with range.startContainer to proceed only if the selection is made on the contentEditable div.
EDIT: According to quirksmode range intro you have to change the window.getSelection() part to be IE compatible.
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
else if (document.selection) { // should come last; Opera!
userSelection = document.selection.createRange();
}
The following will do this in all major browsers (including IE 6). It will also handle cases where the end of the selection is outside your <div> and cases where the selection is contained within a child (or more deeply nested) element inside the <div>.
2019 addendum: The second branch of insertNodeOverSelection is for IE <= 8 only and could be removed now.
function isOrContainsNode(ancestor, descendant) {
var node = descendant;
while (node) {
if (node === ancestor) return true;
node = node.parentNode;
}
return false;
}
function insertNodeOverSelection(node, containerNode) {
var sel, range, html;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
if (isOrContainsNode(containerNode, range.commonAncestorContainer)) {
range.deleteContents();
range.insertNode(node);
} else {
containerNode.appendChild(node);
}
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
if (isOrContainsNode(containerNode, range.parentElement())) {
html = (node.nodeType == 3) ? node.data : node.outerHTML;
range.pasteHTML(html);
} else {
containerNode.appendChild(node);
}
}
}
<input type="button" onmousedown="insertNodeOverSelection(document.createTextNode('[NODE]'), document.getElementById('test'));" value="insert">
<div contenteditable="true">
<div id="test" style="background-color: lightgoldenrodyellow">
This is the editable element where the insertion will happen. Select something or place the cursor in here, then hit the button above
</div>
<div>
No insertion will happen here
</div>
</div>
I am writing a script on which the user needs to be able to select some text which is sent via ajax to the backend script for further process.
I can select plain text nodes fine or text nodes that have bold, italic or underlined text inside it.
For e.g
<p>This is <strong>some</strong> cool <em>italic</em> text, <u>really!</u></p>
So, that works, that is cool.
However, the issue is, if the text node starts with hsome bold, italic or underlined text OR even headings it outputs the following error on firefox console:
The boundary-points of a range does not meet specific requirements." code: "1 range.surroundContents($('<span...wAnno_'+newLen+'"></span>').get(0));
The error is output when the user selects something like:
<strong>Mark says</strong> Hi
OR
<em>Mark says</em> Hi
OR
<u>Mark says</u> Hi
The same error outputs even if a text is enclosed inside heading tags e.g <h2>test</h2>
My code looks like:
var select = window.getSelection();
var parents = $(select.focusNode).parents('.the-content');
if($(select.focusNode).parent().hasClass('.highlighted')) {
alert('This text is already highlighted');
} else {
for(var i = 0; i < select.rangeCount; i++) {
var range = select.getRangeAt(i);
range.surroundContents($('<span class="newHighlight" id="newHigh_'+newLen+'"></span>').get(0));
}
}
var selectedText = select.toString();
I need help with fixing this.
Help with the code will be awesome.
The problem is that the surroundContents method of Range can't work on a Range where the start and end boundaries lie within different elements, because surrounding the contents of such a Range within an element would not produce valid HTML. If changing the background colour of your Range is all you need to do, you could use the following trick with document.execCommand:
function highlight(colour) {
var range, sel;
if (window.getSelection) {
// Non-IE case
sel = window.getSelection();
if (sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
// Use HiliteColor since some browsers apply BackColor to the whole block
if ( !document.execCommand("HiliteColor", false, colour) ) {
document.execCommand("BackColor", false, colour);
}
document.designMode = "off";
} else if (document.selection && document.selection.createRange) {
// IE case
range = document.selection.createRange();
range.execCommand("BackColor", false, colour);
}
}
Otherwise, you'll need to walk through the text nodes within the range and surround each with a <span>, which is not trivial. I've been working on a cross-browser range and selection library that includes a module for applying a CSS class to the contents of a selection or Range at http://code.google.com/p/rangy/, although that module is a few days away from being documented and released.