Making a Chrome extension with Vue.js

Making a Chrome extension with Vue.js

In this article, we will make a Chrome extension that displays the weather from the OpenWeatherMap API. We will add a search to let users look up the current weather and forecast from the API and display it in the extension’s popup box.

The most popular web browsers, Chrome and Firefox support extensions. Extensions are small apps that you can add to your browser to get the functionality that is not included in your browser. This makes extending browser functionality very easy. All a user has to do is to add browser add-ons from the online stores like the Chrome Web Store or the Firefox Store to add browser add-ons.

Browser extensions are just normal HTML apps packages in a specific way. This means that we can use HTML, CSS, and JavaScript to build our own extensions.

Chrome and Firefox extensions follow the Web Extension API standard.

In this article, we will make a Chrome extension that displays the weather from the OpenWeatherMap API. We will add a search to let users look up the current weather and forecast from the API and display it in the extension’s popup box.

We will use Vue.js to build the browser extension. To begin building it, we start with creating the project with Vue CLI. Run npx @vue/cli create weather-app to create the project. In the wizard, select Babel and Vuex.

You can register for an API key here. Once you got an API key, create an .env file in the root folder and add VUE_APP_APIKEY as the key and the API key as the value.

Next, we use the vue-cli-plugin-browser-extension to add the files for writing and compiling the Chrome extension. The package settings and details are located at https://www.npmjs.com/package/vue-cli-plugin-browser-extension.

To add it to our project, we run vue add browser-extension to add the files needed to build the extension. The command will change the file structure of our project.

After that command is run, we have to remove some redundant files. We should remove App.vue and main.js from the src folder and leave the files with the same name in the popup folder alone. Then we run npm run serve to build the files as we modify the code.

Next, we have to install the Extension Reload to reload the extension as we are changing the files. Install it from https://chrome.google.com/webstore/detail/extensions-reloader/fimgfedafeadlieiabdeeaodndnlbhid to get hot reloading of our extension in Chrome.

Then we go to the chrome://extensions/ URL in Chrome and toggle on Developer Mode. We should see the Load unpacked button on the top left corner. Click that, and then select the dist folder in our project to load our extension into Chrome.

Next, we have to install some libraries that we will use. We need Axios for making HTTP requests, BootstrapVue for styling, and Vee-Validate for form validation. To install them, we run npm i axios bootstrap-vue vee-validate to install them.

With all the packages installed we can start writing our code. Create CurrentWeather.vue in the components folder and add:

<template>
  <div>
    <br />
    <b-list-group v-if="weather.main">
      <b-list-group-item>Current Temparature: {{weather.main.temp - 273.15}} C</b-list-group-item>
      <b-list-group-item>High: {{weather.main.temp_max - 273.15}} C</b-list-group-item>
      <b-list-group-item>Low: {{weather.main.temp_min - 273.15}} C</b-list-group-item>
      <b-list-group-item>Pressure: {{weather.main.pressure }}mb</b-list-group-item>
      <b-list-group-item>Humidity: {{weather.main.humidity }}%</b-list-group-item>
    </b-list-group>
  </div>
</template>

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

export default {
  name: "CurrentWeather",
  mounted() {},
  mixins: [requestsMixin],
  computed: {
    keyword() {
      return this.$store.state.keyword;
    }
  },
  data() {
    return {
      weather: {}
    };
  },
  watch: {
    async keyword(val) {
      const response = await this.searchWeather(val);
      this.weather = response.data;
    }
  }
};
</script>

<style scoped>
p {
  font-size: 20px;
}
</style>

This component displays the current weather from the OpenWeatherMap API as the keyword from the Vuex store is updated. We will create the Vuex store later. The this.searchWeather function is from the requestsMixin , which is a Vue mixin that we will create. The computed block gets the keyword from the store via this.$store.state.keyword and return the latest value.

Next, create Forecast.vue in the same folder and add:

<template>
  <div>
    <br />
    <b-list-group v-for="(l, i) of forecast.list" :key="i">
      <b-list-group-item>
        <b>Date: {{l.dt_txt}}</b>
      </b-list-group-item>
      <b-list-group-item>Temperature: {{l.main.temp - 273.15}} C</b-list-group-item>
      <b-list-group-item>High: {{l.main.temp_max - 273.15}} C</b-list-group-item>
      <b-list-group-item>Low: {{l.main.temp_min }}mb</b-list-group-item>
      <b-list-group-item>Pressure: {{l.main.pressure }}mb</b-list-group-item>
    </b-list-group>
  </div>
</template>

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

export default {
  name: "Forecast",
  mixins: [requestsMixin],
  computed: {
    keyword() {
      return this.$store.state.keyword;
    }
  },
  data() {
    return {
      forecast: []
    };
  },
  watch: {
    async keyword(val) {
      const response = await this.searchForecast(val);
      this.forecast = response.data;
    }
  }
};
</script>

<style scoped>
p {
  font-size: 20px;
}
</style>

It’s very similar to CurrentWeather.vue . The only difference is that we are getting the current weather instead of the weather forecast.

Next, we create a mixins folder in the src folder and add:

const APIURL = "http://api.openweathermap.org";
const axios = require("axios");

export const requestsMixin = {
  methods: {
    searchWeather(loc) {
      return axios.get(
        `${APIURL}/data/2.5/weather?q=${loc}&appid=${process.env.VUE_APP_APIKEY}`
      );
    },

    searchForecast(loc) {
      return axios.get(
        `${APIURL}/data/2.5/forecast?q=${loc}&appid=${process.env.VUE_APP_APIKEY}`
      );
    }
  }
};

