27d5fc790cab38bf221c901b2873c6e289ed6298
2 * These plugins provide extra functionality for interaction with textareas.
4 * - encapsulateSelection: Ported from skins/common/edit.js by Trevor Parscal
5 * © 2009 Wikimedia Foundation (GPLv2) - http://www.wikimedia.org
6 * - getCaretPosition, scrollToCaretPosition: Ported from Wikia's LinkSuggest extension
7 * https://github.com/Wikia/app/blob/c0cd8b763/extensions/wikia/LinkSuggest/js/jquery.wikia.linksuggest.js
8 * © 2010 Inez Korczyński (korczynski@gmail.com) & Jesús Martínez Novo (martineznovo@gmail.com) (GPLv2)
12 * @class jQuery.plugin.textSelection
14 * Do things to the selection in a `<textarea>`, or a textarea-like editable element.
16 * var $textbox = $( '#wpTextbox1' );
17 * $textbox.textSelection( 'setContents', 'This is bold!' );
18 * $textbox.textSelection( 'setSelection', { start: 8, end: 12 } );
19 * $textbox.textSelection( 'encapsulateSelection', { pre: '<b>', post: '</b>' } );
20 * // Result: Textbox contains 'This is <b>bold</b>!', with cursor before the '!'
24 * Do things to the selection in a `<textarea>`, or a textarea-like editable element.
26 * var $textbox = $( '#wpTextbox1' );
27 * $textbox.textSelection( 'setContents', 'This is bold!' );
28 * $textbox.textSelection( 'setSelection', { start: 8, end: 12 } );
29 * $textbox.textSelection( 'encapsulateSelection', { pre: '<b>', post: '</b>' } );
30 * // Result: Textbox contains 'This is <b>bold</b>!', with cursor before the '!'
32 * @param {string} command Command to execute, one of:
34 * - {@link jQuery.plugin.textSelection#getContents getContents}
35 * - {@link jQuery.plugin.textSelection#setContents setContents}
36 * - {@link jQuery.plugin.textSelection#getSelection getSelection}
37 * - {@link jQuery.plugin.textSelection#encapsulateSelection encapsulateSelection}
38 * - {@link jQuery.plugin.textSelection#getCaretPosition getCaretPosition}
39 * - {@link jQuery.plugin.textSelection#setSelection setSelection}
40 * - {@link jQuery.plugin.textSelection#scrollToCaretPosition scrollToCaretPosition}
41 * - {@link jQuery.plugin.textSelection#register register}
42 * - {@link jQuery.plugin.textSelection#unregister unregister}
43 * @param {Mixed} [options] Options to pass to the command
44 * @return {Mixed} Depending on the command
46 $.fn
.textSelection = function ( command
, options
) {
53 * Get the contents of the textarea.
58 getContents: function () {
63 * Set the contents of the textarea, replacing anything that was there before.
66 * @param {string} content
68 setContents: function ( content
) {
73 * Get the currently selected text in this textarea.
78 getSelection: function () {
85 retval
= el
.value
.substring( el
.selectionStart
, el
.selectionEnd
);
92 * Insert text at the beginning and end of a text selection, optionally
93 * inserting text at the caret when selection is empty.
95 * Also focusses the textarea.
98 * @param {Object} [options]
99 * @param {string} [options.pre] Text to insert before the cursor/selection
100 * @param {string} [options.peri] Text to insert between pre and post and select afterwards
101 * @param {string} [options.post] Text to insert after the cursor/selection
102 * @param {boolean} [options.ownline=false] Put the inserted text on a line of its own
103 * @param {boolean} [options.replace=false] If there is a selection, replace it with peri
104 * instead of leaving it alone
105 * @param {boolean} [options.selectPeri=true] Select the peri text if it was inserted (but not
106 * if there was a selection and replace==false, or if splitlines==true)
107 * @param {boolean} [options.splitlines=false] If multiple lines are selected, encapsulate
108 * each line individually
109 * @param {number} [options.selectionStart] Position to start selection at
110 * @param {number} [options.selectionEnd=options.selectionStart] Position to end selection at
114 encapsulateSelection: function ( options
) {
115 return this.each( function () {
116 var selText
, scrollTop
, insertText
,
117 isSample
, startPos
, endPos
,
123 * Check if the selected text is the same as the insert text
125 function checkSelectedText() {
127 selText
= options
.peri
;
129 } else if ( options
.replace
) {
130 selText
= options
.peri
;
132 while ( selText
.charAt( selText
.length
- 1 ) === ' ' ) {
133 // Exclude ending space char
134 selText
= selText
.slice( 0, -1 );
137 while ( selText
.charAt( 0 ) === ' ' ) {
138 // Exclude prepending space char
139 selText
= selText
.slice( 1 );
147 * Do the splitlines stuff.
149 * Wrap each line of the selected text with pre and post
151 * @param {string} selText Selected text
152 * @param {string} pre Text before
153 * @param {string} post Text after
154 * @return {string} Wrapped text
156 function doSplitLines( selText
, pre
, post
) {
159 selTextArr
= selText
.split( '\n' );
160 for ( i
= 0; i
< selTextArr
.length
; i
++ ) {
161 insertText
+= pre
+ selTextArr
[ i
] + post
;
162 if ( i
!== selTextArr
.length
- 1 ) {
171 if ( options
.selectionStart
!== undefined ) {
172 $( this ).textSelection( 'setSelection', { start
: options
.selectionStart
, end
: options
.selectionEnd
} );
175 selText
= $( this ).textSelection( 'getSelection' );
176 startPos
= this.selectionStart
;
177 endPos
= this.selectionEnd
;
178 scrollTop
= this.scrollTop
;
181 options
.selectionStart
!== undefined &&
182 endPos
- startPos
!== options
.selectionEnd
- options
.selectionStart
184 // This means there is a difference in the selection range returned by browser and what we passed.
185 // This happens for Chrome in the case of composite characters. Ref bug #30130
186 // Set the startPos to the correct position.
187 startPos
= options
.selectionStart
;
190 insertText
= pre
+ selText
+ post
;
191 if ( options
.splitlines
) {
192 insertText
= doSplitLines( selText
, pre
, post
);
194 if ( options
.ownline
) {
195 if ( startPos
!== 0 && this.value
.charAt( startPos
- 1 ) !== '\n' && this.value
.charAt( startPos
- 1 ) !== '\r' ) {
196 insertText
= '\n' + insertText
;
199 if ( this.value
.charAt( endPos
) !== '\n' && this.value
.charAt( endPos
) !== '\r' ) {
204 this.value
= this.value
.slice( 0, startPos
) + insertText
+
205 this.value
.slice( endPos
);
206 // Setting this.value scrolls the textarea to the top, restore the scroll position
207 this.scrollTop
= scrollTop
;
208 if ( isSample
&& options
.selectPeri
&& ( !options
.splitlines
|| ( options
.splitlines
&& selText
.indexOf( '\n' ) === -1 ) ) ) {
209 this.selectionStart
= startPos
+ pre
.length
;
210 this.selectionEnd
= startPos
+ pre
.length
+ selText
.length
;
212 this.selectionStart
= startPos
+ insertText
.length
;
213 this.selectionEnd
= this.selectionStart
;
215 $( this ).trigger( 'encapsulateSelection', [ options
.pre
, options
.peri
, options
.post
, options
.ownline
,
216 options
.replace
, options
.splitlines
] );
221 * Get the current cursor position (in UTF-16 code units) in a textarea.
224 * @param {Object} [options]
225 * @param {Object} [options.startAndEnd=false] Return range of the selection rather than just start
227 * - When `startAndEnd` is `false`: number
228 * - When `startAndEnd` is `true`: array with two numbers, for start and end of selection
230 getCaretPosition: function ( options
) {
231 function getCaret( e
) {
235 caretPos
= e
.selectionStart
;
236 endPos
= e
.selectionEnd
;
238 return options
.startAndEnd
? [ caretPos
, endPos
] : caretPos
;
240 return getCaret( this.get( 0 ) );
244 * Set the current cursor position (in UTF-16 code units) in a textarea.
247 * @param {Object} [options]
248 * @param {number} options.start
249 * @param {number} [options.end=options.start]
253 setSelection: function ( options
) {
254 return this.each( function () {
255 // Opera 9.0 doesn't allow setting selectionStart past
256 // selectionEnd; any attempts to do that will be ignored
257 // Make sure to set them in the right order
258 if ( options
.start
> this.selectionEnd
) {
259 this.selectionEnd
= options
.end
;
260 this.selectionStart
= options
.start
;
262 this.selectionStart
= options
.start
;
263 this.selectionEnd
= options
.end
;
269 * Scroll a textarea to the current cursor position. You can set the cursor
270 * position with #setSelection.
273 * @param {Object} [options]
274 * @param {string} [options.force=false] Whether to force a scroll even if the caret position
275 * is already visible.
279 scrollToCaretPosition: function ( options
) {
280 return this.each( function () {
282 clientHeight
= this.clientHeight
,
283 origValue
= this.value
,
284 origSelectionStart
= this.selectionStart
,
285 origSelectionEnd
= this.selectionEnd
,
286 origScrollTop
= this.scrollTop
,
289 // Delete all text after the selection and scroll the textarea to the end.
290 // This ensures the selection is visible (aligned to the bottom of the textarea).
291 // Then restore the text we deleted without changing scroll position.
292 this.value
= this.value
.slice( 0, this.selectionEnd
);
293 this.scrollTop
= this.scrollHeight
;
294 // Chrome likes to adjust scroll position when changing value, so save and re-set later.
295 // Note that this is not equal to scrollHeight, it's scrollHeight minus clientHeight.
296 calcScrollTop
= this.scrollTop
;
297 this.value
= origValue
;
298 this.selectionStart
= origSelectionStart
;
299 this.selectionEnd
= origSelectionEnd
;
301 if ( !options
.force
) {
302 // Check if all the scrolling was unnecessary and if so, restore previous position.
303 // If the current position is no more than a screenful above the original,
304 // the selection was previously visible on the screen.
305 if ( calcScrollTop
< origScrollTop
&& origScrollTop
- calcScrollTop
< clientHeight
) {
306 calcScrollTop
= origScrollTop
;
310 this.scrollTop
= calcScrollTop
;
312 $( this ).trigger( 'scrollToPosition' );
320 * Register an alternative textSelection API for this element.
323 * @param {Object} functions Functions to replace. Keys are command names (as in #textSelection,
324 * except 'register' and 'unregister'). Values are functions to execute when a given command is
331 * Unregister the alternative textSelection API for this element (see #register).
336 alternateFn
= $( this ).data( 'jquery.textSelection' );
340 // case 'getContents': // no params
341 // case 'setContents': // no params with defaults
342 // case 'getSelection': // no params
343 case 'encapsulateSelection':
344 options
= $.extend( {
352 selectionStart
: undefined,
353 selectionEnd
: undefined
356 case 'getCaretPosition':
357 options
= $.extend( {
362 options
= $.extend( {
366 if ( options
.end
=== undefined ) {
367 options
.end
= options
.start
;
370 case 'scrollToCaretPosition':
371 options
= $.extend( {
377 throw new Error( 'Another textSelection API was already registered' );
379 $( this ).data( 'jquery.textSelection', options
);
380 // No need to update alternateFn as this command only stores the options.
381 // A command that uses it will set it again.
384 $( this ).removeData( 'jquery.textSelection' );
388 retval
= ( alternateFn
&& alternateFn
[ command
] || fn
[ command
] ).call( this, options
);
397 * @method textSelection
398 * @inheritdoc jQuery.plugin.textSelection#textSelection