Categories
Top Vue Packages

Vue Plugins You Don’t Know You May Need

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at some Vue packages you don’t know you want to add to your app.

Vue-Dummy

The vue-dummy package lets us add dummy text to our app when while we’re developing it so we won’t have to worry about generating the text ourselves. We can also add placeholder images with it.

We can install it by running:

npm install --save vue-dummy

It’s also available as a standalone script that we can add by adding:

<script src="https://unpkg.com/vue-dummy"></script>

to our HTML code.

Then we can use it as follows:

main.js :

import Vue from "vue";
import App from "./App.vue";
import VueDummy from "vue-dummy";

Vue.use(VueDummy);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

App.vue :

<template>
  <div id="app">
    <p v-dummy="150"></p>
    <img v-dummy="'200x150'">
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

In the code above, we added a p element and bind it to the v-dummy directive. We set the value to 150 so that we get 150 fake words on the page.

Next, we added an image that’s 200px wide by 150px high.

We can also have a random number of words between a range for the dummy text. To use this feature, we can write:

<template>
  <div id="app">
    <p v-dummy="'100,130'"></p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

Then we get a random block of text between 100 to 130 words. It can also be written as a directive argument. For instance, we can write:

<template>
  <div id="app">
    <p v-dummy:100,130></p>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

to do the same thing we did in the previous example.

Likewise, dummy images dimensions can be set as directive arguments or modifiers.

To set dummy image dimensions as an argument, we can write:

<img v-dummy:300x200>

To set dummy image dimensions as a directive modifer, we can write:

<img v-dummy.400x300>

We can also set the width and height attributes of the img tag to set its dimensions:

<img v-dummy width="250" height="150">

We can also create randomly sized images:

<img v-dummy="'100,400x200,400'">

The code above will create images that’s between 100 to 400px wide and 200 to 400px high.

We can also put the numbers as a directive argument as follows:

<img v-dummy:100,400x200,400>

The dummy component is also available to generate the placeholder text or image as follows:

<dummy text="100"></dummy>
<dummy img="420x300"></dummy>

The first line creates a text block that’s 100 words long, and the 2nd creates a placeholder image that’s 420px wide by 300px high.

We can also create a table with fake content as follows:

<table v-dummy></table>

Vue.ImagesLoaded

The Vue.ImagesLoaded directive detects whether an image has been loaded.

It calls a callback when the image is loaded or fails to load. We can install it by running:

npm install vue-images-loaded --save

Then we can use it as follows:

<template>
  <div id="app">
    {{loaded}}
    <div v-images-loaded:on.progress="imageProgress">
      <img
        src="https://images.unsplash.com/photo-1562953208-602ead7f3d47?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=375&q=80"
      >
    </div>
  </div>
</template>

<script>
import imagesLoaded from "vue-images-loaded";

export default {
  name: "App",
  directives: {
    imagesLoaded
  },
  data() {
    return { loaded: "" };
  },
  methods: {
    imageProgress(instance, image) {
      this.loaded = image.isLoaded ? "loaded" : "broken";
    }
  }
};
</script>

In the code above, we registered the imagesLoaded directive from the vue-image-loaded package in our App component.

Then we added the v-images-loaded:on.progress=”imageProgress” directive with the imageProgress method as the value.

Then we can get whether the image is loaded from the image parameter’s isLoaded property.

The instance parameter has the elements property with an array for the parent of the image element that’s being loaded. The images property has an array of image elements for the images that are being loaded.

Other modifiers for the v-images.on directive include always, done, fail, progress to watch for all image loading events, watching only when the image is successfully loaded, watch only when it fails to load or watch the image when it’s loading respectively.

Conclusion

The v-dummy package lets us create placeholder text and images during development so we don’t have to do that ourselves.

To watch the progress of image loading, we can use the Vue.ImageLoaded package to do that.

Categories
Vue

How to Create a Vue Plugin

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to create a Vue plugin to expand our app’s functionality.

How to Create a Plugin

Creating a new Vue plugin is simple. We just have to create a module that exports an object with the install method inside it.

The install method has a Vue parameter for the Vue instance, and an options object that takes various options that we pass in by calling Vue.use to register the plugin.

