Categories
Vuetify

Vuetify — Expansion Panels

Vuetify is a popular UI framework for Vue apps.

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

Expansion Panels

The v-expansion-panel component is useful for reducing vertical space with large amount of information.

With the multiple prop, the expansion panel can stay open until it’s closed explicitly.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <div>
          <div class="d-flex">
            <v-checkbox v-model="disabled" label="Disabled"></v-checkbox>
          </div>

          <v-expansion-panels v-model="panel" :disabled="disabled" multiple>
            <v-expansion-panel>
              <v-expansion-panel-header>Panel 1</v-expansion-panel-header>
              <v-expansion-panel-content>Some content</v-expansion-panel-content>
            </v-expansion-panel>

<v-expansion-panel>
              <v-expansion-panel-header>Panel 2</v-expansion-panel-header>
              <v-expansion-panel-content>Some content</v-expansion-panel-content>
            </v-expansion-panel>

            <v-expansion-panel>
              <v-expansion-panel-header>Panel 3</v-expansion-panel-header>
              <v-expansion-panel-content>Some content</v-expansion-panel-content>
            </v-expansion-panel>
          </v-expansion-panels>
        </div>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    panel: [0, 1],
    disabled: false,
    readonly: false,
  }),
};
</script>

We have the disabled checkbox to disable the panel when we check it.

Also, we have the v-expansion-panels to create the expansion panel container

Then v-expansion-panel component is the expansion panel itself.

The header is displayed and v-expansion-panel-content is displayed when we click on the heading.

The panel state is used for v-model and has the indexes of the panels to open.

Readonly

We can make an expansion panel read-only.

It does the same thing as disabled but doesn’t change the styling.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <div>
          <div class="d-flex">
            <v-checkbox v-model="readonly" label="readonly"></v-checkbox>
          </div>

          <v-expansion-panels v-model="panel" :readonly="readonly" multiple>
            <v-expansion-panel>
              <v-expansion-panel-header>Panel 1</v-expansion-panel-header>
              <v-expansion-panel-content>Some content</v-expansion-panel-content>
            </v-expansion-panel>

<v-expansion-panel>
              <v-expansion-panel-header>Panel 2</v-expansion-panel-header>
              <v-expansion-panel-content>Some content</v-expansion-panel-content>
            </v-expansion-panel>

            <v-expansion-panel>
              <v-expansion-panel-header>Panel 3</v-expansion-panel-header>
              <v-expansion-panel-content>Some content</v-expansion-panel-content>
            </v-expansion-panel>
          </v-expansion-panels>
        </div>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    panel: [0, 1],
    readonly: false,
  }),
};
</script>

Popout

We can make expansion panels have the popout design.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-expansion-panels popout>
          <v-expansion-panel v-for="(item,i) in 5" :key="i">
            <v-expansion-panel-header>Item</v-expansion-panel-header>
            <v-expansion-panel-content>Lorem ipsum.</v-expansion-panel-content>
          </v-expansion-panel>
        </v-expansion-panels>
      </v-col>
    </v-row>
  </v-container>
</template>

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

We just add the popout prop to make the expanded expansion panel wider than the others.

Conclusion

We can add expansion panels to display vertical content that can be expanded.

Categories
Vuetify

Vuetify — Dividing List Items

Vuetify is a popular UI framework for Vue apps.

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

Dividers and Subheaders

We can add dividers to subheaders.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-card>
          <v-toolbar color="orange lighten-1" dark>
            <v-app-bar-nav-icon></v-app-bar-nav-icon>
            <v-toolbar-title>Message Board</v-toolbar-title>
            <v-spacer></v-spacer>
            <v-btn icon>
              <v-icon>mdi-magnify</v-icon>
            </v-btn>
          </v-toolbar>

          <v-list two-line>
            <template v-for="(item, index) in items">
              <v-divider v-if="item.divider" :key="index" inset></v-divider>
              <v-list-item v-else :key="item.title" ripple>
                <v-list-item-avatar>
                  <img :src="item.avatar" />
                </v-list-item-avatar>
                <v-list-item-content>
                  <v-list-item-title v-html="item.title"></v-list-item-title>
                  <v-list-item-subtitle v-html="item.subtitle"></v-list-item-subtitle>
                </v-list-item-content>
              </v-list-item>
            </template>
          </v-list>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",
  data: () => ({
    items: [
      { divider: true },
      {
        avatar: "https://picsum.photos/250/300?image=660",
        title: "Meeting",
        subtitle: "subtitle",
      },
      { divider: true },
      {
        avatar: "https://picsum.photos/250/300?image=146",
        title: "So long",
        subtitle: "subtitle",
      },
      { divider: true },
      {
        avatar: "https://picsum.photos/250/300?image=1008",
        title: "Breakfast",
        subtitle: "subtitle",
      },
    ],
  }),
};
</script>

We added the v-divider dynamically in with the v-divider and v-if directive.

We used it to divide the v-list-item .

v-list-item has the items we’re dividing.

Dividers in Portrait View

Also, we can add dividers in portrait view.

For example, we can write:

<template>
  <v-container>
    <v-row>
      <v-col col="12">
        <v-card>
          <v-card-title class="cyan darken-1">
            <span class="headline white--text">Sarah Smith</span>

            <v-spacer></v-spacer>

            <v-btn dark icon>
              <v-icon>mdi-chevron-left</v-icon>
            </v-btn>

            <v-btn dark icon>
              <v-icon>mdi-pencil</v-icon>
            </v-btn>

            <v-btn dark icon>
              <v-icon>mdi-dots-vertical</v-icon>
            </v-btn>
          </v-card-title>

          <v-list>
            <v-list-item>
              <v-list-item-action>
                <v-icon>mdi-phone</v-icon>
              </v-list-item-action>

              <v-list-item-content>
                <v-list-item-title>123-456-7890</v-list-item-title>
              </v-list-item-content>
              <v-list-item-action>
                <v-icon>mdi-message-text</v-icon>
              </v-list-item-action>
            </v-list-item>

            <v-divider inset></v-divider>

            <v-list-item>
              <v-list-item-action>
                <v-icon>mdi-email</v-icon>
              </v-list-item-action>

              <v-list-item-content>
                <v-list-item-title>sarah@example.com</v-list-item-title>
              </v-list-item-content>
            </v-list-item>

            <v-divider inset></v-divider>

            <v-list-item>
              <v-list-item-action>
                <v-icon>mdi-map-marker</v-icon>
              </v-list-item-action>

              <v-list-item-content>
                <v-list-item-title>New York</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
          </v-list>

          <v-img src="http://placekitten.com/600/200" height="200px"></v-img>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

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

We have the v-divider between the v-list-item s with the inset props.

This will make the divider shown below the text on the right side.

The icons won’t have the divider below them.

Conclusion

We can add dividers to separate list items.

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.