Is it possible to find out if an HTMLElement is totally enclosed within the selection?
I have a scenario where user selects some text in a HTML editor and applies some custom style from a list. Now I need to change the class attribute of each span element that is enclosed in that selection and surrounding the selection with a new span with the selected style.
Am able to find out if a particular span element is in selection by using DOM Range's compareBoundaryPoints method in firefox and safari but it will not work for IE.
Is there any way to find out if an element is totally enclosed with in the selected range for IE?
Thanks
Kapil
As #standardModel says, Rangy gives you full* DOM Range support in IE and has a helpful getNodes() method that you could use:
var sel = rangy.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0);
var spans = range.getNodes([1], function(node) {
return node.nodeName.toLowerCase() == "span" && range.containsNode(node);
});
// Do stuff with spans here
}
If you'd rather not use something as bulky as Rangy, the following function will tell you if an element is completely selected:
function isSelected(el) {
if (window.getSelection) {
var sel = window.getSelection();
var elRange = document.createRange();
elRange.selectNodeContents(el);
for (var i = 0, range; i < sel.rangeCount; ++i) {
range = sel.getRangeAt(i);
if (range.compareBoundaryPoints(range.START_TO_START, elRange) <= 0
&& range.compareBoundaryPoints(range.END_TO_END, elRange) >= 0) {
return true;
}
}
} else if (document.selection && document.selection.type == "Text") {
var textRange = document.selection.createRange();
var elTextRange = textRange.duplicate();
elTextRange.moveToElementText(el);
return textRange.inRange(elTextRange);
}
return false;
}
jsFiddle example: http://jsfiddle.net/54eGr/1/
(*) Apart from handling Range updates under DOM mutation
You may want to take a look at Rangy. This makes xbrowser Ranges and Selections a lot easier.
Related
I have found a code snippet (can't remember where), and it's working fine - almost :-)
The problem is, that it copies the selection no matter where the selection is made on the entire website, and it must only copy the selection if it is in a specific div - but how is that done?
function getHTMLOfSelection () {
var range;
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
else if (window.getSelection) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
range = selection.getRangeAt(0);
var clonedSelection = range.cloneContents();
var div = document.createElement('div');
div.appendChild(clonedSelection);
return div.innerHTML;
} else {
return '';
}
} else {
return '';
}
}
$(document).ready(function() {
$("#test").click(function() {
var kopitekst = document.getElementById("replytekst");
var kopitjek=getHTMLOfSelection(kopitekst);
if (kopitjek=='')
{
alert("Please select some content");
}
else
{
alert(kopitjek);
}
});
});
I have made a Jsfiddle
This is my first post here. Hopefully I done it right :-)
That's because it checks the entire document with:
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
Not a specific section. If you want to check specific sections for selected text, you need to identify that you are searching for them in the search selection, something that nails your range down to a particular div:
range = $('#replytekst');
Specify a particular DOM element instead of using document object.
var oDiv = document.getElementById( 'selDiv' );
then use
if ( oDiv.selection && oDiv.selection.createRange ) {
range = oDiv.selection.createRange();
return range.htmlText;
}
You need to check if the section contains the selection. This is separate from getting the selection. There is a method for doing this in this answer: How to know if selected text is inside a specific div
I've updated your fiddle
Basically you need to check the id of the parent/ascendant of the selected text node.
selection.baseNode.parentElement.id or selection.baseNode.parentElement.parentElement.id will give you that.
Edit: I've thought of another, somewhat hack-y, way of doing it.
If
kopitekst.innerHTML.indexOf(kopitjek) !== -1
gives true, you've selected the right text.
DEMO1
DEMO2
(these work in Chrome and Firefox, but you might want to restructure the getHTMLOfSelection function a little)
If it possible for you I recommend to use rangy framework. Then your code might look like this:
// get the selection
var sel = rangy.getSelection();
var ranges = sel.getAllRanges();
if (!sel.toString() || !sel.toString().length)
return;
// create range for element, where selection is allowed
var cutRange = rangy.createRange();
cutRange.selectNode(document.getElementById("replytekst"));
// make an array of intersections of current selection ranges and the cutRange
var goodRanges = [];
$.each(ranges, function(j, tr) {
var rr = cutRange.intersection(tr);
if (rr)
goodRanges.push(rr);
});
sel.setRanges(goodRanges);
// do what you want with corrected selection
alert(sel.toString());
// release
sel.detach();
In this code if text was selected in your specific div then it will be kept, if there was selection where other elements take part too, these selection ranges will be cut off.
I know how to set an <a /> tag with the href attribute in a contenteditable like this:
execCommand("CreateLink", false, "#jumpmark");
which will result in
selection
However I cannot figure out how to set an anchor name instead of the href.
This is my desired result:
<a name="jumpmark">selection</a>
Can anyone help me?
Side notes: I am using jQuery and Rangy as libraries, however I would prefer a solution that works directly with execCommand.
Update: Here's a jsfiddle: http://jsfiddle.net/fjYHr/ Select some text and click the button. All I want is that with the button click a link is inserted with a name attribute set instead of the href.
You could use something like the following, which is adapted from the pasteHtmlAtCaret() function from this answer of mine:
Demo: http://jsfiddle.net/F8Zny/
Code:
function surroundSelectedText(element) {
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
element.appendChild( document.createTextNode(range.toString()) );
range.deleteContents();
range.insertNode(element);
// Preserve the selection
range = range.cloneRange();
range.setStartAfter(element);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
} else if (document.selection && document.selection.type != "Control") {
// IE < 9
var selRange = document.selection.createRange();
element.appendChild( document.createTextNode(selRange.text) );
selRange.pasteHTML(element.outerHTML);
}
}
If you must use document.execCommand() then you could use the InsertHTML command in non-IE browsers. However, IE does not support it.
document.execCommand("InsertHTML", false, '<a name="jumpmark">selection</a>');
I see you're using Rangy, but I don't how to use it at all. Before I realized what Rangy was, I looked up how to get the current selection. I found a function that gets it and replaces it with a passed in value. I ended up modfiying it, but here it is:
http://jsfiddle.net/fjYHr/1/
$(document).ready(function () {
$("#setlink").click(function () {
replaceSelectedText("jumplink");
});
});
function replaceSelectedText(nameValue) {
var sel, sel2, range;
if (window.getSelection) {
sel = window.getSelection();
sel2 = ""+sel; // Copy selection value
if (sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
var newA = document.createElement("a");
newA.name = nameValue;
newA.innerHTML = sel2;
range.insertNode(newA);
}
} else if (document.selection && document.selection.createRange) {
// Not sure what to do here
range = document.selection.createRange();
var newA = "<a name='" + nameValue.replace(/'/g, "") + "'>" + range.text + "</a>";
range.text = newA;
}
}
Notice how I store the original current selection, then replace it with an <a> element that gets its name set with the passed-in value.
As for the document.selection part (which seems to be used by IE < 9), I'm not 100% sure that the code I provided will work (actually allow HTML in the selection, and not escaping it). But it's my attempt :)
As you've seen execCommand is rather limited in the attributes you can set, as such you cannot set the name attribute using it - only the href.
As you have jQuery set as a tag, you can use that as an alternative:
var $a = $('<a></a>').attr('name', 'jumpmark').appendTo('body');
Update
I need to work on the current selection. Specifically I don't have a jQuery object that I can append to, meaning I don't have a DOM node that I can work on
In this case use a plugin such as Rangy to get the selection which you can then amend with jQuery as required.
I'm looking for function which allows me to build some element before or after selected text. Something similar like this one javascript replace selection all browsers but for adding some content before or after selection instead of replacing it, like after() and before() jQuery methods. Should I use some DOM selection method, if yes which one? Or does exist something easier to carry it out?
Here's a pair of functions to do this.
Live example: http://jsfiddle.net/hjfVw/
Code:
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);
})();
In MSIE:
collapse the given range and the use pasteHTML to insert the element
Others:
Also collapse the given Range and insert the element via insertNode
Both collapse-methods accept an optional argument which defines to where you want to collapse to.
If you want to put the element at the end, collapse to the end, otherwise to the start.
function yourFunction() {
const sel = window.getSelection ? window.getSelection() : document.selection.createRange()
if (!sel) return false
if (sel.getRangeAt) {
const range = sel.getRangeAt(0)
const text = range.toString()
console.log(text)
range.deleteContents()
range.insertNode(document.createTextNode(`before text${text}after`))
} else {
sel.pasteHTML(`[s=спойлер]${sel.htmlText}after`)
}
}
Is there a simple js function I can use to replace the current document's selection with some html of mine?
For instance say the document contains a <p>AHAHAHA</p> somewhere and user selects the 1st "ha" text chunk.
Now I want to replace this with something like: <span><font color="red">hoho</font></span>
When I google for *javascript replace selection * I can't get a simple straightforward answer!
Yes. The following will do it in all major browsers, with an option to select the inserted content afterwards as requested in the comments (although this part is not implemented for IE <= 8):
Live demo: http://jsfiddle.net/bXsWQ/147/
Code:
function replaceSelection(html, selectInserted) {
var sel, range, fragment;
if (typeof window.getSelection != "undefined") {
// IE 9 and other non-IE browsers
sel = window.getSelection();
// Test that the Selection object contains at least one Range
if (sel.getRangeAt && sel.rangeCount) {
// Get the first Range (only Firefox supports more than one)
range = window.getSelection().getRangeAt(0);
range.deleteContents();
// Create a DocumentFragment to insert and populate it with HTML
// Need to test for the existence of range.createContextualFragment
// because it's non-standard and IE 9 does not support it
if (range.createContextualFragment) {
fragment = range.createContextualFragment(html);
} else {
// In IE 9 we need to use innerHTML of a temporary element
var div = document.createElement("div"), child;
div.innerHTML = html;
fragment = document.createDocumentFragment();
while ( (child = div.firstChild) ) {
fragment.appendChild(child);
}
}
var firstInsertedNode = fragment.firstChild;
var lastInsertedNode = fragment.lastChild;
range.insertNode(fragment);
if (selectInserted) {
if (firstInsertedNode) {
range.setStartBefore(firstInsertedNode);
range.setEndAfter(lastInsertedNode);
}
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != "Control") {
// IE 8 and below
range = document.selection.createRange();
range.pasteHTML(html);
}
}
Example:
replaceSelection('<span><font color="red">hoho</font></span>', true);
You can use the Rangy library
http://code.google.com/p/rangy/
You can then do
var sel = rangy.getSelection();
var range = sel.getRangeAt(0);
range.deleteContents();
var node = range.createContextualFragment('<span><font color="red">hoho</font></span>');
range.insertNode(node);
It is possible to get whatever the user has selected with the mouse using Javascript, like this: http://www.motyar.info/2010/02/get-user-selected-text-with-jquery-and.html
My issue is that I do not just need this text, but I also need:
to get the html surrounding this text (eg. if the user selects "hello" and this hello is in the source produced as: "<div><span>hello</span></div>" that is what it should return).
to do the same for graphics
Can anyone guide me through this process, or are there alternatives if this is not possible?
This will do it in all major browsers. There are separate branches for IE and more standards-compliant browsers. In IE, it's slightly easier because the proprietary TextRange object created from the selection has a handy htmlText property. In other browsers, you have to use the cloneContents() method of DOM Range to create a DocumentFragment containing a copy of the selected content and obtain the HTML from this by appending the fragment to an element and returning the element's innerHTML property.
function getSelectionHtml() {
var html = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
html = document.selection.createRange().htmlText;
}
}
return html;
}
alert(getSelectionHtml());
You can also do like this to get user selected HTML
function getUserSelectedHtml() {
const range = window.getSelection().getRangeAt(0);
const node = document.createElement('div');
node.appendChild(range.cloneContents());
return node.innerHTML;
}