darrenjennings / vue-autosuggest

🔍 Vue autosuggest component.
https://darrenjennings.github.io/vue-autosuggest
MIT License
621 stars 91 forks source link

[feature requests] add more slots #99

Closed wujekbogdan closed 5 years ago

wujekbogdan commented 5 years ago

It's a common practice to show a message like "no match", or "type more than x characters", especially when the results are loaded via AJAX. Currently, I use a workaround that is not very elegant and also doesn't produce a semanticcally correct HTML.

I store single-element arrays with boolean indicators in the suggestions array

computed: {
    suggestions() {
      if (!this.hasSearchPhrase) {
        return [];
      }

      if (this.searchPhraseTooShort) {
        return [{
          tooShort: true,
        }];
      }

      if (!this.hasResults && !this.searchPhraseTooShort) {
        return [{
          noMatch: true,
        }];
      }

      return this.searchTerms;
    },
}

And I use the slot to render, either the results or the messages, depending on the indicators:

<template slot-scope="{ suggestion }">

  <template v-if="suggestion.item.tooShort">
    <span class="autosuggest__item-label">
      Enter at least characters.
    </span>
  </template>

  <template v-if="suggestion.item.noMatch">
    <span class="autosuggest__item-label">
      No results for: <strong>{{searchPhrase}}</strong>
    </span>
  </template>

  <template v-else>
    <span class="autosuggest__item-label">
      {{ suggestion.item.name }}
    </span>
    <span class="autosuggest__item-count">
      {{ suggestion.item.count }} items
    </span>
  </template>

</template>

It works, but showing messages as list items isn't semantiacly correct (especially when aria- elements indicate that they are results, not messages).

So it would be perfect if there was an additional slot outside the autosuggest__results-container element - or even 2: one before and one after.

I know that there are header and footer slots but they show up inside the container, and also don't show up when the results list is empty. So they have a different purpose, I guess.

darrenjennings commented 5 years ago

Yea I use the header (or footer will work) slots in one of my apps at work to show "no results". This enhanced behavior of should-render-suggestions should get you what you want here. From the 2.0 roadmap codesandbox: https://codesandbox.io/s/7z7p8mx2y1

e.g.

<vue-autosuggest
  :should-render-suggestions="(size, loading) => size >= 0 && !loading"

(To be documented)

Default: :should-render-suggestions="(size, loading) => size > 0 && !loading"

size is the number of suggestions, and loading (you can name it whatever you want right now, open to bikeshedding), is a state variable that I use internally to track when a user may have clicked a suggestion or clicked outside of the results to have more intuitive interactions.

wujekbogdan commented 5 years ago

@darrenjennings Perfect, with :should-render-suggestions additional slots are no longer needed. I think we can close this ticket.


PS Maybe renaming these slots to before-suggestions and after-suggestions would be a good idea? These names are more generic than header and footer ... on the other hand, it would break the backwards compatibility.

darrenjennings commented 5 years ago

I like the before/after naming convention. With 2.0, I want to break as many things as make sense, because I don't want to ever have to release 3.0 😅, unless Vue itself gives me more tools to make it better in the future. What do you think about before-results, after-results? Less characters, follows naming convention of class names. It has the downside that I use suggestions in prop names (renderSuggestion, shouldRenderSuggestions).

darrenjennings commented 5 years ago

All slots in vue-autosuggestion@2.0.0:

<!-- WARNING PSEUDO CODE, NOT RUNNING CODE OR REAL CLASS NAMES -->
<vue-autosuggest>
  <slot name="before-input"/>
  <input />
  <slot name="after-input"/>
  <div class="container">
    <div class="results">
      <slot name="before-suggestions" />
      <ul class="suggestions">
        <slot name="before-section-<section.name>" />

        <li><slot name="default" /></li>
        <slot name="after-section-{section.name}" />
        <slot name="after-section-default" />
      </ul>
    </div>
    <slot name="after-suggestions" />
  </div>
</vue-autosuggest>