Categories
Vuetify

Vuetify — Customize Autocomplete

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Custom Filter on Autocomplete

We can add a custom filter with the v-autocomplete component.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-card class="overflow-hidden" color="purple lighten-1" dark>
          <v-card-text>
            <v-text-field color="white" label="Name"></v-text-field>
            <v-autocomplete
              :items="fruits"
              :filter="customFilter"
              color="white"
              item-text="name"
              label="Fruit"
            ></v-autocomplete>
          </v-card-text>
          <v-divider></v-divider>
          <v-card-actions>
            <v-spacer></v-spacer>
            <v-btn color="success" @click="save">Save</v-btn>
          </v-card-actions>
          <v-snackbar
            v-model="hasSaved"
            :timeout="2000"
            absolute
            bottom
            left
          >Your profile has been updated</v-snackbar>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      hasSaved: false,
      model: null,
      fruits: [
        { name: "apple", id: 1 },
        { name: "grape", id: 2 },
        { name: "orange", id: 3 },
      ],
    };
  },

  methods: {
    customFilter(item, queryText, itemText) {
      const text = item.name.toLowerCase();
      const searchText = queryText.toLowerCase();
      return text.indexOf(searchText) > -1;
    },
    save() {
      this.isEditing = !this.isEditing;
      this.hasSaved = true;
    },
  },
};
</script>

We added the v-autocomplete component with the items prop.

The items prop has the items for the autocomplete dropdown.

The filter prop has the filter method that we want to do the filtering with.

Dense Autocomplete

We can add the dense prop to reduce the autocomplete height and decrease the max height.

For instance, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-card class="overflow-hidden" color="purple lighten-1" dark>
          <v-card-text>
            <v-autocomplete
              dense
              :items="fruits"
              :filter="customFilter"
              color="white"
              item-text="name"
              label="Fruit"
            ></v-autocomplete>
          </v-card-text>
          <v-divider></v-divider>
          <v-card-actions>
            <v-spacer></v-spacer>
            <v-btn color="success" @click="save">Save</v-btn>
          </v-card-actions>
          <v-snackbar
            v-model="hasSaved"
            :timeout="2000"
            absolute
            bottom
            left
          >Your profile has been updated</v-snackbar>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      hasSaved: false,
      model: null,
      fruits: [
        { name: "apple", id: 1 },
        { name: "grape", id: 2 },
        { name: "orange", id: 3 },
      ],
    };
  },

  methods: {
    customFilter(item, queryText, itemText) {
      const text = item.name.toLowerCase();
      const searchText = queryText.toLowerCase();
      return text.indexOf(searchText) > -1;
    },
    save() {
      this.isEditing = !this.isEditing;
      this.hasSaved = true;
    },
  },
};
</script>

We just add the dense prop to the v-autocomplete to make it shorter.

Slots

