Search code examples
javascripthtmlcssmaterialize

How to match select input to selected option dynamically in Materialize CSS?


I'm using Materialize CSS 1.0.0 (so no jQuery) and I have a select drop-down where users can select different themes (auto, light, dark). The default is auto (it uses the user's system preference - light or dark). Every page has this drop-down. It all works as it should except if I open the site and go to the select and select, let's say, light, and then I navigate to another page, the select input says auto. However, the drop-down has light selected (in Materialize, the selected item has a grey background), and then when I close the drop-down by clicking outside of it, not clicking the light option, the input says light.

So, to reiterate, I select a theme other than the default (auto), that option is selected across the site, but the input does not match the selected option across the site UNTIL I open the drop-down where the current theme IS selected, and then click outside of it, and then the input matches what was selected (changes from auto to light, for instance). It's odd because everything is working as it should except the input doesn't initially match the selected option. I think it's because there's some kind of conflict between my JS for switching the themes and Materialize's select "preset" which relies on its own JS in the materialize.js file. I tested my code for switching themes using just a regular HTML select, and it worked perfectly. The input matched the selected option across the site.

So, it must be materialize.js and whatever is going on in that file. I tried looking at the code in that file to see if there was anything I could alter, but I'm really not at a level to discern what would need to be changed without breaking everything.

For my project, this would constitute an annoying little bug.

Here's my code:

HTML

<div class="input-field">
    <select name="theme" id="theme">
        <option value="auto">Auto</option>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
    </select>
</div>

<script>
    const select = document.querySelector('select');
    M.FormSelect.init(select, {});
</script>

CSS

:root {
    --dark-background-color: black;
    --dark-color: white;
}

body {
    /* Light theme */
    --background-color: white;
    --color: black;

    background-color: var(--background-color);
    color: var(--color);
}

body.theme-dark {
    --background-color: var(--dark-background-color);
    --color: var(--dark-color);   
}

@media (prefers-color-scheme: dark) {
    body.theme-auto {
        --background-color: var(--dark-background-color);
        --color: var(--dark-color);   
    }
}

.input-field input {
    color: var(--color);
}

JS

function applyTheme(theme) {
    document.body.classList.remove('theme-auto', 'theme-light', 'theme-dark');
    document.body.classList.add(`theme-${theme}`);
}

document.addEventListener('DOMContentLoaded', () => {
    const savedTheme = localStorage.getItem('theme') || 'auto';

    applyTheme(savedTheme);

    for (const optionElement of document.querySelectorAll('#theme option')) {
        optionElement.selected = savedTheme === optionElement.value;
    }

    document.querySelector('#theme').addEventListener('change', function() {
        localStorage.setItem('theme', this.value);
        applyTheme(this.value);
    });
});

Materialize JS for Select: EDIT: I think I've narrowed it down to the offending code.

      /**
       * Handle Input Click
       */

    }, {
      key: "_handleInputClick",
      value: function _handleInputClick() {
        if (this.dropdown && this.dropdown.isOpen) {
          this._setValueToInput();
          this._setSelectedStates();
        }
      }

        // Add input dropdown
        this.input = document.createElement('input');
        $(this.input).addClass('select-dropdown dropdown-trigger');
        this.input.setAttribute('type', 'text');
        this.input.setAttribute('readonly', 'true');
        this.input.setAttribute('data-target', this.dropdownOptions.id);
        if (this.el.disabled) {
          $(this.input).prop('disabled', 'true');
        }

        this.$el.before(this.input);
        this._setValueToInput();

The solution may be not to use Materialize. I guess I'm just wondering if anyone more familiar with the underlying mechanics of Materialize (its JS) could discern a fix here.

Here's a GIF of the problem in action

Here's a GIF of the problem in action


Solution

  • Added this to my JS.

    // added
    const themeSelect = document.getElementById('theme');
    
    const savedTheme = localStorage.getItem('theme');
    if (savedTheme) themeSelect.value = savedTheme;
    
    themeSelect.addEventListener('change', function () {
        localStorage.setItem('theme', this.value);
    });
    

    What is strange is that this solution seems redundant. The original JS from the question (not materialize.js) should be doing the same thing yet it doesn't but then add this and it finally takes control of the input away from the materialize.js file. Perhaps they can be merged without losing the fix. We may never know.