xref: /dokuwiki/lib/scripts/textselection.js (revision 618191d008b98cb421694c541145c863d7b300ce)
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