jquery-textcomplete 让 textarea 支持自动完成功能

发布于 2020-04-05 16:56:53 字数 16263 浏览 1844 评论 0

jquery-textcomplete 是一个 jQuery 插件,它主要是给 Textarea 提供了自动补完的功能。 可以对多种不同文本内容实现自动补全的 JS 插件, 包括 @ 用户名, 文本关键词和 Emoji 关键词等内容的自动补全。

快速使用

$('textarea').textcomplete([{
    match: /(^|\b)(\w{2,})$/,
    search: function (term, callback) {
        var words = ['google', 'facebook', 'github', 'microsoft', 'yahoo'];
        callback($.map(words, function (word) {
            return word.indexOf(term) === 0 ? word : null;
        }));
    },
    replace: function (word) {
        return word + ' ';
    }
}]);

使用方法

jQuery 库必须最新引入:

<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, contenteditable elements or input[type="text"].

$('textarea').textcomplete(strategies, option);  // Recommended.
// $('[contenteditable="true"]').textcomplete(strategies, option);
// $('input[type="text"]').textcomplete(strategies, option);

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, template, cache, context and idProperty.

var strategy = {
  // Required
  match:      matchRegExpOrFunc,
  search:     searchFunc,
  replace:    replaceFunc,

  // Optional                 // Default
  cache:      cacheBoolean,   // false
  context:    contextFunc,    // function (text) { return true; }
  id:         idString,       // null
  idProperty: idPropertyStr,  // null
  index:      indexNumber,    // 2
  template:   templateFunc,   // function (value) { return value; }
}

The matchRegExpOrFunc MUST be a RegExp or a Function which returns a RegExp. And indexNumber and contextFunc MUST be a Number and a Function respectively.

contextFunc is called with the current value of the target textarea and it works as a preprocessor. When it returns false, the strategy is skipped. When it returns a String, matchRegExpOrFunc tests the returned string.

matchRegExpOrFunc MUST contain capturing groups and SHOULD end with $. The word captured by indexNumber-th group is going to be the term argument of searchFunc. indexNumber defaults to 2.

// Detect the word starting with '@' as a query term.
var matchRegExpOrFunc = /(^|\s)@(\w*)$/;
var indexNumber = 2;
// Normalizing the input text.
var contextFunc = function (text) { return text.toLowerCase(); };

The searchFunc MUST be a Function which gets two arguments, term and callback. It MAY have the third argument match which is the result of regexp matching. 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 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.

TextComplete automatically make the dropdown unique when the callbacked array consists of Strings. If it consists of Objects and the dropdown should be unique, use idPropertyStr for teaching the specified property is good to identify each elements.

var searchFunc = function (term, callback, match) {
  // term === match[indexNumber]
  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 iterator for the array given to the callback of searchFunc. You can change the style of each dropdown item.

var templateFunc = function (value, term) {
  // `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, an Array of two Strings or undefined. It is invoked when a user will click and select an item of autocomplete dropdown.

var replaceFunc = function (value, event) { return '$1@' + value + ' '; };

The result is going to be used to replace the value of textarea using String.prototype.replace with matchRegExpOrFunc:

textarea.value = textarea.value.replace(matchRegExpOrFunc, replaceFunc(value, event));

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 + '>'];
};

If undefined is returned from a replaceFunc, textcomplete does not replace the text.

If idString is given, textcomplete sets the value as data-strategy attribute of the dropdown element. You can change dropdown style by using the property.

The option is an optional Object which MAY have appendTo, height , maxCount, placement, header, footer, zIndex, debounce and onKeydown. If appendTo is given, the element of dropdown is appended into the specified element. If height is given, the dropdown element's height will be fixed.

var option = {
  adapter:           adapterClass,              // undefined
  appendTo:          appendToString,            // 'body'
  className:         classNameStr,              // DEPRECATED ''
  debounce:          debounceNumber,            // undefined
  dropdownClassName: dropdownClassNameStr,      // 'dropdown-menu textcomplete-dropdown'
  footer:            footerStrOrFunc,           // undefined
  header:            headerStrOrFunc,           // undefined
  height:            heightNumber,              // undefined
  maxCount:          maxCountNumber,            // 10
  noResultsMessage:  noResultsMessageStrOrFunc, // undefined
  onKeydown:         onKeydownFunc,             // undefined
  placement:         placementStr,              // ''
  rightEdgeOffset:   rightEdgeOffsetInteger,    // 30
  zIndex:            zIndexStr,                 // '100'
};

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.

