1/** 2 * Text selection related functions. 3 */ 4 5/** 6 * selection prototype 7 * 8 * Object that capsulates the selection in a textarea. Returned by DWgetSelection. 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 DWgetSelection(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 // count number of newlines in str to work around stupid IE selection bug 108 var countNL = function(str) { 109 var m = str.split("\r\n"); 110 if (!m || !m.length) return 0; 111 return m.length-1; 112 }; 113 sel.fix = countNL(sel.obj.value.substring(0,sel.start)); 114 115 } 116 return sel; 117} 118 119/** 120 * Set the selection 121 * 122 * You need to get a selection object via DWgetSelection() first, then modify the 123 * start and end properties and pass it back to this function. 124 * 125 * @link http://groups.drupal.org/node/1210 126 * @author Andreas Gohr <andi@splitbrain.org> 127 * @param {selection_class} selection a selection object as returned by DWgetSelection() 128 */ 129function DWsetSelection(selection){ 130 if(document.getSelection){ // FF 131 // what a pleasure in FF ;) 132 selection.obj.setSelectionRange(selection.start,selection.end); 133 if(selection.scroll) selection.obj.scrollTop = selection.scroll; 134 } else if(document.selection) { // IE 135 selection.rangeCopy.collapse(true); 136 selection.rangeCopy.moveStart('character',selection.start - selection.fix); 137 selection.rangeCopy.moveEnd('character',selection.end - selection.start); 138 selection.rangeCopy.select(); 139 } 140} 141 142/** 143 * Inserts the given text at the current cursor position or replaces the current 144 * selection 145 * 146 * @author Andreas Gohr <andi@splitbrain.org> 147 * @param {string} text the new text to be pasted 148 * @param {selection_class} selection selection object returned by DWgetSelection 149 * @param {int} opts.startofs number of charcters at the start to skip from new selection 150 * @param {int} opts.endofs number of characters at the end to skip from new selection 151 * @param {boolean} opts.nosel set true if new text should not be selected 152 */ 153function pasteText(selection,text,opts){ 154 if(!opts) opts = {}; 155 // replace the content 156 157 selection.obj.value = 158 selection.obj.value.substring(0, selection.start) + text + 159 selection.obj.value.substring(selection.end, selection.obj.value.length); 160 161 // set new selection 162 if (is_opera) { 163 // Opera replaces \n by \r\n when inserting text. 164 selection.end = selection.start + text.replace(/\r?\n/g, '\r\n').length; 165 } else { 166 selection.end = selection.start + text.length; 167 } 168 169 // modify the new selection if wanted 170 if(opts.startofs) selection.start += opts.startofs; 171 if(opts.endofs) selection.end -= opts.endofs; 172 173 // no selection wanted? set cursor to end position 174 if(opts.nosel) selection.start = selection.end; 175 176 DWsetSelection(selection); 177} 178 179 180/** 181 * Format selection 182 * 183 * Apply tagOpen/tagClose to selection in textarea, use sampleText instead 184 * of selection if there is none. 185 * 186 * @author Andreas Gohr <andi@splitbrain.org> 187 */ 188function insertTags(textAreaID, tagOpen, tagClose, sampleText){ 189 var txtarea = jQuery('#' + textAreaID)[0]; 190 191 var selection = DWgetSelection(txtarea); 192 var text = selection.getText(); 193 var opts; 194 195 // don't include trailing space in selection 196 if(text.charAt(text.length - 1) == ' '){ 197 selection.end--; 198 text = selection.getText(); 199 } 200 201 if(!text){ 202 // nothing selected, use the sample text and select it 203 text = sampleText; 204 opts = { 205 startofs: tagOpen.length, 206 endofs: tagClose.length 207 }; 208 }else{ 209 // place cursor at the end 210 opts = { 211 nosel: true 212 }; 213 } 214 215 // surround with tags 216 text = tagOpen + text + tagClose; 217 218 // do it 219 pasteText(selection,text,opts); 220} 221 222/** 223 * Wraps around pasteText() for backward compatibility 224 * 225 * @author Andreas Gohr <andi@splitbrain.org> 226 */ 227function insertAtCarret(textAreaID, text){ 228 var txtarea = jQuery('#' + textAreaID)[0]; 229 var selection = DWgetSelection(txtarea); 230 pasteText(selection,text,{nosel: true}); 231} 232