Selection range deleteContents leaves empty nodes? - javascript

I have a script to wrap html tags around the selection.
function wrap(tagName)
{
var selection;
var elements = [];
var ranges = [];
var rangeCount = 0;
if (window.getSelection)
{
selection = window.getSelection();
if (selection.rangeCount)
{
rangeCount = selection.rangeCount;
for (var i=0; i<rangeCount; i++)
{
ranges[i] = selection.getRangeAt(i).cloneRange();
elements[i] = document.createElement(tagName);
elements[i].appendChild(ranges[i].cloneContents());
ranges[i].deleteContents();
ranges[i].insertNode(elements[i]);
ranges[i].selectNode(elements[i]);
}
selection.removeAllRanges();
for (var i=0; i<ranges.length; i++)
{
selection.addRange(ranges[i]);
}
}
}
}
It works fine, but when I try to select the following text and wrap tags around the following code
<strong>W</strong>elcom<strong>e</strong>
The code changes to
<strong></strong><u><strong>W</strong>elcom<strong>e</strong></u><strong></strong>
instead of
<u><strong>W</strong>elcom<strong>e</strong></u>
I have inspected the code with firebug and it goes wrong at the function deleteContents(). The function deleteContents() leaves two empty nodes <strong></strong> so it doesn't delete the whole content. How does this happen?

Related

Replace text on gmail using js

I am working on a chrome extension, which replaces part of the text. It does not work as expected on Gmail though.
Scanario:
Gmail composer has following text -
i am now to this.
i can do this.
I want to replace it to -
i am new to this.
i can do this.
However, whenever I execute the code, it does not replace the text on the correct location.
It sees where my cursor was and appends the text there instead of replacing the intended text.
This snippet works on other websites, which have contenteditable editors.
My current implementation looks like the following:
const range = document.createRange();
const ele = <div tag element for 'i am now to this.' sentence>
// rangeStart and rangeEnd are the index which wraps word 'now'
range.setStart(ele.childNodes[0], rangeStart);
range.setEnd(ele.childNodes[0], rangeEnd);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
setTimeout(()=>{
document.execCommand(
"insertText",
false,
"new"
);
},0)
There are a couple of things that might go wrong about it.
One is that I noticed the div initially containing a single textNode with some text switched to a node containing multiple textNodes with exactly the same over all text and that can create a number of problems considering that you are operating only with children[0].
Another thing that didn't work for me was the document executing the isertText
How so ever something that worked for me:
var range = document.createRange();
var rangeStart = el.innerText.indexOf(s);
var rangeEnd = rangeStart + s.length;
var selection = window.getSelection();
range.setStart(el.childNodes[0], rangeStart);
range.setEnd(el.childNodes[0], rangeEnd);
selection.removeAllRanges();
selection.addRange(range);
range.deleteContents();
range.insertNode(document.createTextNode(ss))
My entire testing scenario:
(()=>{
var all = document.getElementsByTagName('div');
var n = all.length;
var find = (s)=>{
var result = [];
for (var i = 0; i < n; i++) {
var el = all[i];
var text = el && el.childNodes && el.childNodes[0] && el.childNodes[0].wholeText;
if (text && text.match(s)) {
result.push(el);
}
}
return result.length ? result : false;
}
;
var replacer = (s,ss)=>(el)=>{
try {
var range = document.createRange();
var rangeStart = el.innerText.indexOf(s);
var rangeEnd = rangeStart + s.length;
range.setStart(el.childNodes[0], rangeStart);
range.setEnd(el.childNodes[0], rangeEnd);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
range.deleteContents();
range.insertNode(document.createTextNode(ss))
}catch(ex){
}
}
;
var elements = find('now');
if (elements) {
elements.map(replacer('now', 'new'))
}
}
)(window);
Good luck :)

removeAllRanges() method is not working on chrome

hi there the idea is to copy the selected nodes and there text then remove the selection but i dont know why it's not working:
var sel = window.getSelection();
var range = sel.getRangeAt(0);
var ancestor = sel.getRangeAt(0).commonAncestorContainer;
var child = ancestor.childNodes;
var len = child.length, div=[], text = [];
for (var i =0; i<len; i++){
div.push(child[i].localName);
text.push(child[i].textContent);
}
sel.removeAllRanges();

