define([
  'jquery',
  '../utils'
], function ($, Utils) {
  function AttachBody (decorated, $element, options) {
    this.$dropdownParent = $(options.get('dropdownParent') || document.body);
    decorated.call(this, $element, options);
  }
  AttachBody.prototype.bind = function (decorated, container, $container) {
    var self = this;
    decorated.call(this, container, $container);
    container.on('open', function () {
      self._showDropdown();
      self._attachPositioningHandler(container);
      // Must bind after the results handlers to ensure correct sizing
      self._bindContainerResultHandlers(container);
    });
    container.on('close', function () {
      self._hideDropdown();
      self._detachPositioningHandler(container);
    });
    this.$dropdownContainer.on('mousedown', function (evt) {
      evt.stopPropagation();
    });
  };
  AttachBody.prototype.destroy = function (decorated) {
    decorated.call(this);
    this.$dropdownContainer.remove();
  };
  AttachBody.prototype.position = function (decorated, $dropdown, $container) {
    // Clone all of the container classes
    $dropdown.attr('class', $container.attr('class'));
    $dropdown.removeClass('select2');
    $dropdown.addClass('select2-container--open');
    $dropdown.css({
      position: 'absolute',
      top: -999999
    });
    this.$container = $container;
  };
  AttachBody.prototype.render = function (decorated) {
    var $container = $('<span></span>');
    var $dropdown = decorated.call(this);
    $container.append($dropdown);
    this.$dropdownContainer = $container;
    return $container;
  };
  AttachBody.prototype._hideDropdown = function (decorated) {
    this.$dropdownContainer.detach();
  };
  AttachBody.prototype._bindContainerResultHandlers =
      function (decorated, container) {
    // These should only be bound once
    if (this._containerResultsHandlersBound) {
      return;
    }
    var self = this;
    container.on('results:all', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });
    container.on('results:append', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });
    container.on('results:message', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });
    container.on('select', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });
    container.on('unselect', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });
    this._containerResultsHandlersBound = true;
  };
  AttachBody.prototype._attachPositioningHandler =
      function (decorated, container) {
    var self = this;
    var scrollEvent = 'scroll.select2.' + container.id;
    var resizeEvent = 'resize.select2.' + container.id;
    var orientationEvent = 'orientationchange.select2.' + container.id;
    var $watchers = this.$container.parents().filter(Utils.hasScroll);
    $watchers.each(function () {
      Utils.StoreData(this, 'select2-scroll-position', {
        x: $(this).scrollLeft(),
        y: $(this).scrollTop()
      });
    });
    $watchers.on(scrollEvent, function (ev) {
      var position = Utils.GetData(this, 'select2-scroll-position');
      $(this).scrollTop(position.y);
    });
    $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
      function (e) {
      self._positionDropdown();
      self._resizeDropdown();
    });
  };
  AttachBody.prototype._detachPositioningHandler =
      function (decorated, container) {
    var scrollEvent = 'scroll.select2.' + container.id;
    var resizeEvent = 'resize.select2.' + container.id;
    var orientationEvent = 'orientationchange.select2.' + container.id;
    var $watchers = this.$container.parents().filter(Utils.hasScroll);
    $watchers.off(scrollEvent);
    $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
  };
  AttachBody.prototype._positionDropdown = function () {
    var $window = $(window);
    var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
    var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
    var newDirection = null;
    var offset = this.$container.offset();
    offset.bottom = offset.top + this.$container.outerHeight(false);
    var container = {
      height: this.$container.outerHeight(false)
    };
    container.top = offset.top;
    container.bottom = offset.top + container.height;
    var dropdown = {
      height: this.$dropdown.outerHeight(false)
    };
    var viewport = {
      top: $window.scrollTop(),
      bottom: $window.scrollTop() + $window.height()
    };
    var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
    var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
    var css = {
      left: offset.left,
      top: container.bottom
    };
    // Determine what the parent element is to use for calculating the offset
    var $offsetParent = this.$dropdownParent;
    // For statically positioned elements, we need to get the element
    // that is determining the offset
    if ($offsetParent.css('position') === 'static') {
      $offsetParent = $offsetParent.offsetParent();
    }
    var parentOffset = {
      top: 0,
      left: 0
    };
    if (
      $.contains(document.body, $offsetParent[0]) ||
      $offsetParent[0].isConnected
      ) {
      parentOffset = $offsetParent.offset();
    }
    css.top -= parentOffset.top;
    css.left -= parentOffset.left;
    if (!isCurrentlyAbove && !isCurrentlyBelow) {
      newDirection = 'below';
    }
    if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
      newDirection = 'above';
    } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
      newDirection = 'below';
    }
    if (newDirection == 'above' ||
      (isCurrentlyAbove && newDirection !== 'below')) {
      css.top = container.top - parentOffset.top - dropdown.height;
    }
    if (newDirection != null) {
      this.$dropdown
        .removeClass('select2-dropdown--below select2-dropdown--above')
        .addClass('select2-dropdown--' + newDirection);
      this.$container
        .removeClass('select2-container--below select2-container--above')
        .addClass('select2-container--' + newDirection);
    }
    this.$dropdownContainer.css(css);
  };
  AttachBody.prototype._resizeDropdown = function () {
    var css = {
      width: this.$container.outerWidth(false) + 'px'
    };
    if (this.options.get('dropdownAutoWidth')) {
      css.minWidth = css.width;
      css.position = 'relative';
      css.width = 'auto';
    }
    this.$dropdown.css(css);
  };
  AttachBody.prototype._showDropdown = function (decorated) {
    this.$dropdownContainer.appendTo(this.$dropdownParent);
    this._positionDropdown();
    this._resizeDropdown();
  };
  return AttachBody;
});
 
  |