0

I want to combine the functions of jQueryUI autocmoplete and a tag input library (unfortunaly I don't remember where I got the library from, but the source code is inside the snippet).


A little documentation about the tag input library:

The tag input library basically hides the original <input/> and adds a new <input class="tag-input" /> when initialized by new Tags('#testTags'). New tags can be added with addTags("someTag");


What works so far:

When I receive some values from autocomplete and the user selects the value, I call addTags(ui.item.value); - which is perfectly fine. enter image description here


Where I have troubles:

Many users don't pick the value from the autocompleter, they rather just input a text and click on 'search'. I want to ensure that only values from the autocompleter are inserted. I understand, that only clicking on autocompleter values is not the best for user experience, so I want that if a user enters a text and click on 'search', a matching value from the autocompleter is automatically picked (if one exists).

I tried this little piece of code:

$('#search').on('click',function(){
  if($( '.tag-input[placeholder="test1"]' ).val() != "") //user did not finish entry
  {
    testTags.addTags($( '.tag-input[placeholder="test1"]' ).val());
  }
});

It checks on button click if the input contains a value, that isn't transformed into a tag yet. If so, I add this value as tag. My problem is, that this value should be matched against the autocompleter values before "transforming" to a tag. How to access those autocompleter suggestions outside of .autocomplete({}) ?

A related question would be this: How do I get the jQuery UI autocomplete value from outside an onchange event? The difference is, that I rather need the autocomplete suggestions instead of the selected value.


!function(){"use strict";function e(e,t){return"string"==typeof e?(t||document).querySelectorAll(e):[e]}function t(e,t){return"string"==typeof e?(t||document).querySelector(e):e}function n(e,t){var n=document.createElement(e);if(t)for(var i in t)void 0!==n[i]&&(n[i]=t[i]);return n}function i(){var e=document.documentElement,t={transition:"transitionend",WebkitTransition:"webkitTransitionEnd",MozTransition:"mozTransitionEnd",OTransition:"oTransitionEnd otransitionend"};for(var n in t)if(void 0!==e.style[n])return t[n];return!1}function r(e,t,n,i){i=i||!1,e.addEventListener(t,function t(r){n.call(this,r),e.removeEventListener(r.type,t,i)},i)}function a(e,t){return new RegExp("(^|\\s+)"+e+"(\\s+|$)").test(t.className)}function o(e,t){if(!a(e,t))return t.className+=""===t.className?e:" "+e}function s(e,t){t.className=t.className.replace(new RegExp("(^|\\s+)"+e+"(\\s+|$)"),"")}function u(u){function l(){y=n("div",{className:"tags-container"}),N=n("input",{type:"text",className:"tag-input",placeholder:h.placeholder||""}),y.appendChild(N),""!==h.value.trim()&&c(),h.type="hidden",h.parentNode.insertBefore(y,h.nextSibling),y.addEventListener("click",v,!1),y.addEventListener("keydown",d,!1),y.addEventListener("keyup",g,!1)}function c(){var e=h.value.trim().split(",");e.forEach(function(e){if(e=e.trim(),!~x.indexOf(e)){var t=f(e);x.push(e),y.insertBefore(t,N)}})}function f(e){var t=n("div",{className:"tag",innerHTML:'<span class="tag__name">'+e+'</span><button class="tag__remove">&times;</button>'});return t}function v(e){if(e.preventDefault(),"tag__remove"===e.target.className){var n=e.target.parentNode,i=t(".tag__name",n);y.removeChild(n),x.splice(x.indexOf(i.textContent),1),h.value=x.join(",")}N.focus()}function d(e){if("INPUT"===e.target.tagName&&"tag-input"===e.target.className){var t=e.target,n=e.which||e.keyCode;N.previousSibling&&n!==C.BACK&&s("tag--marked",N.previousSibling);var i=t.value.trim();n===C.ENTER?(t.blur(),m(i),E&&clearTimeout(E),E=setTimeout(function(){t.focus()},10)):n===C.BACK&&(""!==e.target.value||L||(L=!0,p()))}}function g(e){L=!1}function m(t){if(t=t.toString().replace(/,/g,"").trim(),""===t)return N.value="";if(~x.indexOf(t)){var n=e(".tag",y);return Array.prototype.forEach.call(n,function(e){e.firstChild.textContent===t&&(o("tag--exists",e),k?r(e,k,function(){s("tag--exists",e)}):s("tag--exists",e))}),N.value=""}var i=f(t);y.insertBefore(i,N),x.push(t),N.value="",h.value+=""===h.value?t:","+t}function p(){if(0!==x.length){var t=e(".tag",y),n=t[t.length-1];if(!a("tag--marked",n))return void o("tag--marked",n);x.pop(),y.removeChild(n),h.value=x.join(",")}}var h=t(u);if(!h.instance){h.instance=this;var E,y,N,T=h.type,k=i(),x=[],C={ENTER:13,BACK:8},L=!1;l(),this.getTags=function(){return x},this.clearTags=function(){h.instance&&(x.length=0,h.value="",y.innerHTML="",y.appendChild(N))},this.addTags=function(e){if(h.instance){if(Array.isArray(e))for(var t=0,n=e.length;t<n;t++)m(e[t]);else m(e);return x}},this.destroy=function(){h.instance&&(y.removeEventListener("click",v,!1),y.removeEventListener("keydown",d,!1),y.removeEventListener("keyup",d,!1),y.parentNode.removeChild(y),h.type=T,T=null,x=null,E=null,y=null,N=null,delete h.instance)}}}window.Tags=u}();




var testTags = new Tags('#testTags');

$( function() {
    var availableTags = [
      "ActionScript",
      "AppleScript",
      "Asp",
      "BASIC",
      "C",
      "C++",
      "Clojure",
      "COBOL",
      "ColdFusion",
      "Erlang",
      "Fortran",
      "Groovy",
      "Haskell",
      "Java",
      "JavaScript",
      "Lisp",
      "Perl",
      "PHP",
      "Python",
      "Ruby",
      "Scala",
      "Scheme"
    ];
    
    $( '.tag-input[placeholder="test1"]' ).autocomplete({
      source: availableTags,
      select: function(event, ui){
                event.preventDefault();
                event.stopPropagation();

                testTags.addTags(ui.item.value);

                return false;
            }
    });
    
    $('#search').on('click',function(){
      if($( '.tag-input[placeholder="test1"]' ).val() != "") //user did not finish entry
      {
        testTags.addTags($( '.tag-input[placeholder="test1"]' ).val());
      }
    });
    
 });
*,:after,:before{box-sizing:border-box}.tags-container{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-bottom:15px;width:100%;min-height:34px;padding:2px 5px;font-size:14px;line-height:1.6;background-color:transparent;border:1px solid #ccc;border-radius:1px;overflow:hidden;word-wrap:break-word;box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}input.tag-input{-ms-flex:3;flex:3;border:0;outline:0}.tag{position:relative;margin:2px 6px 2px 0;padding:1px 20px 1px 8px;font-size:inherit;font-weight:400;text-align:center;color:#fff;background-color:#317caf;border-radius:3px;transition:background-color .3s ease;cursor:default}.tag:first-child{margin-left:0}.tag--marked{background-color:#6fadd7}.tag--exists{background-color:#edb5a1;-webkit-animation:a 1s linear;animation:a 1s linear}.tag__name{margin-right:3px}.tag__remove{position:absolute;right:0;bottom:0;width:20px;height:100%;padding:0 5px;font-size:16px;font-weight:400;transition:opacity .3s ease;opacity:.5;cursor:pointer;border:0;background-color:transparent;color:#fff;line-height:1}.tag__remove:hover{opacity:1}@-webkit-keyframes a{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}}@keyframes a{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.js"></script>

<input id="testTags" placeholder="test1">
<button id="search">search</button>
toffler
  • 1,231
  • 10
  • 27

1 Answers1

1

It's very unclear what you are trying to accomplish. I am guessing, that if the User enter "apple", moves away from the input, and clicks the search button, you want to grab the first match from availableTags that might work?

Consider the following.

  $('#search').on('click', function() {
    if ($('.tag-input[placeholder="test1"]').val() != "") {
      //user did not finish entry
      var partialTag = $('.tag-input[placeholder="test1"]').val();
      var tag = $.ui.autocomplete.filter(availableTags, partialTag)[0];
      testTags.addTags(tag);
    }
  });

The use of $.ui.autocomplete.filter() can help filter the results in the same way Autocomplete would. It returns an Array of results. It is demonstrated in the following: https://jqueryui.com/autocomplete/#multiple

It is not heavily documented, yet you can find a bit more here: https://api.jqueryui.com/autocomplete/

Here is a full example.

! function() {
  "use strict";

  function e(e, t) {
    return "string" == typeof e ? (t || document).querySelectorAll(e) : [e]
  }

  function t(e, t) {
    return "string" == typeof e ? (t || document).querySelector(e) : e
  }

  function n(e, t) {
    var n = document.createElement(e);
    if (t)
      for (var i in t) void 0 !== n[i] && (n[i] = t[i]);
    return n
  }

  function i() {
    var e = document.documentElement,
      t = {
        transition: "transitionend",
        WebkitTransition: "webkitTransitionEnd",
        MozTransition: "mozTransitionEnd",
        OTransition: "oTransitionEnd otransitionend"
      };
    for (var n in t)
      if (void 0 !== e.style[n]) return t[n];
    return !1
  }

  function r(e, t, n, i) {
    i = i || !1, e.addEventListener(t, function t(r) {
      n.call(this, r), e.removeEventListener(r.type, t, i)
    }, i)
  }

  function a(e, t) {
    return new RegExp("(^|\\s+)" + e + "(\\s+|$)").test(t.className)
  }

  function o(e, t) {
    if (!a(e, t)) return t.className += "" === t.className ? e : " " + e
  }

  function s(e, t) {
    t.className = t.className.replace(new RegExp("(^|\\s+)" + e + "(\\s+|$)"), "")
  }

  function u(u) {
    function l() {
      y = n("div", {
        className: "tags-container"
      }), N = n("input", {
        type: "text",
        className: "tag-input",
        placeholder: h.placeholder || ""
      }), y.appendChild(N), "" !== h.value.trim() && c(), h.type = "hidden", h.parentNode.insertBefore(y, h.nextSibling), y.addEventListener("click", v, !1), y.addEventListener("keydown", d, !1), y.addEventListener("keyup", g, !1)
    }

    function c() {
      var e = h.value.trim().split(",");
      e.forEach(function(e) {
        if (e = e.trim(), !~x.indexOf(e)) {
          var t = f(e);
          x.push(e), y.insertBefore(t, N)
        }
      })
    }

    function f(e) {
      var t = n("div", {
        className: "tag",
        innerHTML: '<span class="tag__name">' + e + '</span><button class="tag__remove">&times;</button>'
      });
      return t
    }

    function v(e) {
      if (e.preventDefault(), "tag__remove" === e.target.className) {
        var n = e.target.parentNode,
          i = t(".tag__name", n);
        y.removeChild(n), x.splice(x.indexOf(i.textContent), 1), h.value = x.join(",")
      }
      N.focus()
    }

    function d(e) {
      if ("INPUT" === e.target.tagName && "tag-input" === e.target.className) {
        var t = e.target,
          n = e.which || e.keyCode;
        N.previousSibling && n !== C.BACK && s("tag--marked", N.previousSibling);
        var i = t.value.trim();
        n === C.ENTER ? (t.blur(), m(i), E && clearTimeout(E), E = setTimeout(function() {
          t.focus()
        }, 10)) : n === C.BACK && ("" !== e.target.value || L || (L = !0, p()))
      }
    }

    function g(e) {
      L = !1
    }

    function m(t) {
      if (t = t.toString().replace(/,/g, "").trim(), "" === t) return N.value = "";
      if (~x.indexOf(t)) {
        var n = e(".tag", y);
        return Array.prototype.forEach.call(n, function(e) {
          e.firstChild.textContent === t && (o("tag--exists", e), k ? r(e, k, function() {
            s("tag--exists", e)
          }) : s("tag--exists", e))
        }), N.value = ""
      }
      var i = f(t);
      y.insertBefore(i, N), x.push(t), N.value = "", h.value += "" === h.value ? t : "," + t
    }

    function p() {
      if (0 !== x.length) {
        var t = e(".tag", y),
          n = t[t.length - 1];
        if (!a("tag--marked", n)) return void o("tag--marked", n);
        x.pop(), y.removeChild(n), h.value = x.join(",")
      }
    }
    var h = t(u);
    if (!h.instance) {
      h.instance = this;
      var E, y, N, T = h.type,
        k = i(),
        x = [],
        C = {
          ENTER: 13,
          BACK: 8
        },
        L = !1;
      l(), this.getTags = function() {
        return x
      }, this.clearTags = function() {
        h.instance && (x.length = 0, h.value = "", y.innerHTML = "", y.appendChild(N))
      }, this.addTags = function(e) {
        if (h.instance) {
          if (Array.isArray(e))
            for (var t = 0, n = e.length; t < n; t++) m(e[t]);
          else m(e);
          return x
        }
      }, this.destroy = function() {
        h.instance && (y.removeEventListener("click", v, !1), y.removeEventListener("keydown", d, !1), y.removeEventListener("keyup", d, !1), y.parentNode.removeChild(y), h.type = T, T = null, x = null, E = null, y = null, N = null, delete h.instance)
      }
    }
  }
  window.Tags = u
}();




var testTags = new Tags('#testTags');

$(function() {
  var availableTags = [
    "ActionScript",
    "AppleScript",
    "Asp",
    "BASIC",
    "C",
    "C++",
    "Clojure",
    "COBOL",
    "ColdFusion",
    "Erlang",
    "Fortran",
    "Groovy",
    "Haskell",
    "Java",
    "JavaScript",
    "Lisp",
    "Perl",
    "PHP",
    "Python",
    "Ruby",
    "Scala",
    "Scheme"
  ];

  $('.tag-input[placeholder="test1"]').autocomplete({
    source: availableTags,
    select: function(event, ui) {
      event.preventDefault();
      event.stopPropagation();
      testTags.addTags(ui.item.value);
      return false;
    }
  });

  $('#search').on('click', function() {
    if ($('.tag-input[placeholder="test1"]').val() != "") {
      //user did not finish entry
      var partialTag = $('.tag-input[placeholder="test1"]').val();
      var tag = $.ui.autocomplete.filter(availableTags, partialTag)[0];
      testTags.addTags(tag);
    }
  });

});
*,
:after,
:before {
  box-sizing: border-box
}

.tags-container {
  display: -ms-flexbox;
  display: flex;
  -ms-flex-flow: row wrap;
  flex-flow: row wrap;
  margin-bottom: 15px;
  width: 100%;
  min-height: 34px;
  padding: 2px 5px;
  font-size: 14px;
  line-height: 1.6;
  background-color: transparent;
  border: 1px solid #ccc;
  border-radius: 1px;
  overflow: hidden;
  word-wrap: break-word;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1)
}

input.tag-input {
  -ms-flex: 3;
  flex: 3;
  border: 0;
  outline: 0
}

.tag {
  position: relative;
  margin: 2px 6px 2px 0;
  padding: 1px 20px 1px 8px;
  font-size: inherit;
  font-weight: 400;
  text-align: center;
  color: #fff;
  background-color: #317caf;
  border-radius: 3px;
  transition: background-color .3s ease;
  cursor: default
}

.tag:first-child {
  margin-left: 0
}

.tag--marked {
  background-color: #6fadd7
}

.tag--exists {
  background-color: #edb5a1;
  -webkit-animation: a 1s linear;
  animation: a 1s linear
}

.tag__name {
  margin-right: 3px
}

.tag__remove {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 20px;
  height: 100%;
  padding: 0 5px;
  font-size: 16px;
  font-weight: 400;
  transition: opacity .3s ease;
  opacity: .5;
  cursor: pointer;
  border: 0;
  background-color: transparent;
  color: #fff;
  line-height: 1
}

.tag__remove:hover {
  opacity: 1
}

@-webkit-keyframes a {
  0%,
  to {
    -webkit-transform: translateZ(0);
    transform: translateZ(0)
  }
  10%,
  30%,
  50%,
  70%,
  90% {
    -webkit-transform: translate3d(-5px, 0, 0);
    transform: translate3d(-5px, 0, 0)
  }
  20%,
  40%,
  60%,
  80% {
    -webkit-transform: translate3d(5px, 0, 0);
    transform: translate3d(5px, 0, 0)
  }
}

@keyframes a {
  0%,
  to {
    -webkit-transform: translateZ(0);
    transform: translateZ(0)
  }
  10%,
  30%,
  50%,
  70%,
  90% {
    -webkit-transform: translate3d(-5px, 0, 0);
    transform: translate3d(-5px, 0, 0)
  }
  20%,
  40%,
  60%,
  80% {
    -webkit-transform: translate3d(5px, 0, 0);
    transform: translate3d(5px, 0, 0)
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.js"></script>

<input id="testTags" placeholder="test1">
<button id="search">search</button>
Twisty
  • 30,304
  • 2
  • 26
  • 45
  • Thank's for your explanation. I can access the elements of autocomplete with `$.ui.autocomplete`, but what if the source is from an ajax call? – toffler May 27 '22 at 05:51
  • 1
    @toffler you will need to make a decision, make the ajax call first, and cache the results, or let it perform the call each time. – Twisty May 27 '22 at 14:18