Introduces autocompleting power to textareas, like a GitHub comment form has.
Demo.
- jQuery (>= 1.7.0) OR Zepto (>= 1.0)
Note: The key words "MUST", "SHOULD", and "MAY" in this document are to be interpreted as described in RFC 2119.
jQuery MUST be loaded ahead.
<script src="path/to/jquery.js"></script>
<script src="path/to/jquery.textcomplete.js"></script>
Then jQuery.fn.textcomplete
is defined. The method MUST be called for textarea elements.
$('textarea').textcomplete(strategies);
The strategies
is an Array. Each element is called as strategy object.
var strategies = [
// There are two strategies.
strategy,
{ /* the other strategy */ }
];
The strategy
is an Object which MUST have match
, search
and replace
and MAY have index
, maxCount
, template
, cache
, placement
, header
and footer
.
var strategy = {
// Required
match: matchRegExp,
search: searchFunc,
replace: replaceFunc,
// Optional
index: indexNumber, // 2
maxCount: maxCountNumber, // 10
template: templateFunc, // function (value) { return value; }
cache: cacheBoolean, // false
placement: placementStr // undefined
header: headerStr // undefined
footer: footerStr // undefined
}
The matchRegExp
and indexNumber
MUST be a RegExp and a Number respectively. matchRegExp
MUST contain capturing groups and SHOULD end with $
. indexNumber
defaults to 2. The word captured by indexNumber
-th group is going to be the term
argument of searchFunc
.
// Detect the word starting with '@' as a query term.
var matchRegExp = /(^|\s)@(\w*)$/;
var indexNumber = 2;
The searchFunc
MUST be a Function which gets two arguments, term
and callback
. It MUST invoke callback
with an Array. It is guaranteed that the function will be invoked exclusively even though it contains async call.
If you want to execute callback
multiple times per a search, you SHOULD give true
to the second argument while additional execution remains. This is useful to use data located at both local and remote. Note that you MUST invoke callback
without truthy second argument at least once per a search.
The maxCountNumber
MUST be a Number and default to 10. Even if searchFunc
callbacks with large array, the array will be truncated into maxCountNumber
elements.
The cacheBoolean
MUST be a Boolean. It defaults to false
. If it is true
the searchFunc
will be memoized by term
argument. This is useful to prevent excessive API access.
var searchFunc = function (term, callback) {
callback(cache[term], true); // Show local cache immediately.
$.getJSON('/search', { q: term })
.done(function (resp) {
callback(resp); // `resp` must be an Array
})
.fail(function () {
callback([]); // Callback must be invoked even if something went wrong.
});
};
The templateFunc
MUST be a Function which returns a string. The function is going to be called as an iteretor for the array given to the callback
of searchFunc
. You can change the style of each dropdown item.
var templateFunc = function (value) {
// `value` is an element of array callbacked by searchFunc.
return '<b>' + value + '</b>';
};
// Default:
// templateFunc = function (value) { return value; };
The replaceFunc
MUST be a Function which returns a String or an Array of two Strings. It is going to be invoked when a user will click and select an item of autocomplete dropdown.
var replaceFunc = function (value) { return '$1@' + value + ' '; };
The result is going to be used to replace the value of textarea using String.prototype.replace
with matchRegExp
:
textarea.value = textarea.value.replace(matchRegExp, replaceFunc(value));
Suppose you want to do autocomplete for HTML elements, you may want to reposition the cursor in the middle of elements after the autocomplete. In this case, you can do that by making replaceFunc
return an Array of two Strings. Then the cursor points between these two strings.
var replaceFunc = function (value) {
return ['$1<' + value + '>', '</' + value + '>'];
};
placementStr
accepts only 'top' and which will cause the drop-down menu to render above the caret instead of below it.
var placementStr = 'top';
Finally, if you want to stop autocompleting, give 'destroy'
to textcomplete
method as follows:
$('textarea').textcomplete('destroy');
$('textarea').textcomplete([
{ // mention strategy
match: /(^|\s)@(\w*)$/,
search: function (term, callback) {
callback(cache[term], true);
$.getJSON('/search', { q: term })
.done(function (resp) { callback(resp); })
.fail(function () { callback([]); });
},
replace: function (value) {
return '$1@' + value + ' ';
},
cache: true
},
{ // emoji strategy
match: /(^|\s):(\w*)$/,
search: function (term, callback) {
var regexp = new RegExp('^' + term);
callback($.grep(emojies, function (emoji) {
return regexp.test(emoji);
}));
},
replace: function (value) {
return '$1:' + value + ': ';
}
}
]);
// You can append strategies to an already existing textcomplete object.
$('textarea').textcomplete([
{
// Yet another strategy
}
]);
The HTML generated by jquery-textcomplete is compatible with Bootstrap's dropdown. So all Bootstrap oriented css files are available.
This repository also includes jquery.autocomplete.css. It is useful to be used as the starting point.
textComplete fires a number of events.
textComplete:show
- Fired when a dropdown is shown.textComplete:hide
- Fired when a dropdown is hidden.textComplete:select
- Fired with the selected value when a dropdown is selected.
$('#textarea')
.textcomplete([/* ... */])
.on({
'textComplete:select': function (e, value) {
alert(value);
},
'textComplete:show': function (e) {
$(this).data('autocompleting', true);
},
'textComplete:hide': function (e) {
$(this).data('autocompleting', false);
}
});
Use the following match
and replace
at your strategy:
match: /(^|\s)@(\w*)$/,
replace: function (value) { return '$1@' + value + ' '; }
If you use @
just for trigger and want to remove it when a user makes her choice:
match: /(^|\s)(@\w*)$/
replace: function (value) { return '$1' + value + ' '; }
Invoking callback(localData, true)
and callback(remoteData)
is what you have to do.
search: function (term, callback) {
callback(cache[term], true);
$.getJSON('/search', { q: term })
.done(function (resp) { callback(resp); })
.fail(function () { callback([]); });
}
Turn on the cache
option.
Apply display: block
too.
textarea {
width: 100%;
display: block; /* Default: inline-block */
}
If you need inline-block
textarea for design reason, you have to wrap it by inline-block
element.
<div style="width: 70%; display: inline-block">
<textarea style="display: block; width: 100%;"></textarea>
</div>
<div style="width: 30%;">
30% width contents
</div>
Feel free to callback searchFunc
with an Array of Object. templateFunc
and replaceFunc
will be invoked with an element of the array.
TextComplete is applied to all textareas in the jQuery object.
// All class="commentBody" elements share strategies.
$('.commentBody').textcomplete([ /* ... */ ]);
- Write tests
- Content editable support (at v0.2)
- Support placement option.
- Emacs-style prev/next keybindings.
- Replay searchFunc for the last term on slow network env.
- Several bugfixes.
- Several bugfixes.
- Support RTL positioning.
- Enable to append strategies on the fly.
- Enable to stop autocompleting.
- Enable to apply multiple textareas at once.
- Don't show popup on pressing arrow up and down keys.
- Hide dropdown by pressing ESC key.
- Prevent showing a dropdown when it just autocompleted.
Support RTL positioning.
- Introduce
textComplete:show
,textComplete:hide
andtextComplete:select
events.
- Now strategies argument is an Array of strategy objects.
- Several bugfixes.
- Up and Down arrows cycle instead of exit.
- Support Zepto.
- Support jQuery.overlay.
- Some performance improvement.
- Imprement lazy callbacking on search function.
- Support IE8.
- Some performance improvement.
- Imprement cache option.
- Initial release.
Licensed under the MIT License