Categories
Quasar

Developing Vue Apps with the Quasar Library — File Type and Size Validation and Forms

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

File Type and Size Validation

We can validate file type and size with Quasar’a file picker in our Vue app.

To add it, we write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            style="max-width: 300px;"
            v-model="filesMaxSize"
            filled
            label="Filtered"
            multiple
            :filter="checkFileSize"
            @rejected="onRejected"
          >
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          files: [],
          filesMaxSize: null,
          filesPng: null
        },
        methods: {
          checkFileSize(files) {
            return files.filter((file) => file.size < 2048);
          },
          onRejected(rejectedEntries) {
            this.$q.notify({
              type: "negative",
              message: `${rejectedEntries.length} file(s) did not pass validation constraints`
            });
          }
        }
      });
    </script>
  </body>
</html>

We add the checkFileSize method to return files that are less than the given size in bytes.

files has an array of files.

size has the file size.

onRejected lets us display a message if we have any files that don’t meet the requirements.

rejectedEntries has an array of rejected files.

We set the filter prop to the checkFileSize method.

onRejected is the value of the rejected listener.

We can check file types of the selected files with:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            style="max-width: 300px;"
            v-model="filesMaxSize"
            filled
            label="Filtered"
            multiple
            :filter="checkFileType"
            @rejected="onRejected"
          >
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          files: [],
          filesMaxSize: null,
          filesPng: null
        },
        methods: {
          checkFileType(files) {
            return files.filter((file) => file.type === "image/png");
          },
          onRejected(rejectedEntries) {
            this.$q.notify({
              type: "negative",
              message: `${rejectedEntries.length} file(s) did not pass validation constraints`
            });
          }
        }
      });
    </script>
  </body>
</html>

checkFileType lets us check the file type. We get the type property of each file and compare it.

Forms

We can add forms with the q-form component.

For instance, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-form @submit="onSubmit" @reset="onReset" class="q-gutter-md">
            <q-input
              filled
              v-model="name"
              label="Your name *"
              hint="Name"
              lazy-rules
              :rules="[ val => val && val.length > 0 || 'Please type something']"
            >
            </q-input>

            <q-input
              filled
              type="number"
              v-model="age"
              label="Your age *"
              lazy-rules
              :rules="[
                val => val !== null && val !== '' || 'Please type your age',
                val => val > 0 && val < 130 || 'Please type a real age'
              ]"
            >
            </q-input>

            <div>
              <q-btn label="Submit" type="submit" color="primary"></q-btn>
              <q-btn
                label="Reset"
                type="reset"
                color="primary"
                flat
                class="q-ml-sm"
              ></q-btn>
            </div>
          </q-form>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          name: "",
          age: ""
        },
        methods: {
          onSubmit() {
            this.$q.notify({
              color: "green-4",
              textColor: "white",
              icon: "cloud_done",
              message: "Submitted"
            });
          },
          onReset() {
            this.name = null;
            this.age = null;
          }
        }
      });
    </script>
  </body>
</html>

We add the q-input components inside our q-form with the rules prop.

The rules are for validating the inputs.

When we click Submit, the rules will be checked.

They return true if the value is valid or an error message otherwise.

We also add a q-btn with type reset to let us reset the form values.

We listen to the submit and reset events with the methods used as handlers for each event.

Conclusion

We can add forms and file size and type validation into our Vue app with Quasar.

Categories
Quasar

Developing Vue Apps with the Quasar Library — File Upload Progress

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

File Upload Progress

We can add file upload progress display to the file picker with the q-file component.

