finn-k
1/19/2018 - 9:52 AM

Input Autocomplete Dropdown

Eine auf Bootstrap und jQuery basierende InputDropdown, die mit Daten in einem bestimmten Format initialisiert werden muss. Dann wird automatisch eine Suche nach zum Input passenden Datensätzen durchgeführt und angezeigt. Die ID des ausgewählten Datensatzes wird als data-selected an den Input der Dropdown angefügt. Auch wenn der User manuell einen Namen eingibt wird das Eingegebene mit den registrierten Datensätzen verglichen, sobald der Dropdown-Input den Fokus verliert.

<!doctype html>
<html lang="en">
  <head>
    <title>Basic</title>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
    <style>
      .fk-input-dropdown-container .dropdown-menu {
        width: 100%;
      }

      .fk-dropdown-input-container {
        padding: 0;
        border: 0;
        position: relative;
        cursor: text;
        border-radius: .25rem;
      }

      .fk-dropdown-input-container::after {
        display: none;
      }

      .fk-dropdown-input {
        width: 100%;
        position: static;
      }

      .fk-dropdown-toggle-button {
        position: absolute;
        padding: 0.4rem 0.5rem;
        cursor: pointer;
        right: 0px;
        top: calc(50% - 0.85rem);
      }

      .dropdown .dropdown-menu {
          -webkit-transition: max-height 0.3s, opacity 0.2s 0.1s, visibility 0s 0.3s;
          -moz-transition: max-height 0.3s, opacity 0.2s 0.1s, visibility 0s 0.3s;
          -ms-transition: max-height 0.3s, opacity 0.2s 0.1s, visibility 0s 0.3s;
          -o-transition: max-height 0.3s, opacity 0.2s 0.1s, visibility 0s 0.3s;
          transition: max-height 0.3s, opacity 0.2s 0.1s, visibility 0s 0.3s;

          max-height: 0;
          display: block;
          overflow: auto;
          opacity: 0;
          visibility: hidden;
      }

      .dropdown.show .dropdown-menu,
      .dropdown.fk-bs-show .dropdown-menu {
          -webkit-transition: max-height 0.3s, opacity 0.2s, visibility 0s;
          -moz-transition: max-height 0.3s, opacity 0.2s, visibility 0s;
          -ms-transition: max-height 0.3s, opacity 0.2s, visibility 0s;
          -o-transition: max-height 0.3s, opacity 0.2s, visibility 0s;
          transition: max-height 0.3s, opacity 0.2s, visibility 0s;

          max-height: 113px;
          opacity: 1;
          visibility: visible;
      }

    </style>
  </head>
  <body>
    <div class="form-group">
      <div class="dropdown fk-input-dropdown-container">
        <div class="dropdown-toggle fk-dropdown-input-container"  id="example-dropdown-button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
          <input id="example-dropdown-input" type="text" placeholder="Beispiel" class="fk-dropdown-input form-control" />
          <i class="fa fa-caret-down fk-dropdown-toggle-button" aria-hidden="true"></i>
        </div>
        <div id="example-dropdown-menu" class="dropdown-menu" aria-labelledby="fk-trade-creation-wallet-exchange-button">
          <!-- Drodown Elements here -->
        </div>
      </div>
    </div>
    <!-- Optional JavaScript -->
    <script src="https://use.fontawesome.com/b9a84ad649.js"></script>
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
    <script>
      /*
      exampleData = [
        {
          name: "xy",
          id: 1
        },
        {
          name: "cds",
          id: 2
        }
      ]
      */

      /**
      Models a Dropdown with input, that shows the matching results.
      */
      InputDropdown = function(_data, _dropdown, _input, _onSelectCallback) {
        this.input = _input;
        this.data = _data;
        this.dropdown = _dropdown;
        this.$dropdownContainer = this.dropdown.parent();
        this.$dropdownButton = this.$dropdownContainer.children(".fk-dropdown-input-container");
        this.onSelectCallback = _onSelectCallback;
        this.onMatchFunction = function() {};
        this.onNoMatchFunction = function() {};

        this.init = function() {
          this.initiateDropdownBehaviour();
          this.fillWithData(this.data);
          this.handleInput();

          // Reset the input to prevent the browser from auto-fill
          this.input.val("");
        }

        this.setOnInputMatch = function(onMatch) {
            this.onMatchFunction = onMatch;
        };

        this.setOnNoInputMatch = function(onNoMatch) {
            this.onNoMatchFunction = onNoMatch;
        };

        /**
        Stops the dropdown from being closed if the user clicks on the input fields
        twice. Also makes the input getting focused if the user clicks anywhere inside
        of the dropdown button.
        */
        this.initiateDropdownBehaviour = function() {
          let _obj = this;

          this.$dropdownButton.click(function(e) {
            let $dropdownButton = $(this);
            if(e.target === $dropdownButton[0] || e.target === $dropdownButton.children(".fk-dropdown-input")[0]) {
              let $dropdownContainer = $dropdownButton.parents(".fk-input-dropdown-container");
              let $dropdownMenu = $dropdownButton.parent().children(".dropdown-menu");
              if($dropdownContainer.hasClass('show') && _obj.dropdown.hasClass('show')) {
                $dropdownContainer.toggleClass("show");
                _obj.dropdown.toggleClass('show');
                $dropdownButton.attr("aria-expanded", true)
              }
            }
            _obj.input.focus();
          });
        };

        /**
        Default onSelect function that gets fired when the user selects a dropdown
        element.
        */
        this.onSelect = function(_selectedId, _selectedName) {
          this.input.val(_selectedName);
          this.input.attr("data-selected", _selectedId);
        };

        /**
        Adds the onSelect clickhandler to the dropdown items.
        */
        this.initiateActionOnSelect = function() {
          let obj = this;

          // React to users clicks on dropdown elements
          obj.dropdown.children(".dropdown-item").click(function(event) {
            let _selectedId = parseInt($(this).attr("data-id"));
            let _selectedName = $(this).attr("data-name");
            obj.onSelect(_selectedId, _selectedName);
            obj.onSelectCallback(_selectedId, _selectedName, $(this));
          });
        };

        /**
        Inserts the data into the dropdown by creating dropdown elements.
        */
        this.fillWithData = function(_data) {
          console.log(_data);
          this.dropdown.html("");

          if(_data.length > 0) {
            for (var i = 0; i < _data.length; i++) {
              this.dropdown.append("<a class='dropdown-item' href='#' data-name='"+_data[i].name+"' data-id='" + _data[i].id + "'>"+ _data[i].name +"</li>");
            }

            this.initiateActionOnSelect();
          } else {
            this.dropdown.append("<span class='fk-multiple-selection-dropdown-no-matching-data-notification'>Keine passenden Einträge gefunden.</span>");
          }
        };

        /**
        Loops through the data by comparing it to a regular expression.
        */
        this.reload = function(inputText) {
          var dataThatMatchesInput = [];

          for (var i = 0; i < this.data.length; i++) {
            if(this.data[i].name.match(new RegExp('\\b' + inputText, "gi")) !== null) {
              var single = {
                name: this.data[i].name,
                id: this.data[i].id
              }

              dataThatMatchesInput.push(single);
            }
          }

          return dataThatMatchesInput;
        };

        this.getInput = function() {
          return this.input;
        };

        this.handleInput = function() {
          var value = "";
          var obj = this;

          // Insert the basic data when focusing the dropdown
          this.input.focus(function(e) {
            obj.fillWithData(obj.data);
          });

          this.input.keyup(function(e) {
            value = $(this).val();

            /*
            If the user enters something into the
            input, it's not determineable, whether the
            id is still correct. So the id gets resetted
            if the user presses any key. Only if he
            clicked an li it's 100% sure the input
            is valid. To validate manual input the
            value needs to be compared to the actual data.
            */

            obj.input.attr("data-selected", "");

            var dataThatMatchesInput = obj.reload(value);

            obj.fillWithData(dataThatMatchesInput);

          });

          // After the user unfocuses the input, the value can be processed
          obj.input.on("blur", function() {

            if(obj.input.attr("data-selected") === "") {
              let _input = obj.input.val().toLowerCase();

              if(_input !== "") {
                let _matchingDataElementFound = false;

                for(let i = 0; i < obj.data.length; i++) {
                  if(obj.data[i].name.toLowerCase() == _input) {
                    obj.input.attr("data-selected", ""+obj.data[i].id);
                    obj.input.attr("data-name", obj.data[i].name);
                    obj.onMatchFunction(obj.data[i].id, obj.data[i].name);
                    i = obj.data.length;
                    _matchingDataElementFound = true;
                  }
                }

                // no matching wallet or exchange connection found
                if(!_matchingDataElementFound) {
                  obj.input.attr("data-selected", "unknown");
                  obj.onNoMatchFunction();
                }
              } else {
                obj.input.attr("data-selected", "null");
              }
            }
          });
        };

        /**
        Use this method to insert new data into the dropdown
        */
        this.setData = function(_data) {
          this.data = _data;
          this.fillWithData(this.data);
        }

        /**
        Reset the state of the dropdown.
        */
        this.reset = function() {
          this.input.val("");
          this.input.attr("data-selected", "");
          this.fillWithData(this.data);
        };

        this.getSelectedId = function() {
          return this.input.attr("data-selected");
        };

        this.getSelectedName = function() {
          return this.input.val();
        };

        this.setDisabled = function(_disabled) {
          let _dropdownArrow = this.dropdown.parent().find(".fk-dropdown-toggle-button");
          this.input.prop("disabled", _disabled);

          let _preventArrowFromOpeningFunction = function(e) {
            e.stopPropagation();
          }

          if(_disabled) {
            this.$dropdownButton.unbind("click");
            this.$dropdownButton.click(_preventArrowFromOpeningFunction);
            this.$dropdownButton.addClass("fk-bs-disabled");
          } else {
            this.$dropdownButton.unbind("click");
            this.initiateDropdownBehaviour();
            this.$dropdownButton.removeClass("fk-bs-disabled");
          }
        };

      }

    </script>
    <script>
      let exampleData = [
        {
          name: "xy",
          id: 1
        },
        {
          name: "cds",
          id: 2
        }
      ];
      let exampleDropdown = new InputDropdown(exampleData, $("#example-dropdown-menu"), $("#example-dropdown-input"), function() {});
      exampleDropdown.init();
    </script>
  </body>
</html>