How to find the nearest parent when inserting Node? - javascript

Let's say I have the following text:
Hi, <span class='blue_mark'>my name is Bob</span>.
Let's say I want to highlight Bob with a .red_mark. When I do this, the nearest parent would be .blue_mark and not the main parent. I want to calculate this because I don't want any spans nested inside of each other. Only from the main parent.
This is my code:
var selection = document.getSelection();
var range = selection.getRangeAt(0);
var contents = range.extractContents();
var node = document.createElement('span');
node.classList.add('blue_mark');
node.appendChild(contents);
range.insertNode(node);
selection.removeAllRanges(); //Clear the selection, showing highlight
Before I insertNode, I want to check if the span is nested inside another span. If so, don't insert and have an alert come up. If not, then insert the content/

So basically, you don't want to have any overlapping spans. So:
The start of the selection can't be inside a span
The end of the selection can't be inside a span
The selection cannot fully contain a span
First, a utility function:
function isInSpan(node) {
if (node && node.nodeType === 3) {
node = node.parentNode;
}
while (node && node.nodeType === 1) {
if (node.nodeName.toUpperCase() === "SPAN") {
return true;
}
node = node.parentNode;
}
return false;
}
Then, I believe you can check those like this:
if (isInSpan(range.startContainer) ||
isInSpan(range.endContainer) ||
range.cloneContents().querySelector("span")) {
// Do the alert
} else {
// Go ahead and create the span
}
(Was pleasantly surprised to see querySelector on DocumentFragment.)

Related

JavaScript selected text entirely within node

I'm looking into text selection and ranges in JavaScript.
I need to get any nodes that surround the selected text exactly, for example:
<div>this is <span>some simple</span> text</div>
When the user selects the words 'some simple' i need to know that it sits entirely within the node .
Yet if they select just 'some' then this is not entirely within the node as the word 'simple' is NOT selected.
The end requirement is to be able to amend the class on the node only if the whole text within the node is selected.
jquery is also viable. thanks
To add some more context to this, when a user selects some text we add some sytling to it, let's say 'bold'. the user can edit the text in the parent div as often as they wish so each edit could add a new span enclosing the selected text. We could end up with something like this:
<div><span class="text-bold">Hi</span>, <span class="text-red">this <span class="text-italic">is</span></span> a sample text item</div>
So the spans can come and go dependant on what the user wants.
You are looking to get the DOM each time. So you can set id to your HTML Element Objects and get the value from them. For example:
<span id="f_span">some simple</span>
<script>
var x = document.getElementById("f_span");
</script>
Then you can check if x value equals with the value that user had selected.
You can use setInterval to get the selected text every x second. With the joined function you can get the selected text.
For the selection of user :
function getSelected() {
if(window.getSelection) { return window.getSelection(); }
else if(document.getSelection) { return document.getSelection(); }
else {
var selection = document.selection && document.selection.createRange();
if(selection.text) { return selection.text; }
return false;
}
return false;
}
It return an object that give you the offset of the selection. If result.anchorOffset = 0 and result.focusOffset = result.anchorNode.length (if it start at the begining of the node and it have the length of the whole node), then the user selected all your node.
Thanks for your replies, it allowed me to cobble together my solution:
function applyTextFormatClass(className) {
var selection = getSelected();
var parent = selection.getRangeAt(0).commonAncestorContainer; //see comment and link below
if (parent.nodeType !== 1) {
parent = parent.parentNode; //we want the parent node
}
var tagText = parent.innerText;
var selectText = selection.toString();
if (tagText.length !== selectText.length) {
addNodeAroundSelectedText(selection, className); //create new node
} else {
addClass(parent, className); //add class to existing node
}
}
commonAncestorContainer: https://developer.mozilla.org/en-US/docs/Web/API/Range/commonAncestorContainer

If text-selection is inside a specific div

