User:V111P/js/valSel.js

/* valSel.js

* ver. 2013-10

*

* A cross-browser textarea and text input value and selection manipulation

* library plug-in for jQuery

* Home: http://en.wikipedia.org/wiki/User:V111P/js/valSel

*

* This script contains code from Rangy Text Inputs (Version from 5 November 2010),

* Copyright 2010, Tim Down, licensed under the MIT license.

* http://code.google.com/p/rangyinputs/

*

* You can use the code not from Rangy Text Inputs under the CC0 license

*/

(function($) {

var UNDEF = "undefined";

var getSelection, setSelection, collapseSelection;

var inited; // whether init() has already been called or not

var rReG = /\r/g;

// Trio of isHost* functions taken from Peter Michaux's article:

// http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting

function isHostMethod(object, property) {

var t = typeof object[property];

return t === "function" || (!!(t == "object" && object[property])) || t == "unknown";

}

function isHostProperty(object, property) {

return typeof(object[property]) != UNDEF;

}

function isHostObject(object, property) {

return !!(typeof(object[property]) == "object" && object[property]);

}

function fail(reason) {

if (window.console && window.console.error) {

window.console.error("valSel.js: Unsupported browser: " + reason);

}

}

function adjustOffsets(el, start, end) {

var len = el.value.replace(rReG, '').length;

start = (start > 0 ? start : 0);

start = (start < len ? start : len);

if (typeof end == UNDEF)

end = start;

else {

end = (end > 0 ? end : 0);

end = (end < len ? end : len);

}

if (end < start) {

var t = start;

start = end;

end = t;

}

return { start: start, end: end };

}

function makeSelection(normalizedValue, start, end) {

return {

start: start,

end: end,

length: end - start,

text: normalizedValue.slice(start, end)

};

}

function getBody() {

return isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];

}

function init() {

if (inited)

return;

var testTextArea = document.createElement("textarea");

getBody().appendChild(testTextArea);

if (isHostProperty(testTextArea, "selectionStart") && isHostProperty(testTextArea, "selectionEnd")) {

getSelection = function(el) {

var start = el.selectionStart, end = el.selectionEnd;

return makeSelection(el.value.replace(rReG, ''), start, end);

};

setSelection = function(el, startOffset, endOffset) {

var offsets = adjustOffsets(el, startOffset, endOffset);

el.selectionStart = offsets.start;

el.selectionEnd = offsets.end;

};

collapseSelection = function(el, toStart) {

if (toStart) {

el.selectionEnd = el.selectionStart;

} else {

el.selectionStart = el.selectionEnd;

}

};

} else if (isHostMethod(testTextArea, "createTextRange") && isHostObject(document, "selection")

&& isHostMethod(document.selection, "createRange")) {

getSelection = function(el) {

var start = 0, end = 0, range, normalizedValue = '', textInputRange, len, endRange;

el.focus();

range = document.selection.createRange();

if (range && range.parentElement() == el) {

len = el.value.length;

normalizedValue = el.value.replace(rReG, "");

textInputRange = el.createTextRange();

textInputRange.moveToBookmark(range.getBookmark());

endRange = el.createTextRange();

endRange.collapse(false);

if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {

start = end = len;

} else {

start = -textInputRange.moveStart("character", -len);

if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {

end = len;

} else {

end = -textInputRange.moveEnd("character", -len);

}

}

}

return makeSelection(normalizedValue, start, end);

};

// Moving across a line break only counts as moving one character in a TextRange,

// whereas a line break in the textarea value is two characters.

// This function corrects for that by converting a text offset into a range character offset

// by subtracting one character for every line break in the textarea prior to the offset

var offsetToRangeCharacterMove = function(el, offset) {

return offset;// - (el.value.slice(0, offset).split("\r\n").length - 1);

};

setSelection = function(el, startOffset, endOffset) {

var offsets = adjustOffsets(el, startOffset, endOffset);

var range = el.createTextRange();

var startCharMove = offsetToRangeCharacterMove(el, offsets.start);

range.collapse(true);

if (offsets.start == offsets.end) {

range.move("character", startCharMove);

} else {

range.moveEnd("character", offsetToRangeCharacterMove(el, offsets.end));

range.moveStart("character", startCharMove);

}

range.select();

};

collapseSelection = function(el, toStart) {

var range = document.selection.createRange();

range.collapse(toStart);

range.select();

};

} else {

getBody().removeChild(testTextArea);

fail("No means of finding text input caret position");

return;

}

// Clean up

getBody().removeChild(testTextArea);

inited = true;

} // init

// the functions always automatically return this

// if they don't otherwise return a result

function jQuerify(func) {

return function() {

var el = this.jquery ? this[0] : this;

var nodeName = el.nodeName.toLowerCase();

if (el.nodeType == 1 && (nodeName == "textarea"

|| (nodeName == "input" && el.type == "text"))) {

if (!inited) init(); // because $(init) won't work after an error from another script

var args = [el].concat(Array.prototype.slice.call(arguments));

var result = func.apply(this, args);

if (typeof result != 'undefined') {

return result;

}

}

return this;

};

}

/* Rangy Text Inputs code ends */

function getValue(el) {

return el.value.replace(rReG, ''); // remove \r

}

function setValue(el, val) {

var scrollPosObj = scrollPos(el);

el.value = val;

scrollPos(el, scrollPosObj);

}

function scrollPos(el, scrollPosObj) {

if (scrollPosObj) {

el.scrollTop = scrollPosObj.top;

el.scrollLeft = scrollPosObj.left;

}

else {

return {

top: el.scrollTop,

left: el.scrollLeft

};

}

}

var valParts = function (el, arg1, selText, textAfter) {

var parts;

var setValParts = function (el, parts) {

setValue(el, parts.join(''));

var beforeLen = parts[0].length;

setSelection(el, beforeLen, beforeLen + parts[1].length);

}

if (typeof arg1 == 'object') {

parts = arg1;

}

else if (typeof arg1 == 'function') {

}

else if (typeof arg1 == 'string') {

parts = [arg1, selText, textAfter];

}

if (parts) {

setValParts(el, parts);

return;

}

else {

var s = getSelection(el);

var text = getValue(el);

parts = [text.slice(0, s.start), s.text, text.slice(s.end)];

if (typeof arg1 == 'function') {

var parts = arg1(parts[0], parts[1], parts[2], text.length);

if (parts)

setValParts(el, parts);

return;

}

return parts;

}

} // valParts

var sel = function (el, arg1, arg2) {

var sel;

var setSel = function (sel) {

if (typeof sel.start == 'number') {

if (typeof sel.end != 'number')

sel.end = sel.start;

if (typeof sel.text == 'string')

setValue(sel.text);

setSelection(el, sel.start, sel.end);

}

else if (typeof sel.text == 'string') {

valParts(el, function (pre, currSel, post) {

var replacementSel = sel.text;

if (collapse < 0) {

post = replacementSel + post;

replacementSel = '';

}

else if (collapse > 0) {

post += replacementSel;

replacementSel = '';

}

return [pre, replacementSel, post];

});

}

}

if (typeof arg1 == 'undefined') {

return getSelection(el);

}

switch (typeof arg1) {

case 'function':

var selObj = getSelection(el);

sel = arg1(selObj.text, selObj.start, selObj.end, selObj.length);

if (typeof sel == 'string')

sel = {text: sel};

if (typeof sel == 'object')

setSel(sel);

break;

case 'string':

var collapse = (typeof arg2 == 'number' ? arg2 : 0);

setSel({text: arg1, collapse: arg2});

break;

case 'number':

var start = arg1, end = arg2;

setSelection(el, start, end);

break;

}

} // sel

var aroundSel = function (el) {

var a = arguments;

var before = a[1];

var after, include, collapse;

var excludedBefore = , excludedAfter = ;

if (typeof before != 'string')

return this; // error - do nothing

if (typeof a[2] != 'string') {

// aroundSel(string surround, [bool include, [int collapse]])

after = before;

include = a[2];

collapse = a[3];

}

else if (typeof a[3] != 'string') {

// aroundSel(string before, string after, [bool include, [int collapse]])

after = a[2];

include = a[3];

collapse = a[4];

}

else {

// aroundSel(string before, string prepend, string append, string after, [int collapse])

excludedBefore = before;

before = a[2];

after = a[3];

excludedAfter = a[4];

collapse = a[5];

include = true;

}

include = (typeof include == 'boolean' ? include : true);

if (collapse === true)

collapse = 1;

else if (typeof collapse != 'number')

collapse = 0;

valParts(el, function (pre, sel, post) {

if (include)

sel = before + sel + after;

else {

pre = pre + before;

post = after + post;

}

if (collapse < 0) {

post = sel + post;

sel = '';

}

else if (collapse > 0) {

pre += sel;

sel = '';

}

return [pre, sel, post];

});

} // aroundSel

$.fn.extend({

valParts: jQuerify(valParts),

sel: jQuerify(sel),

aroundSel: jQuerify(aroundSel),

collapseSel: jQuerify(collapseSelection)

});

$(init);

})(jQuery);