apply Highlight HTML text using javascript as in Microsoft word

I have the following code for highlighting html text using javascript:
var counter = 1;
function BindEventForHighlight() {
$('#ParaFontSize').bind("mouseup", HighlightText);
$('#ParaFontSize').unbind("mouseup", RemoveHighlight);
}
function UnBindHighlightEvent() {
$('#ParaFontSize').bind("mouseup", RemoveHighlight);
$('#ParaFontSize').unbind("mouseup", HighlightText);
}
function HighlightText() {
var text = window.getSelection();
var start = text.anchorOffset;
var end = text.focusOffset - text.anchorOffset;
range = window.getSelection().getRangeAt(0);
range1 = window.getSelection().toString();
var selectionContents = range.extractContents();
var span = document.createElement("span");
span.appendChild(selectionContents);
span.setAttribute("id", counter);
span.setAttribute("class", "highlight");
range.insertNode(span);
counter++;
}
function RemoveAllHighlights() {
var selection = document.getElementById('ParaFontSize');
var spans = selection.getElementsByTagName("span");
for (var i = 0; i < spans.length; i++) {
spans[i].className = "";
}
}
function RemoveHighlight() {
var selection = window.getSelection();
if (selection.toString() !== "" &&
selection.anchorNode.parentNode.nodeName === "SPAN") {
selection.anchorNode.parentNode.className = "";
}
else {
return false;
}
}
The HighlightText function uses range object to highlight text. and i want to save start and end of selected text into Data base, and when user get back to the same page he will see the highlighted text from previous time. also i want other scenarios as follow:
when the user highlight some text, he will be able to unhighlight all or part of this highlighted text and keep the remaining text highlighted.
the user able to clear all highlighted text in the page.
let's say i want this highlighter functions as the highlighter in MS word.
Thanks in advance
Really appreciated.

Weird behavour wysiwyg?

I have a code to wrap elements around text it works fine until i try the following format in my editor:
<u><strong>T</strong>es<strong>t</strong></u>
It automatic adds two empty strong elements before the underlined element and after like this:
<strong></strong>
<u><strong>T</strong>es<strong>t</strong></u>
<strong></strong>
Here's the code that i use and i have buttons that have actions like wrap('strong'):
function wrap(tagName)
{
var selection;
var elements = [];
var ranges = [];
var rangeCount = 0;
if (window.getSelection)
{
selection = window.getSelection();
if (selection.rangeCount)
{
rangeCount = selection.rangeCount;
for (var i=0; i<rangeCount; i++)
{
ranges[i] = selection.getRangeAt(i).cloneRange();
elements[i] = document.createElement(tagName);
elements[i].appendChild(ranges[i].extractContents());
ranges[i].insertNode(elements[i]);
ranges[i].selectNode(elements[i]);
}
selection.removeAllRanges();
for (var i=0; i<ranges.length; i++)
{
selection.addRange(ranges[i]);
}
}
}
}
WYSIWYG is hard. Especially with HTML, which is nothing what it looks like.
I'm just guessing here, but if you start with
<u>Test</u>
ant you select T in WYSIWYG then the actual selected code will probably be <u>T. Since you can't wrap that in strong (because <strong><u>T</strong> is not valid markup then the editor will wrap everything before the tag in strong and everything after tag in strong, which results in
<strong></strong>
<u><strong>T</strong>es<strong>t</strong></u>
<strong></strong>
that you are getting.
I to avoid that you could check if the text that you are wrapping has the lenght of 0, end then if so - not wrap it with anything.
I'd suggest using DOM manipulation to wrap text nodes within the selection individually. My Rangy library can help a little with this, providing splitBoundaries() and getNodes() extension methods to its Range objects.
Live demo: http://jsfiddle.net/5cdMn/
Code:
function isNodeInsideElementWithTagName(node, tagName) {
tagName = tagName.toLowerCase();
while (node) {
if (node.nodeType == 1 && node.tagName.toLowerCase() == tagName) {
return true;
}
node = node.parentNode;
}
return false;
}
function wrapSelection(tagName) {
var range, textNode, i, len, j, jLen, el;
var ranges = rangy.getSelection().getAllRanges();
for (i = 0, len = ranges.length; i < len; ++i) {
range = ranges[i];
range.splitBoundaries();
textNodes = range.getNodes([3]/* Array of node types to retrieve */);
for (j = 0, jLen = textNodes.length; j < jLen; ++j) {
textNode = textNodes[j];
if (!isNodeInsideElementWithTagName(textNode, tagName)) {
el = document.createElement(tagName);
textNode.parentNode.insertBefore(el, textNode);
el.appendChild(textNode);
}
}
}
}