We can change the output of the select with slots.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-card color="blue-grey darken-1" dark :loading="isUpdating">
          <v-form>
            <v-container>
              <v-row>
                <v-col cols="12" md="6">
                  <v-text-field
                    v-model="name"
                    :disabled="isUpdating"
                    filled
                    color="blue-grey lighten-2"
                    label="Name"
                  ></v-text-field>
                </v-col>
                <v-col cols="12">
                  <v-autocomplete
                    v-model="friends"
                    :disabled="isUpdating"
                    :items="people"
                    filled
                    chips
                    color="blue-grey lighten-2"
                    label="Select"
                    item-text="name"
                    item-value="name"
                    multiple
                  >
                    <template v-slot:selection="data">
                      <v-chip
                        v-bind="data.attrs"
                        :input-value="data.selected"
                        close
                        @click="data.select"
                        @click:close="remove(data.item)"
                      >
                        <v-avatar left>
                          <v-img :src="data.item.avatar"></v-img>
                        </v-avatar>
                        {{ data.item.name }}
                      </v-chip>
                    </template>
                    <template v-slot:item="data">
                      <template v-if="typeof data.item !== 'object'">
                        <v-list-item-content v-text="data.item"></v-list-item-content>
                      </template>
                      <template v-else>
                        <v-list-item-avatar>
                          <img :src="data.item.avatar" />
                        </v-list-item-avatar>
                        <v-list-item-content>
                          <v-list-item-title v-html="data.item.name"></v-list-item-title>
                          <v-list-item-subtitle v-html="data.item.group"></v-list-item-subtitle>
                        </v-list-item-content>
                      </template>
                    </template>
                  </v-autocomplete>
                </v-col>
              </v-row>
            </v-container>
          </v-form>
          <v-divider></v-divider>
          <v-card-actions>
            <v-btn
              :disabled="autoUpdate"
              :loading="isUpdating"
              color="blue-grey darken-3"
              depressed
              [@click](http://twitter.com/click "Twitter profile for @click")="isUpdating = true"
            >
              <v-icon left>mdi-update</v-icon>Update Now
            </v-btn>
          </v-card-actions>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    const src = "https://cdn.vuetifyjs.com/images/lists/1.jpg";
    return {
      autoUpdate: true,
      friends: ["Sandra Adams", "Britta Holt"],
      isUpdating: false,
      name: "",
      people: [
        { name: "Sandra Adams", avatar: src },
        { name: "Mary Smith", avatar: src },
        { name: "James Wong", avatar: src },
      ],
    };
  },

  watch: {
    isUpdating(val) {
      if (val) {
        setTimeout(() => (this.isUpdating = false), 3000);
      }
    },
  },

  methods: {
    remove(item) {
      const index = this.friends.indexOf(item.name);
      if (index >= 0) this.friends.splice(index, 1);
    },
  },
};
</script>

We have the selection slot to populate the name text and the avatar of the person entry.

The v-chip component is a container for both of these items.

This code:

<template v-slot:selection="data">
  <v-chip
  v-bind="data.attrs"
  :input-value="data.selected"
  close
  @click="data.select"
  @click:close="remove(data.item)"
  >
  <v-avatar left>
      <v-img :src="data.item.avatar"></v-img>
  </v-avatar>
  {{ data.item.name }}
  </v-chip>
</template>

has the chip shown when we select an item.

Conclusion

We can add custom filtering and show custom content for autocompletes with Vuetify.

Categories
Vuetify

Vuetify — Autocomplete

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Autocompletes

We use the v-autocomplete component to lets us add a autocomplete input to our app.

For instance, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-card color="red lighten-2" dark>
          <v-card-text>
            <v-autocomplete
              v-model="model"
              :items="items"
              :loading="isLoading"
              :search-input.sync="search"
              color="white"
              hide-no-data
              hide-selected
              item-text="Description"
              item-value="API"
              label="Public APIs"
              placeholder="Start typing to Search"
              prepend-icon="mdi-database-search"
              return-object
            ></v-autocomplete>
          </v-card-text>
          <v-divider></v-divider>
          <v-expand-transition>
            <v-list v-if="model" class="red lighten-3">
              <v-list-item v-for="(field, i) in fields" :key="i">
                <v-list-item-content>
                  <v-list-item-title v-text="field.value"></v-list-item-title>
                  <v-list-item-subtitle v-text="field.key"></v-list-item-subtitle>
                </v-list-item-content>
              </v-list-item>
            </v-list>
          </v-expand-transition>
          <v-card-actions>
            <v-spacer></v-spacer>
            <v-btn :disabled="!model" color="grey darken-3" @click="model = null">
              Clear
              <v-icon right>mdi-close-circle</v-icon>
            </v-btn>
          </v-card-actions>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    descriptionLimit: 60,
    entries: [],
    isLoading: false,
    model: null,
    search: null,
  }),

  computed: {
    fields() {
      if (!this.model) return [];

      return Object.keys(this.model).map((key) => {
        return {
          key,
          value: this.model[key],
        };
      });
    },
    items() {
      return this.entries.map((entry) => {
        const Description = entry.Description.slice(0,  this.descriptionLimit);

      return Object.assign({}, entry, { Description });
      });
    },
  },

  watch: {
    search(val) {
      if (this.items.length > 0) return;
      if (this.isLoading) return;
      this.isLoading = true;

      fetch("https://api.publicapis.org/entries")
        .then((res) => res.json())
        .then((res) => {
          const { count, entries } = res;
          this.count = count;
          this.entries = entries;
        })
        .catch((err) => {
          console.log(err);
        })
        .finally(() => (this.isLoading = false));
    },
  },
};
</script>

