1/* 2* $Id: documentselection.js 164 2006-12-30 02:09:25Z wingedfox $ 3* $HeadURL: https://svn.debugger.ru/repos/jslibs/BrowserExtensions/tags/BrowserExtensions.003/documentselection.js $ 4* 5* Class implements cross-browser work with text selection 6* 7* @author Ilya Lebedev 8* @author $Author: wingedfox $ 9* @modified $Date: 2006-12-30 05:09:25 +0300 (Сбт, 30 Дек 2006) $ 10* @version $Rev: 164 $ 11* @license LGPL 12*/ 13 14/* 15* @class DocumentSelection 16*/ 17DocumentSelection = new function () { 18 var self = this; 19 /* 20 * Stores hash of keys, applied to elements 21 * 22 * @type Object 23 * @scope private 24 */ 25 var keys = { 26 'selectionStart' : '__DSselectionStart', 27 'selectionEnd' : '__DSselectionEnd' 28 } 29 /* 30 * Properties to save current scrolling position in Mozilla 31 * 32 * 33 */ 34 var scrollTop, scrollLeft; 35 //--------------------------------------------------------------------------- 36 // SETTERS 37 //--------------------------------------------------------------------------- 38 /** 39 * getSelectionRange wrapper/emulator 40 * adapted version 41 * 42 * @see http://www.bazon.net/mishoo/articles.epl?art_id=1292 43 * @param {HTMLElement} 44 * @param {Number} start position 45 * @param {Number} end position 46 * @param {Boolean} related indicates calculation of range relatively to current start point 47 * @return void 48 * @scope public 49 */ 50 this.setRange = function(el, start, end, related) { 51 /* 52 * set range on relative coordinates 53 */ 54 if (related) { 55 var st = self.getStart(el); 56 end = st+end; 57 start = st+start; 58 } 59 if ('function' == typeof el.setSelectionRange) { 60 /* 61 * for Mozilla 62 */ 63 try {el.setSelectionRange(start, end)} catch (e) {} 64 } else { 65 /* 66 * for IE 67 */ 68 var range; 69 /* 70 * just try to create a range.... 71 */ 72 try { 73 range = el.createTextRange(); 74 } catch(e) { 75 try { 76 range = document.body.createTextRange(); 77 range.moveToElementText(el); 78 } catch(e) { 79 range = false; 80 } 81 } 82 // if cannot create range 83 if (!range) return false; 84 range.collapse(true); 85 86 range.moveStart("character", start); 87 range.moveEnd("character", end - start); 88 range.select(); 89 } 90 self.setCursorPosition(el,start,end); 91 } 92 /** 93 * Set sursor position for supplied child 94 * 95 * @param {HTMLElement} element to set cursor position on 96 * @param {Number} start selection start 97 * @param {Number} end selection end 98 * @scope public 99 */ 100 this.setCursorPosition = function (el,start,end) { 101 el[keys['selectionStart']] = parseInt(start); 102 el[keys['selectionEnd']] = parseInt(end); 103// if (scrollTop) el.scrollTop = scrollTop; 104// if (scrollLeft) el.scrollLeft = scrollLeft; 105// scrollTop = null; 106// scrollLeft= null; 107 } 108 //--------------------------------------------------------------------------- 109 // GETTERS 110 //--------------------------------------------------------------------------- 111 /** 112 * Return contents of the current selection 113 * 114 * @param {HTMLElement} el element to look position on 115 * @return {String} 116 * @scope public 117 */ 118 this.getSelection = function(el) { 119 var s = self.getCursorPosition(el), 120 e = self.getEnd(el); 121 /* 122 * w/o this check content might be duplicated on delete 123 */ 124 if (e<s) e = s; 125 /* 126 * check for IE, because Opera does use \r\n sequence, but calculate positions correctly 127 */ 128 var tmp = document.selection?el.value.replace(/\r/g,""):el.value; 129 return tmp.substring(s,e); 130 } 131 /** 132 * getSelectionStart wrapper/emulator 133 * adapted version 134 * 135 * @see http://www.bazon.net/mishoo/articles.epl?art_id=1292 136 * @param {HTMLElement} el element to calculate end position for 137 * @param {HTMLElement} force force calculation 138 * @return {Number} start position 139 * @scope public 140 */ 141 this.getStart = function (el, force) { 142 var start; 143 /* 144 * for IE 145 */ 146 try { 147 start = Math.abs(document.selection.createRange().moveStart("character", -100000000)); // start 148 if (start>0 || force) { 149 try { 150 var endReal = Math.abs(el.createTextRange().moveEnd("character", -100000000)); 151 /* 152 * calculate node offset 153 */ 154 var r = document.body.createTextRange(); 155 r.moveToElementText(el); 156 var sTest = Math.abs(r.moveStart("character", -100000000)); 157 var eTest = Math.abs(r.moveEnd("character", -100000000)); 158 /* 159 * test for the TEXTAREA's dumb behavior 160 */ 161 if (el.tagName.toLowerCase() != 'input' && eTest - endReal == sTest) { 162 start -= sTest; 163 } 164 } catch(err) {} 165 } 166 } catch (e) { 167 /* 168 * for Mozilla 169 */ 170 try { start = el.selectionStart } catch (e) { start = -1 } 171 } 172 return start<1?(start==0&&force?0:(parseInt(el[keys['selectionStart']])?parseInt(el[keys['selectionStart']]):0)):start; 173 } 174 /* 175 * getSelectionStart wrapper/emulator 176 * adapted version 177 * 178 * @see http://www.bazon.net/mishoo/articles.epl?art_id=1292 179 * @param {HTMLElement} el element to calculate end position for 180 * @param {HTMLElement} force force calculation 181 * @return {Number} start position 182 * @scope public 183 */ 184 this.getEnd = function (el,force) { 185 var end; 186 /* 187 * for IE 188 */ 189 try { 190 end = Math.abs(document.selection.createRange().moveEnd("character", -100000000)); // end 191 if (end>0 || force) { 192 try { 193 var endReal = Math.abs(el.createTextRange().moveEnd("character", -100000000)); 194 /* 195 * calculate node offset 196 */ 197 var r = document.body.createTextRange(); 198 r.moveToElementText(el); 199 var sTest = Math.abs(r.moveStart("character", -100000000)); 200 var eTest = Math.abs(r.moveEnd("character", -100000000)); 201 /* 202 * test for the TEXTAREA's dumb behavior 203 */ 204 if (el.tagName.toLowerCase() != 'input' && eTest - endReal == sTest) { 205 end -= sTest; 206 } 207 } catch(err) {} 208 } 209 } catch (e) { 210 /* 211 * for Mozilla 212 */ 213 try { end = el.selectionEnd } catch (e) { end = -1 } 214 } 215 return end<1?(end==0&&force?0:(parseInt(el[keys['selectionEnd']])?parseInt(el[keys['selectionEnd']]):0)):end; 216 } 217 /* 218 * Return cursor position for supplied field 219 * 220 * @param {HTMLElement} element to get cursor position from 221 * @return {Number} position 222 * @scope public 223 */ 224 this.getCursorPosition = function (el) { 225// scrollTop = el.scrollTop; 226// scrollLeft = el.scrollLeft; 227 228 return self.getStart(el); 229 } 230 //--------------------------------------------------------------------------- 231 // MICS FUNCTIONS 232 //--------------------------------------------------------------------------- 233 /* 234 * Used to save cursor position on click. 235 * 236 * @param {MouseEvent} click event 237 * @scope protected 238 */ 239 this.saveCursorPosition = function (e) { 240 var el = e.srcElement||e.target; 241 if (!el || el.tagName.toLowerCase() == 'select') return false; 242 self.setCursorPosition(el,self.getStart(el,true),self.getEnd(el,true)); 243 } 244 /* 245 * Insert text at cursor position 246 * 247 * @param {HTMLElement} text field to insert text 248 * @param {String} text to insert 249 * @scope public 250 */ 251 this.insertAtCursor = function (fld, val) { 252 var r = self.getCursorPosition(fld); 253 /* 254 * check for IE, because Opera does use \r\n sequence, but calculate positions correctly 255 */ 256 var tmp = document.selection?fld.value.replace(/\r/g,""):fld.value; 257 fld.value = tmp.substring(0, r)+val+tmp.substring(r,tmp.length); 258 self.setRange(fld,r+val.length,r+val.length); 259 } 260 /* 261 * Deletes char at cursor position 262 * 263 * @param {HTMLElement} text field to delete text 264 * @param {Boolean} delete text before (backspace) or after (del) cursor 265 * @scope public 266 */ 267 this.deleteAtCursor = function (fld, after) { 268 if (!after) after = false; 269 var r = self.getCursorPosition(fld), 270 e = self.getEnd(fld); 271 /* 272 * w/o this check content might be duplicated on delete 273 */ 274 if (e<r) e = r; 275 if (r==e) { 276 r=after?r:r-1<0?0:r-1; 277 e=after?e+1:e; 278 } 279 /* 280 * check for IE, because Opera does use \r\n sequence, but calculate positions correctly 281 */ 282 var tmp = document.selection?fld.value.replace(/\r/g,""):fld.value; 283 fld.value = tmp.substring(0, r)+tmp.substring(e,tmp.length); 284 self.setRange(fld, r, r); 285 } 286 /** 287 * Removes the selection, if available 288 * 289 * @param {HTMLElement} fld field to delete text from 290 * @scope public 291 */ 292 this.deleteSelection = function (fld) { 293 var r = self.getCursorPosition(fld), 294 e = self.getEnd(fld); 295 if (r==e) return; 296 /* 297 * check for IE, because Opera does use \r\n sequence, but calculate positions correctly 298 */ 299 var tmp = document.selection?fld.value.replace(/\r/g,""):fld.value; 300 fld.value = tmp.substring(0, r)+tmp.substring(e,tmp.length); 301 self.setRange(fld, r, r); 302 } 303} 304/* 305* Add cursor position saving 306*/ 307if (document.attachEvent) { 308 document.attachEvent('onmouseup', DocumentSelection.saveCursorPosition); 309 document.attachEvent('onkeyup', DocumentSelection.saveCursorPosition); 310} else if (document.addEventListener) { 311 document.addEventListener('mouseup', DocumentSelection.saveCursorPosition,false); 312 document.addEventListener('keyup', DocumentSelection.saveCursorPosition,false); 313} 314