With with steps above, we can build our own plugin. We can add a mixin with the Vue.mixin function.

Mixins let us access Vue lifecycle hooks.

Also, we can add properties to Vue.prototype to add methods to our components.

To create a bare-bones plugin, we can write the following:

plugin.js

export default {
  install(Vue, options) {
    Vue.mixin({
      created() {
        console.log("hello");
      }
    });
  }
};

main.js

import Vue from "vue";
import App from "./App.vue";
import plugin from "./plugin";

Vue.config.productionTip = false;
Vue.use(plugin);

new Vue({
  render: h => h(App)
}).$mount("#app");

In plugin.js , we exported an object with a default export that has the install method inside, which has the Vue parameter with the Vue instance and options with options, we pass into a Vue object.

We called Vue.mixin with the created method to modify the created hook globally so that whatever we have inside will be called when any component is initialized and the created hook is called.

Since we have console.log('hello') inside the mixin, that’ll be called when our component loads.

In main.js , we registered our plugin globally by calling Vue.use with the plugin we imported as the argument.

We’ll see 'hello' logged in the console log output.

To accept options with our plugin, we can pass them into Vue.use and use it our plugin as follows:

plugin.js

export default {
  install(Vue, options) {
    Vue.mixin({
      created() {
        const { greeting } = options;
        console.log(greeting);
      }
    });
  }
};

main.js

import Vue from "vue";
import App from "./App.vue";
import plugin from "./plugin";

Vue.config.productionTip = false;
Vue.use(plugin, { greeting: "hello jane" });

new Vue({
  render: h => h(App)
}).$mount("#app");

In the code above, we get the greeting property from the options parameter. Then since we passed in an object with the greeting property as the 2nd argument of the Vue.use , we’ll see 'hello jane' from the console log output.

Instance Properties

We can add our own instance properties to our plugin by adding properties to the Vue.prototype object.

The properties that we add should start with a $ symbol to distinguish them from properties that we define in our components.

For instance, we can add our own properties as follows:

plugin.js

export default {
  install(Vue, options) {
    Vue.prototype.$bold = text => {
      return `<b>${text}</b>`;
    };
  }
};

App.vue

<template>
  <div id="app" v-html="greeting"></div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      greeting: "hello"
    };
  },
  beforeMount() {
    this.greeting = this.$bold(this.greeting);
  }
};
</script>

In the code above, we have the following method in the install method:

Vue.prototype.$bold = text => {
  return `<b>${text}</b>`;
};

which can be called within our component since it’s part of the Vue prototype.

Then in App.vue , we can call it as follows:

this.greeting = this.$bold(this.greeting);

and then we can set the text by using the v-html directive as follows in App.vue :

<div id="app" v-html="greeting"></div>

Then we see the text shown on the screen.

Global Filters

We can add a global filter with the Vue.filter method.

The filter method takes a string with the filter name as the first argument and the function which takes an input and value and returns the value formatted as we like as the 2nd argument.

For instance, we can write the following code to do that:

plugin.js

export default {
  install(Vue, options) {
    Vue.filter("localeDateString", value => {
      if (!(value instanceof Date)) {
        return value;
      }
      return value.toLocaleDateString();
    });
  }
};

In the code above, we have the localeDateString filter, which returns the date string generated from a date object.

Then in App.vue , we can use it as follows:

<template>
  <div id="app">{{new Date() | localeDateString}}</div>
</template>

<script>
export default {
  name: "App"
};
</script>

And we get the locale-dependent date string displayed instead of the default date string.

Custom Directives

We can call the Vue.directive method to add a directive in our plugin.

The method takes a directive name string as the first argument and the directive object as the 2nd argument.

For instance, we can use it as follows:

plugin.js

export default {
  install(Vue, options) {
    Vue.directive("highlight", {
      inserted(el) {
        el.style.color = "red";
      }
    });
  }
};

In the code above, we have a highlight directive that changes the element’s content to the color red when we bind the directive to it.

Then when we use it as follows:

<template>
  <div id="app" v-highlight>foo</div>
</template>

<script>
export default {
  name: "App"
};
</script>

We get that ‘foo’ is red.

Conclusion

We can create our own Vue plugin by creating a module that exports and object with an install method.