If placementStr includes 'top', it positions the drop-down to above the caret. If placementStr includes 'absleft' and 'absright', it positions the drop-down absolutely to the very left and right respectively. You can mix them.

You can override the z-index property and the class attribute of dropdown element using zIndex and dropdownClassName option respectively.

If you want to add some additional keyboard shortcut, set a function to onKeydown option. The function will be called with two arguments, the keydown event and commands hash.

var onKeydownFunc = function (e, commands) {
  // `commands` has `KEY_UP`, `KEY_DOWN`, `KEY_ENTER`, `KEY_PAGEUP`, `KEY_PAGEDOWN`,
  // `KEY_ESCAPE` and `SKIP_DEFAULT`.
  if (e.ctrlKey && e.keyCode === 74) {
    // Treat CTRL-J as enter key.
    return commands.KEY_ENTER;
  }
  // If the function does not return a result or undefined is returned,
  // the plugin uses default behavior.
};

Textcomplete debounces debounceNumber milliseconds, so searchFunc is not called until user stops typing.

var placementStr = 'top|absleft';

If you want to use textcomplete with a rich editor, please write an adapter for it and give the adapter as adapterClass.

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 + ': ';
    }
  }
], { maxCount: 20, debounce: 500 });

自定义样式

The HTML generated by jquery-textcomplete is compatible with Bootstrap's dropdown. So all Bootstrap oriented css files are available.

Sample

Dropdown element's HTML structure is something like this:

<ul class="dropdown-menu textcomplete-dropdown">
  <li class="textcomplete-item active" data-index="0"><a>...</a></li>
  <li class="textcomplete-item" data-index="1"><a>...</a></li>
  <li class="textcomplete-item" data-index="2"><a>...</a></li>
  <li class="textcomplete-item" data-index="3"><a>...</a></li>
  <li class="textcomplete-item" data-index="4"><a>...</a></li>
</ul>

Children of a elements (... above) depend on your templateFunc.

If you don't use Bootstrap, you can use the following sample to start writing your own style.

.textcomplete-dropdown {
    border: 1px solid #ddd;
    background-color: white;
}

.textcomplete-dropdown li {
    border-top: 1px solid #ddd;
    padding: 2px 5px;
}

.textcomplete-dropdown li:first-child {
    border-top: none;
}

.textcomplete-dropdown li:hover,
.textcomplete-dropdown .active {
    background-color: rgb(110, 183, 219);
}

.textcomplete-dropdown {
    list-style: none;
    padding: 0;
    margin: 0;
}

.textcomplete-dropdown a:hover {
    cursor: pointer;
}

方法

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, strategy) {
            alert(value);
        },
        'textComplete:show': function (e) {
            $(this).data('autocompleting', true);
        },
        'textComplete:hide': function (e) {
            $(this).data('autocompleting', false);
        }
    });

依赖性

  • jQuery (>= 1.7.0) OR Zepto (>= 1.0)

FAQ

Can I change the trigger token, like use @ instead of :?

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 + ' '; }

Can I use both local data and remote data per a search?

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([]);   });
}

I want to cache the remote server's response.

Turn on the cache option.

I want to send back value / name combos.

Feel free to callback searchFunc with an Array of Object. templateFunc and replaceFunc will be invoked with an element of the array.

I want to use same strategies to autocomplete on several textareas.

TextComplete is applied to all textareas in the jQuery object.

// All class="commentBody" elements share strategies.
$('.commentBody').textcomplete([ /* ... */ ]);

How to trigger textcomplete manually?

Use trigger as follows:

// Put manual search query.
$('textarea').textcomplete('trigger', 'query');

// Use current texts. It depends on the position of cursor.
$('textarea').textcomplete('trigger');

If you want to show textcomplete when a textarea gets focus, trigger MUST be called at next tick.

$('textarea').on('focus', function () {
    var element = this;
    // Cursor has not set yet. And wait 100ms to skip global click event.
    setTimeout(function () {
        // Cursor is ready.
        $(element).textcomplete('trigger');
    }, 100);
});

I want to search case-insensitivly.

You can do case-insensitive comparison inside the search callback:

search: function (term, callback) {
    term = term.toLowerCase();
    callback($.map(words, function (word) {
        return word.toLowerCase().indexOf(term) === 0 ? word : null;
    }));
},

or normalize the term with context:

context: function (text) { return text.toLowerCase(); },

相关链接

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文