使用键盘滚动无序列表

时间:2018-04-04 10:48:06

标签: javascript html css autocomplete typeahead

我目前正在使用Javascript编写自动完成组件,根据邮政编码完成两个输入字段plz(邮政编码)和ort(城市)。输入3位数后,自动完成功能就会触发。

现在很遗憾德国的许多邮政编码都不能作为标识符 - 几个小城市都可以使用相同的邮政编码。

要试用我的代码,请输入

562

标记为 PLZ 的输入中的

将打开一个自动提示列表,其中最多可包含10个可一次显示的项目。使用列表。

我需要一个问题的解决方案,当使用↑活动项目不会导致列表滚动>

如果有人能指出我在这方面的正确方向,我会非常乐意自己实施。

let plz = [{"plz":"56244","ort":"Rückeroth","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ötzingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Niedersayn","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Vielbach","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Hartenfels","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ewighausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Leuterod","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Kuhnhöfen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Goddert","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Freirachdorf","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Maxsain","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Freilingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Weidenhahn","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Helferskirchen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Arnshöfen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Hahn am See","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Sessenhausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Wölferlingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Steinen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Schenkelberg","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Krümmel","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ettinghausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"}]

let plzAutoCompleteConfig = {
  minCharactersToRun: 3,
  maxResults: 100,
  allowedKeyCodes: [8, 9, 13, 37, 38, 39, 40, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105]
}


let plzOrtAutoComplete = function plzOrtAutoComplete() {
  if (!document.querySelector('[data-has-plz-ort-autocomplete]')) return;

  // find all plz autocompletes
  let autocompletes = Array.from(document.querySelectorAll('[data-has-plz-ort-autocomplete]'))

  for (let autocomplete of autocompletes) {
    let plzInput = document.getElementById(autocomplete.getAttribute('data-plz'))
    let ortInput = document.getElementById(autocomplete.getAttribute('data-ort'))
    let suggestList = document.createElement('ul')
    suggestList.flush = function() {
      this.innerHTML = ''
    }
    suggestList.className = 'autocomplete-suggest list-unstyled'
    plzInput.parentNode.appendChild(suggestList)

    for (let eventName of ['input', 'focus']) {
      plzInput.addEventListener(eventName, function(evt) {
        const checkInput = () => {
          let matches = plz.filter(x => {
            return x.plz.startsWith(this.value)
          })
          switch (true) {
            case (matches.length > plzAutoCompleteConfig.maxResults):
              suggestList.flush()
              break
            case ((matches.length <= plzAutoCompleteConfig.maxResults && matches.length > 1) || (matches.length === 1 && this.value.length < 5)):
              suggestList.flush()
              for (let match of matches) {
                let li = document.createElement('li')
                li.textContent = `${match.plz} ${match.ort}`
                li.title = `${match.plz} ${match.ort} in ${match.bundesland}, ${match.kreis} übernehmen durch Enter oder Klick`
                li.addEventListener('click', () => {
                  plzInput.value = match.plz
                  ortInput.value = match.ort
                  ortInput.focus()
                  suggestList.flush()
                })
                li.addEventListener('mouseenter', function() {
                  this.classList.add('active')
                })
                li.addEventListener('mouseleave', function() {
                  this.classList.remove('active')
                })
                suggestList.appendChild(li)
              }
              this.parentNode.appendChild(suggestList)
              break
            case (matches.length === 1 && this.value.length === 5):
              if (event.type !== 'focus' && ['deleteContentBackward', 'deleteContentForward'].indexOf(evt.inputType) === -1) {
                suggestList.flush()
                plzInput.value = matches[0].plz
                ortInput.value = matches[0].ort
                ortInput.focus()
              }
              break
            default:
              {
                suggestList.flush()
                break
              }
          }
        }
        if (isNaN(Number(this.value))) {
          this.value = ''
          return
        }
        if (this.value.length >= plzAutoCompleteConfig.minCharactersToRun) {
          if (['deleteContentBackward', 'deleteContentForward'].indexOf(evt.inputType) > -1) {
            console.log(this.value)
          }
          checkInput()
        }
      })
    }

    plzInput.addEventListener('keydown', function(evt) {
      let keyCode = evt.keyCode || evt.which;
      let activeLi = suggestList.querySelector('li.active')
      if (keyCode) {
        if (!plzAutoCompleteConfig.allowedKeyCodes.includes(keyCode)) {
          evt.preventDefault()
        } else {
          switch (keyCode) {
            case 8: // backspace
              suggestList.flush()
              break
            case 13: // Enter
              evt.preventDefault()
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (!activeLi) {
                return
              } else {
                plzInput.value = activeLi.textContent.substr(0, 5)
                ortInput.value = activeLi.textContent.substr(6)
                suggestList.flush()
                ortInput.focus()
              }
              break
            case 37:
              break
            case 38: // cursor up
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (activeLi) {
                activeLi.classList.remove('active')
                let prevLi = activeLi.previousSibling
                if (prevLi) {
                  prevLi.classList.add('active')
                } else {
                  suggestList.querySelector('li:last-of-type').classList.add('active')
                }
              } else {
                suggestList.querySelector('li:last-of-type').classList.add('active')
              }
              break
            case 39:
              break
            case 40:
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (activeLi) {
                activeLi.classList.remove('active')
                let nextLi = activeLi.nextSibling
                if (nextLi) {
                  nextLi.classList.add('active')
                } else {
                  suggestList.querySelector('li:first-of-type').classList.add('active')
                }
              } else {
                suggestList.querySelector('li:first-of-type').classList.add('active')
              }
              break
            case 46: // delete
              suggestList.flush()
              break
            default:
              break
          }
        }
      }
    })
    plzInput.addEventListener('blur', function(evt) {
      setTimeout(function() {
        suggestList.flush()
      }, 250)
    })
    ortInput.addEventListener('input', function(evt) {
      console.log(this.value)
    })
  }
}

