gitlabhq/gitlabhq

View on GitHub
app/assets/javascripts/gl_dropdown.js

Summary

Maintainability
F
6 days
Test Coverage
/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, vars-on-top, no-unused-vars, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */
/* global fuzzaldrinPlus */

import $ from 'jquery';
import _ from 'underscore';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import axios from './lib/utils/axios_utils';
import { visitUrl } from './lib/utils/url_utility';
import { isObject } from './lib/utils/type_utility';

var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput;

GitLabDropdownInput = (function() {
  function GitLabDropdownInput(input, options) {
    var $inputContainer, $clearButton;
    var _this = this;
    this.input = input;
    this.options = options;
    this.fieldName = this.options.fieldName || 'field-name';
    $inputContainer = this.input.parent();
    $clearButton = $inputContainer.find('.js-dropdown-input-clear');
    $clearButton.on(
      'click',
      (function(_this) {
        // Clear click
        return function(e) {
          e.preventDefault();
          e.stopPropagation();
          return _this.input
            .val('')
            .trigger('input')
            .focus();
        };
      })(this),
    );

    this.input
      .on('keydown', function(e) {
        var keyCode = e.which;
        if (keyCode === 13 && !options.elIsInput) {
          e.preventDefault();
        }
      })
      .on('input', function(e) {
        var val = e.currentTarget.value || _this.options.inputFieldName;
        val = val
          .split(' ')
          .join('-') // replaces space with dash
          .replace(/[^a-zA-Z0-9 -]/g, '')
          .toLowerCase() // replace non alphanumeric
          .replace(/(-)\1+/g, '-'); // replace repeated dashes
        _this.cb(_this.options.fieldName, val, {}, true);
        _this.input
          .closest('.dropdown')
          .find('.dropdown-toggle-text')
          .text(val);
      });
  }

  GitLabDropdownInput.prototype.onInput = function(cb) {
    this.cb = cb;
  };

  return GitLabDropdownInput;
})();

GitLabDropdownFilter = (function() {
  var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;

  BLUR_KEYCODES = [27, 40];

  ARROW_KEY_CODES = [38, 40];

  HAS_VALUE_CLASS = 'has-value';

  function GitLabDropdownFilter(input, options) {
    var $clearButton, $inputContainer, ref, timeout;
    this.input = input;
    this.options = options;
    this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true;
    $inputContainer = this.input.parent();
    $clearButton = $inputContainer.find('.js-dropdown-input-clear');
    $clearButton.on(
      'click',
      (function(_this) {
        // Clear click
        return function(e) {
          e.preventDefault();
          e.stopPropagation();
          return _this.input
            .val('')
            .trigger('input')
            .focus();
        };
      })(this),
    );
    // Key events
    timeout = '';
    this.input
      .on('keydown', function(e) {
        var keyCode = e.which;
        if (keyCode === 13 && !options.elIsInput) {
          e.preventDefault();
        }
      })
      .on(
        'input',
        function() {
          if (this.input.val() !== '' && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
            $inputContainer.addClass(HAS_VALUE_CLASS);
          } else if (this.input.val() === '' && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
            $inputContainer.removeClass(HAS_VALUE_CLASS);
          }
          // Only filter asynchronously only if option remote is set
          if (this.options.remote) {
            clearTimeout(timeout);
            return (timeout = setTimeout(
              function() {
                $inputContainer.parent().addClass('is-loading');

                return this.options.query(
                  this.input.val(),
                  function(data) {
                    $inputContainer.parent().removeClass('is-loading');
                    return this.options.callback(data);
                  }.bind(this),
                );
              }.bind(this),
              250,
            ));
          } else {
            return this.filter(this.input.val());
          }
        }.bind(this),
      );
  }

  GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
    return BLUR_KEYCODES.indexOf(keyCode) !== -1;
  };

  GitLabDropdownFilter.prototype.filter = function(search_text) {
    var data, elements, group, key, results, tmp;
    if (this.options.onFilter) {
      this.options.onFilter(search_text);
    }
    data = this.options.data();
    if (data != null && !this.options.filterByText) {
      results = data;
      if (search_text !== '') {
        // When data is an array of objects therefore [object Array] e.g.
        // [
        //   { prop: 'foo' },
        //   { prop: 'baz' }
        // ]
        if (_.isArray(data)) {
          results = fuzzaldrinPlus.filter(data, search_text, {
            key: this.options.keys,
          });
        } else {
          // If data is grouped therefore an [object Object]. e.g.
          // {
          //   groupName1: [
          //     { prop: 'foo' },
          //     { prop: 'baz' }
          //   ],
          //   groupName2: [
          //     { prop: 'abc' },
          //     { prop: 'def' }
          //   ]
          // }
          if (isObject(data)) {
            results = {};
            for (key in data) {
              group = data[key];
              tmp = fuzzaldrinPlus.filter(group, search_text, {
                key: this.options.keys,
              });
              if (tmp.length) {
                results[key] = tmp.map(function(item) {
                  return item;
                });
              }
            }
          }
        }
      }
      return this.options.callback(results);
    } else {
      elements = this.options.elements();
      if (search_text) {
        elements.each(function() {
          var $el, matches;
          $el = $(this);
          matches = fuzzaldrinPlus.match($el.text().trim(), search_text);
          if (!$el.is('.dropdown-header')) {
            if (matches.length) {
              return $el.show().removeClass('option-hidden');
            } else {
              return $el.hide().addClass('option-hidden');
            }
          }
        });
      } else {
        elements.show().removeClass('option-hidden');
      }

      elements
        .parent()
        .find('.dropdown-menu-empty-item')
        .toggleClass('hidden', elements.is(':visible'));
    }
  };

  return GitLabDropdownFilter;
})();

