forked from bitovi/jquerypp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathselection.js
More file actions
228 lines (215 loc) · 7.09 KB
/
selection.js
File metadata and controls
228 lines (215 loc) · 7.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
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 $;
});