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