Categories
Vue 3

Add Infinite Scrolling to a Vue.js 3 App with the Intersection Observer API

Infinite scrolling is something that we’ve to add often into our Vue 3 app.

In this article, we’ll look at how to add infinite scrolling to a Vue 3 app with the Intersection Observer API.

Add Infinite Scrolling with the Intersection Observer API

The Intersection API lets us add infinite scrolling easily in our Vue 3 app.

To do this, we assign a ref to the last element and watch when that element is displayed on the screen.

Then when the array changes, we reassign the ref to the last element that’s added then.

For instance, we can write:

<template>
  <div
    v-for="(a, i) of arr"
    :key="a"
    :ref="i === arr.length - 1 ? 'last' : undefined"
  >
    {{ a }}
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      page: 1,
      arr: Array(30)
        .fill()
        .map((_, i) => i),
      observer: undefined,
    };
  },
  methods: {
    async addObserver() {
      await this.$nextTick();
      const options = {
        root: document,
        rootMargin: "20px",
        threshold: 1,
      };

      const callback = (entries) => {
        if (entries[0].isIntersecting) {
          this.arr = [
            ...this.arr,
            ...Array(30)
              .fill()
              .map((_, i) => i + 30 * (this.page - 1)),
          ];
          this.page++;
        }
      };
      this.observer = new IntersectionObserver(callback, options);
      this.observer.observe(this.$refs.last);
    },
  },
  mounted() {
    this.addObserver();
  },
  watch: {
    arr: {
      deep: true,
      handler() {
        this.addObserver();
      },
    },
  },
};
</script>

We have a series of divs rendered from the arr array in the component template.

The ref prop is set by checking whether the index of the item is the same as arr.length — 1 .

If it is, then it’s the last item, so we assign a ref to that.

Otherwise, we don’t assign a ref to it.

The data method returns the page number and the arr array with the data to render.

In the methods object, we have the addObserver methgod to add the Intersection Observer and use that to watch when the element that we assigned the ref to appears on the screen.

To do this, we call $nextTick to make sure the elements are rendered.

Then we set the options for detecting intersection.

root is the element that has the items we watch for.

rootMargin is the margin to determine when the item is considered to be on the screen.

threshold is the threshold for the element to be displayed. It’s the portion of the element that’s appeared on the screen. And it’s a number between 0 and 1.

Next, we have the callback function that checks the isIntersecting property to see if the last element appeared.

If it has, then we update the arr array by putting more entries in it.

We also update the page value.

Next, we create the IntersectionObserver instance with the callback and options .

Then we call observe on it with the element that’s been assigned the ref to watch it appear on the screen.

We call this method with the component is mounted and when the arr array changes.

The mounted hook runs after the component is rendered, so we can get the element with the ref assigned and watch it appear on the screen.

Now when we scroll down, we should see more items appear.

Conclusion

We can use the Intersection Observer API easily to add infinite scrolling easily into our Vue 3 app.

Categories
Vue

How to Add Native Notifications to Your Vue.js App

With the HTML5 Notification API, browsers can display native popup notifications to users. With notifications, you can display text and icons, and also play sound with them. The full list of options are located at https://developer.mozilla.org/en-US/docs/Web/API/notification. Users have to grant permission to display notifications when they visit a web app to see browser notifications.

Developers have done the hard work for us if we use React because a React component is created to display browser notifications. The Vue-Native-Notification package, located at https://www.npmjs.com/package/vue-native-notification can let us display popups and handle the events that are associated with display the notifications like when use clicks on the notification or handle cases when permissions or granted or denied for display notifications.

In this article, we will build a password manager that lets you enter, edit and delete password to the websites and show notifications whenever these actions are taken. We will use Vue.js to build the app.

To start we create the project by running npx @vue/cli create password-manager . In the wizard, choose ‘Manually select features’ and choose to include Babel, Vue Router, and Vuex in our app.

Next we install some libraries we need. We need Axios for making HTTP requests, Bootstrap Vue for styling, V-Clipboard for the copy to clipboard functionality, Vue-Native-Notification for showing native browser notifications and Vee-Validate for form validation. We install them by running:

npm i axios bootstrap-vue v-clipboard vee-validate `vue-native-notification`

After we install the libraries, we can start building the app. First in the components folder, create a file called PasswordForm.vue for our password form. Then in there, we add:

<template>
  <ValidationObserver ref="observer" v-slot="{ invalid }">
    <b-form @submit.prevent="onSubmit" novalidate>
      <b-form-group label="Name">
        <ValidationProvider name="name" rules="required" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.name"
            required
            placeholder="Name"
            name="name"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">Name is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-form-group label="URL">
        <ValidationProvider name="url" rules="required|url" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.url"
            required
            placeholder="URL"
            name="url"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">{{errors.join('. ')}}</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-form-group label="Username">
        <ValidationProvider name="username" rules="required" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.username"
            required
            placeholder="Username"
            name="username"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">Username is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-form-group label="Password">
        <ValidationProvider name="password" rules="required" v-slot="{ errors }">
          <b-form-input
            type="password"
            :state="errors.length == 0"
            v-model="form.password"
            required
            placeholder="Password"
            name="password"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">Password is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>

      <b-button type="submit" variant="primary" style="margin-right: 10px">Submit</b-button>
      <b-button type="reset" variant="danger" @click="cancel()">Cancel</b-button>
    </b-form>
  </ValidationObserver>
</template>

<script>
import { requestsMixin } from "@/mixins/requestsMixin";

export default {
  name: "PasswordForm",
  mixins: [requestsMixin],
  props: {
    edit: Boolean,
    password: Object
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }

      if (this.edit) {
        await this.editPassword(this.form);
        this.$notification.show(
          "Password edited",
          {
            body: "Password edited"
          },
          {}
        );
      } else {
        await this.addPassword(this.form);
        this.$notification.show(
          "Password added",
          {
            body: "Password added"
          },
          {}
        );
      }
      const response = await this.getPasswords();
      this.$store.commit("setPasswords", response.data);
      this.$emit("saved");
    },
    cancel() {
      this.$emit("cancelled");
    }
  },
  data() {
    return {
      form: {}
    };
  },
  watch: {
    password: {
      handler(p) {
        this.form = JSON.parse(JSON.stringify(p || {}));
      },
      deep: true,
      immediate: true
    }
  }
};
</script>

We have the password form in this component. The form includes name, URL, username, and password fields. All of them are required. We use Vee-Validate to validate the form fields. The ValidationObserver component is for validating the whole form, while the ValidationProvider component is for validating the form fields that it wraps around. The validation rule is specified by the rule prop of each field. We have a special url rule for the URL field. We show the validation error messages when the errors object from the scope slot has a non-zero length. The state prop is for setting the validation state which shows the green when errors has length 0 and red otherwise. The error messages are shown in the b-form-invalid-feedback component.

When the user clicks to Save button, onSubmit function is called. We get the validation state of the form by using this.$refs.observer.validate(); . The ref refers to the ref of the ValidationObserver . If it resolves to true , then we call addPassword or editPassword to save the entry depending on the edit prop. Then we get the passwords by calling getPasswords and then put it in our Vuex store by dispatching the setPasswords mutation. Then we emit the saved event to close the modal in the home page. The notifications are shown by calling this.$notification.show , provided by Vue-Native-Notification. The first argument is the notification title, the second contains the body, and the third argument are optional event handlers that you can add if needed. The full list of event handlers are at https://www.npmjs.com/package/vue-native-notification.

We have a watch block mainly used when an existing entry is being edited, we get the password prop and set it to this.form by making a copy of the prop so that we only update the form object and nothing when data is binding.

Next we create a mixins folder and add requestsMixin.js inside it. In the file, add:

const APIURL = "[http://localhost:3000](http://localhost:3000)";
const axios = require("axios");

export const requestsMixin = {
  methods: {
    getPasswords() {
      return axios.get(`${APIURL}/passwords`);
    },

    addPassword(data) {
      return axios.post(`${APIURL}/passwords`, data);
    },

    editPassword(data) {
      return axios.put(`${APIURL}/passwords/${data.id}`, data);
    },

    deletePassword(id) {
      return axios.delete(`${APIURL}/passwords/${id}`);
    }
  }
};

This contains the code to make the HTTP requests in the back end. We include this mixin in our components so that we can make requests to back end from them.

Next in Home.vue , we replace the existing code with:

<template>
  <div class="page">
    <h1 class="text-center">Password Manager</h1>
    <b-button-toolbar>
      <b-button @click="openAddModal()">Add Password</b-button>
    </b-button-toolbar>
    <br />
    <b-table-simple responsive>
      <b-thead>
        <b-tr>
          <b-th>Name</b-th>
          <b-th>URL</b-th>
          <b-th>Username</b-th>
          <b-th>Password</b-th>
          <b-th></b-th>
          <b-th></b-th>
          <b-th></b-th>
          <b-th></b-th>
        </b-tr>
      </b-thead>
      <b-tbody>
        <b-tr v-for="p in passwords" :key="p.id">
          <b-td>{{p.name}}</b-td>
          <b-td>{{p.url}}</b-td>
          <b-td>{{p.username}}</b-td>
          <b-td>******</b-td>
          <b-td>
            <b-button
              v-clipboard="() => p.username"
              @click="notify('Username copied', 'Username copied')"
            >Copy Username</b-button>
          </b-td>
          <b-td>
            <b-button
              v-clipboard="() => p.password"
              @click="notify('Password copied', 'Password copied')"
            >Copy Password</b-button>
          </b-td>
          <b-td>
            <b-button @click="openEditModal(p)">Edit</b-button>
          </b-td>
          <b-td>
            <b-button @click="deleteOnePassword(p.id)">Delete</b-button>
          </b-td>
        </b-tr>
      </b-tbody>
    </b-table-simple>

    <b-modal id="add-modal" title="Add Password" hide-footer>
      <PasswordForm @saved="closeModal()" @cancelled="closeModal()" :edit="false"></PasswordForm>
    </b-modal>

    <b-modal id="edit-modal" title="Edit Password" hide-footer>
      <PasswordForm
        @saved="closeModal()"
        @cancelled="closeModal()"
        :edit="true"
        :password="selectedPassword"
      ></PasswordForm>
    </b-modal>
  </div>
</template>

<script>
import { requestsMixin } from "@/mixins/requestsMixin";
import PasswordForm from "@/components/PasswordForm";

export default {
  name: "home",
  components: {
    PasswordForm
  },
  mixins: [requestsMixin],
  computed: {
    passwords() {
      return this.$store.state.passwords;
    }
  },
  beforeMount() {
    this.getAllPasswords();
  },
  data() {
    return {
      selectedPassword: {}
    };
  },
  methods: {
    notify(title, body) {
      this.$notification.show(
        title,
        {
          body
        },
        {}
      );
    },
    openAddModal() {
      this.$bvModal.show("add-modal");
    },
    openEditModal(password) {
      this.$bvModal.show("edit-modal");
      this.selectedPassword = password;
    },
    closeModal() {
      this.$bvModal.hide("add-modal");
      this.$bvModal.hide("edit-modal");
      this.selectedPassword = {};
    },
    async deleteOnePassword(id) {
      await this.deletePassword(id);
      this.$notification.show(
        "Password deleted",
        {
          body: "Password deleted"
        },
        {}
      );
      this.getAllPasswords();
    },
    async getAllPasswords() {
      const response = await this.getPasswords();
      this.$store.commit("setPasswords", response.data);
    }
  }
};
</script>

In this file, we have a table to display a list of password entries and let users open and close the add and edit modals. We have buttons in each row to copy the username and passwords, and also to let users edit or delete each entry.

In the scripts section, we have the beforeMount hook to get all the password entries during page load with the getPasswords function we wrote in our mixin. When the Edit button is clicked, the selectedPassword variable is set, and we pass it to the PasswordForm for editing.

To delete a password, we call deletePassword in our mixin to make the request to the back end.

The copy to clipboard functionality is added here. For the copy username and password buttons, we use the v-clipboard directive to let us copy the username and password respectively to the clipboard when the button is clicked.

We have notifications for deleting an entry here. They are called the same as in PasswordForm . Also, we added a notify function so that we can show notifications when username or password are copied by click the respective buttons.

Next in App.vue , we replace the existing code with:

<template>
  <div id="app">
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand href="#">Password Manager</b-navbar-brand>

      <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

      <b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item to="/" :active="path  == '/'">Home</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    <router-view />
  </div>
</template>

<script>
export default {
  data() {
    return {
      path: this.$route && this.$route.path
    };
  },
  watch: {
    $route(route) {
      this.path = route.path;
    }
  }
};
</script>

<style lang="scss">
.page {
  padding: 20px;
}

button {
  margin-right: 10px;
}
</style>