GitLabDropdownRemote = (function() {
  function GitLabDropdownRemote(dataEndpoint, options) {
    this.dataEndpoint = dataEndpoint;
    this.options = options;
  }

  GitLabDropdownRemote.prototype.execute = function() {
    if (typeof this.dataEndpoint === 'string') {
      return this.fetchData();
    } else if (typeof this.dataEndpoint === 'function') {
      if (this.options.beforeSend) {
        this.options.beforeSend();
      }
      return this.dataEndpoint(
        '',
        (function(_this) {
          // Fetch the data by calling the data funcfion
          return function(data) {
            if (_this.options.success) {
              _this.options.success(data);
            }
            if (_this.options.beforeSend) {
              return _this.options.beforeSend();
            }
          };
        })(this),
      );
    }
  };

  GitLabDropdownRemote.prototype.fetchData = function() {
    if (this.options.beforeSend) {
      this.options.beforeSend();
    }

    // Fetch the data through ajax if the data is a string
    return axios.get(this.dataEndpoint).then(({ data }) => {
      if (this.options.success) {
        return this.options.success(data);
      }
    });
  };

  return GitLabDropdownRemote;
})();

GitLabDropdown = (function() {
  var ACTIVE_CLASS,
    FILTER_INPUT,
    NO_FILTER_INPUT,
    INDETERMINATE_CLASS,
    LOADING_CLASS,
    PAGE_TWO_CLASS,
    NON_SELECTABLE_CLASSES,
    SELECTABLE_CLASSES,
    CURSOR_SELECT_SCROLL_PADDING,
    currentIndex;

  LOADING_CLASS = 'is-loading';

  PAGE_TWO_CLASS = 'is-page-two';

  ACTIVE_CLASS = 'is-active';

  INDETERMINATE_CLASS = 'is-indeterminate';

  currentIndex = -1;

  NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-item';

  SELECTABLE_CLASSES = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ', .option-hidden)';

  CURSOR_SELECT_SCROLL_PADDING = 5;

  FILTER_INPUT = '.dropdown-input .dropdown-input-field:not(.dropdown-no-filter)';

  NO_FILTER_INPUT = '.dropdown-input .dropdown-input-field.dropdown-no-filter';

  function GitLabDropdown(el1, options) {
    var searchFields, selector, self;
    this.el = el1;
    this.options = options;
    this.updateLabel = this.updateLabel.bind(this);
    this.hidden = this.hidden.bind(this);
    this.opened = this.opened.bind(this);
    this.shouldPropagate = this.shouldPropagate.bind(this);
    self = this;
    selector = $(this.el).data('target');
    this.dropdown = selector != null ? $(selector) : $(this.el).parent();
    // Set Defaults
    this.filterInput = this.options.filterInput || this.getElement(FILTER_INPUT);
    this.noFilterInput = this.options.noFilterInput || this.getElement(NO_FILTER_INPUT);
    this.highlight = !!this.options.highlight;
    this.icon = !!this.options.icon;
    this.filterInputBlur =
      this.options.filterInputBlur != null ? this.options.filterInputBlur : true;
    // If no input is passed create a default one
    self = this;
    // If selector was passed
    if (_.isString(this.filterInput)) {
      this.filterInput = this.getElement(this.filterInput);
    }
    searchFields = this.options.search ? this.options.search.fields : [];
    if (this.options.data) {
      // If we provided data
      // data could be an array of objects or a group of arrays
      if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
        this.fullData = this.options.data;
        currentIndex = -1;
        this.parseData(this.options.data);
        this.focusTextInput();
      } else {
        this.remote = new GitLabDropdownRemote(this.options.data, {
          dataType: this.options.dataType,
          beforeSend: this.toggleLoading.bind(this),
          success: (function(_this) {
            return function(data) {
              _this.fullData = data;
              _this.parseData(_this.fullData);
              _this.focusTextInput();
              if (
                _this.options.filterable &&
                _this.filter &&
                _this.filter.input &&
                _this.filter.input.val() &&
                _this.filter.input.val().trim() !== ''
              ) {
                return _this.filter.input.trigger('input');
              }
            };
            // Remote data
          })(this),
          instance: this,
        });
      }
    }
    if (this.noFilterInput.length) {
      this.plainInput = new GitLabDropdownInput(this.noFilterInput, this.options);
      this.plainInput.onInput(this.addInput.bind(this));
    }
    // Init filterable
    if (this.options.filterable) {
      this.filter = new GitLabDropdownFilter(this.filterInput, {
        elIsInput: $(this.el).is('input'),
        filterInputBlur: this.filterInputBlur,
        filterByText: this.options.filterByText,
        onFilter: this.options.onFilter,
        remote: this.options.filterRemote,
        query: this.options.data,
        keys: searchFields,
        instance: this,
        elements: (function(_this) {
          return function() {
            selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')';
            if (_this.dropdown.find('.dropdown-toggle-page').length) {
              selector = '.dropdown-page-one ' + selector;
            }
            return $(selector, this.instance.dropdown);
          };
        })(this),
        data: (function(_this) {
          return function() {
            return _this.fullData;
          };
        })(this),
        callback: (function(_this) {
          return function(data) {
            _this.parseData(data);
            if (_this.filterInput.val() !== '') {
              selector = SELECTABLE_CLASSES;
              if (_this.dropdown.find('.dropdown-toggle-page').length) {
                selector = '.dropdown-page-one ' + selector;
              }
              if ($(_this.el).is('input')) {
                currentIndex = -1;
              } else {
                $(selector, _this.dropdown)
                  .first()
                  .find('a')
                  .addClass('is-focused');
                currentIndex = 0;
              }
            }
          };
        })(this),
      });
    }
    // Event listeners
    this.dropdown.on('shown.bs.dropdown', this.opened);
    this.dropdown.on('hidden.bs.dropdown', this.hidden);
    $(this.el).on('update.label', this.updateLabel);
    this.dropdown.on('click', '.dropdown-menu, .dropdown-menu-close', this.shouldPropagate);
    this.dropdown.on(
      'keyup',
      (function(_this) {
        return function(e) {
          // Escape key
          if (e.which === 27) {
            return $('.dropdown-menu-close', _this.dropdown).trigger('click');
          }
        };
      })(this),
    );
    this.dropdown.on(
      'blur',
      'a',
      (function(_this) {
        return function(e) {
          var $dropdownMenu, $relatedTarget;
          if (e.relatedTarget != null) {
            $relatedTarget = $(e.relatedTarget);
            $dropdownMenu = $relatedTarget.closest('.dropdown-menu');
            if ($dropdownMenu.length === 0) {
              return _this.dropdown.removeClass('show');
            }
          }
        };
      })(this),
    );
    if (this.dropdown.find('.dropdown-toggle-page').length) {
      this.dropdown.find('.dropdown-toggle-page, .dropdown-menu-back').on(
        'click',
        (function(_this) {
          return function(e) {
            e.preventDefault();
            e.stopPropagation();
            return _this.togglePage();
          };
        })(this),
      );
    }
    if (this.options.selectable) {
      selector = '.dropdown-content a';
      if (this.dropdown.find('.dropdown-toggle-page').length) {
        selector = '.dropdown-page-one .dropdown-content a';
      }
      this.dropdown.on(
        'click',
        selector,
        function(e) {
          var $el, selected, selectedObj, isMarking;
          $el = $(e.currentTarget);
          selected = self.rowClicked($el);
          selectedObj = selected ? selected[0] : null;
          isMarking = selected ? selected[1] : null;
          if (this.options.clicked) {
            this.options.clicked.call(this, {
              selectedObj,
              $el,
              e,
              isMarking,
            });
          }

          // Update label right after all modifications in dropdown has been done
          if (this.options.toggleLabel) {
            this.updateLabel(selectedObj, $el, this);
          }

          $el.trigger('blur');
        }.bind(this),
      );
    }
  }

  // Finds an element inside wrapper element
  GitLabDropdown.prototype.getElement = function(selector) {
    return this.dropdown.find(selector);
  };

  GitLabDropdown.prototype.toggleLoading = function() {
    return $('.dropdown-menu', this.dropdown).toggleClass(LOADING_CLASS);
  };

  GitLabDropdown.prototype.togglePage = function() {
    var menu;
    menu = $('.dropdown-menu', this.dropdown);
    if (menu.hasClass(PAGE_TWO_CLASS)) {
      if (this.remote) {
        this.remote.execute();
      }
    }
    menu.toggleClass(PAGE_TWO_CLASS);
    // Focus first visible input on active page
    return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus();
  };

  GitLabDropdown.prototype.parseData = function(data) {
    var full_html, groupData, html, name;
    this.renderedData = data;
    if (this.options.filterable && data.length === 0) {
      // render no matching results
      html = [this.noResults()];
    } else {
      // Handle array groups
      if (isObject(data)) {
        html = [];
        for (name in data) {
          groupData = data[name];
          html.push(
            this.renderItem(
              {
                header: name,
                // Add header for each group
              },
              name,
            ),
          );
          this.renderData(groupData, name).map(function(item) {
            return html.push(item);
          });
        }
      } else {
        // Render each row
        html = this.renderData(data);
      }
    }
    // Render the full menu
    full_html = this.renderMenu(html);
    return this.appendMenu(full_html);
  };

  GitLabDropdown.prototype.renderData = function(data, group) {
    if (group == null) {
      group = false;
    }
    return data.map(
      (function(_this) {
        return function(obj, index) {
          return _this.renderItem(obj, group, index);
        };
      })(this),
    );
  };

  GitLabDropdown.prototype.shouldPropagate = function(e) {
    var $target;
    if (this.options.multiSelect || this.options.shouldPropagate === false) {
      $target = $(e.target);
      if (
        $target &&
        !$target.hasClass('dropdown-menu-close') &&
        !$target.hasClass('dropdown-menu-close-icon') &&
        !$target.data('isLink')
      ) {
        e.stopPropagation();
        return false;
      } else {
        return true;
      }
    }
  };

  GitLabDropdown.prototype.filteredFullData = function() {
    return this.fullData.filter(
      r =>
        typeof r === 'object' &&
        !Object.prototype.hasOwnProperty.call(r, 'beforeDivider') &&
        !Object.prototype.hasOwnProperty.call(r, 'header'),
    );
  };

  GitLabDropdown.prototype.opened = function(e) {
    var contentHtml;
    this.resetRows();
    this.addArrowKeyEvent();

    const dropdownToggle = this.dropdown.find('.dropdown-menu-toggle');
    const hasFilterBulkUpdate = dropdownToggle.hasClass('js-filter-bulk-update');
    const shouldRefreshOnOpen = dropdownToggle.hasClass('js-gl-dropdown-refresh-on-open');
    const hasMultiSelect = dropdownToggle.hasClass('js-multiselect');

    // Makes indeterminate items effective
    if (this.fullData && (shouldRefreshOnOpen || hasFilterBulkUpdate)) {
      this.parseData(this.fullData);
    }

    // Process the data to make sure rendered data
    // matches the correct layout
    const inputValue = this.filterInput.val();
    if (this.fullData && hasMultiSelect && this.options.processData && inputValue.length === 0) {
      this.options.processData.call(
        this.options,
        inputValue,
        this.filteredFullData(),
        this.parseData.bind(this),
      );
    }

    contentHtml = $('.dropdown-content', this.dropdown).html();
    if (this.remote && contentHtml === '') {
      this.remote.execute();
    } else {
      this.focusTextInput();
    }

    if (this.options.showMenuAbove) {
      this.positionMenuAbove();
    }

    if (this.options.opened) {
      if (this.options.preserveContext) {
        this.options.opened(e);
      } else {
        this.options.opened.call(this, e);
      }
    }

    return this.dropdown.trigger('shown.gl.dropdown');
  };

  GitLabDropdown.prototype.positionMenuAbove = function() {
    var $menu = this.dropdown.find('.dropdown-menu');

    $menu.addClass('dropdown-open-top');
    $menu.css('top', 'initial');
    $menu.css('bottom', '100%');
  };

  GitLabDropdown.prototype.hidden = function(e) {
    var $input;
    this.resetRows();
    this.removeArrayKeyEvent();
    $input = this.dropdown.find('.dropdown-input-field');
    if (this.options.filterable) {
      $input.blur();
    }
    if (this.dropdown.find('.dropdown-toggle-page').length) {
      $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
    }
    if (this.options.hidden) {
      this.options.hidden.call(this, e);
    }
    return this.dropdown.trigger('hidden.gl.dropdown');
  };

  // Render the full menu
  GitLabDropdown.prototype.renderMenu = function(html) {
    if (this.options.renderMenu) {
      return this.options.renderMenu(html);
    } else {
      var ul = document.createElement('ul');

      for (var i = 0; i < html.length; i += 1) {
        var el = html[i];

        if (el instanceof $) {
          el = el.get(0);
        }

        if (typeof el === 'string') {
          ul.innerHTML += el;
        } else {
          ul.appendChild(el);
        }
      }

      return ul;
    }
  };

  // Append the menu into the dropdown
  GitLabDropdown.prototype.appendMenu = function(html) {
    return this.clearMenu().append(html);
  };

  GitLabDropdown.prototype.clearMenu = function() {
    var selector;
    selector = '.dropdown-content';
    if (this.dropdown.find('.dropdown-toggle-page').length) {
      if (this.options.containerSelector) {
        selector = this.options.containerSelector;
      } else {
        selector = '.dropdown-page-one .dropdown-content';
      }
    }

    return $(selector, this.dropdown).empty();
  };

  GitLabDropdown.prototype.renderItem = function(data, group, index) {
    var field, html, selected, text, url, value, rowHidden;

    if (!this.options.renderRow) {
      value = this.options.id ? this.options.id(data) : data.id;

      if (value) {
        value = value.toString().replace(/'/g, "\\'");
      }
    }

    // Hide element
    if (this.options.hideRow && this.options.hideRow(value)) {
      rowHidden = true;
    }
    if (group == null) {
      group = false;
    }
    if (index == null) {
      // Render the row
      index = false;
    }
    html = document.createElement('li');

    if (data === 'divider' || data === 'separator') {
      html.className = data;
      return html;
    }
    // Header
    if (data.header != null) {
      html.className = 'dropdown-header';
      html.innerHTML = data.header;
      return html;
    }
    if (this.options.renderRow) {
      // Call the render function
      html = this.options.renderRow.call(this.options, data, this);
    } else {
      if (!selected) {
        const { fieldName } = this.options;

        if (value) {
          field = this.dropdown.parent().find(`input[name='${fieldName}'][value='${value}']`);
          if (field.length) {
            selected = true;
          }
        } else {
          field = this.dropdown.parent().find(`input[name='${fieldName}']`);
          selected = !field.length;
        }
      }
      // Set URL
      if (this.options.url != null) {
        url = this.options.url(data);
      } else {
        url = data.url != null ? data.url : '#';
      }
      // Set Text
      if (this.options.text != null) {
        text = this.options.text(data);
      } else {
        text = data.text != null ? data.text : '';
      }
      if (this.highlight) {
        text = data.template
          ? this.highlightTemplate(text, data.template)
          : this.highlightTextMatches(text, this.filterInput.val());
      }
      // Create the list item & the link
      var link = document.createElement('a');

      link.href = url;

      if (this.icon) {
        text = `<span>${text}</span>`;
        link.classList.add('d-flex', 'align-items-center');
        link.innerHTML = data.icon ? data.icon + text : text;
      } else if (this.highlight) {
        link.innerHTML = text;
      } else {
        link.textContent = text;
      }

      if (selected) {
        link.classList.add('is-active');
      }

      if (group) {
        link.dataset.group = group;
        link.dataset.index = index;
      }

      html.appendChild(link);
    }
    return html;
  };

  GitLabDropdown.prototype.highlightTemplate = function(text, template) {
    return `"<b>${_.escape(text)}</b>" ${template}`;
  };

  GitLabDropdown.prototype.highlightTextMatches = function(text, term) {
    const occurrences = fuzzaldrinPlus.match(text, term);
    const { indexOf } = [];

    return text
      .split('')
      .map(function(character, i) {
        if (indexOf.call(occurrences, i) !== -1) {
          return '<b>' + character + '</b>';
        } else {
          return character;
        }
      })
      .join('');
  };

  GitLabDropdown.prototype.noResults = function() {
    var html;
    return '<li class="dropdown-menu-empty-item"><a>No matching results</a></li>';
  };

  GitLabDropdown.prototype.rowClicked = function(el) {
    var field, groupName, isInput, selectedIndex, selectedObject, value, isMarking;

    const { fieldName } = this.options;
    isInput = $(this.el).is('input');
    if (this.renderedData) {
      groupName = el.data('group');
      if (groupName) {
        selectedIndex = el.data('index');
        selectedObject = this.renderedData[groupName][selectedIndex];
      } else {
        selectedIndex = el.closest('li').index();
        selectedObject = this.renderedData[selectedIndex];
      }
    }

    if (this.options.vue) {
      if (el.hasClass(ACTIVE_CLASS)) {
        el.removeClass(ACTIVE_CLASS);
      } else {
        el.addClass(ACTIVE_CLASS);
      }

      return [selectedObject];
    }

    field = [];
    value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
    if (isInput) {
      field = $(this.el);
    } else if (value != null) {
      field = this.dropdown
        .parent()
        .find(
          "input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, "\\'") + "']",
        );
    }

    if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) {
      return [selectedObject];
    }

    if (el.hasClass(ACTIVE_CLASS) && value !== 0) {
      isMarking = false;
      el.removeClass(ACTIVE_CLASS);
      if (field && field.length) {
        this.clearField(field, isInput);
      }
    } else if (el.hasClass(INDETERMINATE_CLASS)) {
      isMarking = true;
      el.addClass(ACTIVE_CLASS);
      el.removeClass(INDETERMINATE_CLASS);
      if (field && field.length && value == null) {
        this.clearField(field, isInput);
      }
      if ((!field || !field.length) && fieldName) {
        this.addInput(fieldName, value, selectedObject);
      }
    } else {
      isMarking = true;
      if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
        this.dropdown.find('.' + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
        if (!isInput) {
          this.dropdown
            .parent()
            .find("input[name='" + fieldName + "']")
            .remove();
        }
      }
      if (field && field.length && value == null) {
        this.clearField(field, isInput);
      }
      // Toggle active class for the tick mark
      el.addClass(ACTIVE_CLASS);
      if (value != null) {
        if ((!field || !field.length) && fieldName) {
          this.addInput(fieldName, value, selectedObject);
        } else if (field && field.length) {
          field.val(value).trigger('change');
        }
      }
    }

    return [selectedObject, isMarking];
  };

  GitLabDropdown.prototype.focusTextInput = function() {
    if (this.options.filterable) {
      const initialScrollTop = $(window).scrollTop();

      if (this.dropdown.is('.show') && !this.filterInput.is(':focus')) {
        this.filterInput.focus();
      }

      if ($(window).scrollTop() < initialScrollTop) {
        $(window).scrollTop(initialScrollTop);
      }
    }
  };

  GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject, single) {
    var $input;
    // Create hidden input for form
    if (single) {
      $('input[name="' + fieldName + '"]').remove();
    }

    $input = $('<input>')
      .attr('type', 'hidden')
      .attr('name', fieldName)
      .val(value);
    if (this.options.inputId != null) {
      $input.attr('id', this.options.inputId);
    }

    if (this.options.multiSelect) {
      Object.keys(selectedObject).forEach(attribute => {
        $input.attr(`data-${attribute}`, selectedObject[attribute]);
      });
    }

    if (this.options.inputMeta) {
      $input.attr('data-meta', selectedObject[this.options.inputMeta]);
    }

    this.dropdown.before($input).trigger('change');
  };

  GitLabDropdown.prototype.selectRowAtIndex = function(index) {
    var $el, selector;
    // If we pass an option index
    if (typeof index !== 'undefined') {
      selector = SELECTABLE_CLASSES + ':eq(' + index + ') a';
    } else {
      selector = '.dropdown-content .is-focused';
    }
    if (this.dropdown.find('.dropdown-toggle-page').length) {
      selector = '.dropdown-page-one ' + selector;
    }
    // simulate a click on the first link
    $el = $(selector, this.dropdown);
    if ($el.length) {
      var href = $el.attr('href');
      if (href && href !== '#') {
        visitUrl(href);
      } else {
        $el.trigger('click');
      }
    }
  };

  GitLabDropdown.prototype.addArrowKeyEvent = function() {
    var $input, ARROW_KEY_CODES, selector;
    ARROW_KEY_CODES = [38, 40];
    $input = this.dropdown.find('.dropdown-input-field');
    selector = SELECTABLE_CLASSES;
    if (this.dropdown.find('.dropdown-toggle-page').length) {
      selector = '.dropdown-page-one ' + selector;
    }
    return $('body').on(
      'keydown',
      (function(_this) {
        return function(e) {
          var $listItems, PREV_INDEX, currentKeyCode;
          currentKeyCode = e.which;
          if (ARROW_KEY_CODES.indexOf(currentKeyCode) !== -1) {
            e.preventDefault();
            e.stopImmediatePropagation();
            PREV_INDEX = currentIndex;
            $listItems = $(selector, _this.dropdown);
            // if @options.filterable
            //   $input.blur()
            if (currentKeyCode === 40) {
              // Move down
              if (currentIndex < $listItems.length - 1) {
                currentIndex += 1;
              }
            } else if (currentKeyCode === 38) {
              // Move up
              if (currentIndex > 0) {
                currentIndex -= 1;
              }
            }
            if (currentIndex !== PREV_INDEX) {
              _this.highlightRowAtIndex($listItems, currentIndex);
            }
            return false;
          }
          if (currentKeyCode === 13 && currentIndex !== -1) {
            e.preventDefault();
            _this.selectRowAtIndex();
          }
        };
      })(this),
    );
  };

  GitLabDropdown.prototype.removeArrayKeyEvent = function() {
    return $('body').off('keydown');
  };

  GitLabDropdown.prototype.resetRows = function resetRows() {
    currentIndex = -1;
    $('.is-focused', this.dropdown).removeClass('is-focused');
  };

  GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
    var $dropdownContent,
      $listItem,
      dropdownContentBottom,
      dropdownContentHeight,
      dropdownContentTop,
      dropdownScrollTop,
      listItemBottom,
      listItemHeight,
      listItemTop;

    if (!$listItems) {
      $listItems = $(SELECTABLE_CLASSES, this.dropdown);
    }

    // Remove the class for the previously focused row
    $('.is-focused', this.dropdown).removeClass('is-focused');
    // Update the class for the row at the specific index
    $listItem = $listItems.eq(index);
    $listItem.find('a:first-child').addClass('is-focused');
    // Dropdown content scroll area
    $dropdownContent = $listItem.closest('.dropdown-content');
    dropdownScrollTop = $dropdownContent.scrollTop();
    dropdownContentHeight = $dropdownContent.outerHeight();
    dropdownContentTop = $dropdownContent.prop('offsetTop');
    dropdownContentBottom = dropdownContentTop + dropdownContentHeight;
    // Get the offset bottom of the list item
    listItemHeight = $listItem.outerHeight();
    listItemTop = $listItem.prop('offsetTop');
    listItemBottom = listItemTop + listItemHeight;
    if (!index) {
      // Scroll the dropdown content to the top
      $dropdownContent.scrollTop(0);
    } else if (index === $listItems.length - 1) {
      // Scroll the dropdown content to the bottom
      $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight'));
    } else if (listItemBottom > dropdownContentBottom + dropdownScrollTop) {
      // Scroll the dropdown content down
      $dropdownContent.scrollTop(
        listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING,
      );
    } else if (listItemTop < dropdownContentTop + dropdownScrollTop) {
      // Scroll the dropdown content up
      return $dropdownContent.scrollTop(
        listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING,
      );
    }
  };

  GitLabDropdown.prototype.updateLabel = function(selected, el, instance) {
    if (selected == null) {
      selected = null;
    }
    if (el == null) {
      el = null;
    }
    if (instance == null) {
      instance = null;
    }

    let toggleText = this.options.toggleLabel(selected, el, instance);
    if (this.options.updateLabel) {
      // Option to override the dropdown label text
      toggleText = this.options.updateLabel;
    }

    return $(this.el)
      .find('.dropdown-toggle-text')
      .text(toggleText);
  };

  GitLabDropdown.prototype.clearField = function(field, isInput) {
    return isInput ? field.val('') : field.remove();
  };

  return GitLabDropdown;
})();

$.fn.glDropdown = function(opts) {
  return this.each(function() {
    if (!$.data(this, 'glDropdown')) {
      return $.data(this, 'glDropdown', new GitLabDropdown(this, opts));
    }
  });
};