plzOrtAutoComplete();
.autocomplete-suggest {
  background-color: #fff;
  border: 1px solid #ddd;
  box-shadow: 3px 3px 5px #ccc;
  max-height: 6em;
  left: 5px;
  opacity: 1;
  overflow-y: auto;
  pointer-events: all;
  position: absolute;
  z-index: 999;
  transition-duration: .2s;
}
.autocomplete-suggest:empty {
  max-height: 0;
  opacity: 0;
  pointer-events: none;
}
.autocomplete-suggest li {
  line-height: 1.5em;
  margin: 0 auto;
  padding: .3em 1.2em .2em .6em;
  transition: all .2s ease-in-out;
  white-space: nowrap;
}
.autocomplete-suggest li:nth-child(even) {
  background-color: rgba(0, 0, 0, 0.05);
}
.autocomplete-suggest li:hover, .autocomplete-suggest li.active {
  cursor: pointer;
  background-color: #a00;
  color: #fff;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div class="form-row" data-has-plz-ort-autocomplete data-plz="eazVersicherterPLZ" data-ort="eazVersicherterOrt">
  <div class="form-group col-sm-4">
    <label for="eazVersicherterPLZ">PLZ</label>
    <input class="form-control" type="text" id="eazVersicherterPLZ" name="eazVersicherterPLZ" data-plz="eazVersicherterOrt" maxlength=5 />
  </div>

  <div class="form-group col-sm-8">
    <label for="eazVersicherterOrt">Ort</label>
    <input class="form-control" type="text" id="eazVersicherterOrt" name="eazVersicherterOrt">
  </div>
</div>

这是由Javascript生成的自动提示HTML:

<ul class="autocomplete-suggest list-unstyled">
  <li>56244 Rückeroth</li>
  <li>56244 Ötzingen</li>
  <li>56244 Niedersayn</li>
  <li>56244 Vielbach</li>
  <li>56244 Hartenfels</li>
  <li>56244 Ewighausen</li>
  <li>56244 Leuterod</li>
  <li>56244 Kuhnhöfen</li>
  <li>56244 Goddert</li>
  <li>56244 Freirachdorf</li>
  <li>56244 Maxsain</li>
  <li>56244 Freilingen</li>
  <li>56244 Weidenhahn</li>
  <li>56244 Helferskirchen</li>
  <li>56244 Arnshöfen</li>
  <li>56244 Hahn am See</li>
  <li>56244 Sessenhausen</li>
  <li>56244 Wölferlingen</li>
  <li>56244 Steinen</li>
  <li>56244 Schenkelberg</li>
  <li>56244 Krümmel</li>
  <li>56244 Ettinghausen</li>
</ul>

2 个答案:

答案 0 :(得分:1)

我只需将光标向上添加,光标向下添加:

suggestList.scrollTop = suggestList.querySelector('.active').offsetTop;

它将使列表与活动元素一起滚动。

完整代码:

&#13;
&#13;
let plz = [{"plz":"56244","ort":"Rückeroth","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ötzingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Niedersayn","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Vielbach","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Hartenfels","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ewighausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Leuterod","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Kuhnhöfen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Goddert","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Freirachdorf","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Maxsain","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Freilingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Weidenhahn","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Helferskirchen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Arnshöfen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Hahn am See","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Sessenhausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Wölferlingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Steinen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Schenkelberg","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Krümmel","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ettinghausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"}]

let plzAutoCompleteConfig = {
  minCharactersToRun: 3,
  maxResults: 100,
  allowedKeyCodes: [8, 9, 13, 37, 38, 39, 40, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105]
}


let plzOrtAutoComplete = function plzOrtAutoComplete() {
  if (!document.querySelector('[data-has-plz-ort-autocomplete]')) return;

  // find all plz autocompletes
  let autocompletes = Array.from(document.querySelectorAll('[data-has-plz-ort-autocomplete]'))

  for (let autocomplete of autocompletes) {
    let plzInput = document.getElementById(autocomplete.getAttribute('data-plz'))
    let ortInput = document.getElementById(autocomplete.getAttribute('data-ort'))
    let suggestList = document.createElement('ul')
    suggestList.flush = function() {
      this.innerHTML = ''
    }
    suggestList.className = 'autocomplete-suggest list-unstyled'
    plzInput.parentNode.appendChild(suggestList)

    for (let eventName of ['input', 'focus']) {
      plzInput.addEventListener(eventName, function(evt) {
        const checkInput = () => {
          let matches = plz.filter(x => {
            return x.plz.startsWith(this.value)
          })
          switch (true) {
            case (matches.length > plzAutoCompleteConfig.maxResults):
              suggestList.flush()
              break
            case ((matches.length <= plzAutoCompleteConfig.maxResults && matches.length > 1) || (matches.length === 1 && this.value.length < 5)):
              suggestList.flush()
              for (let match of matches) {
                let li = document.createElement('li')
                li.textContent = `${match.plz} ${match.ort}`
                li.title = `${match.plz} ${match.ort} in ${match.bundesland}, ${match.kreis} übernehmen durch Enter oder Klick`
                li.addEventListener('click', () => {
                  plzInput.value = match.plz
                  ortInput.value = match.ort
                  ortInput.focus()
                  suggestList.flush()
                })
                li.addEventListener('mouseenter', function() {
                  this.classList.add('active')
                })
                li.addEventListener('mouseleave', function() {
                  this.classList.remove('active')
                })
                suggestList.appendChild(li)
              }
              this.parentNode.appendChild(suggestList)
              break
            case (matches.length === 1 && this.value.length === 5):
              if (event.type !== 'focus' && ['deleteContentBackward', 'deleteContentForward'].indexOf(evt.inputType) === -1) {
                suggestList.flush()
                plzInput.value = matches[0].plz
                ortInput.value = matches[0].ort
                ortInput.focus()
              }
              break
            default:
              {
                suggestList.flush()
                break
              }
          }
        }
        if (isNaN(Number(this.value))) {
          this.value = ''
          return
        }
        if (this.value.length >= plzAutoCompleteConfig.minCharactersToRun) {
          if (['deleteContentBackward', 'deleteContentForward'].indexOf(evt.inputType) > -1) {
            console.log(this.value)
          }
          checkInput()
        }
      })
    }

    plzInput.addEventListener('keydown', function(evt) {
      let keyCode = evt.keyCode || evt.which;
      let activeLi = suggestList.querySelector('li.active')
      if (keyCode) {
        if (!plzAutoCompleteConfig.allowedKeyCodes.includes(keyCode)) {
          evt.preventDefault()
        } else {
          switch (keyCode) {
            case 8: // backspace
              suggestList.flush()
              break
            case 13: // Enter
              evt.preventDefault()
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (!activeLi) {
                return
              } else {
                plzInput.value = activeLi.textContent.substr(0, 5)
                ortInput.value = activeLi.textContent.substr(6)
                suggestList.flush()
                ortInput.focus()
              }
              break
            case 37:
              break
            case 38: // cursor up
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (activeLi) {
                activeLi.classList.remove('active')
                let prevLi = activeLi.previousSibling
                if (prevLi) {
                  prevLi.classList.add('active')
                } else {
                  suggestList.querySelector('li:last-of-type').classList.add('active')
                }
              } else {
                suggestList.querySelector('li:last-of-type').classList.add('active')
              }
              /*code addedd*/
              suggestList.scrollTop = suggestList.querySelector('.active').offsetTop;
              break
            case 39:
              break
            case 40: //cursor down
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (activeLi) {
                activeLi.classList.remove('active')
                let nextLi = activeLi.nextSibling
                if (nextLi) {
                  nextLi.classList.add('active')
                } else {
                  suggestList.querySelector('li:first-of-type').classList.add('active')
                }
              } else {
                suggestList.querySelector('li:first-of-type').classList.add('active')
              }
              /*added code*/
              suggestList.scrollTop = suggestList.querySelector('.active').offsetTop;
              break
            case 46: // delete
              suggestList.flush()
              break
            default:
              break
          }
        }
      }
    })
    plzInput.addEventListener('blur', function(evt) {
      setTimeout(function() {
        suggestList.flush()
      }, 250)
    })
    ortInput.addEventListener('input', function(evt) {
      console.log(this.value)
    })
  }
}

plzOrtAutoComplete();
&#13;
.autocomplete-suggest {
  background-color: #fff;
  border: 1px solid #ddd;
  box-shadow: 3px 3px 5px #ccc;
  max-height: 20em;
  left: 5px;
  opacity: 1;
  overflow-y: auto;
  pointer-events: all;
  position: absolute;
  z-index: 999;
  transition-duration: .2s;
}
.autocomplete-suggest:empty {
  max-height: 0;
  opacity: 0;
  pointer-events: none;
}
.autocomplete-suggest li {
  line-height: 1.5em;
  margin: 0 auto;
  padding: .3em 1.2em .2em .6em;
  transition: all .2s ease-in-out;
  white-space: nowrap;
}
.autocomplete-suggest li:nth-child(even) {
  background-color: rgba(0, 0, 0, 0.05);
}
.autocomplete-suggest li:hover, .autocomplete-suggest li.active {
  cursor: pointer;
  background-color: #a00;
  color: #fff;
}
&#13;
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div class="form-row" data-has-plz-ort-autocomplete data-plz="eazVersicherterPLZ" data-ort="eazVersicherterOrt">
  <div class="form-group col-md-4">
    <label for="eazVersicherterPLZ">PLZ</label>
    <input class="form-control" type="text" id="eazVersicherterPLZ" name="eazVersicherterPLZ" data-plz="eazVersicherterOrt" maxlength=5 />
  </div>

  <div class="form-group col-md-8">
    <label for="eazVersicherterOrt">Ort</label>
    <input class="form-control" type="text" id="eazVersicherterOrt" name="eazVersicherterOrt">
  </div>
</div>
&#13;
&#13;
&#13;

如果您不希望活动元素始终位于顶部,您也可以使用一些偏移:

suggestList.scrollTop = suggestList.querySelector('.active').offsetTop - x;

您可以调整x值,使活动元素位于底部,中间或保持在顶部。

&#13;
&#13;
let plz = [{"plz":"56244","ort":"Rückeroth","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ötzingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Niedersayn","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Vielbach","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Hartenfels","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ewighausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Leuterod","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Kuhnhöfen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Goddert","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Freirachdorf","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Maxsain","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Freilingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Weidenhahn","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Helferskirchen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Arnshöfen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Hahn am See","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Sessenhausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Wölferlingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Steinen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Schenkelberg","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Krümmel","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ettinghausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"}]

let plzAutoCompleteConfig = {
  minCharactersToRun: 3,
  maxResults: 100,
  allowedKeyCodes: [8, 9, 13, 37, 38, 39, 40, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105]
}


let plzOrtAutoComplete = function plzOrtAutoComplete() {
  if (!document.querySelector('[data-has-plz-ort-autocomplete]')) return;

  // find all plz autocompletes
  let autocompletes = Array.from(document.querySelectorAll('[data-has-plz-ort-autocomplete]'))

  for (let autocomplete of autocompletes) {
    let plzInput = document.getElementById(autocomplete.getAttribute('data-plz'))
    let ortInput = document.getElementById(autocomplete.getAttribute('data-ort'))
    let suggestList = document.createElement('ul')
    suggestList.flush = function() {
      this.innerHTML = ''
    }
    suggestList.className = 'autocomplete-suggest list-unstyled'
    plzInput.parentNode.appendChild(suggestList)

    for (let eventName of ['input', 'focus']) {
      plzInput.addEventListener(eventName, function(evt) {
        const checkInput = () => {
          let matches = plz.filter(x => {
            return x.plz.startsWith(this.value)
          })
          switch (true) {
            case (matches.length > plzAutoCompleteConfig.maxResults):
              suggestList.flush()
              break
            case ((matches.length <= plzAutoCompleteConfig.maxResults && matches.length > 1) || (matches.length === 1 && this.value.length < 5)):
              suggestList.flush()
              for (let match of matches) {
                let li = document.createElement('li')
                li.textContent = `${match.plz} ${match.ort}`
                li.title = `${match.plz} ${match.ort} in ${match.bundesland}, ${match.kreis} übernehmen durch Enter oder Klick`
                li.addEventListener('click', () => {
                  plzInput.value = match.plz
                  ortInput.value = match.ort
                  ortInput.focus()
                  suggestList.flush()
                })
                li.addEventListener('mouseenter', function() {
                  this.classList.add('active')
                })
                li.addEventListener('mouseleave', function() {
                  this.classList.remove('active')
                })
                suggestList.appendChild(li)
              }
              this.parentNode.appendChild(suggestList)
              break
            case (matches.length === 1 && this.value.length === 5):
              if (event.type !== 'focus' && ['deleteContentBackward', 'deleteContentForward'].indexOf(evt.inputType) === -1) {
                suggestList.flush()
                plzInput.value = matches[0].plz
                ortInput.value = matches[0].ort
                ortInput.focus()
              }
              break
            default:
              {
                suggestList.flush()
                break
              }
          }
        }
        if (isNaN(Number(this.value))) {
          this.value = ''
          return
        }
        if (this.value.length >= plzAutoCompleteConfig.minCharactersToRun) {
          if (['deleteContentBackward', 'deleteContentForward'].indexOf(evt.inputType) > -1) {
            console.log(this.value)
          }
          checkInput()
        }
      })
    }

    plzInput.addEventListener('keydown', function(evt) {
      let keyCode = evt.keyCode || evt.which;
      let activeLi = suggestList.querySelector('li.active')
      if (keyCode) {
        if (!plzAutoCompleteConfig.allowedKeyCodes.includes(keyCode)) {
          evt.preventDefault()
        } else {
          switch (keyCode) {
            case 8: // backspace
              suggestList.flush()
              break
            case 13: // Enter
              evt.preventDefault()
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (!activeLi) {
                return
              } else {
                plzInput.value = activeLi.textContent.substr(0, 5)
                ortInput.value = activeLi.textContent.substr(6)
                suggestList.flush()
                ortInput.focus()
              }
              break
            case 37:
              break
            case 38: // cursor up
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (activeLi) {
                activeLi.classList.remove('active')
                let prevLi = activeLi.previousSibling
                if (prevLi) {
                  prevLi.classList.add('active')
                } else {
                  suggestList.querySelector('li:last-of-type').classList.add('active')
                }
              } else {
                suggestList.querySelector('li:last-of-type').classList.add('active')
              }
              /*code addedd*/
              suggestList.scrollTop = suggestList.querySelector('.active').offsetTop - 100;
              break
            case 39:
              break
            case 40: //cursor down
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (activeLi) {
                activeLi.classList.remove('active')
                let nextLi = activeLi.nextSibling
                if (nextLi) {
                  nextLi.classList.add('active')
                } else {
                  suggestList.querySelector('li:first-of-type').classList.add('active')
                }
              } else {
                suggestList.querySelector('li:first-of-type').classList.add('active')
              }
              /*added code*/
              suggestList.scrollTop = suggestList.querySelector('.active').offsetTop - 100;
              break
            case 46: // delete
              suggestList.flush()
              break
            default:
              break
          }
        }
      }
    })
    plzInput.addEventListener('blur', function(evt) {
      setTimeout(function() {
        suggestList.flush()
      }, 250)
    })
    ortInput.addEventListener('input', function(evt) {
      console.log(this.value)
    })
  }
}

plzOrtAutoComplete();
&#13;
.autocomplete-suggest {
  background-color: #fff;
  border: 1px solid #ddd;
  box-shadow: 3px 3px 5px #ccc;
  max-height: 20em;
  left: 5px;
  opacity: 1;
  overflow-y: auto;
  pointer-events: all;
  position: absolute;
  z-index: 999;
  transition-duration: .2s;
}
.autocomplete-suggest:empty {
  max-height: 0;
  opacity: 0;
  pointer-events: none;
}
.autocomplete-suggest li {
  line-height: 1.5em;
  margin: 0 auto;
  padding: .3em 1.2em .2em .6em;
  transition: all .2s ease-in-out;
  white-space: nowrap;
}
.autocomplete-suggest li:nth-child(even) {
  background-color: rgba(0, 0, 0, 0.05);
}
.autocomplete-suggest li:hover, .autocomplete-suggest li.active {
  cursor: pointer;
  background-color: #a00;
  color: #fff;
}
&#13;
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div class="form-row" data-has-plz-ort-autocomplete data-plz="eazVersicherterPLZ" data-ort="eazVersicherterOrt">
  <div class="form-group col-md-4">
    <label for="eazVersicherterPLZ">PLZ</label>
    <input class="form-control" type="text" id="eazVersicherterPLZ" name="eazVersicherterPLZ" data-plz="eazVersicherterOrt" maxlength=5 />
  </div>

  <div class="form-group col-md-8">
    <label for="eazVersicherterOrt">Ort</label>
    <input class="form-control" type="text" id="eazVersicherterOrt" name="eazVersicherterOrt">
  </div>
</div>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

对于那些感兴趣的人,以下是我最终实现它的方式:

&#13;
&#13;
let plz = [{"plz":"56244","ort":"Rückeroth","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ötzingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Niedersayn","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Vielbach","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Hartenfels","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ewighausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Leuterod","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Kuhnhöfen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Goddert","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Freirachdorf","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Maxsain","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Freilingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Weidenhahn","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Helferskirchen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Arnshöfen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Hahn am See","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Sessenhausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Wölferlingen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Steinen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Schenkelberg","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Krümmel","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"},{"plz":"56244","ort":"Ettinghausen","bundesland":"Rheinland-Pfalz","kreis":"Westerwaldkreis"}]

let plzAutoCompleteConfig = {
  minCharactersToRun: 3,
  maxResults: 100,
  allowedKeyCodes: [8, 9, 13, 37, 38, 39, 40, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105]
}


let plzOrtAutoComplete = function plzOrtAutoComplete() {
  if (!document.querySelector('[data-has-plz-ort-autocomplete]')) return;

  // find all plz autocompletes
  let autocompletes = Array.from(document.querySelectorAll('[data-has-plz-ort-autocomplete]'))

  for (let autocomplete of autocompletes) {
    let plzInput = document.getElementById(autocomplete.getAttribute('data-plz'))
    let ortInput = document.getElementById(autocomplete.getAttribute('data-ort'))
    let suggestList = document.createElement('ul')
    suggestList.flush = function() {
      this.innerHTML = ''
    }
    suggestList.className = 'autocomplete-suggest list-unstyled'
    plzInput.parentNode.appendChild(suggestList)

    for (let eventName of ['input', 'focus']) {
      plzInput.addEventListener(eventName, function(evt) {
        const checkInput = () => {
          let matches = plz.filter(x => {
            return x.plz.startsWith(this.value)
          })
          switch(true) {
            case(matches.length > plzAutoCompleteConfig.maxResults):
              suggestList.flush()
              break
            case((matches.length <= plzAutoCompleteConfig.maxResults && matches.length > 1) || (matches.length === 1 && this.value.length < 5)):
              suggestList.flush()
              for (let match of matches) {
                let li = document.createElement('li')
                li.textContent = `${match.plz} ${match.ort}`
                li.title = `${match.plz} ${match.ort} in ${match.bundesland}, ${match.kreis} übernehmen durch Enter oder Klick`
                li.addEventListener('click', () => {
                  plzInput.value = match.plz
                  ortInput.value = match.ort
                  ortInput.focus()
                  suggestList.flush()
                })
                li.addEventListener('mouseenter', function() {
                  this.classList.add('active')
                })
                li.addEventListener('mouseleave', function() {
                  this.classList.remove('active')
                })
                suggestList.appendChild(li)
              }
              this.parentNode.appendChild(suggestList)
              break
            case(matches.length === 1 && this.value.length === 5):
              if (event.type !== 'focus' && ['deleteContentBackward', 'deleteContentForward'].indexOf(evt.inputType) === -1) {
                suggestList.flush()
                plzInput.value = matches[0].plz
                ortInput.value = matches[0].ort
                ortInput.focus()
              }
              break
            default: {
              suggestList.flush()
              break
            }
          }
        }
        if (isNaN(Number(this.value))) {
          this.value = ''
          return
        }
        if (this.value.length >= plzAutoCompleteConfig.minCharactersToRun) {
          if (['deleteContentBackward', 'deleteContentForward'].indexOf(evt.inputType) > -1) {
            console.log(this.value)
          }
          checkInput()
        }
      })
    }

    plzInput.addEventListener('keydown', function(evt) {
      let keyCode = evt.keyCode || evt.which;
      let activeLi = suggestList.querySelector('.active')
      if (keyCode) {
        if (!plzAutoCompleteConfig.allowedKeyCodes.includes(keyCode)) {
          evt.preventDefault()
        } else {
          switch (keyCode) {
            case 8: // backspace
              suggestList.flush()
              break
            case 13: // Enter
              evt.preventDefault()
              if (!suggestList.hasChildNodes()) {
                return
              }
              if (!activeLi) {
                return
              } else {
                plzInput.value = activeLi.textContent.substr(0,5)
                ortInput.value = activeLi.textContent.substr(6)
                suggestList.flush()
                ortInput.focus()
              }
              break
            case 37:
              break
            case 38: // cursor up
              if (!suggestList.hasChildNodes()) {
                return
              }
              for (let item of Array.from(suggestList.querySelectorAll('li'))) {
                if (item !== activeLi) {
                  item.classList.remove('active')
                }
              }
              if (activeLi) {
                activeLi.classList.remove('active')
                let prevLi = activeLi.previousSibling || suggestList.querySelector('li:last-of-type')
                if (prevLi) {
                  prevLi.classList.add('active')
                  if (prevLi.offsetTop < suggestList.scrollTop || prevLi.offsetTop > suggestList.scrollTop + suggestList.getBoundingClientRect().height) {
                    suggestList.scrollTop = prevLi.offsetTop
                  }
                }
              }
              break
            case 39:
              break
            case 40: // cursor down
              if (!suggestList.hasChildNodes()) {
                return
              }
              for (let item of Array.from(suggestList.querySelectorAll('li'))) {
                if (item !== activeLi) {
                  item.classList.remove('active')
                }
              }
              if (activeLi) {
                activeLi.classList.remove('active')
                let nextLi = activeLi.nextSibling || suggestList.querySelector('li:first-of-type')
                if (nextLi) {
                  activeLi.classList.remove('active')
                  nextLi.classList.add('active')
                  if (nextLi.offsetTop < suggestList.scrollTop || nextLi.offsetTop > suggestList.scrollTop + suggestList.getBoundingClientRect().height - 32) {
                    suggestList.scrollTop = nextLi.offsetTop - suggestList.getBoundingClientRect().height + 32
                  }
                }
              } else {
                suggestList.querySelector('li:first-of-type').classList.add('active')
              }
              break
            case 46: // delete
              suggestList.flush()
              break
            default:
              break
          }
        }
      }
    })
    plzInput.addEventListener('blur', function(evt) {
      setTimeout(function() { suggestList.flush() }, 200500)
    })
    ortInput.addEventListener('input', function(evt) {
      console.log(this.value)
    })
  }
}

plzOrtAutoComplete();
&#13;
.autocomplete-suggest {
  background-color: #fff;
  border: 1px solid #ddd;
  box-shadow: 3px 3px 5px #ccc;
  max-height: 6em;
  left: 5px;
  opacity: 1;
  overflow-y: auto;
  pointer-events: all;
  position: absolute;
  z-index: 999;
  transition-duration: .2s;
}
.autocomplete-suggest:empty {
  max-height: 0;
  opacity: 0;
  pointer-events: none;
}
.autocomplete-suggest li {
  line-height: 1.5em;
  margin: 0 auto;
  padding: .3em 1.2em .2em .6em;
  white-space: nowrap;
}
.autocomplete-suggest li:hover, .autocomplete-suggest li.active {
  cursor: pointer;
  background-color: #a00;
  color: #fff;
}
&#13;
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div class="form-row" data-has-plz-ort-autocomplete data-plz="eazVersicherterPLZ" data-ort="eazVersicherterOrt">
  <div class="form-group col-sm-4">
    <label for="eazVersicherterPLZ">PLZ</label>
    <input class="form-control" type="text" id="eazVersicherterPLZ" name="eazVersicherterPLZ" data-plz="eazVersicherterOrt" maxlength=5 />
  </div>

  <div class="form-group col-sm-8">
    <label for="eazVersicherterOrt">Ort</label>
    <input class="form-control" type="text" id="eazVersicherterOrt" name="eazVersicherterOrt">
  </div>
</div>
&#13;
&#13;
&#13;