We add the v-autocomplete component.

v-model has the model.

loading prop has the loading state.

color sets the text color.

hide-no-data means that we don’t hide any data.

hide-selected means we hide the selected item.

item-text is the property of the description.

item-value is the property of the items.

label is the input label.

placeholder has the input placeholder.

prepend-icon is the icon name to add to the item.

return-object returns the result as objects.

The v-list has the items to show and show the results.

The search watcher fetches the data as the search value changes.

search changes when we type.

The fields computed property is what we render as the search results.

items has the selected items.

Conclusion

We can add an autocomplete input with the v-autocomplete component.

Categories
Vuetify

Vuetify — Autocomplete and Combobox

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Async Autocomplete

The v-autocomplete components work with async data sources.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-toolbar dark color="teal">
          <v-toolbar-title>State</v-toolbar-title>
          <v-autocomplete
            v-model="select"
            :loading="loading"
            :items="items"
            :search-input.sync="search"
            cache-items
            class="mx-4"
            flat
            hide-no-data
            hide-details
            label="State"
            solo-inverted
          ></v-autocomplete>
          <v-btn icon>
            <v-icon>mdi-dots-vertical</v-icon>
          </v-btn>
        </v-toolbar>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      loading: false,
      items: [],
      search: null,
      select: null,
      states: [
        "Alabama",
        "Alaska",
        "American Samoa",
        "Arizona",
        "Arkansas"
      ],
    };
  },
  watch: {
    search(val) {
      val && val !== this.select && this.querySelections(val);
    },
  },
  methods: {
    querySelections(v) {
      this.loading = true;
      setTimeout(() => {
        this.items = this.states.filter((e) => {
          return (e || "").toLowerCase().indexOf((v || "").toLowerCase()) > -1;
        });
        this.loading = false;
      }, 500);
    },
  },
};
</script>

We have the select state which is the model for the autocomplete.

And we have a watcher search to make the query when the search state changes.

In the querySelections method calls filter to filter the items.

Combobox

The v-combobox component is a v-autocomplete that lets us enter values that don’t exist with the provided items.

The created items will be returned as strings.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-combobox
          v-model="select"
          :items="items"
          label="Select activity"
          multiple
        ></v-combobox>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      select: ["Vue"],
      items: ["Programming", "Vue", "Vuetify"],
    };
  },
};
</script>

to add a v-combobox component to create the dropdown.

items populate the items with the items array.

We have the multiple prop to enable multiple selections.

label has the label for dropdown.

v-model sets the state from the selected item.

Now we see a dropdown with the items we can select.

Also, we can populate the data slot with our own choice.

For instance, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-combobox v-model="select" :items="items" label="Select activity" multiple chips>
          <template v-slot:selection="data">
            <v-chip
              :key="JSON.stringify(data.item)"
              v-bind="data.attrs"
              :input-value="data.selected"
              :disabled="data.disabled"
              @click:close="data.parent.selectItem(data.item)"
            >
              <v-avatar
                class="accent white--text"
                left
                v-text="data.item.slice(0, 1).toUpperCase()"
              ></v-avatar>
              {{ data.item }}
            </v-chip>
          </template>
        </v-combobox>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      select: ["Vue"],
      items: ["Programming", "Vue", "Vuetify"],
    };
  },
};
</script>

We have populate the selection slot with the v-chip component.

Dense

We can use the dense prop to reduce the combo box height and max height of the list items:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-combobox v-model="select" :items="items" label="Select activity" multiple dense></v-combobox>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      select: ["Vue"],
      items: ["Programming", "Vue", "Vuetify"],
    };
  },
};
</script>

Conclusion

We can get async data with autocomplete.

Also, we can add a combobox to add a dropdown where we can add items.

Categories
Vuetify

Vuetify — Nested Dialogs

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Loader Dialog