To do this, we write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            :value="files"
            @input="updateFiles"
            label="Pick files"
            outlined
            multiple
            :clearable="!isUploading"
            style="max-width: 400px;"
          >
            <template v-slot:file="{ index, file }">
              <q-chip
                class="full-width q-my-xs"
                :removable="isUploading && uploadProgress[index].percent < 1"
                square
              >
                <q-linear-progress
                  class="absolute-full full-height"
                  :value="uploadProgress[index].percent"
                  :color="uploadProgress[index].color"
                  track-color="grey-2"
                >
                </q-linear-progress>

                <q-avatar>
                  <q-icon :name="uploadProgress[index].icon"></q-icon>
                </q-avatar>

                <div class="ellipsis relative-position">
                  {{ file.name }}
                </div>

                <q-tooltip>
                  {{ file.name }}
                </q-tooltip>
              </q-chip>
            </template>

            <template v-slot:after v-if="canUpload">
              <q-btn
                color="primary"
                dense
                icon="cloud_upload"
                round
                @click="upload"
                :disable="!canUpload"
                :loading="isUploading"
              >
              </q-btn>
            </template>
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          files: null,
          uploadProgress: [],
          uploading: null,
          isUploading: false
        },
        computed: {
          isUploading() {
            return this.uploading !== null;
          },

          canUpload() {
            return this.files !== null;
          }
        },
        methods: {
          updateFiles(files) {
            this.files = files;
            this.uploadProgress = (files || []).map((file) => ({
              error: false,
              color: "green-1",
              percent: 0,
              icon:
                file.type.indexOf("video/") === 0
                  ? "movie"
                  : file.type.indexOf("image/") === 0
                  ? "photo"
                  : file.type.indexOf("audio/") === 0
                  ? "audiotrack"
                  : "insert_drive_file"
            }));
          },
          upload() {
            clearTimeout(this.uploading);

            const allDone = this.uploadProgress.every(
              (progress) => progress.percent === 1
            );

            this.uploadProgress = this.uploadProgress.map((progress) => ({
              ...progress,
              error: false,
              color: "green-2",
              percent: allDone === true ? 0 : progress.percent
            }));

            this.__updateUploadProgress();
          },

          __updateUploadProgress() {
            let done = true;

            this.uploadProgress = this.uploadProgress.map((progress) => {
              if (progress.percent === 1 || progress.error === true) {
                return progress;
              }

              const percent = Math.min(
                1,
                progress.percent + Math.random() / 10
              );
              const error = percent < 1 && Math.random() > 0.95;

              if (error === false && percent < 1 && done === true) {
                done = false;
              }

              return {
                ...progress,
                error,
                color: error === true ? "red-2" : "green-2",
                percent
              };
            });

            this.uploading =
              done !== true
                ? setTimeout(this.__updateUploadProgress, 300)
                : null;
          }
        },
        beforeDestroy() {
          clearTimeout(this.uploading);
        }
      });
    </script>
  </body>
</html>

We listen for file changes with the updateFiles method.

In the method, we get the files from the parameter and then call map to return an array of object to display the icons we want.

The upload method is called when we click on the button on the right.

We get the progress from the uploadProgress property and then update it by getting the progress from the progress.percent property.

We set the percent from in the method and set the as the value of the value prop in the q-linear-progress component to display the progress.

Conclusion

We can add a progress bar to the file input to show file upload progress with Quasar.

Categories
Quasar

Developing Vue Apps with the Quasar Library — File Input Options

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

Clearable File Input

We can add a clearable file input with the clearable prop:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            clearable
            standout
            v-model="model"
            label="Label"
            counter
            clearable
          >
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          model: undefined
        }
      });
    </script>
  </body>
</html>

Disable and Readonly File Inputs

We can add the disable and readonly prop to disable interaction with the file input.

disable changes the styles but readonly doesn’t.

For instance, we can use it by writing:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="[https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons](https://medium.com/r/?url=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DRoboto%3A100%2C300%2C400%2C500%2C700%2C900%7CMaterial%2BIcons)"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="[https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css](https://medium.com/r/?url=https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fquasar%401.12.13%2Fdist%2Fquasar.min.css)"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="[https://cdn.jsdelivr.net/npm/vue@](https://medium.com/r/?url=https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fvue%40)^2.0.0/dist/vue.min.js"></script>
    <script src="[https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js](https://medium.com/r/?url=https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fquasar%401.12.13%2Fdist%2Fquasar.umd.min.js)"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            disable
            filled
            v-model="model"
            hint="Disable"
            style="width: 250px;"
          >
          </q-file>

          <q-file
            readonly
            filled
            v-model="model"
            hint="Readonly"
            style="width: 250px;"
          >
          </q-file>

          <q-file
            disable
            readonly
            filled
            v-model="model"
            hint="Disable and readonly"
            style="width: 250px;"
          >
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          model: undefined
        }
      });
    </script>
  </body>
</html>

Select Mutliple Files

We can let users select multiple files with the multiple prop:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            v-model="files"
            label="Pick files"
            filled
            multiple
            style="max-width: 300px;"
          >
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          files: undefined
        }
      });
    </script>
  </body>
</html>

Appending Files

By default, the file input resets the selected files each time we select files.

With the append prop, we can let users keep adding files:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            v-model="files"
            label="Pick files"
            filled
            multiple
            append
            style="max-width: 300px;"
          >
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          files: undefined
        }
      });
    </script>
  </body>
</html>

File Input Counter

The file input comes with a counter for the number of files selected and the total size of the selected files.

We just have to add the counter prop to enable it:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            v-model="files"
            label="Pick files"
            filled
            counter
            style="max-width: 300px;"
          >
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          files: undefined
        }
      });
    </script>
  </body>
</html>

Conclusion

We can add file inputs with various options into our Vue app with Quasar.

Categories
Quasar

Developing Vue Apps with the Quasar Library — File Picker

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

File Picker

Quasar comes with a file picker component.

For example, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            rounded
            outlined
            v-model="model"
            label="Choose a file"
          ></q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          model: undefined
        }
      });
    </script>
  </body>
</html>

to add a file input.

The q-file component lets us add a file input easily.

It binds the selected file to a reactive property with v-model .

rounded and outlined are props for styles.

Other props to change styling include square , filled , standout and borderless .

label has the placeholder for the file input.

File Input Decorators

We can add various icons to file inputs.

