Dogfalo / materialize

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

Add a way to update "dropdown-content" dynamically #4714

Closed pingshunhuangalex closed 7 years ago

pingshunhuangalex commented 7 years ago

I'm using the autocomplete dropdown here to create a search bar with autocomplete feature. The example works fine where the data section is static and initialised when the page fires up.

However in my case, things are a bit more complicated. I have an api that's feeding me dynamic data when the input content changes. Right now, I'm calling $().autocomplete every time the data is updated. Unfortunately using this method, the expanded dropdown list won't update itself accordingly right away unless I unfocus and refocus the search bar.

Just wondering if you guys can take a step further and make the dropdown-content updates itself whenever the data is changed. Thanks.

DanielRuf commented 7 years ago

Please do not link me, I am just a contributor, no collaborator or maintainer / owner.

Can you provide some codepen with some code which simulates your case? Afaik closing and reopening is the only solution so far (the most simple and direct way).

DanielRuf commented 7 years ago

I'm unsure if removeAutocomplete() could be of any help. Currently you would have to copy codeparts and execute them in your case to mirror the behavior in the plugin whrn it is closed (blur) and opened (click / focus).

pingshunhuangalex commented 7 years ago

Sorry, I mistake you as a collaborator thanks to your active response in this repository.

At the moment, I'm just using blur to unfocus and refocus the input, but this causes the dropdown content to flash. The right way to do it is to update things based on the data, so autocomplete doesn't need to be called every time things changes. There should be an updateAutocomplete feature somehow...

DanielRuf commented 7 years ago

Right. I think we have to improve this and check if data is set and regenerate the list when needed like you describe it.

I could try to check where we have to implement this and which lines are affected later this day.

Or does someone have any recommended fixes and solutions so far?

pingshunhuangalex commented 7 years ago

I can't think of anything at the moment cuz I'm not sure how this is realised in materializeCSS at the moment. I think there should be some kind of an event listener on the data field, so whenever a data change is detected, the list should be updated in real-time.

I'm always curious about where the owner and the collaborators are. It may be better if maintainers all sit down and work something out.

pingshunhuangalex commented 7 years ago

Hi @acburst Just wondering if this issue will be fixed anytime soon? Or it's one of those issues that'll probably never make it to the priority list. Thanks.

DanielRuf commented 7 years ago

Please test https://github.com/Dogfalo/materialize/pull/4725

Just makes sense if the dropdown is already open (you can check if $('.autocomplete-content.dropdown-content') is not empty).

$(document).ready(function() {
    $('input.autocomplete').autocomplete({
        data: {
            "Apple": null,
            "Microsoft": null,
            "Google": 'http://placehold.it/250x250'
        },
        limit: 20, // The max amount of results that can be shown at once. Default: Infinity.
        onAutocomplete: function(val) {
            // Callback function when value is autcompleted.
        },
        minLength: 1, // The minimum length of the input for the autocomplete to start. Default: 1.
    });

    setTimeout(function() {
        $('input.autocomplete').autocomplete({
            data: {
                "Apple": null,
                "Microsoft": null,
                "Microsoft2": null,
                "Microsoft3": null,
                "Google": 'http://placehold.it/250x250'
            },
            limit: 20, // The max amount of results that can be shown at once. Default: Infinity.
            onAutocomplete: function(val) {
                // Callback function when value is autcompleted.
            },
            minLength: 1, // The minimum length of the input for the autocomplete to start. Default: 1.
        });

        $('input.autocomplete').trigger('updateData');
    }, 10000);
});

loaded the page, clicked in the input and entered m, waited for about 10 seconds and the dropdown was updated.

pingshunhuangalex commented 7 years ago

Thanks for the update @DanielRuf Unfortunately, it doesn't quite work there. I mean the data does get updated, but the dropdown list didn't unless I unfocus and refocus.

This means your trigger there doesn't work cuz you can have the same effect just by recalling the entire autocomplete function, just like what you did above before the trigger.

DanielRuf commented 7 years ago

Well, here it worked. Did you add the code right before the search / keypress method and recompile or directly change the unminified js file?

The dropdown was open and the input focussed and automatically updated itself.

I could also test and integrate some data method (an event with the new data as parameter) if I find the time.

So far the code worked on my side while the input was focused and was open. Maybe I have to check if it is currently opened or not and return / exit if it is not.

pingshunhuangalex commented 7 years ago

I can't test it this weekend. I'll have to test it next week when I get back to work.

Just make sure I get things right. I download the repository from your branch and load the minified JS in the dist folder.

I'm using Vue, so there is no keypress. I run the code whenever there is an input change which should be the same. I also tried it when the page is mounted, and no luck back then. I'll retest it next week.

Another thing is my autocomplete starts when the first letter is input. There is no dropdown when the autocomplete initiated. Will your solution takes care of situation like this?

Thanks again for the updates, if the way I load the file is wrong, please let me know.

DanielRuf commented 7 years ago

Just make sure I get things right. I download the repository from your branch and load the minified JS in the dist folder.

Clone / download the branch f the PR, go to the new directory, run npm install and then grunt and use the generated files from dist or bin.

DanielRuf commented 7 years ago

Another thing is my autocomplete starts when the first letter is input. There is no dropdown when the autocomplete initiated. Will your solution takes care of situation like this?

