1/** 2 * Text selection related functions. 3 */ 4 5/** 6 * selection prototype 7 * 8 * Object that capsulates the selection in a textarea. Returned by getSelection. 9 * 10 * @author Andreas Gohr <andi@splitbrain.org> 11 */ 12function selection_class(){ 13 this.start = 0; 14 this.end = 0; 15 this.obj = null; 16 this.rangeCopy = null; 17 this.scroll = 0; 18 this.fix = 0; 19 20 this.getLength = function(){ 21 return this.end - this.start; 22 }; 23 24 this.getText = function(){ 25 if(!this.obj) return ''; 26 return this.obj.value.substring(this.start,this.end); 27 }; 28} 29 30/** 31 * Get current selection/cursor position in a given textArea 32 * 33 * @link http://groups.drupal.org/node/1210 34 * @author Andreas Gohr <andi@splitbrain.org> 35 * @link http://linebyline.blogspot.com/2006/11/textarea-cursor-position-in-internet.html 36 * @returns object - a selection object 37 */ 38function getSelection(textArea) { 39 var sel = new selection_class(); 40 41 sel.obj = textArea; 42 sel.start = textArea.value.length; 43 sel.end = textArea.value.length; 44 textArea.focus(); 45 if(document.getSelection) { // Mozilla et al. 46 sel.start = textArea.selectionStart; 47 sel.end = textArea.selectionEnd; 48 sel.scroll = textArea.scrollTop; 49 } else if(document.selection) { // MSIE 50 /* 51 * This huge lump of code is neccessary to work around two MSIE bugs: 52 * 53 * 1. Selections trim newlines at the end of the code 54 * 2. Selections count newlines as two characters 55 */ 56 57 // The current selection 58 sel.rangeCopy = document.selection.createRange().duplicate(); 59 if (textArea.tagName === 'INPUT') { 60 var before_range = textArea.createTextRange(); 61 before_range.expand('textedit'); // Selects all the text 62 } else { 63 var before_range = document.body.createTextRange(); 64 before_range.moveToElementText(textArea); // Selects all the text 65 } 66 before_range.setEndPoint("EndToStart", sel.rangeCopy); // Moves the end where we need it 67 68 var before_finished = false, selection_finished = false; 69 var before_text, selection_text; 70 // Load the text values we need to compare 71 before_text = before_range.text; 72 selection_text = sel.rangeCopy.text; 73 74 sel.start = before_text.length; 75 sel.end = sel.start + selection_text.length; 76 77 // Check each range for trimmed newlines by shrinking the range by 1 character and seeing 78 // if the text property has changed. If it has not changed then we know that IE has trimmed 79 // a \r\n from the end. 80 do { 81 if (!before_finished) { 82 if (before_range.compareEndPoints("StartToEnd", before_range) == 0) { 83 before_finished = true; 84 } else { 85 before_range.moveEnd("character", -1); 86 if (before_range.text == before_text) { 87 sel.start += 2; 88 sel.end += 2; 89 } else { 90 before_finished = true; 91 } 92 } 93 } 94 if (!selection_finished) { 95 if (sel.rangeCopy.compareEndPoints("StartToEnd", sel.rangeCopy) == 0) { 96 selection_finished = true; 97 } else { 98 sel.rangeCopy.moveEnd("character", -1); 99 if (sel.rangeCopy.text == selection_text) { 100 sel.end += 2; 101 } else { 102 selection_finished = true; 103 } 104 } 105 } 106 } while ((!before_finished || !selection_finished)); 107 108 109 // count number of newlines in str to work around stupid IE selection bug 110 var countNL = function(str) { 111 var m = str.split("\r\n"); 112 if (!m || !m.length) return 0; 113 return m.length-1; 114 }; 115 sel.fix = countNL(sel.obj.value.substring(0,sel.start)); 116 117 } 118 return sel; 119} 120 121/** 122 * Set the selection 123 * 124 * You need to get a selection object via getSelection() first, then modify the 125 * start and end properties and pass it back to this function. 126 * 127 * @link http://groups.drupal.org/node/1210 128 * @author Andreas Gohr <andi@splitbrain.org> 129 * @param object selection - a selection object as returned by getSelection() 130 */ 131function setSelection(selection){ 132 if(document.getSelection){ // FF 133 // what a pleasure in FF ;) 134 selection.obj.setSelectionRange(selection.start,selection.end); 135 if(selection.scroll) selection.obj.scrollTop = selection.scroll; 136 } else if(document.selection) { // IE 137 selection.rangeCopy.collapse(true); 138 selection.rangeCopy.moveStart('character',selection.start - selection.fix); 139 selection.rangeCopy.moveEnd('character',selection.end - selection.start); 140 selection.rangeCopy.select(); 141 } 142} 143 144/** 145 * Inserts the given text at the current cursor position or replaces the current 146 * selection 147 * 148 * @author Andreas Gohr <andi@splitbrain.org> 149 * @param string text - the new text to be pasted 150 * @param objct selecttion - selection object returned by getSelection 151 * @param int opts.startofs - number of charcters at the start to skip from new selection 152 * @param int opts.endofs - number of characters at the end to skip from new selection 153 * @param bool opts.nosel - set true if new text should not be selected 154 */ 155function pasteText(selection,text,opts){ 156 if(!opts) opts = {}; 157 // replace the content 158 159 selection.obj.value = 160 selection.obj.value.substring(0, selection.start) + text + 161 selection.obj.value.substring(selection.end, selection.obj.value.length); 162 163 // set new selection 164 if (is_opera) { 165 // Opera replaces \n by \r\n when inserting text. 166 selection.end = selection.start + text.replace(/\r?\n/g, '\r\n').length; 167 } else { 168 selection.end = selection.start + text.length; 169 } 170 171 // modify the new selection if wanted 172 if(opts.startofs) selection.start += opts.startofs; 173 if(opts.endofs) selection.end -= opts.endofs; 174 175 // no selection wanted? set cursor to end position 176 if(opts.nosel) selection.start = selection.end; 177 178 setSelection(selection); 179} 180 181 182/** 183 * Format selection 184 * 185 * Apply tagOpen/tagClose to selection in textarea, use sampleText instead 186 * of selection if there is none. 187 * 188 * @author Andreas Gohr <andi@splitbrain.org> 189 */ 190function insertTags(textAreaID, tagOpen, tagClose, sampleText){ 191 var txtarea = $(textAreaID); 192 193 var selection = getSelection(txtarea); 194 var text = selection.getText(); 195 var opts; 196 197 // don't include trailing space in selection 198 if(text.charAt(text.length - 1) == ' '){ 199 selection.end--; 200 text = selection.getText(); 201 } 202 203 if(!text){ 204 // nothing selected, use the sample text and select it 205 text = sampleText; 206 opts = { 207 startofs: tagOpen.length, 208 endofs: tagClose.length 209 }; 210 }else{ 211 // place cursor at the end 212 opts = { 213 nosel: true 214 }; 215 } 216 217 // surround with tags 218 text = tagOpen + text + tagClose; 219 220 // do it 221 pasteText(selection,text,opts); 222} 223 224/** 225 * Wraps around pasteText() for backward compatibility 226 * 227 * @author Andreas Gohr <andi@splitbrain.org> 228 */ 229function insertAtCarret(textAreaID, text){ 230 var txtarea = $(textAreaID); 231 var selection = getSelection(txtarea); 232 pasteText(selection,text,{nosel: true}); 233} 234 235