We can create a loader dialog to show a progress bar inside it.

To do that, we write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <div class="text-center">
          <v-btn
            :disabled="dialog"
            :loading="dialog"
            class="white--text"
            color="purple darken-2"
            @click="dialog = true"
          >Start loading</v-btn>
          <v-dialog v-model="dialog" hide-overlay persistent width="300">
            <v-card color="primary" dark>
              <v-card-text>
                Please wait
                <v-progress-linear indeterminate color="white" class="mb-0"></v-progress-linear>
              </v-card-text>
            </v-card>
          </v-dialog>
        </div>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    dialog: false,
  }),
  watch: {
    dialog(val) {
      if (!val) {
        return;
      }
      setTimeout(() => {
        this.dialog = false;
      }, 5000);
    },
  },
};
</script>

We added a dialog that shows a progress bar with the v-progress-linear component,

The indeterminate prop makes the progress bar move until the dialog is dismissed.

Nested Dialogs

We can open dialog boxes within another dialog.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <div>
          <v-row justify="center">
            <v-btn color="primary" class="ma-2" dark @click="dialog = true">Open Dialog 1</v-btn>
            <v-btn color="primary" class="ma-2" dark @click="dialog2 = true">Open Dialog 2</v-btn>
            <v-btn color="primary" class="ma-2" dark @click="dialog3 = true">Open Dialog 3</v-btn>
            <v-menu bottom offset-y>
              <template v-slot:activator="{ on, attrs }">
                <v-btn class="ma-2" v-bind="attrs" v-on="on">A Menu</v-btn>
              </template>
            </v-menu>
            <v-dialog
              v-model="dialog"
              fullscreen
              hide-overlay
              transition="dialog-bottom-transition"
              scrollable
            >
              <v-card tile>
                <v-card-text>
                  <v-btn color="primary" dark class="ma-2" @click="dialog2 = !dialog2">Open Dialog 2</v-btn>
                  <v-list three-line subheader>
                    <v-subheader>User Controls</v-subheader>
                  </v-list>
                </v-card-text>

                <div style="flex: 1 1 auto;"></div>
              </v-card>
            </v-dialog>

            <v-dialog v-model="dialog2" max-width="500px">
              <v-card>
                <v-card-title>Dialog 2</v-card-title>
                <v-card-text>
                  <v-btn color="primary" dark @click="dialog3 = !dialog3">Open Dialog 3</v-btn>
                </v-card-text>
                <v-card-actions>
                  <v-btn color="primary" text @click="dialog2 = false">Close</v-btn>
                </v-card-actions>
              </v-card>
            </v-dialog>
            <v-dialog v-model="dialog3" max-width="500px">
              <v-card>
                <v-card-title>
                  <span>Dialog 3</span>
                  <v-spacer></v-spacer>
                  <v-menu bottom left>
                    <template v-slot:activator="{ on, attrs }">
                      <v-btn icon v-bind="attrs" v-on="on">
                        <v-icon>mdi-dots-vertical</v-icon>
                      </v-btn>
                    </template>
                  </v-menu>
                </v-card-title>
                <v-card-actions>
                  <v-btn color="primary" text @click="dialog3 = false">Close</v-btn>
                </v-card-actions>
              </v-card>
            </v-dialog>
          </v-row>
        </div>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    dialog: false,
    dialog2: false,
    dialog3: false,
    notifications: false,
    sound: true,
    widgets: false,
  }),
};
</script>

to show dialog boxes with buttons to open other dialog boxes.

They are all created with the v-dialog component.

Conclusion

We can add a dialog box to show a progress bar or other dialog boxes with Vuetify.

Categories
Vuetify

Vuetify — Dialog

Vuetify is a popular UI framework for Vue apps.

In this article, we’ll look at how to work with the Vuetify framework.

Modal