Currently not. A check if the dropdown is populated / shown is not yet implemented.

pingshunhuangalex commented 7 years ago

Hi @DanielRuf

Thanks so much. It works now. Could you please merge it to master so I can get the feature in cdn instead of a local js file? Thanks.

DanielRuf commented 7 years ago

cc @tomscholz

DanielRuf commented 7 years ago

Also should we improve it and add a check if it is open? I guess so. Will prepare another commit later and push it to the branch. Should work much better.

PS: Imho CDNs are not a good idea anymore. http2, hosting the file yourself and having a fallback when the CDN is not available, extra DNS lookups and requests, SRI, ...

pingshunhuangalex commented 7 years ago

Hi @DanielRuf

Just as a feedback, I noticed sometimes when the update kicks in, the dropdown menu will still blink, which is quite annoying. Hope there is a way around it. Thanks.

DanielRuf commented 7 years ago

@pingshunhuangalex can you share a codepen with your code + the modified dropdown component js from the PR? This would be great.

Do you have more info about your envitonment (browser, OS and so on)?

pingshunhuangalex commented 7 years ago

Hi @DanielRuf Thanks for the reply. I'm on Windows 10 64-bit, and use Chrome 59 and IE 11 for testing.

The blinking happens when none of the dropdown items matches the updated search string. I guess it's just removing ul and constructing new ones. What I'm asking is: is it possible to keep the ul and just update the data on it (unless the dropdown item no is smaller than the limit set), so everything just looks consistent.

I can't produce a codepen for testing cuz I probably shouldn't connect it to a server and it's kind of all tangled to other components in a big Vue project. However, I posted my Vue component code related to the search bar below. I put the modified dropdown component js from the PR in this codepen. Thanks.

<template>
  <nav>
    <div class="nav-wrapper" id="nav-bar">
      <div class="input-field" v-show="!expansionMode">
        <input id="search"
            ref="search"
            type="search"
            autocomplete="off"
            placeholder="Search in Objects Library..." required
            v-model="searchTerm"
            @keyup.enter.prevent="searchSubmit">
        <label class="label-icon" for="search"></label>
        <i class="material-icons" @click.prevent="searchSubmit">search</i>
        <i class="material-icons" @click="searchClear">close</i>
      </div>
    </div>
  </nav>
</template>

<script>
import mixin from '@/assets/js/mixin';

export default {
  name: 'NavSearch',
  mixins: [mixin],
  data () {
    return {
      searchTerm: null
    };
  },
  watch: {
    searchTerm () {
      this.searchUpdate();
    }
  },
  methods: {
    searchUpdate () {
      let search = this.$refs.search;

      axios.get(
        `api/v1/search-as-you-type?format=json&query=${this.searchTerm}`
      ).then(response => {
        // console.log(response);
        let searchData = response.data.results;
        let searchDataObj = {};
        let vm = this;

        for (let i = 0; i < searchData.length; i++) {
          searchDataObj[searchData[i]] = null;
        }

        $(search).autocomplete({
          data: searchDataObj,
          limit: 5,
          onAutocomplete () {
            vm.searchSubmit();
          },
          minLength: 1,
        });

        if (search === document.activeElement) {
          $(search).trigger('updateData');
        } else {
          $('.autocomplete-content.dropdown-content').empty();
        }
      });
    }
  }
};
</script>
DanielRuf commented 7 years ago

Sounds like we have to cache / store some data to check against it.

is it possible to keep the ul and just update the data

Requires a bit more work and planning. I did not yet have the time to implement this further but I guess this would be related to https://github.com/Dogfalo/materialize/issues/4714#issuecomment-304250161 where I would also check the length of the children and the last value to decide if rerendering is needed and improve the rendering process to inject the new data on the right position as just appending it might not always be the right solution so I would have to diff the old and new data to get the position to inject it without rerendering the whole part.

DanielRuf commented 7 years ago

Ok, I'll try to create a complete codepen with the given code this evening and try to find the fastest solution for you that fixes this issue and update the linked PR (which is still WIP).

@tomscholz any quick solutions that come to your mind? I'll try to revisit the whole code of the component and my PR this evening and extend this.

Probably copying/emulating virtual DOM concepts?

pingshunhuangalex commented 7 years ago

Thanks @DanielRuf No need to rush and there is no deadline.

Again, it's not the speed of the update, it's more like the way it gets updated in the frontend. You can see when you keep adding on letters to a word, it works pretty well because the ul is not getting destroyed. All I ask is something like that even when the item gets updated, i.e. update the text in ul > li, not destroying and reconstructing them unless the no. of items is smaller than the user set limit. Thanks.

DanielRuf commented 7 years ago

All I ask is something like that even when the item gets updated, i.e. update the text in ul > li, not destroying and reconstructing them unless the no. of items is smaller than the user set limit. Thanks.

Ah ok, I'll make some thoughts about this and the best approach to tackle this.

pingshunhuangalex commented 7 years ago

Hi @DanielRuf

Just wonder if the updateData feature will be built into the next update (which I have no idea when)? It's ok if you can't figure out the best approach we mentioned above, just wonder at least when that feature will be accesible. Thanks.

acburst commented 7 years ago

This will be added in v1 with the chips rewrite

acburst commented 7 years ago

Added in bf24cb7d