按下TAB键时自动完成用户名

3
我正在开发一个“按下TAB键时自动完成用户名”的功能。
我已经能够检测到@符号并在用户名列表中进行搜索。
现在该如何执行自动完成,每次按下TAB键时都使用不同匹配的用户名呢?

var userlist = ['bonjour', 'bleuet', 'bonobo', 'coucou'];

function getCaretPosition(ctrl) {
    var start, end;
    if (ctrl.setSelectionRange) {
        start = ctrl.selectionStart;
        end = ctrl.selectionEnd;
    } else if (document.selection && document.selection.createRange) {
        var range = document.selection.createRange();
        start = 0 - range.duplicate().moveStart('character', -100000);
        end = start + range.text.length;
    }
    return {
        start: start,
        end: end
    }
}

$('#writing').keydown(function(e) {
    if (e.keyCode == 9) {
      var caret = getCaretPosition(this);
      var word = /\S+$/.exec(this.value.slice(0, this.value.indexOf(' ',caret.end)));
      word = word ? word[0] : null;
      if (word.charAt(0) === '@') 
        alert(userlist.filter((x) => x.indexOf(word.slice(1)) === 0));
      e.preventDefault();  
      return false;
    }

    });
#writing { width: 500px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<textarea id="writing">Hello @b what's up? hello @you as well... Type @b and then "TAB" key it should autocomplete usernames 
</textarea>

3个回答

2

以下是您问题的解决方案:

我使用了.on()将keydown绑定到文本框,而不是keypress。

var userlist = ['bonjour', 'bleuet', 'bonobo', 'coucou'];

function getCaretPosition(ctrl) {
    var start, end;
    if (ctrl.setSelectionRange) {
        start = ctrl.selectionStart;
        end = ctrl.selectionEnd;
    } else if (document.selection && document.selection.createRange) {
        var range = document.selection.createRange();
        start = 0 - range.duplicate().moveStart('character', -100000);
        end = start + range.text.length;
    }
    return {
        start: start,
        end: end
    }
}

$('#writing').keydown(function(e) {
    if (e.keyCode == 9) {
      var caret = getCaretPosition(this);
      var word = /\S+$/.exec(this.value.slice(0, this.value.indexOf(' ',caret.end)));
      word = word ? word[0] : null;
      if (word.charAt(0) === '@') 
        //alert(userlist.filter((x) => x.indexOf(word.slice(1)) === 0));
        var stringParts = $(this).val().split('@');
        var nameToInsert = userlist.filter((x) => x.indexOf(word.slice(1)) === 0)[0];
        var completeString = stringParts[0] + '@' + nameToInsert;
        $(this).val(completeString);
      e.preventDefault();  
      return false;
    }

    });
#writing { width: 500px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<textarea id="writing">Hello @b what's up? hello @you as well... Type @b and then "TAB" key it should autocomplete usernames 
</textarea>

现在它完成了命名。但我会努力改进算法,以预测更准确的名称。

当我们在“Hello @b”后使用光标按TAB键时,存在一个错误,所有接下来的单词都会被删除。 - Basj

1

将keypress替换为keydown。我猜测,因为tab使输入失去焦点,所以keypress不会触发。

编辑:

不完美,有时会得到未定义的结果,但你可以从这里开始解决。

编辑: 好吧,不要介意这个,它只完成了一半,@slikts的解决方案显然更好,是从头开始制作的。

var userlist = ['bonjour', 'bleuet', 'bonobo', 'coucou'];
    
    function getCaretPosition(ctrl) {
        var start, end;
        if (ctrl.setSelectionRange) {
            start = ctrl.selectionStart;
            end = ctrl.selectionEnd;
        } else if (document.selection && document.selection.createRange) {
            var range = document.selection.createRange();
            start = 0 - range.duplicate().moveStart('character', -100000);
            end = start + range.text.length;
        }
        return {
            start: start,
            end: end
        }
    }
    function setCaretPosition(elem, caretPos, caretPosEnd) {
        caretPosEnd = caretPosEnd || caretPos;
        if(elem != null) {
            if(elem.createTextRange) {
                var range = elem.createTextRange();
                range.move('character', caretPos);
                range.select();
            }
            else {
                if(elem.selectionStart) {
                    elem.focus();
                    elem.setSelectionRange(caretPos, caretPosEnd);
                }
                else
                    elem.focus();
            }
        }
    }