The method takes a Vue parameter for the Vue instance and an options object with the options that we pass into Vue.use as the 2nd argument.

Then we can define Vue directive, mixins, and filters inside the install method.

Categories
Vuetify

Vuetify — Tabs

Vuetify is a popular UI framework for Vue apps.

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

Tabs

We can add tabs to our Vuetify app with the v-tabs component.

For example, we can write:

<template>
  <v-tabs fixed-tabs background-color="indigo" dark>
    <v-tab>One</v-tab>
    <v-tab>Two</v-tab>
    <v-tab>Three</v-tab>
  </v-tabs>
</template>
<script>
export default {
  name: "HelloWorld",
};
</script>

to show tabs.

background-color has the background color.

fixed-tabs makes its position fixed.

dark makes the text of the non-active tabs gray.

The active tab is always centered.

Tab Items

We can add the v-tab-items component to let us customize the content per tab.

For example, we can write:

<template>
  <v-card>
    <v-tabs v-model="tab" background-color="primary" dark>
      <v-tab v-for="item in items" :key="item.tab">{{ item.tab }}</v-tab>
    </v-tabs>

    <v-tabs-items v-model="tab">
      <v-tab-item v-for="item in items" :key="item.tab">
        <v-card flat>
          <v-card-text>{{ item.content }}</v-card-text>
        </v-card>
      </v-tab-item>
    </v-tabs-items>
  </v-card>
</template>
<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      tab: null,
      items: [
        { tab: "One", content: "Tab 1 Content" },
        { tab: "Two", content: "Tab 2 Content" },
        { tab: "Three", content: "Tab 3 Content" },
        { tab: "Four", content: "Tab 4 Content" },
        { tab: "Five", content: "Tab 5 Content" },
      ],
    };
  },
};
</script>

to add tabs and populate the headings and content.

v-tab has the tab headings and v-tab-items has the tab items.

The tab state has the index of the active tab.

v-model lets us control which tab is active programmatically.

We just render all the tab content with the v-tab-item component and the right one will be displayed automatically.

Grow Tabs

We can add the grow prop to make the tag items take up all available space up to a max-width of 300px:

<template>
  <v-card>
    <v-tabs v-model="tab" background-color="primary" grow dark>
      <v-tab v-for="item in items" :key="item.tab">{{ item.tab }}</v-tab>
    </v-tabs>

    <v-tabs-items v-model="tab">
      <v-tab-item v-for="item in items" :key="item.tab">
        <v-card flat>
          <v-card-text>{{ item.content }}</v-card-text>
        </v-card>
      </v-tab-item>
    </v-tabs-items>
  </v-card>
</template>
<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      tab: null,
      items: [
        { tab: "One", content: "Tab 1 Content" },
        { tab: "Two", content: "Tab 2 Content" },
        { tab: "Three", content: "Tab 3 Content" },
        { tab: "Four", content: "Tab 4 Content" },
        { tab: "Five", content: "Tab 5 Content" },
      ],
    };
  },
};
</script>

We have the grow prop to make it grow to fix the screen.

Pagination

The show-arrows prop will make the arrow display so that we can scroll through the tabs:

<template>
  <v-card>
    <v-tabs v-model="tab" background-color="primary" show-arrows dark>
      <v-tab v-for="item in items" :key="item.tab">{{ item.tab }}</v-tab>
    </v-tabs>

    <v-tabs-items v-model="tab">
      <v-tab-item v-for="item in items" :key="item.tab">
        <v-card flat>
          <v-card-text>{{ item.content }}</v-card-text>
        </v-card>
      </v-tab-item>
    </v-tabs-items>
  </v-card>
</template>
<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      tab: null,
      items: [
        { tab: "One", content: "Tab 1 Content" },
        { tab: "Two", content: "Tab 2 Content" },
        { tab: "Three", content: "Tab 3 Content" },
        { tab: "Four", content: "Tab 4 Content" },
        { tab: "Five", content: "Tab 5 Content" },
      ],
    };
  },
};
</script>

The arrows will show when the tabs overflow the page.

Conclusion

We can add tabs with the v-tabs and tab content with the v-tab-item component.

Categories
Vuetify

Vuetify — Table Sorting and Pagination

