selection.js | |
---|---|
(function ($) {
var getWindow = function (element) {
return element ? element.ownerDocument.defaultView || element.ownerDocument.parentWindow : window
}, | |
A helper that uses range to abstract out getting the current start and endPos. | getElementsSelection = function (el, win) { |
get a copy of the current range and a range that spans the element | var current = $.Range.current(el).clone(),
entireElement = $.Range(el).select(el); |
if there is no overlap, there is nothing selected | if (!current.overlaps(entireElement)) {
return null;
} |
if the current range starts before our element | if (current.compare("START_TO_START", entireElement) < 1) { |
the selection within the element begins at 0 | startPos = 0; |
move the current range to start at our element | current.move("START_TO_START", entireElement);
} else { |
Make a copy of the element's range. Move it's end to the start of the selected range The length of the copy is the start of the selected range. | fromElementToCurrent = entireElement.clone();
fromElementToCurrent.move("END_TO_START", current);
startPos = fromElementToCurrent.toString().length
} |
If the current range ends after our element | if (current.compare("END_TO_END", entireElement) >= 0) { |
the end position is the last character | endPos = entireElement.toString().length
} else { |
otherwise, it's the start position plus the current range TODO: this doesn't seem like it works if current extends to the left of the element. | endPos = startPos + current.toString().length
}
return {
start: startPos,
end: endPos,
width: endPos - startPos
};
}, |
Text selection works differently for selection in an input vs normal html elements like divs, spans, and ps. This function branches between the various methods of getting the selection. | getSelection = function (el) {
var win = getWindow(el); |
| if (el.selectionStart !== undefined) {
if (document.activeElement && document.activeElement != el && el.selectionStart == el.selectionEnd && el.selectionStart == 0) {
return {
start: el.value.length,
end: el.value.length,
width: 0
};
}
return {
start: el.selectionStart,
end: el.selectionEnd,
width: el.selectionEnd - el.selectionStart
};
} |
getSelection means a 'normal' element in a standards browser. | else if (win.getSelection) {
return getElementsSelection(el, win)
} else { |
IE will freak out, where there is no way to detect it, so we provide a callback if it does. | try { |
The following typically works for input elements in IE: | if (el.nodeName.toLowerCase() == 'input') {
var real = getWindow(el).document.selection.createRange(),
r = el.createTextRange();
r.setEndPoint("EndToStart", real);
var start = r.text.length
return {
start: start,
end: start + real.text.length,
width: real.text.length
}
} |
This works on textareas and other elements | else {
var res = getElementsSelection(el, win)
if (!res) {
return res;
} |
we have to clean up for ie's textareas which don't count for newlines correctly | var current = $.Range.current().clone(),
r2 = current.clone().collapse().range,
r3 = current.clone().collapse(false).range;
r2.moveStart('character', -1)
r3.moveStart('character', -1) |
if we aren't at the start, but previous is empty, we are at start of newline | if (res.startPos != 0 && r2.text == "") {
res.startPos += 2;
} |
do a similar thing for the end of the textarea | if (res.endPos != 0 && r3.text == "") {
res.endPos += 2;
}
return res
}
} catch (e) {
return {
start: el.value.length,
end: el.value.length,
width: 0
};
}
}
}, |
Selects text within an element. Depending if it's a form element or not, or a standards based browser or not, we do different things. | select = function (el, start, end) {
var win = getWindow(el); |
IE behaves bad even if it sorta supports getSelection so we have to try the IE methods first. barf. | if (el.setSelectionRange) {
if (end === undefined) {
el.focus();
el.setSelectionRange(start, start);
} else {
el.select();
el.selectionStart = start;
el.selectionEnd = end;
}
} else if (el.createTextRange) {
var r = el.createTextRange();
r.moveStart('character', start);
end = end || start;
r.moveEnd('character', end - el.value.length);
r.select();
} else if (win.getSelection) {
var doc = win.document,
sel = win.getSelection(),
range = doc.createRange(),
ranges = [start, end !== undefined ? end : start];
getCharElement([el], ranges);
range.setStart(ranges[0].el, ranges[0].count);
range.setEnd(ranges[1].el, ranges[1].count); |
removeAllRanges is necessary for webkit | sel.removeAllRanges();
sel.addRange(range);
} else if (win.document.body.createTextRange) { //IE's weirdness
var range = document.body.createTextRange();
range.moveToElementText(el);
range.collapse()
range.moveStart('character', start)
range.moveEnd('character', end !== undefined ? end : start)
range.select();
}
}, |
If one of the range values is within start and len, replace the range value with the element and its offset. | replaceWithLess = function (start, len, range, el) {
if (typeof range[0] === 'number' && range[0] < len) {
range[0] = {
el: el,
count: range[0] - start
};
}
if (typeof range[1] === 'number' && range[1] <= len) {
range[1] = {
el: el,
count: range[1] - start
};
}
},
getCharElement = function (elems, range, len) {
var elem, start;
len = len || 0;
for (var i = 0; elems[i]; i++) {
elem = elems[i]; |
Get the text from text nodes and CDATA nodes | if (elem.nodeType === 3 || elem.nodeType === 4) {
start = len
len += elem.nodeValue.length; |
check if len is now greater than what's in counts | replaceWithLess(start, len, range, elem) |
Traverse everything else, except comment nodes | } else if (elem.nodeType !== 8) {
len = getCharElement(elem.childNodes, range, len);
}
}
return len;
};
$.fn.selection = function (start, end) {
if (start !== undefined) {
return this.each(function () {
select(this, start, end)
})
} else {
return getSelection(this[0])
}
}; |
for testing | $.fn.selection.getCharElement = getCharElement;
return $;
})(jQuery);
|