to add a Bootstrap navigation bar to the top of our pages, and a router-view to display the routes we define.

Next in main.js , replace the code with:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import BootstrapVue from "bootstrap-vue";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import Clipboard from "v-clipboard";
import { required } from "vee-validate/dist/rules";
import VueNativeNotification from "vue-native-notification";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";

extend("required", required);
extend("url", {
  validate: value => {
    return /^(http://www.|https://www.|http://|https://)?[a-z0-9]+([-.]{1}[a-z0-9]+)*.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?$/.test(
      value
    );
  },
  message: "URL is invalid."
});
Vue.use(BootstrapVue);
Vue.use(Clipboard);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.use(VueNativeNotification, {
  requestOnNotify: true
});
Vue.config.productionTip = false;

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

so that we add the libraries we installed to our app so we can use it in our components. We call extend from Vee-Validate to add the form validation rules that we want to use. Also, we add the V-Clipboard library here so we can use it in our home page.

We include the Vue-Native-Notification library here by adding:

Vue.use(VueNativeNotification, {
  requestOnNotify: true
});

The requestOnNotify settings is for showing the permission prompt when the first notification is made if set to true .

In router.js , we replace the existing code with:

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    }
  ]
})

to only include our home page.

Then in store.js , we replace the existing code with:

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    passwords: []
  },
  mutations: {
    setPasswords(state, payload) {
      state.passwords = payload;
    }
  },
  actions: {}
});

to add our passwords state to the store so we can observer it in the computed block of PasswordForm and HomePage components. We have the setPasswords function to update the passwords state and we use it in the components by call this.$store.commit(“setPasswords”, response.data); like we did in PasswordForm . Also, we imported the Bootstrap CSS in this file to get the styles.

Finally, in index.html , replace the existing code with:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title>Password Manager</title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but vue-clipboard-tutorial-app doesn't work properly
        without JavaScript enabled. Please enable it to continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

to change the title.

After all the hard work, we can start our app by running npm start .

To start the back end, we first install the json-server package by running npm i json-server. Then, go to our project folder and run:

json-server --watch db.json

In db.json, change the text to:

{
  "passwords": [
  ]
}

So we have the passwords endpoints defined in the requests.js available.

Categories
Vue

Create an RSS Reader with Vue.js and JavaScript

Vue.js is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create an RSS reader app with Vue.js and JavaScript.

Create the Project

We can create the Vue project with Vue CLI.

To install it, we run:

npm install -g @vue/cli

with NPM or:

yarn global add @vue/cli

with Yarn.

Then we run:

vue create rss-reader

and select all the default options to create the project.

Create the RSS Reader

To create the RSS reader, we write:

<template>
  <div id="app">
    <form @submit.prevent="getRss">
      <div>
        <label> rss url</label>
        <br />
        <input v-model="rssUrl" />
      </div>
      <input type="submit" />
    </form>
    <div v-for="item of items" :key="item.title">
      <h1>{{ item.title }}</h1>
      <p>{{ item.author }}</p>
      <a :href="item.link">{{ item.link }}</a>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      rssUrl: "",
      items: [],
    };
  },
  methods: {
    async getRss() {
      const urlRegex = /(http|ftp|https)://[\w-]+(.[\w-]+)+([\w.,@?^=%&amp;:/~+#-]*[\w@?^=%&amp;/~+#-])?/;
      if (!urlRegex.test(this.rssUrl)) {
        return;
      }
      const res = await fetch(
        `https://api.allorigins.win/get?url=${this.rssUrl}`
      );
      const { contents } = await res.json();
      const feed = new window.DOMParser().parseFromString(contents, "text/xml");
      const items = feed.querySelectorAll("item");
      this.items = [...items].map((el) => ({
        link: el.querySelector("link").innerHTML,
        title: el.querySelector("title").innerHTML,
        author: el.querySelector("author").innerHTML,
      }));
    },
  },
};
</script>

We have a form with a form field to let us enter the RSS feed’s URL.

We bind the inputted value to the rssUrl reactive property.

In the form element, we listen to the submit event, which is triggered when we click on the input button with type submit .

The prevent modifier lets us do client-side submission instead of server-side.

Below that form, we use the v-for directive to render the RSS data, which we store in the items reactive property.

In the script, we have the getRss method.

The method first checks if rssUrl is valid.