Vuetify is a popular UI framework for Vue apps.

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

External Sorting

We can control sorting externally with various props.

For instance, we can write:

<template>
  <div>
    <v-data-table
      :headers="headers"
      :items="desserts"
      :sort-by.sync="sortBy"
      :sort-desc.sync="sortDesc"
      class="elevation-1"
    ></v-data-table>
    <div class="text-center pt-2">
      <v-btn color="primary" class="mr-2" @click="toggleOrder">Toggle sort order</v-btn>
      <v-btn color="primary" @click="nextSort">Sort next column</v-btn>
    </div>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    sortBy: "fat",
    sortDesc: false,
    headers: [
      {
        text: "Dessert (100g serving)",
        align: "start",
        value: "name",
      },
      { text: "Calories", value: "calories" },
      { text: "Fat (g)", value: "fat" },
    ],
    desserts: [
      {
        name: "Frozen Yogurt",
        calories: 159,
        fat: 6.0,
      },
      {
        name: "Ice cream sandwich",
        calories: 237,
        fat: 9.0,
      },
      {
        name: "Eclair",
        calories: 262,
        fat: 16.0,
      },
    ],
  }),
  methods: {
    toggleOrder() {
      this.sortDesc = !this.sortDesc;
    },
    nextSort() {
      let index = this.headers.findIndex((h) => h.value === this.sortBy);
      index = (index + 1) % this.headers.length;
      this.sortBy = this.headers[index].value;
    },
  },
};
</script>

We change the sortBy and sortDesc prop values to let us change the column to sort by and whether we sort ascending or descending respectively.

The sync modifier is required for updating the table properly when the buttons are clicked to change the prop values.

Paginate and Sort Server-Side

We can do the pagination and sorting on the server-side.

For example, we can write:

<template>
  <div>
    <v-data-table
      :headers="headers"
      :items="desserts"
      :options.sync="options"
      :server-items-length="totalDesserts"
      :loading="loading"
      class="elevation-1"
    ></v-data-table>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      totalDesserts: 0,
      desserts: [],
      loading: true,
      options: {},
      headers: [
        {
          text: "Dessert (100g serving)",
          align: "start",
          sortable: false,
          value: "name",
        },
        { text: "Calories", value: "calories" },
        { text: "Fat (g)", value: "fat" },
      ],
    };
  },
  watch: {
    options: {
      async handler() {
        const { items, total } = await this.getDataFromApi();
        this.desserts = items;
        this.totalDesserts = total;
      },
      deep: true,
    },
  },
  async mounted() {
    const { items, total } = await this.getDataFromApi();
    this.desserts = items;
    this.totalDesserts = total;
  },
  methods: {
    getDataFromApi() {
      this.loading = true;
      return new Promise((resolve, reject) => {
        const { sortBy, sortDesc, page, itemsPerPage } = this.options;

        let items = this.getDesserts();
        const total = items.length;

        if (sortBy.length === 1 && sortDesc.length === 1) {
          items = items.sort((a, b) => {
            const sortA = a[sortBy[0]];
            const sortB = b[sortBy[0]];

            if (sortDesc[0]) {
              if (sortA < sortB) return 1;
              if (sortA > sortB) return -1;
              return 0;
            } else {
              if (sortA < sortB) return -1;
              if (sortA > sortB) return 1;
              return 0;
            }
          });
        }

        if (itemsPerPage > 0) {
          items = items.slice((page - 1) * itemsPerPage, page * itemsPerPage);
        }

        setTimeout(() => {
          this.loading = false;
          resolve({
            items,
            total,
          });
        }, 1000);
      });
    },
    getDesserts() {
      return [
        {
          name: "Frozen Yogurt",
          calories: 159,
          fat: 6.0,
        },
        {
          name: "Ice cream sandwich",
          calories: 237,
          fat: 9.0,
        },
        {
          name: "Eclair",
          calories: 262,
          fat: 16.0,
        },
      ];
    },
  },
};
</script>

The getDataFromApi method returns a promise that resolves to the data that we want to populate in the table.

The sorting is done with the sort method in the promise.

In the mounted hook, we get the data and set it.

this.desserts has the items.

