doevisualizations
Version:
Data Visualization Library based on RequireJS and D3.js (v4+)
229 lines (215 loc) • 7.26 kB
JavaScript
steal('jquery','jquerypp/dom/range',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);
// `selectionStart` means this is an input element in a standards browser.
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;
};
/**
* @parent jQuery.selection
* @function jQuery.fn.selection
* @hide
*
* Set or retrieve the currently selected text range. It works on all elements:
*
* $('#text').selection(8, 12)
* $('#text').selection() // -> { start : 8, end : 12, width: 4 }
*
* @param {Number} [start] Start position of the selection range
* @param {Number} [end] End position of the selection range
* @return {Object|jQuery} Returns either the jQuery object when setting the selection or
* an object containing
*
* - __start__ - The number of characters from the start of the element to the start of the selection.
* - __end__ - The number of characters from the start of the element to the end of the selection.
* - __width__ - The width of the selection range.
*
* when no arguments are passed.
*/
$.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 $;
});