If it is, then we call fetch to make a request to get the data from the RSS feed.

We use the allOrigins API to let us make cross-domain requests to the given feed.

We need this since cross-domain communication from the browser isn’t allows with most feeds.

The request has the contents property with the XML RSS feed content.

Then we use the parseFromString method from the DOMParser constructor to let us parse the XML string.

We pass in the XML string as the first argument.

And the 2nd argument has the content type of what we’re parsing.

It returns the XML tree DOM object.

This means we can use methods like querySelectorAll to select nodes.

Then we can select the item nodes with querySelectAll .

And then we use the spread operator to spread the items from the Node list to an array.

Next, we call map to return objects with the content of the nodes we select with querySelector .

Now in the template, the divs below the form should render with the RSS feed content.

Conclusion

We can create an RSS feed easily with Vue.js and JavaScript.

Categories
Vue

Add Charts to a Vue App with Highcharts-Vue — Line and Gantt Charts

Highcharts-Vue is an easy to use library that lets us add charts to our Vue apps.

In this article, we’ll look at how to add charts with the highcharts-vue library.

Line Chart

We can add a line chart by writing:

main.js

import Vue from "vue";
import App from "./App.vue";
import HighchartsVue from "highcharts-vue";

Vue.use(HighchartsVue);

new Vue({
  el: "#app",
  render: (h) => h(App)
});

App.vue

<template>
  <div>
    <highcharts class="hc" :options="chartOptions" ref="chart"></highcharts>
  </div>
</template>

<script>
export default {
  data() {
    return {
      chartOptions: {
        series: [
          {
            data: [1, 2, 3],
          },
        ],
      },
    };
  },
};
</script>

We register the HighchartsVue plugin and then use the highcharts component within App.vue .

The data for the y-axis is in the series.data property.

Gantt Chart

To create a Gantt chart, we write:

main.js

import Vue from "vue";
import App from "./App.vue";
import Highcharts from "highcharts";
import Gantt from "highcharts/modules/gantt";
import HighchartsVue from "highcharts-vue";

Gantt(Highcharts);
Vue.use(HighchartsVue);

new Vue({
  el: "#app",
  render: (h) => h(App)
});

App.vue

<template>
  <div>
    <highcharts
      :constructorType="'ganttChart'"
      class="hc"
      :options="chartOptions"
      ref="chart"
    ></highcharts>
  </div>
</template>

<script>
export default {
  data() {
    return {
      chartOptions: {
        title: {
          text: "Gantt Chart with Progress Indicators",
        },
        xAxis: {
          min: Date.UTC(2021, 10, 17),
          max: Date.UTC(2021, 10, 30),
        },

        series: [
          {
            name: "Project 1",
            data: [
              {
                name: "Start",
                start: Date.UTC(2021, 10, 18),
                end: Date.UTC(2021, 10, 25),
                completed: 0.25,
              },
              {
                name: "Test",
                start: Date.UTC(2021, 10, 27),
                end: Date.UTC(2021, 10, 29),
              },
              {
                name: "Develop",
                start: Date.UTC(2021, 10, 20),
                end: Date.UTC(2021, 10, 25),
                completed: {
                  amount: 0.12,
                  fill: "#fa0",
                },
              },
              {
                name: "Test Again",
                start: Date.UTC(2021, 10, 23),
                end: Date.UTC(2021, 10, 26),
              },
            ],
          },
        ],
      },
    };
  },
};
</script>

We call the Gantt function in main.js to let us add Gantt charts.

Then we can use the highcharts component to render the chart.

The title has the title.

xAxis has the x-axis value range.

series has the data.

name has the name for the bar.

start and end are the start and end dates.

completed has the progress value.

Conclusion

We can add a line and Gantt chart easily with highcharts-vue.

Categories
Vue

Add Charts to a Vue App with Highcharts-Vue

Highcharts-Vue is an easy to use library that lets us add charts to our Vue apps.

In this article, we’ll look at how to add charts with the highcharts-vue library.

Getting Started

We can install the highcharts-vue library by running:

npm i highcharts-vue highcharts

The highcharts library is required for the highcharts-vue library.

Then we can add a simple chart by writing:

main.js

import Vue from "vue";
import HighchartsVue from "highcharts-vue";
import App from "./App.vue";

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

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

App.vue

<template>
  <div id="app">
    <highcharts :options="chartOptions"></highcharts>
  </div>