I am using contenteditable=true trying make a text editor. I have successfully used getSelection() to wrap the selection inside HTML tags.
A problem is though, that if the user selects text outside the editor and the operation button is clicked, that text will be wrapped inside tags as well.
How would I do it with getSelection(), check if the selection is inside a div with the class of editor?
Edit:
Currently using this code:
var sel = window.getSelection ? window.getSelection() : document.selection.createRange();
if(sel.getRangeAt){
var range = sel.getRangeAt(0);
var newNode = document.createElement("p");
newNode.setAttribute('class', operationClass);
range.surroundContents(newNode);
} else {
sel.pasteHTML('<p class="' + operationClass +'">'+sel.htmlText+'</p>');
}
Once you have var range = sel.getRangeAt(0); you can determine if range.commonAncestorContainer or one of its ancestors is the editor container, using code like this:
var ancestor = range.commonAncestorContainer;
while (ancestor.id != "editor" // Check id, class or otherwise
&& ancestor.parentElement != null) {
ancestor = ancestor.parentElement;
}
if (ancestor.id == "editor") {
// Selection is within the editor.
}

When Range Selection expands to parent elements, it creates extra elements if either side of the selection was in the middle of a tag

Code: http://jsfiddle.net/DerNalia/9vHK4/2/
Version showing: the selected html as text (expanded): http://jsfiddle.net/DerNalia/Wnd3j/
So, when I do a Selection like this:
My code generates a "highlight" like this:
Notice how it added an empty <li> before and after the selection.
What it should have done is completely surround the 6,7, 8 pool <li>'s with the selection span.
Here is the code handeling selection expansion:
TBR.Selection.get_HTML_from_range = function(range, sel){
var startEl = sel.anchorNode;
var endEl = sel.focusNode;
if (startEl != endEl){
if (startEl != range.commonAncestorContainer) {
while (startEl.parentNode != range.commonAncestorContainer) {
startEl = startEl.parentNode;
}
}
if (endEl != range.commonAncestorContainer) {
while (endEl.parentNode != range.commonAncestorContainer) {
endEl = endEl.parentNode;
}
}
range.setStartBefore(startEl);
range.setEndAfter(endEl);
}
sel.addRange(range);
var container = document.createElement("span");
container.appendChild(sel.getRangeAt(0).cloneContents());
html = container.innerHTML;
if (html.length < MINIMUM_SELECTION_LENGTH) {return false;}
return html;
}
basically if the start and end of the selection are on different DOM nodes, then the selection needs to expand such that the selection contains no broken DOM elements (all tags much have a matching end tag, etc)
But I have a feeling that the selection is breaking the HTML, and the browser is repairing the HTML before the selection expands all the way. I'm not sure.

Javascript: How do I expand a user selection based on html tags?

Le Code:
http://jsfiddle.net/frf7w/12/
So right now, the current method will take the selected text exactly as... selected, and add tags so that when it is displayed, the page doesn't blow up.
But what I want to do:
Is to, when a user selects a portion of a page, if there are un-matched tags within the selection, the selection will either jump forward or backward (depending on what unmatched tag is in the selection) to the tag(s) that make the selection valid html.
The reason why I want to do this, is because I want a user to be able te select text on a page, and be able to edit that text in a WYSIWYG editor (I can currently do this with the linked code), and then put what they've edited back into the page (currently can't do this, because the method I use adds tags).
The coverAll method in this SO answer has exactly what you want Use javascript to extend a DOM Range to cover partially selected nodes. For some reason extending Selection prototype does not work for me on my chrome, so I extracted the code and substituted this with window.getSelection(). Final code looks like this:
function coverAll() {
var ranges = [];
for(var i=0; i<window.getSelection().rangeCount; i++) {
var range = window.getSelection().getRangeAt(i);
while(range.startContainer.nodeType == 3
|| range.startContainer.childNodes.length == 1)
range.setStartBefore(range.startContainer);
while(range.endContainer.nodeType == 3
|| range.endContainer.childNodes.length == 1)
range.setEndAfter(range.endContainer);
ranges.push(range);
}
window.getSelection().removeAllRanges();
for(var i=0; i<ranges.length; i++) {
window.getSelection().addRange(ranges[i]);
}
return;
}
You can change the boundaries of the selection by adding a range:
var sel = window.getSelection(),
range = sel.getRangeAt(0);
var startEl = sel.anchorNode;
if (startEl != range.commonAncestorContainer) {
while (startEl.parentNode != range.commonAncestorContainer) {
startEl = startEl.parentNode;
}
}
var endEl = sel.focusNode;
if (endEl != range.commonAncestorContainer) {
while (endEl.parentNode != range.commonAncestorContainer) {
endEl = endEl.parentNode;
}
}
range.setStartBefore(startEl);
range.setEndAfter(endEl);
sel.addRange(range);
The above example will give you a selection that is expanded to cover the entire of the tree between the start and end nodes, inclusive (thanks to commonAncestorContainer()).
This treats text nodes as equal to dom elements, but this shouldn't be a problem for you.
Demo: http://jsfiddle.net/Nq6hr/2/
You should work with the nodes given by the selection. It seems extentNode and anchorNode represents the end and the beginning of nodes of the selection both can help you having the "full" selection. https://developer.mozilla.org/fr/DOM/Selection
For the inline editing you should give a try to contentEditable attribute. You can surround the elements of your selection with a span containing this attribute https://developer.mozilla.org/en/DOM/element.contentEditable