We can create a modal dialog with the persistent prop.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-row justify="center">
          <v-dialog v-model="dialog" persistent max-width="290">
            <template v-slot:activator="{ on, attrs }">
              <v-btn color="primary" dark v-bind="attrs" v-on="on">Open Dialog</v-btn>
            </template>
            <v-card>
              <v-card-title class="headline">Title</v-card-title>
              <v-card-text>Lorem ipsum.</v-card-text>
              <v-card-actions>
                <v-spacer></v-spacer>
                <v-btn color="green darken-1" text @click="dialog = false">Cancel</v-btn>
                <v-btn color="green darken-1" text @click="dialog = false">OK</v-btn>
              </v-card-actions>
            </v-card>
          </v-dialog>
        </v-row>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    dialog: false,
  }),
};
</script>

We have the persistent prop on the v-dialog to make it closeable only with the buttons inside.

Scrollable

To make a dialog scrollable, we just add a scrollable prop.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-row justify="center">
          <v-dialog v-model="dialog" scrollable max-width="290">
            <template v-slot:activator="{ on, attrs }">
              <v-btn color="primary" dark v-bind="attrs" v-on="on">Open Dialog</v-btn>
            </template>
            <v-card>
              <v-card-title>Select Country</v-card-title>
              <v-divider></v-divider>
              <v-card-text style="height: 300px;">
                <v-radio-group v-model="country" column>
                  <v-radio :label="c" :value="c" v-for="(c, i) of countries" :key="i"></v-radio>
                </v-radio-group>
              </v-card-text>
              <v-spacer></v-spacer>
              <v-card-actions>
                <v-btn color="green darken-1" text @click="dialog = false">Cancel</v-btn>
                <v-btn color="green darken-1" text @click="dialog = false">OK</v-btn>
              </v-card-actions>
            </v-card>
          </v-dialog>
        </v-row>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    dialog: false,
    country: "",
    countries: [
      "Afghanistan",
      "Albania",
      "Algeria",
      "American Samoa",
      "Andorra",
      "Angola",
      "Anguilla",
      "Antarctica",
      "Antigua and Barbuda",
      "Argentina",
      "Armenia",
      "Aruba",
      "Australia",
      "Austria",
      "Azerbaijan",
    ],
  }),
};
</script>

We display a radio button group that overflows the v-card-text container.

So we add the scrollable prop to the v-dialog to make the radio button group scrollable.

Overflowed

If the modal doesn’t fit the available window space, then the container will be scrolled.

Form

We can add a form inside the v-dialog component.

For instance, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-row justify="center">
          <v-dialog v-model="dialog" persistent max-width="600px">
            <template v-slot:activator="{ on, attrs }">
              <v-btn color="primary" dark v-bind="attrs" v-on="on">Open Dialog</v-btn>
            </template>
            <v-card>
              <v-card-title>
                <span class="headline">User Profile</span>
              </v-card-title>
              <v-card-text>
                <v-container>
                  <v-row>
                    <v-col cols="12" sm="6" md="4">
                      <v-text-field label="first name*" required></v-text-field>
                    </v-col>
                    <v-col cols="12" sm="6" md="4">
                      <v-text-field label="middle name" hint="middle name"></v-text-field>
                    </v-col>
                    <v-col cols="12" sm="6" md="4">
                      <v-text-field label="last name*" hint="last name" persistent-hint required></v-text-field>
                    </v-col>
                    <v-col cols="12">
                      <v-text-field label="Email*" required></v-text-field>
                    </v-col>
                    <v-col cols="12">
                      <v-text-field label="Password*" type="password" required></v-text-field>
                    </v-col>
                    <v-col cols="12">
                      <v-select :items="['0-17', '18-29', '30-54', '54+']" label="Age*" required></v-select>
                    </v-col>
                  </v-row>
                </v-container>
                <small>*required field</small>
              </v-card-text>
              <v-card-actions>
                <v-spacer></v-spacer>
                <v-btn color="blue darken-1" text @click="dialog = false">Close</v-btn>
                <v-btn color="blue darken-1" text @click="dialog = false">Save</v-btn>
              </v-card-actions>
            </v-card>
          </v-dialog>
        </v-row>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    dialog: false,
  }),
};
</script>

to add a form inside the v-dialog .

This way, we’ll see the form when we open the dialog.

Conclusion

We can create a dialog with various kinds of content and behavior with Vuetify.