</template>

<script>
import { Chart } from "highcharts-vue";

export default {
  name: "App",
  data() {
    return {
      chartOptions: {
        series: [
          {
            data: [1, 2, 3],
          },
        ],
      },
    };
  },
  components: {
    highcharts: Chart,
  },
};
</script>

We call Vue.use(HighchartsVue) to register the chart library.

Then we register the Chart component in App.vue by putting it in the comnponents property.

Then we add the highcharts component into the template with the options prop.

It’s set to the chartOptions reactive property which has the series.data property with the data.

Stock Chart

We can add a stock chart by passing in more props to the highcharts component.

To do this, we write:

main.js

import Vue from "vue";
import App from "./App.vue";
import Highcharts from "highcharts";
import Stock from "highcharts/modules/stock";
import HighchartsVue from "highcharts-vue";

Stock(Highcharts);
Vue.use(HighchartsVue);

new Vue({
  el: "#app",
  render: (h) => h(App)
});

App.vue

<template>
  <div>
    <highcharts
      :constructorType="'stockChart'"
      class="hc"
      :options="chartOptions"
      ref="chart"
    ></highcharts>
  </div>
</template>

<script>
export default {
  data() {
    return {
      chartOptions: {
        series: [
          {
            data: [1, 2, 3],
          },
        ],
      },
    };
  },
};
</script>

In main.js , we call the Stock function with Highcharts to let us add a stock chart.

Then in App.vue , we set the constructorType prop to 'stockChart’ and the options as we did before.

Map Chart

We can add a map chart easily with Highcharts-Vue.

We need to install the @highcharts/map-collection library to add a map chart.

To install it, we run:

npm i @highcharts/map-collection

Then, we write:

main.js

import Vue from "vue";
import App from "./App.vue";
import Highcharts from "highcharts";
import Maps from "highcharts/modules/map";
import HighchartsVue from "highcharts-vue";

Maps(Highcharts);
Vue.use(HighchartsVue);

new Vue({
  el: "#app",
  render: (h) => h(App)
});

App.vue

<template>
  <div>
    <highcharts
      :constructorType="'mapChart'"
      class="hc"
      :options="chartOptions"
      ref="chart"
    ></highcharts>
  </div>
</template>

<script>
import worldMap from "@highcharts/map-collection/custom/world.geo.json";

