Hedgehog
11 years ago
8 changed files with 843 additions and 602 deletions
@ -0,0 +1,578 @@ |
|||||||
|
/*! |
||||||
|
* a-tools 1.4.1 |
||||||
|
* |
||||||
|
* Copyright (c) 2009 Andrey Kramarev(andrey.kramarev[at]ampparit.com), Ampparit Inc. (www.ampparit.com) |
||||||
|
* Licensed under the MIT license. |
||||||
|
* http://www.ampparit.fi/a-tools/license.txt
|
||||||
|
* |
||||||
|
* Basic usage: |
||||||
|
|
||||||
|
<textarea></textarea> |
||||||
|
<input type="text" /> |
||||||
|
|
||||||
|
// Get current selection
|
||||||
|
var sel = $("textarea").getSelection() |
||||||
|
|
||||||
|
// Replace current selection
|
||||||
|
$("input").replaceSelection("foo"); |
||||||
|
|
||||||
|
// Count characters
|
||||||
|
alert($("textarea").countCharacters()); |
||||||
|
|
||||||
|
// Set max length without callback function
|
||||||
|
$("textarea").setMaxLength(7); |
||||||
|
|
||||||
|
// Set max length with callback function which will be called when limit is exceeded
|
||||||
|
$("textarea").setMaxLength(10, function() { |
||||||
|
alert("hello") |
||||||
|
}); |
||||||
|
|
||||||
|
// Removing limit:
|
||||||
|
$("textarea").setMaxLength(-1); |
||||||
|
|
||||||
|
// Insert text at current caret position
|
||||||
|
$("#textarea").insertAtCaretPos("hello"); |
||||||
|
|
||||||
|
// Set caret position (1 = beginning, -1 = end)
|
||||||
|
$("#textArea").setCaretPos(10); |
||||||
|
|
||||||
|
// Set Selection
|
||||||
|
$("#textArea").setSelection(10,15); |
||||||
|
|
||||||
|
*/ |
||||||
|
var caretPositionAmp; |
||||||
|
|
||||||
|
jQuery.fn.extend({ |
||||||
|
getSelection: function() { // function for getting selection, and position of the selected text
|
||||||
|
var input = this.jquery ? this[0] : this; |
||||||
|
var start; |
||||||
|
var end; |
||||||
|
var part; |
||||||
|
var number = 0; |
||||||
|
input.onmousedown = function() { // for IE because it loses caret position when focus changed
|
||||||
|
if (document.selection && typeof(input.selectionStart) != "number") { |
||||||
|
document.selection.empty(); |
||||||
|
} else { |
||||||
|
window.getSelection().removeAllRanges(); |
||||||
|
} |
||||||
|
} |
||||||
|
if (document.selection) { |
||||||
|
// part for IE and Opera
|
||||||
|
var s = document.selection.createRange(); |
||||||
|
var minus = 0; |
||||||
|
var position = 0; |
||||||
|
var minusEnd = 0; |
||||||
|
var re; |
||||||
|
var rc; |
||||||
|
if (input.value.match(/\n/g) != null) { |
||||||
|
number = input.value.match(/\n/g).length;// number of EOL simbols
|
||||||
|
} |
||||||
|
if (s.text) { |
||||||
|
part = s.text; |
||||||
|
// OPERA support
|
||||||
|
if (typeof(input.selectionStart) == "number") { |
||||||
|
start = input.selectionStart; |
||||||
|
end = input.selectionEnd; |
||||||
|
// return null if the selected text not from the needed area
|
||||||
|
if (start == end) { |
||||||
|
return { start: start, end: end, text: s.text, length: end - start }; |
||||||
|
} |
||||||
|
} else { |
||||||
|
// IE support
|
||||||
|
var firstRe; |
||||||
|
var secondRe; |
||||||
|
re = input.createTextRange(); |
||||||
|
rc = re.duplicate(); |
||||||
|
firstRe = re.text; |
||||||
|
re.moveToBookmark(s.getBookmark()); |
||||||
|
secondRe = re.text; |
||||||
|
rc.setEndPoint("EndToStart", re); |
||||||
|
// return null if the selectyed text not from the needed area
|
||||||
|
if (firstRe == secondRe && firstRe != s.text) { |
||||||
|
return this; |
||||||
|
} |
||||||
|
start = rc.text.length; |
||||||
|
end = rc.text.length + s.text.length; |
||||||
|
} |
||||||
|
// remove all EOL to have the same start and end positons as in MOZILLA
|
||||||
|
if (number > 0) { |
||||||
|
for (var i = 0; i <= number; i++) { |
||||||
|
var w = input.value.indexOf("\n", position); |
||||||
|
if (w != -1 && w < start) { |
||||||
|
position = w + 1; |
||||||
|
minus++; |
||||||
|
minusEnd = minus; |
||||||
|
} else if (w != -1 && w >= start && w <= end) { |
||||||
|
if (w == start + 1) { |
||||||
|
minus--; |
||||||
|
minusEnd--; |
||||||
|
position = w + 1; |
||||||
|
continue; |
||||||
|
} |
||||||
|
position = w + 1; |
||||||
|
minusEnd++; |
||||||
|
} else { |
||||||
|
i = number; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (s.text.indexOf("\n", 0) == 1) { |
||||||
|
minusEnd = minusEnd + 2; |
||||||
|
} |
||||||
|
start = start - minus; |
||||||
|
end = end - minusEnd; |
||||||
|
|
||||||
|
return { start: start, end: end, text: s.text, length: end - start }; |
||||||
|
} |
||||||
|
input.focus (); |
||||||
|
if (typeof(input.selectionStart) == "number") { |
||||||
|
start = input.selectionStart; |
||||||
|
} else { |
||||||
|
s = document.selection.createRange(); |
||||||
|
re = input.createTextRange(); |
||||||
|
rc = re.duplicate(); |
||||||
|
re.moveToBookmark(s.getBookmark()); |
||||||
|
rc.setEndPoint("EndToStart", re); |
||||||
|
start = rc.text.length; |
||||||
|
} |
||||||
|
if (number > 0) { |
||||||
|
for (var i = 0; i <= number; i++) { |
||||||
|
var w = input.value.indexOf("\n", position); |
||||||
|
if (w != -1 && w < start) { |
||||||
|
position = w + 1; |
||||||
|
minus++; |
||||||
|
} else { |
||||||
|
i = number; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
start = start - minus; |
||||||
|
return { start: start, end: start, text: s.text, length: 0 }; |
||||||
|
} else if (typeof(input.selectionStart) == "number" ) { |
||||||
|
start = input.selectionStart; |
||||||
|
end = input.selectionEnd; |
||||||
|
part = input.value.substring(input.selectionStart, input.selectionEnd); |
||||||
|
return { start: start, end: end, text: part, length: end - start }; |
||||||
|
} else { return { start: undefined, end: undefined, text: undefined, length: undefined }; } |
||||||
|
}, |
||||||
|
|
||||||
|
// function for the replacement of the selected text
|
||||||
|
replaceSelection: function(inputStr) { |
||||||
|
var input = this.jquery ? this[0] : this; |
||||||
|
//part for IE and Opera
|
||||||
|
var start; |
||||||
|
var end; |
||||||
|
var position = 0; |
||||||
|
var rc; |
||||||
|
var re; |
||||||
|
var number = 0; |
||||||
|
var minus = 0; |
||||||
|
var mozScrollFix = ( input.scrollTop == undefined ) ? 0 : input.scrollTop; |
||||||
|
if (document.selection && typeof(input.selectionStart) != "number") { |
||||||
|
var s = document.selection.createRange(); |
||||||
|
|
||||||
|
// IE support
|
||||||
|
if (typeof(input.selectionStart) != "number") { // return null if the selected text not from the needed area
|
||||||
|
var firstRe; |
||||||
|
var secondRe; |
||||||
|
re = input.createTextRange(); |
||||||
|
rc = re.duplicate(); |
||||||
|
firstRe = re.text; |
||||||
|
re.moveToBookmark(s.getBookmark()); |
||||||
|
secondRe = re.text; |
||||||
|
rc.setEndPoint("EndToStart", re); |
||||||
|
if (firstRe == secondRe && firstRe != s.text) { |
||||||
|
return this; |
||||||
|
} |
||||||
|
} |
||||||
|
if (s.text) { |
||||||
|
part = s.text; |
||||||
|
if (input.value.match(/\n/g) != null) { |
||||||
|
number = input.value.match(/\n/g).length;// number of EOL simbols
|
||||||
|
} |
||||||
|
// IE support
|
||||||
|
start = rc.text.length; |
||||||
|
// remove all EOL to have the same start and end positons as in MOZILLA
|
||||||
|
if (number > 0) { |
||||||
|
for (var i = 0; i <= number; i++) { |
||||||
|
var w = input.value.indexOf("\n", position); |
||||||
|
if (w != -1 && w < start) { |
||||||
|
position = w + 1; |
||||||
|
minus++; |
||||||
|
|
||||||
|
} else { |
||||||
|
i = number; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
s.text = inputStr; |
||||||
|
caretPositionAmp = rc.text.length + inputStr.length; |
||||||
|
re.move("character", caretPositionAmp); |
||||||
|
document.selection.empty(); |
||||||
|
input.blur(); |
||||||
|
} |
||||||
|
return this; |
||||||
|
} else if (typeof(input.selectionStart) == "number" && // MOZILLA support
|
||||||
|
input.selectionStart != input.selectionEnd) { |
||||||
|
|
||||||
|
start = input.selectionStart; |
||||||
|
end = input.selectionEnd; |
||||||
|
input.value = input.value.substr(0, start) + inputStr + input.value.substr(end); |
||||||
|
position = start + inputStr.length; |
||||||
|
input.setSelectionRange(position, position); |
||||||
|
input.scrollTop = mozScrollFix; |
||||||
|
return this; |
||||||
|
} |
||||||
|
return this; |
||||||
|
}, |
||||||
|
|
||||||
|
//Set Selection in text
|
||||||
|
setSelection: function(startPosition, endPosition) { |
||||||
|
startPosition = parseInt(startPosition); |
||||||
|
endPosition = parseInt(endPosition); |
||||||
|
|
||||||
|
var input = this.jquery ? this[0] : this; |
||||||
|
input.focus (); |
||||||
|
if (typeof(input.selectionStart) != "number") { |
||||||
|
re = input.createTextRange(); |
||||||
|
if (re.text.length < endPosition) { |
||||||
|
endPosition = re.text.length+1; |
||||||
|
} |
||||||
|
} |
||||||
|
if (endPosition < startPosition) { |
||||||
|
return this; |
||||||
|
} |
||||||
|
if (document.selection) { |
||||||
|
var number = 0; |
||||||
|
var plus = 0; |
||||||
|
var position = 0; |
||||||
|
var plusEnd = 0; |
||||||
|
if (typeof(input.selectionStart) != "number") { // IE
|
||||||
|
re.collapse(true); |
||||||
|
re.moveEnd('character', endPosition); |
||||||
|
re.moveStart('character', startPosition); |
||||||
|
re.select(); |
||||||
|
return this; |
||||||
|
} else if (typeof(input.selectionStart) == "number") { // Opera
|
||||||
|
if (input.value.match(/\n/g) != null) { |
||||||
|
number = input.value.match(/\n/g).length;// number of EOL simbols
|
||||||
|
} |
||||||
|
if (number > 0) { |
||||||
|
for (var i = 0; i <= number; i++) { |
||||||
|
var w = input.value.indexOf("\n", position); |
||||||
|
if (w != -1 && w < startPosition) { |
||||||
|
position = w + 1; |
||||||
|
plus++; |
||||||
|
plusEnd = plus; |
||||||
|
} else if (w != -1 && w >= startPosition && w <= endPosition) { |
||||||
|
if (w == startPosition + 1) { |
||||||
|
plus--; |
||||||
|
plusEnd--; |
||||||
|
position = w + 1; |
||||||
|
continue; |
||||||
|
} |
||||||
|
position = w + 1; |
||||||
|
plusEnd++; |
||||||
|
} else { |
||||||
|
i = number; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
startPosition = startPosition +plus; |
||||||
|
endPosition = endPosition + plusEnd; |
||||||
|
input.selectionStart = startPosition; |
||||||
|
input.selectionEnd = endPosition; |
||||||
|
return this; |
||||||
|
} else { |
||||||
|
return this; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (input.selectionStart) { // MOZILLA support
|
||||||
|
input.focus (); |
||||||
|
input.selectionStart = startPosition; |
||||||
|
input.selectionEnd = endPosition; |
||||||
|
return this; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// insert text at current caret position
|
||||||
|
insertAtCaretPos: function(inputStr) { |
||||||
|
var input = this.jquery ? this[0] : this; |
||||||
|
var start; |
||||||
|
var end; |
||||||
|
var position; |
||||||
|
var s; |
||||||
|
var re; |
||||||
|
var rc; |
||||||
|
var point; |
||||||
|
var minus = 0; |
||||||
|
var number = 0; |
||||||
|
var mozScrollFix = ( input.scrollTop == undefined ) ? 0 : input.scrollTop; |
||||||
|
input.focus(); |
||||||
|
if (document.selection && typeof(input.selectionStart) != "number") { |
||||||
|
if (input.value.match(/\n/g) != null) { |
||||||
|
number = input.value.match(/\n/g).length;// number of EOL simbols
|
||||||
|
} |
||||||
|
point = parseInt(caretPositionAmp); |
||||||
|
if (number > 0) { |
||||||
|
for (var i = 0; i <= number; i++) { |
||||||
|
var w = input.value.indexOf("\n", position); |
||||||
|
if (w != -1 && w <= point) { |
||||||
|
position = w + 1; |
||||||
|
point = point - 1; |
||||||
|
minus++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
caretPositionAmp = parseInt(caretPositionAmp); |
||||||
|
// IE
|
||||||
|
input.onmouseup = function() { // for IE because it loses caret position when focus changed
|
||||||
|
if (document.selection && typeof(input.selectionStart) != "number") { |
||||||
|
input.focus(); |
||||||
|
s = document.selection.createRange(); |
||||||
|
re = input.createTextRange(); |
||||||
|
rc = re.duplicate(); |
||||||
|
re.moveToBookmark(s.getBookmark()); |
||||||
|
rc.setEndPoint("EndToStart", re); |
||||||
|
caretPositionAmp = rc.text.length; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (document.selection && typeof(input.selectionStart) != "number") { |
||||||
|
s = document.selection.createRange(); |
||||||
|
if (s.text.length != 0) { |
||||||
|
return this; |
||||||
|
} |
||||||
|
re = input.createTextRange(); |
||||||
|
textLength = re.text.length; |
||||||
|
rc = re.duplicate(); |
||||||
|
re.moveToBookmark(s.getBookmark()); |
||||||
|
rc.setEndPoint("EndToStart", re); |
||||||
|
start = rc.text.length; |
||||||
|
if (caretPositionAmp > 0 && start ==0) { |
||||||
|
minus = caretPositionAmp - minus; |
||||||
|
re.move("character", minus); |
||||||
|
re.select(); |
||||||
|
s = document.selection.createRange(); |
||||||
|
caretPositionAmp += inputStr.length; |
||||||
|
} else if (!(caretPositionAmp >= 0) && textLength ==0) { |
||||||
|
s = document.selection.createRange(); |
||||||
|
caretPositionAmp = inputStr.length + textLength; |
||||||
|
} else if (!(caretPositionAmp >= 0) && start ==0) { |
||||||
|
re.move("character", textLength); |
||||||
|
re.select(); |
||||||
|
s = document.selection.createRange(); |
||||||
|
caretPositionAmp = inputStr.length + textLength; |
||||||
|
} else if (!(caretPositionAmp >= 0) && start > 0) { |
||||||
|
re.move("character", 0); |
||||||
|
document.selection.empty(); |
||||||
|
re.select(); |
||||||
|
s = document.selection.createRange(); |
||||||
|
caretPositionAmp = start + inputStr.length; |
||||||
|
} else if (caretPositionAmp >= 0 && caretPositionAmp == textLength) { |
||||||
|
if (textLength != 0) { |
||||||
|
re.move("character", textLength); |
||||||
|
re.select(); |
||||||
|
} else { |
||||||
|
re.move("character", 0); |
||||||
|
} |
||||||
|
s = document.selection.createRange(); |
||||||
|
caretPositionAmp = inputStr.length + textLength; |
||||||
|
} else if (caretPositionAmp >= 0 && start != 0 && caretPositionAmp >= start) { |
||||||
|
minus = caretPositionAmp - start; |
||||||
|
re.move("character", minus); |
||||||
|
document.selection.empty(); |
||||||
|
re.select(); |
||||||
|
s = document.selection.createRange(); |
||||||
|
caretPositionAmp = caretPositionAmp + inputStr.length; |
||||||
|
} else if (caretPositionAmp >= 0 && start != 0 && caretPositionAmp < start) { |
||||||
|
re.move("character", 0); |
||||||
|
document.selection.empty(); |
||||||
|
re.select(); |
||||||
|
s = document.selection.createRange(); |
||||||
|
caretPositionAmp = caretPositionAmp + inputStr.length; |
||||||
|
} else { |
||||||
|
document.selection.empty(); |
||||||
|
re.select(); |
||||||
|
s = document.selection.createRange(); |
||||||
|
caretPositionAmp = caretPositionAmp + inputStr.length; |
||||||
|
} |
||||||
|
s.text = inputStr; |
||||||
|
input.focus(); |
||||||
|
|
||||||
|
return this; |
||||||
|
} else if (typeof(input.selectionStart) == "number" && // MOZILLA support
|
||||||
|
input.selectionStart == input.selectionEnd) { |
||||||
|
position = input.selectionStart + inputStr.length; |
||||||
|
start = input.selectionStart; |
||||||
|
end = input.selectionEnd; |
||||||
|
input.value = input.value.substr(0, start) + inputStr + input.value.substr(end); |
||||||
|
input.setSelectionRange(position, position); |
||||||
|
input.scrollTop = mozScrollFix; |
||||||
|
return this; |
||||||
|
} |
||||||
|
return this; |
||||||
|
}, |
||||||
|
|
||||||
|
|
||||||
|
// Set caret position
|
||||||
|
setCaretPos: function(inputStr) { |
||||||
|
|
||||||
|
var input = this.jquery ? this[0] : this; |
||||||
|
var s; |
||||||
|
var re; |
||||||
|
var position; |
||||||
|
var number = 0; |
||||||
|
var minus = 0; |
||||||
|
var w; |
||||||
|
input.focus(); |
||||||
|
if (parseInt(inputStr) == 0) { |
||||||
|
return this; |
||||||
|
} |
||||||
|
//if (document.selection && typeof(input.selectionStart) == "number") {
|
||||||
|
if (parseInt(inputStr) > 0) { |
||||||
|
inputStr = parseInt(inputStr) - 1; |
||||||
|
if (document.selection && typeof(input.selectionStart) == "number" && input.selectionStart == input.selectionEnd) { |
||||||
|
if (input.value.match(/\n/g) != null) { |
||||||
|
number = input.value.match(/\n/g).length;// number of EOL simbols
|
||||||
|
} |
||||||
|
if (number > 0) { |
||||||
|
for (var i = 0; i <= number; i++) { |
||||||
|
w = input.value.indexOf("\n", position); |
||||||
|
if (w != -1 && w <= inputStr) { |
||||||
|
position = w + 1; |
||||||
|
inputStr = parseInt(inputStr) + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else if (parseInt(inputStr) < 0) { |
||||||
|
inputStr = parseInt(inputStr) + 1; |
||||||
|
if (document.selection && typeof(input.selectionStart) != "number") { |
||||||
|
inputStr = input.value.length + parseInt(inputStr); |
||||||
|
if (input.value.match(/\n/g) != null) { |
||||||
|
number = input.value.match(/\n/g).length;// number of EOL simbols
|
||||||
|
} |
||||||
|
if (number > 0) { |
||||||
|
for (var i = 0; i <= number; i++) { |
||||||
|
w = input.value.indexOf("\n", position); |
||||||
|
if (w != -1 && w <= inputStr) { |
||||||
|
position = w + 1; |
||||||
|
inputStr = parseInt(inputStr) - 1; |
||||||
|
minus += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
inputStr = inputStr + minus - number; |
||||||
|
} |
||||||
|
} else if (document.selection && typeof(input.selectionStart) == "number") { |
||||||
|
inputStr = input.value.length + parseInt(inputStr); |
||||||
|
if (input.value.match(/\n/g) != null) { |
||||||
|
number = input.value.match(/\n/g).length;// number of EOL simbols
|
||||||
|
} |
||||||
|
if (number > 0) { |
||||||
|
inputStr = parseInt(inputStr) - number; |
||||||
|
for (var i = 0; i <= number; i++) { |
||||||
|
w = input.value.indexOf("\n", position); |
||||||
|
if (w != -1 && w <= (inputStr)) { |
||||||
|
position = w + 1; |
||||||
|
inputStr = parseInt(inputStr) + 1; |
||||||
|
minus += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else { inputStr = input.value.length + parseInt(inputStr); } |
||||||
|
} else { return this; } |
||||||
|
// IE
|
||||||
|
if (document.selection && typeof(input.selectionStart) != "number") { |
||||||
|
s = document.selection.createRange(); |
||||||
|
if (s.text != 0) { |
||||||
|
return this; |
||||||
|
} |
||||||
|
re = input.createTextRange(); |
||||||
|
re.collapse(true); |
||||||
|
re.moveEnd('character', inputStr); |
||||||
|
re.moveStart('character', inputStr); |
||||||
|
re.select(); |
||||||
|
caretPositionAmp = inputStr; |
||||||
|
|
||||||
|
return this; |
||||||
|
} else if (typeof(input.selectionStart) == "number" && // MOZILLA support
|
||||||
|
input.selectionStart == input.selectionEnd) { |
||||||
|
input.setSelectionRange(inputStr, inputStr); |
||||||
|
return this; |
||||||
|
} |
||||||
|
return this; |
||||||
|
|
||||||
|
}, |
||||||
|
|
||||||
|
countCharacters: function(str) { |
||||||
|
var input = this.jquery ? this[0] : this; |
||||||
|
if (input.value.match(/\r/g) != null) { |
||||||
|
return input.value.length - input.value.match(/\r/g).length; |
||||||
|
} |
||||||
|
return input.value.length; |
||||||
|
}, |
||||||
|
|
||||||
|
setMaxLength: function(max, f) { |
||||||
|
this.each(function() { |
||||||
|
var input = this.jquery ? this[0] : this; |
||||||
|
var type = input.type; |
||||||
|
var isSelected; |
||||||
|
var maxCharacters; |
||||||
|
// remove limit if input is a negative number
|
||||||
|
if (parseInt(max) < 0) { |
||||||
|
max=100000000; |
||||||
|
} |
||||||
|
if (type == "text") { |
||||||
|
input.maxLength = max; |
||||||
|
} |
||||||
|
if (type == "textarea" || type == "text") { |
||||||
|
input.onkeypress = function(e) { |
||||||
|
var spacesR = input.value.match(/\r/g); |
||||||
|
maxCharacters = max; |
||||||
|
if (spacesR != null) { |
||||||
|
maxCharacters = parseInt(maxCharacters) + spacesR.length; |
||||||
|
} |
||||||
|
// get event
|
||||||
|
var key = e || event; |
||||||
|
var keyCode = key.keyCode; |
||||||
|
// check if any part of text is selected
|
||||||
|
if (document.selection) { |
||||||
|
isSelected = document.selection.createRange().text.length > 0; |
||||||
|
} else { |
||||||
|
isSelected = input.selectionStart != input.selectionEnd; |
||||||
|
} |
||||||
|
if (input.value.length >= maxCharacters && (keyCode > 47 || keyCode == 32 || |
||||||
|
keyCode == 0 || keyCode == 13) && !key.ctrlKey && !key.altKey && !isSelected) { |
||||||
|
input.value = input.value.substring(0,maxCharacters); |
||||||
|
if (typeof(f) == "function") { f() } //callback function
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
input.onkeyup = function() { |
||||||
|
var spacesR = input.value.match(/\r/g); |
||||||
|
var plus = 0; |
||||||
|
var position = 0; |
||||||
|
maxCharacters = max; |
||||||
|
if (spacesR != null) { |
||||||
|
for (var i = 0; i <= spacesR.length; i++) { |
||||||
|
if (input.value.indexOf("\n", position) <= parseInt(max)) { |
||||||
|
plus++; |
||||||
|
position = input.value.indexOf("\n", position) + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
maxCharacters = parseInt(max) + plus; |
||||||
|
} |
||||||
|
if (input.value.length > maxCharacters) { |
||||||
|
input.value = input.value.substring(0, maxCharacters); |
||||||
|
if (typeof(f) == "function") { f() } |
||||||
|
return this; |
||||||
|
} |
||||||
|
} |
||||||
|
} else { return this; } |
||||||
|
}) |
||||||
|
return this; |
||||||
|
} |
||||||
|
}); |
@ -0,0 +1,231 @@ |
|||||||
|
/* |
||||||
|
* jQuery textarea suggest plugin |
||||||
|
* |
||||||
|
* Copyright (c) 2009-2010 Roman Imankulov |
||||||
|
* |
||||||
|
* Dual licensed under the MIT and GPL licenses: |
||||||
|
* http://www.opensource.org/licenses/mit-license.php
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
* |
||||||
|
* Requires: |
||||||
|
* - jQuery (tested with 1.3.x and 1.4.x) |
||||||
|
* - jquery.a-tools >= 1.4.1 (http://plugins.jquery.com/project/a-tools)
|
||||||
|
*/ |
||||||
|
|
||||||
|
/*globals jQuery,document */ |
||||||
|
|
||||||
|
(function ($) { |
||||||
|
// workaround for Opera browser
|
||||||
|
if (navigator.userAgent.match(/opera/i)) { |
||||||
|
$(document).keypress(function (e) { |
||||||
|
if ($.asuggestFocused) { |
||||||
|
$.asuggestFocused.focus(); |
||||||
|
$.asuggestFocused = null; |
||||||
|
e.preventDefault(); |
||||||
|
e.stopPropagation(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
$.asuggestKeys = { |
||||||
|
UNKNOWN: 0, |
||||||
|
SHIFT: 16, |
||||||
|
CTRL: 17, |
||||||
|
ALT: 18, |
||||||
|
LEFT: 37, |
||||||
|
UP: 38, |
||||||
|
RIGHT: 39, |
||||||
|
DOWN: 40, |
||||||
|
DEL: 46, |
||||||
|
TAB: 9, |
||||||
|
RETURN: 13, |
||||||
|
ESC: 27, |
||||||
|
COMMA: 188, |
||||||
|
PAGEUP: 33, |
||||||
|
PAGEDOWN: 34, |
||||||
|
BACKSPACE: 8, |
||||||
|
SPACE: 32 |
||||||
|
}; |
||||||
|
$.asuggestFocused = null; |
||||||
|
|
||||||
|
$.fn.asuggest = function (suggests, options) { |
||||||
|
return this.each(function () { |
||||||
|
$.makeSuggest(this, suggests, options); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
$.fn.asuggest.defaults = { |
||||||
|
'delimiters': '\n ', |
||||||
|
'minChunkSize': 1, |
||||||
|
'cycleOnTab': true, |
||||||
|
'autoComplete': true, |
||||||
|
'endingSymbols': ' ', |
||||||
|
'stopSuggestionKeys': [$.asuggestKeys.RETURN, $.asuggestKeys.SPACE], |
||||||
|
'ignoreCase': false |
||||||
|
}; |
||||||
|
|
||||||
|
/* Make suggest: |
||||||
|
* |
||||||
|
* create and return jQuery object on the top of DOM object |
||||||
|
* and store suggests as part of this object |
||||||
|
* |
||||||
|
* @param area: HTML DOM element to add suggests to |
||||||
|
* @param suggests: The array of suggest strings |
||||||
|
* @param options: The options object |
||||||
|
*/ |
||||||
|
$.makeSuggest = function (area, suggests, options) { |
||||||
|
options = $.extend({}, $.fn.asuggest.defaults, options); |
||||||
|
|
||||||
|
var KEY = $.asuggestKeys, |
||||||
|
$area = $(area); |
||||||
|
$area.suggests = suggests; |
||||||
|
$area.options = options; |
||||||
|
|
||||||
|
/* Internal method: get the chunk of text before the cursor */ |
||||||
|
$area.getChunk = function () { |
||||||
|
var delimiters = this.options.delimiters.split(''), // array of chars
|
||||||
|
textBeforeCursor = this.val().substr(0, this.getSelection().start), |
||||||
|
indexOfDelimiter = -1, |
||||||
|
i, |
||||||
|
d, |
||||||
|
idx; |
||||||
|
for (i = 0; i < delimiters.length; i++) { |
||||||
|
d = delimiters[i]; |
||||||
|
idx = textBeforeCursor.lastIndexOf(d); |
||||||
|
if (idx > indexOfDelimiter) { |
||||||
|
indexOfDelimiter = idx; |
||||||
|
} |
||||||
|
} |
||||||
|
if (indexOfDelimiter < 0) { |
||||||
|
return textBeforeCursor; |
||||||
|
} else { |
||||||
|
return textBeforeCursor.substr(indexOfDelimiter + 1); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/* Internal method: get completion. |
||||||
|
* If performCycle is true then analyze getChunk() and and getSelection() |
||||||
|
*/ |
||||||
|
$area.getCompletion = function (performCycle) { |
||||||
|
var text = this.getChunk(), |
||||||
|
selectionText = this.getSelection().text, |
||||||
|
suggests = this.suggests, |
||||||
|
foundAlreadySelectedValue = false, |
||||||
|
firstMatchedValue = null, |
||||||
|
i, |
||||||
|
suggest; |
||||||
|
// search the variant
|
||||||
|
for (i = 0; i < suggests.length; i++) { |
||||||
|
suggest = suggests[i]; |
||||||
|
if ($area.options.ignoreCase) { |
||||||
|
suggest = suggest.toLowerCase(); |
||||||
|
text = text.toLowerCase(); |
||||||
|
} |
||||||
|
// some variant is found
|
||||||
|
if (suggest.indexOf(text) === 0) { |
||||||
|
if (performCycle) { |
||||||
|
if (text + selectionText === suggest) { |
||||||
|
foundAlreadySelectedValue = true; |
||||||
|
} else if (foundAlreadySelectedValue) { |
||||||
|
return suggest.substr(text.length); |
||||||
|
} else if (firstMatchedValue === null) { |
||||||
|
firstMatchedValue = suggest; |
||||||
|
} |
||||||
|
} else { |
||||||
|
return suggest.substr(text.length); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (performCycle && firstMatchedValue) { |
||||||
|
return firstMatchedValue.substr(text.length); |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
$area.updateSelection = function (completion) { |
||||||
|
if (completion) { |
||||||
|
var _selectionStart = $area.getSelection().start, |
||||||
|
_selectionEnd = _selectionStart + completion.length; |
||||||
|
if ($area.getSelection().text === "") { |
||||||
|
if ($area.val().length === _selectionStart) { // Weird IE workaround, I really have no idea why it works
|
||||||
|
$area.setCaretPos(_selectionStart + 10000); |
||||||
|
} |
||||||
|
$area.insertAtCaretPos(completion); |
||||||
|
} else { |
||||||
|
$area.replaceSelection(completion); |
||||||
|
} |
||||||
|
$area.setSelection(_selectionStart, _selectionEnd); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
$area.unbind('keydown.asuggest').bind('keydown.asuggest', function (e) { |
||||||
|
if (e.keyCode === KEY.TAB) { |
||||||
|
if ($area.options.cycleOnTab) { |
||||||
|
var chunk = $area.getChunk(); |
||||||
|
if (chunk.length >= $area.options.minChunkSize) { |
||||||
|
$area.updateSelection($area.getCompletion(true)); |
||||||
|
} |
||||||
|
e.preventDefault(); |
||||||
|
e.stopPropagation(); |
||||||
|
$area.focus(); |
||||||
|
$.asuggestFocused = this; |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
// Check for conditions to stop suggestion
|
||||||
|
if ($area.getSelection().length && |
||||||
|
$.inArray(e.keyCode, $area.options.stopSuggestionKeys) !== -1) { |
||||||
|
// apply suggestion. Clean up selection and insert a space
|
||||||
|
var _selectionEnd = $area.getSelection().end + |
||||||
|
$area.options.endingSymbols.length; |
||||||
|
var _text = $area.getSelection().text + |
||||||
|
$area.options.endingSymbols; |
||||||
|
$area.replaceSelection(_text); |
||||||
|
$area.setSelection(_selectionEnd, _selectionEnd); |
||||||
|
e.preventDefault(); |
||||||
|
e.stopPropagation(); |
||||||
|
this.focus(); |
||||||
|
$.asuggestFocused = this; |
||||||
|
return false; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
$area.unbind('keyup.asuggest').bind('keyup.asuggest', function (e) { |
||||||
|
var hasSpecialKeys = e.altKey || e.metaKey || e.ctrlKey, |
||||||
|
hasSpecialKeysOrShift = hasSpecialKeys || e.shiftKey; |
||||||
|
switch (e.keyCode) { |
||||||
|
case KEY.UNKNOWN: // Special key released
|
||||||
|
case KEY.SHIFT: |
||||||
|
case KEY.CTRL: |
||||||
|
case KEY.ALT: |
||||||
|
case KEY.RETURN: // we don't want to suggest when RETURN key has pressed (another IE workaround)
|
||||||
|
break; |
||||||
|
case KEY.TAB: |
||||||
|
if (!hasSpecialKeysOrShift && $area.options.cycleOnTab) { |
||||||
|
break; |
||||||
|
} |
||||||
|
case KEY.ESC: |
||||||
|
case KEY.BACKSPACE: |
||||||
|
case KEY.DEL: |
||||||
|
case KEY.UP: |
||||||
|
case KEY.DOWN: |
||||||
|
case KEY.LEFT: |
||||||
|
case KEY.RIGHT: |
||||||
|
if (!hasSpecialKeysOrShift && $area.options.autoComplete) { |
||||||
|
$area.replaceSelection(""); |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
if (!hasSpecialKeys && $area.options.autoComplete) { |
||||||
|
var chunk = $area.getChunk(); |
||||||
|
if (chunk.length >= $area.options.minChunkSize) { |
||||||
|
$area.updateSelection($area.getCompletion(false)); |
||||||
|
} |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
}); |
||||||
|
return $area; |
||||||
|
}; |
||||||
|
}(jQuery)); |
@ -1,554 +0,0 @@ |
|||||||
/*! |
|
||||||
* jQuery.textcomplete.js |
|
||||||
* |
|
||||||
* Repositiory: https://github.com/yuku-t/jquery-textcomplete
|
|
||||||
* License: MIT |
|
||||||
* Author: Yuku Takahashi |
|
||||||
*/ |
|
||||||
|
|
||||||
;(function ($) { |
|
||||||
|
|
||||||
'use strict'; |
|
||||||
|
|
||||||
/** |
|
||||||
* Exclusive execution control utility. |
|
||||||
*/ |
|
||||||
var lock = function (func) { |
|
||||||
var free, locked; |
|
||||||
free = function () { locked = false; }; |
|
||||||
return function () { |
|
||||||
var args; |
|
||||||
if (locked) return; |
|
||||||
locked = true; |
|
||||||
args = toArray(arguments); |
|
||||||
args.unshift(free); |
|
||||||
func.apply(this, args); |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Convert arguments into a real array. |
|
||||||
*/ |
|
||||||
var toArray = function (args) { |
|
||||||
var result; |
|
||||||
result = Array.prototype.slice.call(args); |
|
||||||
return result; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Get the styles of any element from property names. |
|
||||||
*/ |
|
||||||
var getStyles = (function () { |
|
||||||
var color; |
|
||||||
color = $('<div></div>').css(['color']).color; |
|
||||||
if (typeof color !== 'undefined') { |
|
||||||
return function ($el, properties) { |
|
||||||
return $el.css(properties); |
|
||||||
}; |
|
||||||
} else { // for jQuery 1.8 or below
|
|
||||||
return function ($el, properties) { |
|
||||||
var styles; |
|
||||||
styles = {}; |
|
||||||
$.each(properties, function (i, property) { |
|
||||||
styles[property] = $el.css(property); |
|
||||||
}); |
|
||||||
return styles; |
|
||||||
}; |
|
||||||
} |
|
||||||
})(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Default template function. |
|
||||||
*/ |
|
||||||
var identity = function (obj) { return obj; }; |
|
||||||
|
|
||||||
/** |
|
||||||
* Memoize a search function. |
|
||||||
*/ |
|
||||||
var memoize = function (func) { |
|
||||||
var memo = {}; |
|
||||||
return function (term, callback) { |
|
||||||
if (memo[term]) { |
|
||||||
callback(memo[term]); |
|
||||||
} else { |
|
||||||
func.call(this, term, function (data) { |
|
||||||
memo[term] = (memo[term] || []).concat(data); |
|
||||||
callback.apply(null, arguments); |
|
||||||
}); |
|
||||||
} |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Determine if the array contains a given value. |
|
||||||
*/ |
|
||||||
var include = function (array, value) { |
|
||||||
var i, l; |
|
||||||
if (array.indexOf) return array.indexOf(value) != -1; |
|
||||||
for (i = 0, l = array.length; i < l; i++) { |
|
||||||
if (array[i] === value) return true; |
|
||||||
} |
|
||||||
return false; |
|
||||||
}; |
|
||||||
|
|
||||||
/** |
|
||||||
* Textarea manager class. |
|
||||||
*/ |
|
||||||
var Completer = (function () { |
|
||||||
var html, css, $baseWrapper, $baseList, _id; |
|
||||||
|
|
||||||
html = { |
|
||||||
wrapper: '<div class="textcomplete-wrapper"></div>', |
|
||||||
list: '<ul class="dropdown-menu"></ul>' |
|
||||||
}; |
|
||||||
css = { |
|
||||||
wrapper: { |
|
||||||
position: 'relative' |
|
||||||
}, |
|
||||||
list: { |
|
||||||
position: 'absolute', |
|
||||||
top: 0, |
|
||||||
left: 0, |
|
||||||
zIndex: '100', |
|
||||||
display: 'none' |
|
||||||
} |
|
||||||
}; |
|
||||||
$baseWrapper = $(html.wrapper).css(css.wrapper); |
|
||||||
$baseList = $(html.list).css(css.list); |
|
||||||
_id = 0; |
|
||||||
|
|
||||||
function Completer($el) { |
|
||||||
var focus; |
|
||||||
this.el = $el.get(0); // textarea element
|
|
||||||
focus = this.el === document.activeElement; |
|
||||||
// Cannot wrap $el at initialize method lazily due to Firefox's behavior.
|
|
||||||
this.$el = wrapElement($el); // Focus is lost
|
|
||||||
this.id = 'textComplete' + _id++; |
|
||||||
this.strategies = []; |
|
||||||
if (focus) { |
|
||||||
this.initialize(); |
|
||||||
this.$el.focus(); |
|
||||||
} else { |
|
||||||
this.$el.one('focus.textComplete', $.proxy(this.initialize, this)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Completer's public methods |
|
||||||
*/ |
|
||||||
$.extend(Completer.prototype, { |
|
||||||
|
|
||||||
/** |
|
||||||
* Prepare ListView and bind events. |
|
||||||
*/ |
|
||||||
initialize: function () { |
|
||||||
var $list, globalEvents; |
|
||||||
$list = $baseList.clone(); |
|
||||||
this.listView = new ListView($list, this); |
|
||||||
this.$el |
|
||||||
.before($list) |
|
||||||
.on({ |
|
||||||
'keyup.textComplete': $.proxy(this.onKeyup, this), |
|
||||||
'keydown.textComplete': $.proxy(this.listView.onKeydown, |
|
||||||
this.listView) |
|
||||||
}); |
|
||||||
globalEvents = {}; |
|
||||||
globalEvents['click.' + this.id] = $.proxy(this.onClickDocument, this); |
|
||||||
globalEvents['keyup.' + this.id] = $.proxy(this.onKeyupDocument, this); |
|
||||||
$(document).on(globalEvents); |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Register strategies to the completer. |
|
||||||
*/ |
|
||||||
register: function (strategies) { |
|
||||||
this.strategies = this.strategies.concat(strategies); |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Show autocomplete list next to the caret. |
|
||||||
*/ |
|
||||||
renderList: function (data) { |
|
||||||
if (this.clearAtNext) { |
|
||||||
this.listView.clear(); |
|
||||||
this.clearAtNext = false; |
|
||||||
} |
|
||||||
if (data.length) { |
|
||||||
if (!this.listView.shown) { |
|
||||||
this.listView |
|
||||||
.setPosition(this.getCaretPosition()) |
|
||||||
.clear() |
|
||||||
.activate(); |
|
||||||
this.listView.strategy = this.strategy; |
|
||||||
} |
|
||||||
data = data.slice(0, this.strategy.maxCount); |
|
||||||
this.listView.render(data); |
|
||||||
} |
|
||||||
|
|
||||||
if (!this.listView.data.length && this.listView.shown) { |
|
||||||
this.listView.deactivate(); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
searchCallbackFactory: function (free) { |
|
||||||
var self = this; |
|
||||||
return function (data, keep) { |
|
||||||
self.renderList(data); |
|
||||||
if (!keep) { |
|
||||||
// This is the last callback for this search.
|
|
||||||
free(); |
|
||||||
self.clearAtNext = true; |
|
||||||
} |
|
||||||
}; |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Keyup event handler. |
|
||||||
*/ |
|
||||||
onKeyup: function (e) { |
|
||||||
var searchQuery, term; |
|
||||||
if (this.skipSearch(e)) { return; } |
|
||||||
|
|
||||||
searchQuery = this.extractSearchQuery(this.getTextFromHeadToCaret()); |
|
||||||
if (searchQuery.length) { |
|
||||||
term = searchQuery[1]; |
|
||||||
if (this.term === term) return; // Ignore shift-key or something.
|
|
||||||
this.term = term; |
|
||||||
this.search(searchQuery); |
|
||||||
} else { |
|
||||||
this.term = null; |
|
||||||
this.listView.deactivate(); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Suppress searching if it returns true. |
|
||||||
*/ |
|
||||||
skipSearch: function (e) { |
|
||||||
if (this.skipNextKeyup) { |
|
||||||
this.skipNextKeyup = false; |
|
||||||
return true; |
|
||||||
} |
|
||||||
switch (e.keyCode) { |
|
||||||
case 40: |
|
||||||
case 38: |
|
||||||
return true; |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
onSelect: function (value) { |
|
||||||
var pre, post, newSubStr; |
|
||||||
pre = this.getTextFromHeadToCaret(); |
|
||||||
post = this.el.value.substring(this.el.selectionEnd); |
|
||||||
|
|
||||||
newSubStr = this.strategy.replace(value); |
|
||||||
if ($.isArray(newSubStr)) { |
|
||||||
post = newSubStr[1] + post; |
|
||||||
newSubStr = newSubStr[0]; |
|
||||||
} |
|
||||||
pre = pre.replace(this.strategy.match, newSubStr); |
|
||||||
this.$el.val(pre + post) |
|
||||||
.trigger('change') |
|
||||||
.trigger('textComplete:select', value); |
|
||||||
this.el.focus(); |
|
||||||
this.el.selectionStart = this.el.selectionEnd = pre.length; |
|
||||||
this.skipNextKeyup = true; |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Global click event handler. |
|
||||||
*/ |
|
||||||
onClickDocument: function (e) { |
|
||||||
if (e.originalEvent && !e.originalEvent.keepTextCompleteDropdown) { |
|
||||||
this.listView.deactivate(); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Global keyup event handler. |
|
||||||
*/ |
|
||||||
onKeyupDocument: function (e) { |
|
||||||
if (this.listView.shown && e.keyCode === 27) { // ESC
|
|
||||||
this.listView.deactivate(); |
|
||||||
this.$el.focus(); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Remove all event handlers and the wrapper element. |
|
||||||
*/ |
|
||||||
destroy: function () { |
|
||||||
var $wrapper; |
|
||||||
this.$el.off('.textComplete'); |
|
||||||
$(document).off('.' + this.id); |
|
||||||
if (this.listView) { this.listView.destroy(); } |
|
||||||
$wrapper = this.$el.parent(); |
|
||||||
$wrapper.after(this.$el).remove(); |
|
||||||
this.$el.data('textComplete', void 0); |
|
||||||
this.$el = null; |
|
||||||
}, |
|
||||||
|
|
||||||
// Helper methods
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
/** |
|
||||||
* Returns caret's relative coordinates from textarea's left top corner. |
|
||||||
*/ |
|
||||||
getCaretPosition: function () { |
|
||||||
// Browser native API does not provide the way to know the position of
|
|
||||||
// caret in pixels, so that here we use a kind of hack to accomplish
|
|
||||||
// the aim. First of all it puts a div element and completely copies
|
|
||||||
// the textarea's style to the element, then it inserts the text and a
|
|
||||||
// span element into the textarea.
|
|
||||||
// Consequently, the span element's position is the thing what we want.
|
|
||||||
|
|
||||||
if (this.el.selectionEnd === 0) return; |
|
||||||
var properties, css, $div, $span, position, dir; |
|
||||||
|
|
||||||
dir = this.$el.attr('dir') || this.$el.css('direction'); |
|
||||||
properties = ['border-width', 'font-family', 'font-size', 'font-style', |
|
||||||
'font-variant', 'font-weight', 'height', 'letter-spacing', |
|
||||||
'word-spacing', 'line-height', 'text-decoration', 'text-align', |
|
||||||
'width', 'padding-top', 'padding-right', 'padding-bottom', |
|
||||||
'padding-left', 'margin-top', 'margin-right', 'margin-bottom', |
|
||||||
'margin-left' |
|
||||||
]; |
|
||||||
css = $.extend({ |
|
||||||
position: 'absolute', |
|
||||||
overflow: 'auto', |
|
||||||
'white-space': 'pre-wrap', |
|
||||||
top: 0, |
|
||||||
left: -9999, |
|
||||||
direction: dir |
|
||||||
}, getStyles(this.$el, properties)); |
|
||||||
|
|
||||||
$div = $('<div></div>').css(css).text(this.getTextFromHeadToCaret()); |
|
||||||
$span = $('<span></span>').text('.').appendTo($div); |
|
||||||
this.$el.before($div); |
|
||||||
position = $span.position(); |
|
||||||
position.top += $span.height() - this.$el.scrollTop(); |
|
||||||
if (dir === 'rtl') { position.left -= this.listView.$el.width(); } |
|
||||||
$div.remove(); |
|
||||||
return position; |
|
||||||
}, |
|
||||||
|
|
||||||
getTextFromHeadToCaret: function () { |
|
||||||
var text, selectionEnd, range; |
|
||||||
selectionEnd = this.el.selectionEnd; |
|
||||||
if (typeof selectionEnd === 'number') { |
|
||||||
text = this.el.value.substring(0, selectionEnd); |
|
||||||
} else if (document.selection) { |
|
||||||
range = this.el.createTextRange(); |
|
||||||
range.moveStart('character', 0); |
|
||||||
range.moveEnd('textedit'); |
|
||||||
text = range.text; |
|
||||||
} |
|
||||||
return text; |
|
||||||
}, |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse the value of textarea and extract search query. |
|
||||||
*/ |
|
||||||
extractSearchQuery: function (text) { |
|
||||||
// If a search query found, it returns used strategy and the query
|
|
||||||
// term. If the caret is currently in a code block or search query does
|
|
||||||
// not found, it returns an empty array.
|
|
||||||
|
|
||||||
var i, l, strategy, match; |
|
||||||
for (i = 0, l = this.strategies.length; i < l; i++) { |
|
||||||
strategy = this.strategies[i]; |
|
||||||
match = text.match(strategy.match); |
|
||||||
if (match) { return [strategy, match[strategy.index]]; } |
|
||||||
} |
|
||||||
return []; |
|
||||||
}, |
|
||||||
|
|
||||||
search: lock(function (free, searchQuery) { |
|
||||||
var term; |
|
||||||
this.strategy = searchQuery[0]; |
|
||||||
term = searchQuery[1]; |
|
||||||
this.strategy.search(term, this.searchCallbackFactory(free)); |
|
||||||
}) |
|
||||||
}); |
|
||||||
|
|
||||||
/** |
|
||||||
* Completer's private functions |
|
||||||
*/ |
|
||||||
var wrapElement = function ($el) { |
|
||||||
return $el.wrap($baseWrapper.clone().css('display', $el.css('display'))); |
|
||||||
}; |
|
||||||
|
|
||||||
return Completer; |
|
||||||
})(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Dropdown menu manager class. |
|
||||||
*/ |
|
||||||
var ListView = (function () { |
|
||||||
|
|
||||||
function ListView($el, completer) { |
|
||||||
this.data = []; |
|
||||||
this.$el = $el; |
|
||||||
this.index = 0; |
|
||||||
this.completer = completer; |
|
||||||
|
|
||||||
this.$el.on('click.textComplete', 'li.textcomplete-item', |
|
||||||
$.proxy(this.onClick, this)); |
|
||||||
} |
|
||||||
|
|
||||||
$.extend(ListView.prototype, { |
|
||||||
shown: false, |
|
||||||
|
|
||||||
render: function (data) { |
|
||||||
var html, i, l, index, val; |
|
||||||
|
|
||||||
html = ''; |
|
||||||
for (i = 0, l = data.length; i < l; i++) { |
|
||||||
val = data[i]; |
|
||||||
if (include(this.data, val)) continue; |
|
||||||
index = this.data.length; |
|
||||||
this.data.push(val); |
|
||||||
html += '<li class="textcomplete-item" data-index="' + index + '"><a>'; |
|
||||||
html += this.strategy.template(val); |
|
||||||
html += '</a></li>'; |
|
||||||
if (this.data.length === this.strategy.maxCount) break; |
|
||||||
} |
|
||||||
this.$el.append(html); |
|
||||||
if (!this.data.length) { |
|
||||||
this.deactivate(); |
|
||||||
} else { |
|
||||||
this.activateIndexedItem(); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
clear: function () { |
|
||||||
this.data = []; |
|
||||||
this.$el.html(''); |
|
||||||
this.index = 0; |
|
||||||
return this; |
|
||||||
}, |
|
||||||
|
|
||||||
activateIndexedItem: function () { |
|
||||||
this.$el.find('.active').removeClass('active'); |
|
||||||
this.getActiveItem().addClass('active'); |
|
||||||
}, |
|
||||||
|
|
||||||
getActiveItem: function () { |
|
||||||
return $(this.$el.children().get(this.index)); |
|
||||||
}, |
|
||||||
|
|
||||||
activate: function () { |
|
||||||
if (!this.shown) { |
|
||||||
this.$el.show(); |
|
||||||
this.completer.$el.trigger('textComplete:show'); |
|
||||||
this.shown = true; |
|
||||||
} |
|
||||||
return this; |
|
||||||
}, |
|
||||||
|
|
||||||
deactivate: function () { |
|
||||||
if (this.shown) { |
|
||||||
this.$el.hide(); |
|
||||||
this.completer.$el.trigger('textComplete:hide'); |
|
||||||
this.shown = false; |
|
||||||
this.data = this.index = null; |
|
||||||
} |
|
||||||
return this; |
|
||||||
}, |
|
||||||
|
|
||||||
setPosition: function (position) { |
|
||||||
this.$el.css(position); |
|
||||||
return this; |
|
||||||
}, |
|
||||||
|
|
||||||
select: function (index) { |
|
||||||
var self = this; |
|
||||||
this.completer.onSelect(this.data[index]); |
|
||||||
// Deactive at next tick to allow other event handlers to know whether
|
|
||||||
// the dropdown has been shown or not.
|
|
||||||
setTimeout(function () { self.deactivate(); }, 0); |
|
||||||
}, |
|
||||||
|
|
||||||
onKeydown: function (e) { |
|
||||||
if (!this.shown) return; |
|
||||||
if (e.keyCode === 38) { // UP
|
|
||||||
e.preventDefault(); |
|
||||||
if (this.index === 0) { |
|
||||||
this.index = this.data.length-1; |
|
||||||
} else { |
|
||||||
this.index -= 1; |
|
||||||
} |
|
||||||
this.activateIndexedItem(); |
|
||||||
} else if (e.keyCode === 40) { // DOWN
|
|
||||||
e.preventDefault(); |
|
||||||
if (this.index === this.data.length - 1) { |
|
||||||
this.index = 0; |
|
||||||
} else { |
|
||||||
this.index += 1; |
|
||||||
} |
|
||||||
this.activateIndexedItem(); |
|
||||||
} else if (e.keyCode === 13 || e.keyCode === 9) { // ENTER or TAB
|
|
||||||
e.preventDefault(); |
|
||||||
|
|
||||||
this.select(parseInt(this.getActiveItem().data('index'), 10)); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
onClick: function (e) { |
|
||||||
var $e = $(e.target); |
|
||||||
e.originalEvent.keepTextCompleteDropdown = true; |
|
||||||
if (!$e.hasClass('textcomplete-item')) { |
|
||||||
$e = $e.parents('li.textcomplete-item'); |
|
||||||
} |
|
||||||
this.select(parseInt($e.data('index'), 10)); |
|
||||||
}, |
|
||||||
|
|
||||||
destroy: function () { |
|
||||||
this.deactivate(); |
|
||||||
this.$el.off('click.textComplete').remove(); |
|
||||||
this.$el = null; |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
return ListView; |
|
||||||
})(); |
|
||||||
|
|
||||||
$.fn.textcomplete = function (strategies) { |
|
||||||
var i, l, strategy, dataKey; |
|
||||||
|
|
||||||
dataKey = 'textComplete'; |
|
||||||
|
|
||||||
if (strategies === 'destroy') { |
|
||||||
return this.each(function () { |
|
||||||
var completer = $(this).data(dataKey); |
|
||||||
if (completer) { completer.destroy(); } |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
for (i = 0, l = strategies.length; i < l; i++) { |
|
||||||
strategy = strategies[i]; |
|
||||||
if (!strategy.template) { |
|
||||||
strategy.template = identity; |
|
||||||
} |
|
||||||
if (strategy.index == null) { |
|
||||||
strategy.index = 2; |
|
||||||
} |
|
||||||
if (strategy.cache) { |
|
||||||
strategy.search = memoize(strategy.search); |
|
||||||
} |
|
||||||
strategy.maxCount || (strategy.maxCount = 10); |
|
||||||
} |
|
||||||
|
|
||||||
return this.each(function () { |
|
||||||
var $this, completer; |
|
||||||
$this = $(this); |
|
||||||
completer = $this.data(dataKey); |
|
||||||
if (!completer) { |
|
||||||
completer = new Completer($this); |
|
||||||
$this.data(dataKey, completer); |
|
||||||
} |
|
||||||
completer.register(strategies); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
})(window.jQuery || window.Zepto); |
|
Loading…
Reference in new issue