For example, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            rounded
            outlined
            bottom-slots
            v-model="model"
            label="Label"
            counter
            max-files="12"
          >
            <template v-slot:before>
              <q-icon name="attachment"></q-icon>
            </template>

            <template v-slot:append>
              <q-icon
                v-if="model !== null"
                name="close"
                @click.stop="model = null"
                class="cursor-pointer"
              ></q-icon>
              <q-icon name="search" @click.stop></q-icon>
            </template>

            <template v-slot:hint>
              Field hint
            </template>
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          model: undefined
        }
      });
    </script>
  </body>
</html>

to add a file input with various icons.

The before slot lets us add icons to the left side of the file input outside it.

append lets us add content inside the input on the right side.

We can populate the hint slot and add the bottoms-slots property to add text below the file input.

File Input Color

To change colors, we can add the label-color , bg-color , and color props:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-file
            clearable
            label-color="yellow"
            bg-color="green"
            standout
            bottom-slots
            v-model="model"
            label="Label"
            counter
          >
            <template v-slot:prepend>
              <q-icon color="yellow" name="attach_file"></q-icon>
            </template>
            <template v-slot:append>
              <q-icon color="yellow" name="favorite"></q-icon>
            </template>

            <template v-slot:hint>
              Field hint
            </template>
          </q-file>
        </div>
      </q-layout>
    </div>
    <script>
      new Vue({
        el: "#q-app",
        data: {
          model: undefined
        }
      });
    </script>
  </body>
</html>

We can change the background color with the bg-color prop.

The label-color prop changes the label color

And the color prop changes the icon color.

Comclusion

We can add a file input into our Vue app with the Quasar library.

Categories
Quasar

Developing Vue Apps with the Quasar Library — Filtering and Autocomplete

Quasar is a popular Vue UI library for developing good looking Vue apps.

In this article, we’ll take a look at how to create Vue apps with the Quasar UI library.

Filtering and Autocomplete

We can add an autocomplete with the q-select component.

For example, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-select
            filled
            v-model="model"
            use-input
            input-debounce="0"
            label="Simple filter"
            :options="options"
            @filter="filterFn"
          >
            <template v-slot:no-option>
              <q-item>
                <q-item-section class="text-grey">
                  No results
                </q-item-section>
              </q-item>
            </template>
          </q-select>
        </div>
      </q-layout>
    </div>
    <script>
      const stringOptions = ["apple", "orange", "grape"];

      new Vue({
        el: "#q-app",
        data: {
          options: [],
          model: []
        },
        methods: {
          filterFn(val, update) {
            if (val === "") {
              update(() => {
                this.options = stringOptions;
              });
              return;
            }

            update(() => {
              const needle = val.toLowerCase();
              this.options = stringOptions.filter((v) =>
                v.toLowerCase().includes(needle)
              );
            });
          }
        }
      });
    </script>
  </body>
</html>

We add the q-select component with the use-input options to let users enter values into the dropdown.

And we add the @filter directive to listen for inputs from the user.

filterFn has the val parameter which has the inputted value.

update is a function that lets us update the options when we type something into the input box.

The no-option slot lets us show something when no options are found.

To add an autocomplete input, we can write:

<!DOCTYPE html>
<html>
  <head>
    <link
      href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
      rel="stylesheet"
      type="text/css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.min.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body class="body--dark">
    <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quasar@1.12.13/dist/quasar.umd.min.js"></script>
    <div id="q-app">
      <q-layout
        view="lHh Lpr lFf"
        container
        style="height: 100vh;"
        class="shadow-2 rounded-borders"
      >
        <div class="q-pa-md">
          <q-select
            filled
            :value="model"
            use-input
            hide-selected
            fill-input
            input-debounce="0"
            :options="options"
            @filter="filterFn"
            @input-value="setModel"
            hint="Text autocomplete"
          >
            <template v-slot:no-option>
              <q-item>
                <q-item-section class="text-grey">
                  No results
                </q-item-section>
              </q-item>
            </template>
          </q-select>
        </div>
      </q-layout>
    </div>
    <script>
      const stringOptions = ["apple", "orange", "grape"];

      new Vue({
        el: "#q-app",
        data: {
          options: [],
          model: []
        },
        methods: {
          filterFn(val, update) {
            if (val === "") {
              update(() => {
                this.options = stringOptions;
              });
              return;
            }

            update(() => {
              const needle = val.toLowerCase();
              this.options = stringOptions.filter((v) =>
                v.toLowerCase().includes(needle)
              );
            });
          },
          setModel(val) {
            this.model = val;
          }
        }
      });
    </script>
  </body>
</html>

We keep the use-input prop and @filterFn directives the same.

And we add the fill-input prop to enable the autocomplete.

We separate the v-model directive into the @input-value directive and the value prop.

We listen to the input-value event to get the inputted value and set it as the value of the model reactive property.

And we set the value to the model reactive property to set the inputted value.

Conclusion

We can add filtering and autocomplete dropdowns into our Vue app with the Quasar library.