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