These functions are for getting the current weather and the forecast respectively from the OpenWeatherMap API. process.env.VUE_APP_APIKEY is obtained from our .env file that we created earlier.

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

<template>
  <div>
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand href="#">Weather App</b-navbar-brand>
    </b-navbar>
    <div class="page">
      <ValidationObserver ref="observer" v-slot="{ invalid }">
        <b-form @submit.prevent="onSubmit" novalidate>
          <b-form-group label="Keyword" label-for="keyword">
            <ValidationProvider name="keyword" rules="required" v-slot="{ errors }">
              <b-form-input
                :state="errors.length == 0"
                v-model="form.keyword"
                type="text"
                required
                placeholder="Keyword"
                name="keyword"
              ></b-form-input>
              <b-form-invalid-feedback :state="errors.length == 0">Keyword is required</b-form-invalid-feedback>
            </ValidationProvider>
          </b-form-group>

          <b-button type="submit" variant="primary">Search</b-button>
        </b-form>
      </ValidationObserver>

<br />

      <b-tabs>
        <b-tab title="Current Weather">
          <CurrentWeather />
        </b-tab>
        <b-tab title="Forecast">
          <Forecast />
        </b-tab>
      </b-tabs>
    </div>
  </div>
</template>

<script>
import CurrentWeather from "@/components/CurrentWeather.vue";
import Forecast from "@/components/Forecast.vue";

export default {
  name: "App",
  components: { CurrentWeather, Forecast },
  data() {
    return {
      form: {}
    };
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }
      localStorage.setItem("keyword", this.form.keyword);
      this.$store.commit("setKeyword", this.form.keyword);
    }
  },
  beforeMount() {
    this.form = { keyword: localStorage.getItem("keyword") || "" };
  },
  mounted(){
    this.$store.commit("setKeyword", this.form.keyword);
  }
};
</script>

<style>
html {
  min-width: 500px;
}

.page {
  padding: 20px;
}
</style>

We add the BootstrapVue b-navbar here to add a top bar to show the extension’s name. Below that, we added the form for searching the weather info. Form validation is done by wrapping the form in the ValidationObserver component and wrapping the input in the ValidationProvider component. We provide the rule for validation in the rules prop of ValidationProvider . The rules will be added in main.js later.

The error messages are displayed in the b-form-invalid-feedback component. We get the errors from the scoped slot in ValidationProvider . It’s where we get the errors object from.

When the user submits the number, the onSubmit function is called. This is where the ValidationObserver becomes useful as it provides us with the this.$refs.observer.validate() function for form validation.

If isValid resolves to true , then we set the keyword in local storage, and also in the Vuex store by running this.$store.commit(“setKeyword”, this.form.keyword); .

In the beforeMount hook, we set the keyword so that it will be populated when the extension first loads if a keyword was set in local storage. In the mounted hook, we set the keyword in the Vuex store so that the tabs will get the keyword to trigger the search for the weather data.

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: {
    keyword: ""
  },
  mutations: {
    setKeyword(state, payload) {
      state.keyword = payload;
    }
  },
  actions: {}
});

to add the Vuex store that we referenced in the components. We have the keyword state for storing the search keyword in the store, and the setKeyword mutation function so that we can set the keyword in our components.

Next in popup/main.js , we replace the existing code with:

import Vue from 'vue'
import App from './App.vue'
import store from "../store";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import BootstrapVue from "bootstrap-vue";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required } from "vee-validate/dist/rules";

/* eslint-disable no-new */

extend("required", required);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.use(BootstrapVue);

Vue.config.productionTip = false;

new Vue({

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

We added the validation rules that we used in the previous files here, as well as include all the libraries we use in the app. We registered ValidationProvider and ValidationObserver by calling Vue.component so that we can use them in our components. The validation rules provided by Vee-Validate are included in the app so that they can be used by the templates by calling extend from Vee-Validate. We called Vue.use(BootstrapVue) to use BootstrapVue in our app.

Finally in index.html , we 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>Weather App</title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but vue-chrome-extension-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 replace the title.

After all the hard work, we get:

Thank you for reading !

Vuejs Vuex JavaScript Extension Programming

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Top 9 Great JavaScript Extensions for Visual Studio Code

Who want to code faster and easier? In this post, you'll see Top 9 Great JavaScript Extensions for Visual Studio Code to Speed Up Your Development

How to paginate data with VueJs and Vuex?

Easy VueJS and Vuex Pagination. In this article, we will be learning about pagination using VueJS and Vuex. We will create a simple example that you can use in your app.

Learning JavaScript: Development Environments for JavaScript Programming

One of the nice things about learning JavaScript these days is that there is a plethora of choices for writing and running JavaScript code. In this article, I’m going to describe a few of these environments and show you the environment I’ll be using in this series of articles.

Learning JavaScript: Data Types and Variables

To paraphrase the title of an old computer science textbook, “Algorithms + Data = Programs.” The first step in learning a programming language such as JavaScript is to learn what types of data the language can work with. The second step is to learn how to store that data in variables. In this article I’ll discuss the different types of data you can work with in a JavaScript program and how to create and use variables to store and manipulate that data.

[ Professor JavaScript ]: Introduction

Professor JavaScript is a JavaScript online learning courses YouTube Channel. Students can learn how to develop codes with JavaScript from basic to advanced levels through the online courses in this YouTube channel.