Dogfalo / materialize

Materialize, a CSS Framework based on Material Design
https://materializecss.com
MIT License
38.86k stars 4.74k forks source link

Opening a long dropdown does not scroll to selected item #6343

Open Mitchimus11 opened 5 years ago

Mitchimus11 commented 5 years ago

Expected Behavior

Regardless of the size of the dropdown list, when the list is opened it should scroll to a position where the currently selected item is visible.

Current Behavior

Here is a list with 20 options, I have previously selected Option 10, opening the list seems to work fine. image

(On open)

image

Now I have selected Option 20, opening the list does not scroll to the correct position: image

(On open)

image

Steps to Reproduce

  1. Have a Select dropdown with more items than will fit on the screen.
  2. Open the list and select an item which would require considerable scrolling of the list to reach it.
  3. Close the list.
  4. Open the list again, the list does not open at the selected position.

Additional Information

This problem seems to be related to the auto sizing of the drop down list should it not be able to fit on the screen. Setting max-height css attribute to 500px on the dropdown-content class (so that it will never grow off screen regardless of if it opens upwards or downwards) appears to resolve the issue, but this is a less than ideal fix:

image

Context

We have a system built on Materialize which contains numerous lists of data, if users open the list and cannot see which item is already selected (previously saved), it is very confusing and difficult to change the selection without a lot of scrolling.

Your Environment

lemontree2000 commented 5 years ago

I did this, Hope it helps you.

$('.dropdown-trigger').dropdown({
    container: document.body
});
Mitchimus11 commented 5 years ago

I've just had another look at this issue myself, and I think I've come up with a viable solution.

in materialize.js, FormSelect _setupDropdown() function

Remove:

var scrollOffset = selectedOption[0].getBoundingClientRect().top - _this71.dropdownOptions.getBoundingClientRect().top; // scroll to selected option
scrollOffset -= _this71.dropdownOptions.clientHeight / 2; // center in dropdown
_this71.dropdownOptions.scrollTop = scrollOffset;

And replace with:

var idealPos = _this71.dropdownOptions.getBoundingClientRect().top + (_this71.dropdownOptions.clientHeight / 2) - (selectedOption[0].clientHeight / 2); // center of dropdown
var scrollOffset = selectedOption[0].getBoundingClientRect().top - idealPos;
_this71.dropdownOptions.scrollTop = _this71.dropdownOptions.scrollTop + scrollOffset; // adjust scrollTop by offset

Not sure if it's just our environment, but the original code did not seem to be doing what it looked like it was meant to.

MaximBalaganskiy commented 4 years ago

I recon, with the current state of Materialize it will never get fixed...

Strangely, simply commenting out the code mentioned above works as well

MaximBalaganskiy commented 4 years ago

A monkey patch

const oldInit = M.Dropdown.init;

(M.Dropdown.init as (els: Element, options?: Partial<M.DropdownOptions>) => M.Dropdown) = function (els: Element, options?: Partial<M.DropdownOptions>): M.Dropdown {
  if (options['forSelect']) {
    options.onOpenEnd = function (this: M.Dropdown, el: Element) {
      const selectedOption = this.dropdownEl.querySelector('.selected');

      if (selectedOption) {
        // Focus selected option in dropdown
        (M as any).keyDown = true;
        this.focusedIndex = indexInParent(selectedOption);
        (this as any)._focusFocusedItem();
        (M as any).keyDown = false;
      }
    };
  }
  return oldInit.call(M.Dropdown, els, options);
};

And create selects with M.FormSelect(element, { dropdownOptions: { forSelect: true } })

DanielRuf commented 4 years ago

This works great. A PR which fixes this would be very welcome.

And replace with:

var idealPos = _this71.dropdownOptions.getBoundingClientRect().top + (_this71.dropdownOptions.clientHeight / 2) - (selectedOption[0].clientHeight / 2); // center of dropdown
var scrollOffset = selectedOption[0].getBoundingClientRect().top - idealPos;
_this71.dropdownOptions.scrollTop = _this71.dropdownOptions.scrollTop + scrollOffset; // adjust scrollTop by offset

Not sure if it's just our environment, but the original code did not seem to be doing what it looked like it was meant to.