Theme for twister
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

232 lines
8.0 KiB

/*
* 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));