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