function getIndexCloserTo(str, char, ref) {  
    if(str.indexOf(char) == -1) return false;
   //flip string and find char beggining from reference point
   var nstr = str.split("").reverse().join("");
   return str.length - 1 - nstr.indexOf(char, str.length - 1 - ref);
}
    var lastWordToMatch = "";
    var lastAddedWord = "";
    var lastIndexUsed = 0;
    $(document).ready( function() {
     $('#writing').keydown(function(e) {
     
          if (e.keyCode == 9) {
       var caret = getCaretPosition(this);
                //Get username input part, from the "@" closer to the cursor
                //to the position of the cursor
                var beginning = getIndexCloserTo(this.value, '@', caret.start);
                if( beginning !== false){
       var word = this.value.slice( beginning , caret.start);
       word = word ? word[0] : null;
       if (word.charAt(0) === '@'){ 
                    //Get array of names that match what is written after the @
        var usermatches = userlist.filter((x) => x.indexOf(word.slice(1)) === 0);
                    //Check if any matches were found
                    if( usermatches.length > 0 ){
                      //If the word is the same as last time, use the next match
          if( word == lastWordToMatch ){
                          //use mod to circle back to beginning of array
           index = (lastIndexUsed + 1 ) % usermatches.length;
           lastIndexUsed = index;
          } else {
                        //otherwise get first match
           index = 0; 
           lastWordToMatch = word;
          }
                      var text = this.value;
                      //Remove @
                      word = word.slice(1);
                      //replace the portion of the word written by the user plus the 
                      //word added by autocompletion, with the match
          $(this).val(text.replace(word+lastAddedWord, usermatches[index]) );
                      //save the replacement for the previous step, without the user input
                      //just what autocompetion added
                      lastAddedWord = usermatches[index].replace(word, '');
                      //put cursor back where it was
          setCaretPosition(this,caret.start);  
                    }
       }
                 }
        e.preventDefault();  
        return false;
      }
    
      });
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="writing">Hello @b what's up? hello @you as well... Type @b and then "TAB" key it should autocomplete usernames 
</textarea>


抱歉,问题是如何实现实际的自动完成(即自动将用户名的开头替换为列表中的用户名)。 - Basj
你需要什么?一个下拉菜单显示所有的名字吗?还是在输入框中放置第一个匹配的名字,然后将光标放在用户输入的最后一个字符上? - mikepa88
将与输入匹配的第一个名称放入输入框中,然后将光标放在用户输入的最后一个字符上。如果再次按下TAB键,则会出现下一个匹配项(无下拉菜单)@mikepa88。 - Basj
不错 @mikepa88!有两个小问题:1)尝试输入“@zz”,然后按 TAB,它会显示“undefined” => 它应该什么都不做 2)如果文本区域中有许多单词,例如 Hello @b what's up? hello @you as well:如果您点击 @b 然后按 TAB:它会删除 所有文本,并只写入用户名。如何在正确的位置插入用户名? - Basj
使用 '$(this).val( $(this).val().replace(word, "@"+usermatches[index] )' 或类似的方法,替换单词而不是整个内容。对于未定义的问题,请检查用户列表过滤器是否返回空结果,在这种情况下不要执行任何操作。 - mikepa88

1

这是一个在现代浏览器(Chrome,Firefox,Edge)中运行的非常基本的示例:

const users = ['asdasd', 'fgsfds', 'Foobar']
const input = document.getElementById('input')
const patt = /\S+$/

input.addEventListener('keydown', e => {
  if (e.key !== 'Tab') {
    return
  }
  e.preventDefault()
  const start = input.selectionStart
  const seg = input.value.slice(0, start)
  const match = (seg.match(patt) || [])[0]
  if (!match) {
    return
  }
  const idx = users.findIndex(x => x.startsWith(match))
  if (idx < 0) {
    return
  }
  const replace = users[users[idx] === match ? (idx + 1) % users.length : idx]
  const newSeg = seg.replace(patt, replace)
  input.value = newSeg + input.value.slice(start)
  input.setSelectionRange(newSeg.length, newSeg.length)
})
<input type="text" id="input" size="50" value="bla asd bla fgs">

它可以在任何位置循环遍历名称和作品。使用Babel和es6-shim可以添加对旧浏览器的支持。

1
为了以后的参考,正如@slikts所建议的那样,const patt = /\b@?(\S+)$/;将使得user + TAB或者@user + TAB都能够工作。非常感谢他! - Basj
为了让它在更多的浏览器上运行,可以将代码复制/粘贴到http://babeljs.io/。 - Basj
你应该将Babel作为构建步骤的一部分。 - slikts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接