How can I get the element in which highlighted text is in?

I am trying to learn how to write a bookmarklet where I can highlight some text, click on the bookmarklet and have it tell me what got highlighted. I can get that far, but next I want to know what element that text is in.
For example:
<div id="some-id">to be highlighted</div>
The bookmarklet code:
javascript:(function(){alert(window.getSelection();})()
If I highlight the text "to be highlighted" and then click on the bookmarklet, it will alert the text. But how can I get the element in which the text is in, in this case the element after that?
So the flow is: highlight text, click bookmarklet, bookmarklet tells you what you highlighted and the element it's in.
Thanks!
Try something similar to this to get the dom element that contains the selected text.
window.getSelection().anchorNode.parentNode
It works on firefox and Chrome, you should test it into the remaining browsers.
It have a quirk, if you select text that beholds to more than an element, only the first one is returned. But maybe you can live with this.
Just for reference on what is the anchorNode property:
http://help.dottoro.com/ljkstboe.php
On internet explorer this snippet should do the trick (I can't test it)
document.selection.createRange().parentElement();
as stated into
http://msdn.microsoft.com/en-us/library/ms535872.aspx and
http://msdn.microsoft.com/en-us/library/ms536654.aspx
A range explanation on quirksmode: http://www.quirksmode.org/dom/range_intro.html
You can do this relatively simply in all major browsers. Code is below, live example: http://jsfiddle.net/timdown/Q9VZT/
function getSelectionTextAndContainerElement() {
var text = "", containerElement = null;
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var node = sel.getRangeAt(0).commonAncestorContainer;
containerElement = node.nodeType == 1 ? node : node.parentNode;
text = sel.toString();
}
} else if (typeof document.selection != "undefined" &&
document.selection.type != "Control") {
var textRange = document.selection.createRange();
containerElement = textRange.parentElement();
text = textRange.text;
}
return {
text: text,
containerElement: containerElement
};
}
I don't think you can, he only way to know which element is currently being used would be to use a onclick event on the element. Other than that there is no sure way. You can however search each element until you find an element with the same text,
jQuery('*:contains(' + selected + ').
You can add an event to get the current element with this code though (untested)
var all = document.getElementsByTagName('*');
for (var i = 0; i < all.length; i++)
all[i].onclick = function(e){
window.selectedElement = all[i];
//preventDefault $ StopBubble &
return false;
}
And it will be stored in selectedElement
Ok try This:
var all = document.getElementsByTagName('*');
for (var i = 0; i < all.length; i++)
all[i].onclick = function(e) {
window.selectedElement = this;
((e && e.stopPropagation && e.stopPropagation()) ||
(window.event && (window.event.cancelBubble = true)));
return false;
}
DEMO: http://jsfiddle.net/HQC6Z/1/
Better yet: http://jsfiddle.net/HQC6Z/
After looking at the other answers, I take back my solution and offer theirs:
How can I get the element in which highlighted text is in?
How can I get the element in which highlighted text is in?

Categories

Resources