I'm trying to add classes to selected text in a document. The problem is that I don't want the user to be able to select ALL the text on the page (using command+A for example...) So I would like to remove nodes from a range but I don't know how to do that. This page has a lot of text / nested divs so using the added class for checking if it is in an element that is selectable would be very time consuming / slow.
I made a fiddle to explain what I'm trying to do a bit better. http://jsfiddle.net/thomasjonas/BhKFt/20/
If you select all the text in this example the class is added to all the divs. I want it to only apply the css to the divs with the 'yes' class. Is there a nice, non-browser-crashing solution for this?
Thanks in advance!
The following is a little heavy-handed and inefficient because it checks each element with class "yes" intersects the selection. You could improve it by checking in advance whether the selection lies completely within a single element with the "yes" class, for example. It uses Rangy's proprietary intersection()
method of range objects.
Demo: http://jsfiddle.net/timdown/BhKFt/23/
Code:
// getElementsByClassName implementation for browsers without it
// (IE <= 7, for example)
var getElementsByClassName =
(typeof document.documentElement.getElementsByClassName != "undefined") ?
function(el, cssClass) {
return el.getElementsByClassName(cssClass);
} :
function(el, cssClass) {
var allEls = el.getElementsByTagName("*");
var elsWithClass = [];
var classRegex = new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)");
for (var i = 0, len = allEls.length, el; i < len; ++i) {
el = allEls[i];
if (el.className && classRegex.test(el.className)) {
elsWithClass.push(el);
}
}
return elsWithClass;
};
$(document).ready(function(){
rangy.init();
$(document).mouseup(function(){
var sel = rangy.getSelection();
var range = sel.getRangeAt(0);
var classApplier = rangy.createCssClassApplier("tmp");
var els = getElementsByClassName(document.body, "yes");
// Create an array of ranges that represent the intersection of
// the selection with each "yes" element
var rangesWithClass = [];
for (var i = 0, len = els.length, elRange; i < len; ++i) {
if (range.intersectsNode(els[i])) {
elRange = rangy.createRange();
elRange.selectNode(els[i]);
rangesWithClass.push(range.intersection(elRange));
elRange.detach();
}
}
// Apply the class to the ranges obtained in the last step
for (i = 0, len = rangesWithClass.length; i < len; ++i) {
classApplier.applyToRange(rangesWithClass[i]);
rangesWithClass[i].detach();
}
sel.removeAllRanges();
});
});
It may be useful if there was some kind of filtering option to the options object passed into rangy.createCssClassApplier()
. I'll have a think.