remove selected (highlighted elements) except for input#cursor

I have this function which removes selected (cursor) highlighted elements:
function deleteSelection() {
if (window.getSelection) {
// Mozilla
var selection = window.getSelection();
if (selection.rangeCount > 0) {
window.getSelection().deleteFromDocument();
window.getSelection().removeAllRanges();
}
} else if (document.selection) {
// Internet Explorer
var ranges = document.selection.createRangeCollection();
for (var i = 0; i < ranges.length; i++) {
ranges[i].text = "";
}
}
}
What I actually want to do is remove all the cursor- highlighted elements, except for the input#cursor element.
edit:
so lets say if I highlighted these elements with my cursor:
<span>a</span>
<span>b</span>
<input type='text' id = 'cursor' />
<span>c</span>
<span>d</span>
on a keyup, this function should not remove the <input> ...only the <span>
Phew, that was a little harder than expected!
Here's the code, I've tested it in IE8 and Chrome 16/FF5. The JSFiddle I used to test is available here. AFAIK, this is probably the best performance-wise you'll get.
The docs I used for Moz: Selection, Range, DocumentFragment. IE: TextRange
function deleteSelection() {
// get cursor element
var cursor = document.getElementById('cursor');
// mozilla
if(window.getSelection) {
var selection = window.getSelection();
var containsCursor = selection.containsNode(cursor, true);
if(containsCursor) {
var cursorFound = false;
for(var i=0; i < selection.rangeCount; i++) {
var range = selection.getRangeAt(i);
if(!cursorFound) {
// extracts tree from DOM and gives back a fragment
var contents = range.extractContents();
// check if tree fragment contains our cursor
cursorFound = containsChildById(contents, 'cursor');
if(cursorFound) range.insertNode(cursor); // put back in DOM
}
else {
// deletes everything in range
range.deleteContents();
}
}
}
else {
selection.deleteFromDocument();
}
// removes highlight
selection.removeAllRanges();
}
// ie
else if(document.selection) {
var ranges = document.selection.createRangeCollection();
var cursorFound = false;
for(var i=0; i < ranges.length; i++) {
if(!cursorFound) {
// hacky but it will work
cursorFound = (ranges[i].htmlText.indexOf('id=cursor') != -1);
if(cursorFound)
ranges[i].pasteHTML(cursor.outerHTML); // replaces html with parameter
else
ranges[i].text = '';
}
else {
ranges[i].text = '';
}
}
}
}
// simple BFS to find an id in a tree, not sure if you have a
// library function that does this or not
function containsChildById(source, id) {
q = [];
q.push(source);
while(q.length > 0) {
var current = q.shift();
if(current.id == id)
return true;
for(var i=0; i < current.childNodes.length; i++) {
q.push(current.childNodes[i]);
}
}
return false;
}
I would suggest removing the element you want keep, extracting the contents of the selection using Range's extractContents() method and then reinserting the element you want to keep using the range's insertNode() method.
For IE < 9, which doesn't support DOM Range, the code is trickier, so for simplicity you could use my Rangy library, which also adds convenience methods such as the containsNode() method of Range.
Live demo: http://jsfiddle.net/DFxH8/1/
Code:
function deleteSelectedExcept(node) {
var sel = rangy.getSelection();
if (!sel.isCollapsed) {
for (var i = 0, len = sel.rangeCount, range, nodeInRange; i < len; ++i) {
range = sel.getRangeAt(i);
nodeInRange = range.containsNode(node);
range.extractContents();
if (nodeInRange) {
range.insertNode(node);
}
}
}
}
deleteSelectedExcept(document.getElementById("cursor"));

Categories

Resources