yavsc/web/Scripts/rangyinputs-jquery-1.1.2.js

274 lines
11 KiB
JavaScript

/**
* @license Rangy Inputs, a jQuery plug-in for selection and caret manipulation within textareas and text inputs.
*
* https://github.com/timdown/rangyinputs
*
* For range and selection features for contenteditable, see Rangy.
* http://code.google.com/p/rangy/
*
* Depends on jQuery 1.0 or later.
*
* Copyright 2013, Tim Down
* Licensed under the MIT license.
* Version: 1.1.2
* Build date: 6 September 2013
*/
(function($) {
var UNDEF = "undefined";
var getSelection, setSelection, deleteSelectedText, deleteText, insertText;
var replaceSelectedText, surroundSelectedText, extractSelectedText, collapseSelection;
// 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.log) {
window.console.log("RangyInputs not supported in your browser. Reason: " + reason);
}
}
function adjustOffsets(el, start, end) {
if (start < 0) {
start += el.value.length;
}
if (typeof end == UNDEF) {
end = start;
}
if (end < 0) {
end += el.value.length;
}
return { start: start, end: end };
}
function makeSelection(el, start, end) {
return {
start: start,
end: end,
length: end - start,
text: el.value.slice(start, end)
};
}
function getBody() {
return isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];
}
$(document).ready(function() {
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, 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, normalizedValue, textInputRange, len, endRange;
var range = document.selection.createRange();
if (range && range.parentElement() == el) {
len = el.value.length;
normalizedValue = el.value.replace(/\r\n/g, "\n");
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);
start += normalizedValue.slice(0, start).split("\n").length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
}
}
return makeSelection(el, 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);
deleteText = function(el, start, end, moveSelection) {
var val;
if (start != end) {
val = el.value;
el.value = val.slice(0, start) + val.slice(end);
}
if (moveSelection) {
setSelection(el, start, start);
}
};
deleteSelectedText = function(el) {
var sel = getSelection(el);
deleteText(el, sel.start, sel.end, true);
};
extractSelectedText = function(el) {
var sel = getSelection(el), val;
if (sel.start != sel.end) {
val = el.value;
el.value = val.slice(0, sel.start) + val.slice(sel.end);
}
setSelection(el, sel.start, sel.start);
return sel.text;
};
var updateSelectionAfterInsert = function(el, startIndex, text, selectionBehaviour) {
var endIndex = startIndex + text.length;
selectionBehaviour = (typeof selectionBehaviour == "string") ?
selectionBehaviour.toLowerCase() : "";
if ((selectionBehaviour == "collapsetoend" || selectionBehaviour == "select") && /[\r\n]/.test(text)) {
// Find the length of the actual text inserted, which could vary
// depending on how the browser deals with line breaks
var normalizedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
endIndex = startIndex + normalizedText.length;
var firstLineBreakIndex = startIndex + normalizedText.indexOf("\n");
if (el.value.slice(firstLineBreakIndex, firstLineBreakIndex + 2) == "\r\n") {
// Browser uses \r\n, so we need to account for extra \r characters
endIndex += normalizedText.match(/\n/g).length;
}
}
switch (selectionBehaviour) {
case "collapsetostart":
setSelection(el, startIndex, startIndex);
break;
case "collapsetoend":
setSelection(el, endIndex, endIndex);
break;
case "select":
setSelection(el, startIndex, endIndex);
break;
}
};
insertText = function(el, text, index, selectionBehaviour) {
var val = el.value;
el.value = val.slice(0, index) + text + val.slice(index);
if (typeof selectionBehaviour == "boolean") {
selectionBehaviour = selectionBehaviour ? "collapseToEnd" : "";
}
updateSelectionAfterInsert(el, index, text, selectionBehaviour);
};
replaceSelectedText = function(el, text, selectionBehaviour) {
var sel = getSelection(el), val = el.value;
el.value = val.slice(0, sel.start) + text + val.slice(sel.end);
updateSelectionAfterInsert(el, sel.start, text, selectionBehaviour || "collapseToEnd");
};
surroundSelectedText = function(el, before, after, selectionBehaviour) {
if (typeof after == UNDEF) {
after = before;
}
var sel = getSelection(el), val = el.value;
el.value = val.slice(0, sel.start) + before + sel.text + after + val.slice(sel.end);
var startIndex = sel.start + before.length;
updateSelectionAfterInsert(el, startIndex, sel.text, selectionBehaviour || "select");
};
function jQuerify(func, returnThis) {
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"))) {
var args = [el].concat(Array.prototype.slice.call(arguments));
var result = func.apply(this, args);
if (!returnThis) {
return result;
}
}
if (returnThis) {
return this;
}
};
}
$.fn.extend({
getSelection: jQuerify(getSelection, false),
setSelection: jQuerify(setSelection, true),
collapseSelection: jQuerify(collapseSelection, true),
deleteSelectedText: jQuerify(deleteSelectedText, true),
deleteText: jQuerify(deleteText, true),
extractSelectedText: jQuerify(extractSelectedText, false),
insertText: jQuerify(insertText, true),
replaceSelectedText: jQuerify(replaceSelectedText, true),
surroundSelectedText: jQuerify(surroundSelectedText, true)
});
});
})(jQuery);