export default {
  data() {
    return {
      chartOptions: {
        chart: {
          map: worldMap,
        },
        title: {
          text: "Highmaps",
        },
        subtitle: {
          text: "<b>World map</b>",
        },
        mapNavigation: {
          enabled: true,
          buttonOptions: {
            alignTo: "spacingBox",
          },
        },
        colorAxis: {
          min: 0,
        },
        series: [
          {
            name: "Random data",
            states: {
              hover: {
                color: "#BADA55",
              },
            },
            dataLabels: {
              enabled: true,
              format: "{point.name}",
            },
            allAreas: false,
            data: [
              ["fo", 0],
              ["um", 1],
              ["us", 2],
              ["jp", 3],
              ["sc", 4],
              ["in", 5],
              ["fr", 6],
              ["fm", 7],
              ["cn", 8],
              ["pt", 9],
              ["sw", 10],
              ["sh", 11],
              ["br", 12],
              ["ki", 13],
              ["ph", 14],
              ["mx", 15],
              ["es", 16],
              ["bu", 17],
              ["mv", 18],
              ["sp", 19],
              ["gb", 20],
              ["gr", 21],
              ["as", 22],
              ["dk", 23],
              ["gl", 24],
              ["gu", 25],
              ["mp", 26],
              ["pr", 27],
              ["vi", 28],
              ["ca", 29],
              ["st", 30],
              ["cv", 31],
              ["dm", 32],
              ["nl", 33],
              ["jm", 34],
              ["ws", 35],
              ["om", 36],
              ["vc", 37],
              ["tr", 38],
              ["bd", 39],
              ["lc", 40],
              ["nr", 41],
              ["no", 42],
              ["kn", 43],
              ["bh", 44],
              ["to", 45],
              ["fi", 46],
              ["id", 47],
              ["mu", 48],
              ["se", 49],
              ["tt", 50],
              ["my", 51],
              ["pa", 52],
              ["pw", 53],
              ["tv", 54],
              ["mh", 55],
              ["cl", 56],
              ["th", 57],
              ["gd", 58],
              ["ee", 59],
              ["ag", 60],
              ["tw", 61],
              ["bb", 62],
              ["it", 63],
              ["mt", 64],
              ["vu", 65],
              ["sg", 66],
              ["cy", 67],
              ["lk", 68],
              ["km", 69],
              ["fj", 70],
              ["ru", 71],
              ["va", 72],
              ["sm", 73],
              ["kz", 74],
              ["az", 75],
              ["tj", 76],
              ["ls", 77],
              ["uz", 78],
              ["ma", 79],
              ["co", 80],
              ["tl", 81],
              ["tz", 82],
              ["ar", 83],
              ["sa", 84],
              ["pk", 85],
              ["ye", 86],
              ["ae", 87],
              ["ke", 88],
              ["pe", 89],
              ["do", 90],
              ["ht", 91],
              ["pg", 92],
              ["ao", 93],
              ["kh", 94],
              ["vn", 95],
              ["mz", 96],
              ["cr", 97],
              ["bj", 98],
              ["ng", 99],
              ["ir", 100],
              ["sv", 101],
              ["sl", 102],
              ["gw", 103],
              ["hr", 104],
              ["bz", 105],
              ["za", 106],
              ["cf", 107],
              ["sd", 108],
              ["cd", 109],
              ["kw", 110],
              ["de", 111],
              ["be", 112],
              ["ie", 113],
              ["kp", 114],
              ["kr", 115],
              ["gy", 116],
              ["hn", 117],
              ["mm", 118],
              ["ga", 119],
              ["gq", 120],
              ["ni", 121],
              ["lv", 122],
              ["ug", 123],
              ["mw", 124],
              ["am", 125],
              ["sx", 126],
              ["tm", 127],
              ["zm", 128],
              ["nc", 129],
              ["mr", 130],
              ["dz", 131],
              ["lt", 132],
              ["et", 133],
              ["er", 134],
              ["gh", 135],
              ["si", 136],
              ["gt", 137],
              ["ba", 138],
              ["jo", 139],
              ["sy", 140],
              ["mc", 141],
              ["al", 142],
              ["uy", 143],
              ["cnm", 144],
              ["mn", 145],
              ["rw", 146],
              ["so", 147],
              ["bo", 148],
              ["cm", 149],
              ["cg", 150],
              ["eh", 151],
              ["rs", 152],
              ["me", 153],
              ["tg", 154],
              ["la", 155],
              ["af", 156],
              ["ua", 157],
              ["sk", 158],
              ["jk", 159],
              ["bg", 160],
              ["qa", 161],
              ["li", 162],
              ["at", 163],
              ["sz", 164],
              ["hu", 165],
              ["ro", 166],
              ["ne", 167],
              ["lu", 168],
              ["ad", 169],
              ["ci", 170],
              ["lr", 171],
              ["bn", 172],
              ["iq", 173],
              ["ge", 174],
              ["gm", 175],
              ["ch", 176],
              ["td", 177],
              ["kv", 178],
              ["lb", 179],
              ["dj", 180],
              ["bi", 181],
              ["sr", 182],
              ["il", 183],
              ["ml", 184],
              ["sn", 185],
              ["gn", 186],
              ["zw", 187],
              ["pl", 188],
              ["mk", 189],
              ["py", 190],
              ["by", 191],
              ["cz", 192],
              ["bf", 193],
              ["na", 194],
              ["ly", 195],
              ["tn", 196],
              ["bt", 197],
              ["md", 198],
              ["ss", 199],
              ["bw", 200],
              ["bs", 201],
              ["nz", 202],
              ["cu", 203],
              ["ec", 204],
              ["au", 205],
              ["ve", 206],
              ["sb", 207],
              ["mg", 208],
              ["is", 209],
              ["eg", 210],
              ["kg", 211],
              ["np", 212],
            ],
          },
        ],
      },
    };
  },
};
</script>

In main.js , we call the Maps function to create the maps.

We import the worldMap component to let us add a map chart.

And we put all the options in the charrOptions object.

title has the main title.

subtitle has the subtitle.

mapNavihation lets us add navigation to the map.

colorAxis has the color axis options. min is the minimum value for the axis.

series has the data.

dataLabels lets us format the data labels.

Conclusion

We can create various kinds of charts with Highcharts-Vue.