totalDesserts is a number with the total number of desserts.

We set totalDessert as the value of server-items-length .

And items has the desserts array as its value.

Conclusion

We can sort and paginate data from the client or server-side with Vuetify.

Categories
Vuetify

Vuetify — Table Slots and Pagination

Vuetify is a popular UI framework for Vue apps.

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

Customizing Default Header

We can customize the default header with the header.<name> slot.

<name> is the name of the value property in the header item sent to headers .

For example, we can write:

<template>
  <v-data-table :headers="headers" :items="desserts" class="elevation-1">
    <template v-slot:header.name="{ header }">{{ header.text.toUpperCase() }}</template>
  </v-data-table>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    headers: [
      {
        text: "Dessert (100g serving)",
        align: "start",
        value: "name",
      },
      { text: "Calories", value: "calories" },
      { text: "Fat (g)", value: "fat" },
    ],
    desserts: [
      {
        name: "Frozen Yogurt",
        calories: 159,
        fat: 6.0,
      },
      {
        name: "Ice cream sandwich",
        calories: 237,
        fat: 9.0,
      },
      {
        name: "Eclair",
        calories: 262,
        fat: 16.0,
      },
    ],
  }),
};
</script>

to add a table with our own headings.

We populate the header.name slot to customize the display of the name column heading.

We display it as upper case.

Customizing Columns

We can customize some columns with the item.<name> slot.

<name> is the property name of the column we want to change.

For example, we can write:

<template>
  <v-data-table :headers="headers" :items="desserts" class="elevation-1">
    <template v-slot:item.calories="{ item }">
      <v-chip :color="getColor(item.calories)" dark>{{ item.calories }}</v-chip>
    </template>
  </v-data-table>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    headers: [
      {
        text: "Dessert (100g serving)",
        align: "start",
        value: "name",
      },
      { text: "Calories", value: "calories" },
      { text: "Fat (g)", value: "fat" },
    ],
    desserts: [
      {
        name: "Frozen Yogurt",
        calories: 159,
        fat: 6.0,
      },
      {
        name: "Ice cream sandwich",
        calories: 237,
        fat: 9.0,
      },
      {
        name: "Eclair",
        calories: 262,
        fat: 16.0,
      },
    ],
  }),
  methods: {
    getColor(calories) {
      if (calories > 400) return "red";
      else if (calories > 200) return "orange";
      else return "green";
    },
  },
};
</script>

We add a chip to the item.calories slot to change how the calories column is displayed.

External Pagination

We can add external pagination with the options prop.

For instance, we can write:

<template>
  <div>
    <v-data-table
      :headers="headers"
      :items="desserts"
      :page.sync="page"
      :items-per-page="itemsPerPage"
      hide-default-footer
      class="elevation-1"
      [@page](http://twitter.com/page "Twitter profile for @page")-count="pageCount = $event"
    ></v-data-table>
    <div class="text-center pt-2">
      <v-pagination v-model="page" :length="pageCount"></v-pagination>
      <v-text-field
        :value="itemsPerPage"
        label="Items per page"
        type="number"
        min="-1"
        max="15"
        [@input](http://twitter.com/input "Twitter profile for @input")="itemsPerPage = parseInt($event, 10)"
      ></v-text-field>
    </div>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data: () => ({
    page: 1,
    pageCount: 0,
    itemsPerPage: 10,
    headers: [
      {
        text: "Dessert (100g serving)",
        align: "start",
        value: "name",
      },
      { text: "Calories", value: "calories" },
      { text: "Fat (g)", value: "fat" },
    ],
    desserts: [
      {
        name: "Frozen Yogurt",
        calories: 159,
        fat: 6.0,
      },
      {
        name: "Ice cream sandwich",
        calories: 237,
        fat: 9.0,
      },
      {
        name: "Eclair",
        calories: 262,
        fat: 16.0,
      },
    ],
  }),
  methods: {
    getColor(calories) {
      if (calories > 400) return "red";
      else if (calories > 200) return "orange";
      else return "green";
    },
  },
};
</script>

to add a text field to our page to let users change the items per page.

Also, we added the v-pagination component to add the pagination buttons.

Conclusion

We can change how columns and pagination buttons are displayed with various components and slots.