Using Vue.js To Create An Interactive Weather Dashboard With APIs
Using Vue.js To Create An Interactive Weather Dashboard With APIs
Souvik Sarkar
(This is a sponsored article.) In this tutorial, you will build a simple weather dashboard from scratch. It will be a client-end application that is neither a “Hello World” example, nor too intimidating in its size and complexity.
The entire project will be developed using tools from the Node.js + npm ecosystem. In particular, we will be heavily relying on the Dark Sky API for the data, Vue.js for all the heavy lifting, and FusionCharts for data visualization.
Prerequisites
We expect that you are familiar with the following:
- HTML5 and CSS3 (we will also be using the basic features provided by Bootstrap;
- JavaScript (especially ES6 way of using the language);
- Node.js and npm (the basics of the environment and package management is just fine).
Apart from the ones mentioned above, it would be great if you have familiarity with Vue.js, or any other similar JavaScript framework. We don’t expect you to know about FusionCharts — it’s so easy to use that you will learn it on the fly!
Expected Learnings
Your key learnings from this project will be:
- How to plan about implementing a good dashboard
- How to develop applications with Vue.js
- How to create data-driven applications
- How to visualize data using FusionCharts
In particular, each of the sections take you a step closer to the learning goals:
- An Introduction To The Weather Dashboard
This chapter gives you an overview of different aspects of the undertaking. - Create The Project
In this section, you learn about creating a project from scratch using the Vue command-line tool. - Customize The Default Project Structure
The default project scaffolding that you get in the previous section is not enough; here you learn the additional stuff needed for the project from a structural point of view. - Data Acquisition And Processing
This section is the meat of the project; all the critical code for acquiring and processing data from the API is showcased here. Expect to spend maximum time on this section. - Data Visualization With FusionCharts
Once we have all the data and other moving parts of the project stabilized, this section is dedicated towards visualizing the data using FusionCharts and a bit of CSS.
1. The Dashboard Workflow
Before we dive into the implementation, it is important to be clear about our plan. We break our plan into four distinct aspects:
Requirements
What are our requirements for this project? In other words, what are the things that we want to showcase through our Weather Dashboard? Keeping in mind that our intended audience are probably mere mortals with simple tastes, we would like to show them the following:
- Details of the location for which they want to see the weather, along with some primary information about the weather. Since there are no stringent requirements, we will figure out the boring details later. However, at this stage, it is important to note that we will have to provide the audience a search box, so that they can provide input for the location of their interest.
- Graphical information about the weather of their location of interest, such as:
- Temperature variation for the day of query
- Highlights of today’s weather:
- Wind Speed and Direction
- Visibility
- UV Index
Note: The data obtained from the API provides information regarding many other aspects of the weather. We choose not to use all of them for the sake of keeping the code to a minimum.
Structure
Based on the requirements, we can structure our dashboard as shown below:

Data
Our dashboard is as good as the data we get, because there will be no pretty visualizations without proper data. There are plenty of public APIs that provide weather data — some of them are free, and some are not. For our project, we will collect data from the Dark Sky API. However, we will not be able to poll the API endpoint from the client end directly. Don’t worry, we have a workaround that will be revealed just at the right time! Once we get the data for the searched location, we will do some data processing and formatting — you know, the type of technicalities that helps us pay the bills.
Visualization
Once we get clean and formatted data, we plug it in to FusionCharts. There are very few JavaScript libraries in the world as capable as FusionCharts. Out of the vast number of offerings from FusionCharts, we will use only a few — all written in JavaScript, but works seamlessly when integrated with the Vue wrapper for FusionCharts.
Armed with the bigger picture, let’s get our hands dirty — it’s time to make things concrete! In the next section, you will create the basic Vue project, on top of which we will build further.
2. Creating The Project
To create the project, execute the following steps:
- Install Node.js + npm
(If you have Node.js installed on your computer, skip this step.)
Node.js comes with npm bundled with it, so you don’t need to install npm separately. Depending on the operating system, download and install Node.js according to the instructions given here.Once installed, it’s probably a good idea to verify if the software is working correctly, and what are their versions. To test that, open the command-line/terminal and execute the following commands:
node --version npm --version
- Install packages with npm
Once you have npm up and running, execute the following command to install the basic packages necessary for our project.npm install -g vue@2 vue-cli@2
- Initialize project scaffolding with
vue-cli
Assuming that the previous step has gone all well, the next step is to use thevue-cli
— a command-line tool from Vue.js, to initialize the project. To do that, execute the following: - Initialize the scaffolding with webpack-simple template.
vue init webpack-simple vue_weather_dashboard
You will be asked a bunch of questions — accepting the defaults for all but the last question will be good enough for this project; answer
N
for the last one.(Large preview) Keep in mind that although
webpack-simple
is excellent for quick prototyping and light application like ours, it is not particularly suited for serious applications or production deployment. If you want to use any other template (although we would advise against it if you are a newbie), or would like to name your project something else, the syntax is:vue init [template-name] [project-name]
- Navigate to the directory created by vue-cli for the project.
cd vue_weather_dashboard
- Install all the packages mentioned in the
package.json
, which has been created by thevue-cli
tool for thewebpack-simple
template.npm install
- Start the development server and see your default Vue project working in the browser!
npm run dev
If you are new to Vue.js, take a moment to savor your latest achievement — you have created a small Vue application and its running at localhost:8080!

Brief Explanation Of The Default Project Structure
It’s time to take a look at the structure inside the directory vue_weather_dashboard
, so that you have an understanding of the basics before we start modifying it.
The structure looks something like this:
vue_weather_dashboard
|--- README.md
|--- node_modules/
| |--- ...
| |--- ...
| |--- [many npm packages we installed]
| |--- ...
| |--- ...
|--- package.json
|--- package-lock.json
|--- webpack.config.js
|--- index.html
|--- src
| |--- App.vue
| |--- assets
| | |--- logo.png
| |--- main.js
Although it might be tempting to skip getting familiar with the default files and directories, if you are new to Vue, we strongly recommend at least taking a look at the contents of the files. It can be a good educational session and trigger questions that you should pursue on your own, especially the following files:
package.json
, and just a glance at its cousinpackage-lock.json
webpack.config.js
index.html
src/main.js
src/App.vue
A brief explanation of each of the files and directories shown in the tree diagram are given below:
- README.md
No prize for guessing — it is primarily for humans to read and understand the steps necessary for creating the project scaffolding. - node_modules/
This is the directory where npm downloads the packages necessary for kickstarting the project. The information about the packages necessary are available in thepackage.json
file. - package.json
This file is created by the vue-cli tool based on the requirements of thewebpack-simple
template, and contains information about the npm packages (including with their versions and other details) that must be installed. Take a hard look at the content of this file — this is where you should visit and perhaps edit to add/delete packages necessary for the project, and then run npm install. Read more aboutpackage.json
here. - package-lock.json
This file is created by npm itself, and is primarily meant for keeping a log of things that npm downloaded and installed. - webpack.config.js
This a JavaScript file that contains the configuration of webpack — a tool that bundles different aspects of our project together (code, static assets, configuration, environments, mode of use, etc.), and minifies before serving it to the user. The benefit is that all things are tied together automatically, and the user experience enhances greatly because of the improvement in the application’s performance (pages are served quickly and loads faster on the browser). As you might encounter later, this is the file that needs to be inspected when something in the build system does not works the way it is intended to be. Also, when you want to deploy the application, this is one of the key files that needs to be edited (read more here). - index.html
This HTML file serves as the matrix (or you can say, template) where data and code is to be embedded dynamically (that’s what Vue primarily does), and then served to the user. - src/main.js
This JavaScript file contains code that primarily manages top/project level dependencies, and defines the topmost level Vue component. In short, it orchestrates the JavaScript for the entire project, and serves as the entry point of the application. Edit this file when you need to declare project-wide dependencies on certain node modules, or you want something to be changed about the topmost Vue component in the project. - src/App.vue
In the previous point, when we were talking about the “topmost Vue component”, we were essentially talking about this file. Each .vue file in the project is a component, and components are hierarchically related. At the start, we have only one.vue
file, i.e.App.vue
, as our only component. But shortly we will add more components to our project (primarily following the structure of the dashboard), and link them in accordance to our desired hierarchy, with App.vue being the ancestor of all. These.vue
files will contain code in a format that Vue wants us to write. Don’t worry, they are JavaScript code written maintaining a structure that can keep us sane and organized. You have been warned — by the end of this project, if you are new to Vue, you may get addicted to thetemplate — script — style
way of organizing code!
Now that we have created the foundation, it’s time to:
- Modify the templates and tweak the configuration files a bit, so that the project behaves just the way we want.
- Create new
.vue
files, and implement the dashboard structure with Vue code.
We will learn them in the next section, which is going to be a bit long and demands some attention. If you need caffeine or water, or want to discharge — now is the time!
3. Customizing The Default Project Structure
It’s time to tinker with the foundation that the scaffolded project has given us. Before you start, ensure that the development server provided by webpack
is running. The advantage of running this server continuously is that any changes you make in the source code — one you save it and refresh the web page — it gets immediately reflected on the browser.
If you want to start the development server, just execute the following command from the terminal (assuming your current directory is the project directory):
npm run dev
In the following sections, we will modify some of the existing files, and add some new files.
It will be followed by brief explanations of the content of those files, so that you have an idea of what those changes are meant to do.
Modify Existing Files
index.html
Our application is literally a single page application, because there is just one webpage that gets displayed on the browser. We will talk about this later, but first let’s just make our first change — altering the text within the <title>
tag.
With this small revision, the HTML file looks like the following:
<!DOCTYPE html>
<html lang="en"> <head> <meta charset="utf-8"> <!-- Modify the text of the title tag below --> <title>Vue Weather Dashboard</title> </head> <body> /dist/build.js </body>
</html>
Take a moment to refresh the webpage at localhost:8080
, and see the change reflected on the title bar of the tab on the browser — it should say “Vue Weather Dashboard”. However, this was just to demonstrate you the process of making changes and verifying if it’s working. We have more things to do!
This simple HTML page lacks many things that we want in our project, especially the following:
- Some meta information
- CDN links to Bootstrap (CSS framework)
- link to custom stylesheet (yet to be added in the project)
- Pointers to the Google Maps Geolocation API from
tag
After adding those things, the final index.html
has the following content:
Weather Dashboard </head> <body> /dist/build.js </body>
</html>
Save the file, and refresh the webpage. You might have noticed a slight bump while the page was getting loaded — it is primarily due to the fact that the page style is now being controlled by Bootstrap, and the style elements like fonts, spacing, etc. are different from the default we had earlier (if you are not sure, roll back to the default and see the difference).

Note: One important thing before we move on — the URL for the Google Maps API contains a key which is a property of FusionCharts. For now, you can use this key to build the project, as we don’t want you to get bogged down by these type of minute details (which can be distractions while you are new). However, we strongly urge you to generate and use your own Google Maps API key once you have made some progress and feel comfortable to pay attention to these tiny details.
package.json
At the time of writing this, we used certain versions of the npm packages for our project, and we know for sure that those things work together. However, by the time you are executing the project, it is very much possible that the latest stable versions of the packages that npm downloads for you are not the same as we used, and this might break the code (or do things that are beyond our control). Thus, it is very important to have the exact same package.json
file that was used to build this project, so that our code/explanations and the results you get are consistent.
The content of the package.json
file should be:
{ "name": "vue_weather_dashboard", "description": "A Vue.js project", "version": "1.0.0", "author": "FusionCharts", "license": "MIT", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" }, "dependencies": { "axios": "^0.18.0", "babel": "^6.23.0", "babel-cli": "^6.26.0", "babel-polyfill": "^6.26.0", "fusioncharts": "^3.13.3", "moment": "^2.22.2", "moment-timezone": "^0.5.21", "vue": "^2.5.11", "vue-fusioncharts": "^2.0.4" }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ], "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-env": "^1.6.0", "babel-preset-stage-3": "^6.24.1", "cross-env": "^5.0.5", "css-loader": "^0.28.7", "file-loader": "^1.1.4", "vue-loader": "^13.0.5", "vue-template-compiler": "^2.4.4", "webpack": "^3.6.0", "webpack-dev-server": "^2.9.1" }
}
We encourage you to go through the new package.json
, and figure out what are functions of different objects in the json. You may prefer changing the value of the “author
” key to your name. Also, the packages mentioned in the dependencies will reveal themselves at the right time in the code. For the time being, it’s sufficient to know that:
babel
-related packages are for properly handling the ES6 style code by the browser;axios
deals with Promise-based HTTP requests;moment
and moment-timezone are for date/time manipulation;fusioncharts
andvue-fusioncharts
are responsible for rendering charts:vue
, for obvious reasons.
webpack.config.js
As with package.json
, we suggest you to maintain a webpack.config.js
file that is consistent with the one we used for building the project. However, before making any changes, we recommend you to carefully compare the default code in the webpack.config.js
, and the code we have provided below. You will notice quite a few differences — google them and have a basic idea of what they mean. Since explaining webpack configurations in depth is out of the scope of this article, you are on your own in this regard.
The customized webpack.config.js
file is as follows:
var path = require('path')
var webpack = require('webpack') module.exports = { entry: ['babel-polyfill', './src/main.js'], output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'build.js' }, module: { rules: [ { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ], }, { test: /\.vue$/, loader: 'vue-loader', options: { loaders: { } // other vue-loader options go here } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { name: '[name].[ext]?[hash]' } } ] }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' }, extensions: ['*', '.js', '.vue', '.json'] }, devServer: { historyApiFallback: true, noInfo: true, overlay: true, host: '0.0.0.0', port: 8080 }, performance: { hints: false }, devtool: '#eval-source-map'
} if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' // http://vue-loader.vuejs.org/en/workflow/production.html module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ])
}
With changes made to the project’s webpack.config.js
, it’s imperative that you stop the development server which is running (Ctrl + C), and restart it with the following command executed from the project’s directory after installing all the packages mentioned in the package.json
file:
npm install npm run dev
With this, the ordeal of tweaking the configurations and ensuring that the right packages are in place ends. However, this also marks the journey of modifying and writing code, which is a bit long but also very rewarding!
src/main.js
This file is the key to top-level orchestration of the project — it is here that we define:
- What the top level dependencies are (where to get the most important npm packages necessary);
- How to resolve the dependencies, along with instructions to Vue on using plugins/wrappers, if any;
- A Vue instance that manages the topmost component in the project:
src/App.vue
(the nodal.vue
file).
In line with our goals for the src/main.js
file, the code should be:
// Import the dependencies and necessary modules
import Vue from 'vue';
import App from './App.vue';
import FusionCharts from 'fusioncharts';
import Charts from 'fusioncharts/fusioncharts.charts';
import Widgets from 'fusioncharts/fusioncharts.widgets';
import PowerCharts from 'fusioncharts/fusioncharts.powercharts';
import FusionTheme from 'fusioncharts/themes/fusioncharts.theme.fusion';
import VueFusionCharts from 'vue-fusioncharts'; // Resolve the dependencies
Charts(FusionCharts);
PowerCharts(FusionCharts);
Widgets(FusionCharts);
FusionTheme(FusionCharts); // Globally register the components for project-wide use
Vue.use(VueFusionCharts, FusionCharts); // Instantiate the Vue instance that controls the application
new Vue({ el: '#app', render: h => h(App)
})
src/App.vue
This is one of the most important files in the entire project, and represents the topmost component in the hierarchy — the entire application itself, as a whole. For our project, this component will do all the heavy lifting, which we will explore later. For now, we want to get rid of the default boilerplate, and put something of our own.
If you are new to Vue’s way of organizing code, it would be better to get an idea of the general structure within the .vue
files. The .vue
files comprises of three sections:
- Template
This is where the HTML template for the page is defined. Apart from the static HTML, this section also contains Vue’s way of embedding dynamic content, using the double curly braces{{ }}
. - Script
JavaScript rules this section, and is responsible for generating dynamic content that goes and sits within the HTML template at appropriate places. This section is primarily an object that is exported, and consists of:- Data
This is a function itself, and usually it returns some desired data encapsulated within a nice data structure. - Methods
An object that consists of one or more functions/methods, each of which usually manipulates data in some way or the other, and also controls the dynamic content of the HTML template. - Computed
Much like the method object discussed above with one important distinction — while all the functions within the method object are executed whenever any one of them is called, the functions within the computed object behaves much more sensibly, and executes if and only if it has been called.
- Data
- Style
This section is for CSS styling that applies to the HTML of the page (written within template) — put the good old CSS here to make your pages beautiful!
Keeping the above paradigm in mind, let’s minimally customize the code in App.vue
:
<template> This component’s code is in {{ filename }}
</template>
export default { data() { return { filename: 'App.vue' } }, methods: { }, computed: { },
}
<style> </style>
Remember that the above code snippet is simply for testing out that App.vue
is working with our own code in it. It will later go on through a lot of changes, but first save the file and refresh the page on the browser.

At this point, it’s probably a good idea to get some help in tooling. Check out the Vue devtools for Chrome, and if you don’t have much problems in using Google Chrome as your default browser for development, install the tool and play around with it a bit. It will come in extremely handy for further development and debugging, when things becomes more complicated.
Additional Directories And Files
The next step would be to add additional files, so that the structure of our project becomes complete. We would add the following directories and files:
src/css/
—style.css
src/assets/
—calendar.svg
—vlocation.svg
—search.svg
—winddirection.svg
—windspeed.svg
src/components/
—Content.vue
—Highlights.vue
—TempVarChart.vue
—UVIndex.vue
—Visibility.vue
—WindStatus.vue
Note: Save the hyperlinked .svg
files in your project.
Create the directories and files mentioned above. The final project structure should like look (remember to delete folders and files from the default structure that are now unnecessary):
vue_weather_dashboard/
|--- README.md
|--- node_modules/
| |--- ...
| |--- ...
| |--- [many npm packages we installed]
| |--- ...
| |--- ...
|--- package.json
|--- package-lock.json
|--- webpack.config.js
|--- index.html
|--- src/
| |--- App.vue
| |--- css/
| | |--- style.css | |--- assets/
| | |--- calendar.svg
| | |--- location.svg
| | |--- location.svg
| | |--- winddirection.svg
| | |--- windspeed.svg
| |--- main.js
| |--- components/
| | |--- Content.vue
| | |--- Highlights.vue
| | |--- TempVarChart.vue
| | |--- UVIndex.vue
| | |--- Visibility.vue
| | |--- WindStatus.vue
There might be some other files, like .babelrc
, .gitignore
, .editorconfig
, etc. in the project’s root folder. You may ignore them safely for now.
In the following section, we will add minimal content to the newly added files, and test whether they are properly working.
src/css/style.css
Although it will not be of much use immediately, copy the following code to the file:
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500"); :root { font-size: 62.5%;
} body { font-family: Roboto; font-weight: 400; width: 100%; margin: 0; font-size: 1.6rem;
} #sidebar { position: relative; display: flex; flex-direction: column; background-image: linear-gradient(-180deg, #80b6db 0%, #7da7e2 100%);
} #search { text-align: center; height: 20vh; position: relative;
} #location-input { height: 42px; width: 100%; opacity: 1; border: 0; border-radius: 2px; background-color: rgba(255, 255, 255, 0.2); margin-top: 16px; padding-left: 16px; color: #ffffff; font-size: 1.8rem; line-height: 21px;
} #location-input:focus { outline: none;
} ::placeholder { color: #FFFFFF; opacity: 0.6;
} #current-weather { color: #ffffff; font-size: 8rem; line-height: 106px; position: relative;
} #current-weather>span { color: #ffffff; font-size: 3.6rem; line-height: 42px; vertical-align: super; opacity: 0.8; top: 15px; position: absolute;
} #weather-desc { font-size: 2.0rem; color: #ffffff; font-weight: 500; line-height: 24px;
} #possibility { color: #ffffff; font-size: 16px; font-weight: 500; line-height: 19px;
} #max-detail,
#min-detail { color: #ffffff; font-size: 2.0rem; font-weight: 500; line-height: 24px;
} #max-detail>i,
#min-detail>i { font-style: normal; height: 13.27px; width: 16.5px; opacity: 0.4;
} #max-detail>span,
#min-detail>span { color: #ffffff; font-family: Roboto; font-size: 1.2rem; line-height: 10px; vertical-align: super;
} #max-summary,
#min-summary { opacity: 0.9; color: #ffffff; font-size: 1.4rem; line-height: 16px; margin-top: 2px; opacity: 0.7;
} #search-btn { position: absolute; right: 0; top: 16px; padding: 2px; z-index: 999; height: 42px; width: 45px; background-color: rgba(255, 255, 255, 0.2); border: none;
} #dashboard-content { text-align: center; height: 100vh;
} #date-desc,
#location-desc { color: #ffffff; font-size: 1.6rem; font-weight: 500; line-height: 19px; margin-bottom: 15px;
} #date-desc>img { top: -3px; position: relative; margin-right: 10px;
} #location-desc>img { top: -3px; position: relative; margin-left: 5px; margin-right: 15px;
} #location-detail { opacity: 0.7; color: #ffffff; font-size: 1.4rem; line-height: 20px; margin-left: 35px;
} .centered { position: fixed; top: 45%; left: 50%; transform: translate(-50%, -50%);
} .max-desc { width: 80px; float: left; margin-right: 28px;
} .temp-max-min { margin-top: 40px
} #dashboard-content { background-color: #F7F7F7;
} .custom-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 20px !important;
} .custom-content-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 0px !important;
} .header-card { height: 50vh;
} .content-card { height: 43vh;
} .card-divider { margin-top: 0;
} .content-header { color: #8786A4; font-size: 1.4rem; line-height: 16px; font-weight: 500; padding: 15px 10px 5px 15px;
} .highlights-item { min-height: 37vh; max-height: 38vh; background-color: #FFFFFF;
} .card-heading { color: rgb(33, 34, 68); font-size: 1.8rem; font-weight: 500; line-height: 21px; text-align: center;
} .card-sub-heading { color: #73748C; font-size: 1.6rem; line-height: 19px;
} .card-value { color: #000000; font-size: 1.8rem; line-height: 21px;
} span text { font-weight: 500 !important;
} hr { padding-top: 1.5px; padding-bottom: 1px; margin-bottom: 0; margin-top: 0; line-height: 0.5px;
} @media only screen and (min-width: 768px) { #sidebar { height: 100vh; } #info { position: fixed; bottom: 50px; width: 100%; padding-left: 15px; } .wrapper-right { margin-top: 80px; }
} @media only screen and (min-width:1440px) { #sidebar { width: 350px; max-width: 350px; flex: auto; } #dashboard-content { width: calc(100% — 350px); max-width: calc(100% — 350px); flex: auto; }
}
src/assets/
In this directory, download and save the .svg
files mentioned below:
src/components/Content.vue
This is what we call a dumb component — a placeholder, that is there just to maintain the hierarchy, and essentially passes on data to its child components.
Remember that there is no technical bar for writing all our code in the App.vue
file, but we take the approach of splitting up the code by nesting the components for two reasons:
- To write clean code, which aids readability and maintainability;
- To replicate the same structure that we will see on screen, i.e., the hierarchy.
Before we nest the component defined in Content.vue
within the root component App.vue
, let’s write some toy (but educational) code for Content.vue
:
<template> This child components of Content.vue are:
- {{ child }}
</template>
export default { data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { },
}
<style> </style>
In the code, carefully observe and understand the following:
- Within the
tag (where we obviously write some JavaScript code), we define an object that is exported (made available to other files) by default. This object contains a function
data()
, that returns an array object calledchildComponents
, with its elements being names of the component files that should be nested further. - Within the
tag (where we write some HTML template), the thing of interest is the
.- Within the unordered list, each list item should be names of the intended child components, as defined in the array object
childComponents
. Moreover, the list should automatically extend till the last element of the array. Seems like we should write afor
-loop, isn’t it? We do that by using thev-for
directive provided by Vue.js. Thev-for
directive:- Acts as an attribute of the
tag, iterates through the array, renders the names of the child components where the iterator is mentioned within the
{{ }}
brackets (where we write the text for the list items).
- Acts as an attribute of the
- Within the unordered list, each list item should be names of the intended child components, as defined in the array object
The code and the explanation above forms the basis of your subsequent understanding of how the script and the template are interrelated, and how we can use the directives provided by Vue.js.
We have learnt quite a lot, but even after all these, we have one thing left to learn about seamlessly connecting components in hierarchy — passing data down from the parent component to its children. For now, we need to learn how to pass some data from src/App.vue
to src/components/Content.vue
, so that we can use the same techniques for the rest of the component nesting in this project.
Data trickling down from the parent to the child components might sound simple, but the devil is in the details! As briefly explained below, there are multiple steps involved in making it work:
- Defining and the data
For now, we want some static data to play with — an object containing hard-coded values about different aspects of weather will just be fine! We create an object calledweather_data
and return it from thedata()
function ofApp.vue
. Theweather_data
object is given in the snippet below:
weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "N-E", }, visibility: "12 km", }, },
- Passing the data from the parent
To pass the data, we need a destination where we want to send the data! In this case, the destination is theContent.vue
component, and the way to implement it is to:- Assign the
weather_data
object to a custom attribute of thetag
- Bind the attribute with the data using the
v-bind
: directive provided by Vue.js, which makes the attribute value dynamic (responsive to changes made in the original data).
- Assign the
Defining and passing the data is handled at the source side of the handshake, which in our case is the App.vue
file.
The code for the App.vue
file, at its current status, is given below:
This component’s code is in {{ filename }}
import Content from './components/Content.vue' export default { name: 'app', components: { 'Content': Content }, data () { return { filename: 'App.vue', weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "N-E", }, visibility: "12 km", }, }, } }, methods: { }, computed: { },
}
<style> </style>

With the data defined and passed from the source (parent component), it is now the child’s responsibility to receive the data and render it appropriately, as explained in the next two steps.
- Receiving the data by the child
The child component, in this caseContent.vue
, must receive theweather_data
object send to it by the parent componentApp.vue
. Vue.js provides a mechanism to do so — all you need is an array object calledprops
, defined in the default object exported byContent.vue
. Each element of the arrayprops
is a name of the data objects it wants to receive from its parent. For now, the only data object that it is supposed to receive isweather_data
from App.vue. Thus, theprops
array looks like:
<template> // HTML template code here
</template>
export default { props: ["weather_data"], data () { return { // data here } },
}
<style> // component specific CSS here
</style>
- Rendering the data in the page
Now that we have ensured receiving the data, the last task we need to complete is to render the data. For this example, we will directly dump the received data on the web page, just to illustrate the technique. However, in real applications (like the one we are about to build), data normally goes through lots of processing, and only the relevant parts of it are displayed in ways that suits the purpose. For example, in this project we will eventually get raw data from the weather API, clean and format it, feed the data to the data structures necessary for the charts, and then visualize it. Anyway, to display the raw data dump, we will just use the{{ }}
brackets that Vue understands, as shown in the snippet below:
<template> // other template code here {{ weather_data }}
</template>
It’s now time to assimilate all the bits and pieces. The code for Content.vue
— at its current status — is given below:
<template> This child components of Content.vue are:
- {{ child }}
{{ weather_data }}
</template>
export default { props: ["weather_data"], data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { },
}
<style>
#pagecontent { border: 1px solid black; padding: 2px;
}
</style>

After making the changes discussed above, refresh the webpage on the browser and see how it looks. Take a moment to appreciate the complexity that Vue handles — if you modify the weather_data
object in App.vue
, it gets silently conveyed to Content.vue
, and eventually to the browser displaying the webpage! Try by changing the value for the key location.
Although we have learned about props and data binding using static data, we will be using dynamic data collected using web APIs in the application, and will change the code accordingly.
Summary
Before we move on to the rest of the .vue
files, let’s summarize what we have learnt while we wrote the code for App.vue
and components/Content.vue
:
- The
App.vue
file is what we call the root component — the one that sits at the top of the component hierarchy. The rest of the.vue
files represents components that are its direct child, grandchild, and so on. - The
Content.vue
file is a dummy component — its responsibility is to pass on the data to levels below and maintain the structural hierarchy, so that our code remains consistent with the philosophy “*what we see is what we implement*”. - The parent-child relationship of component does not happen out of thin air — you must register a component (either globally or locally, depending on the intended usage of the component), and then nest it using custom HTML tags (whose spellings are the exact same as that of the names with which the components has been registered).
- Once registered and nested, data is passed on from parent to child components, and the flow is never reverse (bad things will happen if the project architecture allows backflow). The parent component is the relative source of the data, and it passes down relevant data to its children using the
v-bind
directive for the attributes of the custom HTML elements. The child receives the data intended for it using props, and then decides on its own what to do with the data.
For the rest of the components, we will not indulge in detailed explanation — we will just write the code based on the learnings from the above summary. The code will be self-evident, and if you get confused about the hierarchy, refer to the diagram below:

The diagram says that TempVarChart.vue
and Highlights.vue
are the direct child of Content.vue
. Thus, it might be a good idea to prepare Content.vue
for sending data to those components, which we do using the code below:
<template> This child components of Content.vue are:
- {{ child }}
{{ weather_data }}
</template>
import TempVarChart from './TempVarChart.vue'
import Highlights from './Highlights.vue' export default { props: ["weather_data"], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights }, data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'], tempVar: this.weather_data.temperature, highlights: this.weather_data.highlights, } }, methods: { }, computed: { },
}
<style> </style>
Once you save this code, you will get errors — don’t worry, it is expected. It will be fixed once you have the rest of the component files ready. If it bothers you not to be able to see the output, comment out the lines containing the custom element tags <temp-var-chart>
and <today-highlights>
.
For this section, this is the final code of Content.vue
. For the rest of this section, we will reference to this code, and not the previous ones that we wrote for learning.
src/components/TempVarChart.vue
With its parent component Content.vue
passing on the data, TempVarChart.vue
must be set up to receive and render the data, as shown in the code below:
<template> Temperature Information:
{{ tempVar }}
</template> export default { props: ["tempVar"], data () { return { } }, methods: { }, computed: { },
}
<style> </style>
src/components/Highlights.vue
This component will also receive data from App.vue
— its parent component. After that, it should be linked with its child components, and relevant data should be passed on to them.
Let’s first see the code for receiving data from the parent:
<template> Weather Highlights:
{{ highlights }}
</template> export default { props: ["highlights"], data () { return { } }, methods: { }, computed: { },
}
<style> </style>
At this point, the web page looks like the image below:

Now we need to modify the code of Highlights.vue
to register and nest its child components, followed by passing the data to children. The code for it is as follows:
<template> Weather Highlights:
{{ highlights }}
</template>
import UVIndex from './UVIndex.vue';
import Visibility from './Visibility.vue';
import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { },
}
<style> </style>
Once you save the code and see the web page, you are expected to see errors in the Developer Console tool provided by the browser; they appear because although Highlights.vue
is sending data, nobody is receiving them. We are yet to write the code for the children of Highlights.vue
.
Observe that we have not done much of the data processing, i.e, we have not extracted the individual factors of weather data that goes under the Highlights section of the dashboard. We could have done that in the data()
function, but we preferred to keep Highlights.vue
a dumb component that just passes on the entire data dump it receives to each of the children, who then own their own extracts what is necessary for them. However, we encourage you to try out extracting data in the Highlights.vue
, and send relevant data down to each child component — it’s a good practice exercise nonetheless!
src/components/UVIndex.vue
The code for this component receives the data dump of highlights from Highlights.vue
, extracts the data for UV Index, and renders it on the page.
<template> UV Index: {{ uvindex }}
</template> export default { props: ["highlights"], data () { return { uvindex: this.highlights.uvindex } }, methods: { }, computed: { },
}
<style> </style>
src/components/Visibility.vue
The code for this component receives the data dump of highlights from Highlights.vue
, extracts the data for Visibility, and renders it on the page.
<template> Visibility: {{ visibility }}
</template> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility, } }, methods: { }, computed: { },
}
<style> </style>
src/components/WindStatus.vue
The code for this component receives the data dump of highlights from Highlights.vue
, extracts the data for Wind Status (speed and direction), and renders it on the page.
<template> Wind Status:
Speed — {{ speed }}; Direction — {{ direction }}
</template> export default { props: ["highlights"], data () { return { speed: this.highlights.windstatus.speed, direction: this.highlights.windstatus.direction } }, methods: { }, computed: { },
}
<style> </style>
After adding the code for all the components, take a look at the web page on the browser.

Not to dishearten, but all these toiling was just to link the components in hierarchy, and test out whether data flow is happening between them or not! In the next section, we will throw away most of the code we have written so far, and add a lot more pertaining to the actual project. However, we will certainly retain the structure and nesting of the components; the learnings from this section will allow us to build a decent dashboard with Vue.js.
4. Data Acquisition And Processing
Remember the weather_data
object in App.vue
? It had some hard-coded data that we used to test whether all the components are working correctly, and also to help you learn some basic aspects of Vue application without getting bogged down in the details of real-world data. However, it’s now time that we shed our shell, and step out into the real world, where data from the API will dominate most of our code.
Preparing Child Components To Receive And Process Real Data
In this section, you will get code dump for all the components except App.vue
. The code will handle receiving real data from App.vue
(unlike the code we wrote in the previous section to receive and render dummy data).
We strongly encourage to read the code of each component carefully, so that you form an idea of what data each of those components are expecting, and will eventually use in visualization.
Some of the code, and the overall structure, will be similar to the ones you have seen in the previous structure — so you will not face something drastically different. However, the devil is in the details! So examine the code carefully, and when you have understood them reasonably well, copy the code to the respective component files in your project.
Note: All the components in this section are in the src/components/
directory. So each time, the path will not be mentioned — only the .vue
file name will be mentioned to identify the component.
Content.vue
<template>
</template>
import TempVarChart from './TempVarChart.vue';
import Highlights from './Highlights.vue'; export default { props: ['highlights', 'tempVar'], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights },
}
The following changes have been made from the previous code:
- In the
<template>
, text and data within{{ }}
has been removed, since we are now just receiving data and passing down to the children, with no rendering specific this component. - In the
export default {}
:- The
props
have been changed to match the data objects that will be send by the parent:App.vue
. The reason for changing the props is thatApp.vue
itself will display some of the data it acquires from the weather API and other online resources, based on the search query of the user, and pass on the rest of the data. In the dummy code we wrote earlier,App.vue
was passing on the entire dummy data dump, without any discrimination, and the props ofContent.vue
was set up accordingly. - The data() function now returns nothing, as we are not doing any data manipulation in this component.
- The
TempVarChart.vue
This component is supposed to receive detailed temperature projections for the rest of the current day, and eventually display them using FusionCharts. But for the time being, we will display them only as text on the webpage.
<template> {{ tempVar.tempToday }}
</template>
export default { props: ["tempVar"], components: {}, data() { return { }; }, methods: { }, };
<style> </style>
Highlights.vue
<template>
</template>
import UVIndex from './UVIndex.vue';
import Visibility from './Visibility.vue';
import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { },
}
<style> </style>
The changes made from the previous code are:
- In the
<template>
, the text and the data within{{ }}
has been removed, because this is a dumb component, just likeContent.vue
, whose only job is to pass on the data to children while maintaining the structural hierarchy. Remember that dumb components likeHighlights.vue
andContent.vue
exists to maintain the parity between the visual structure of the dashboard, and the code we write.
UVIndex.vue
The changes made to the previous code are as follows:
- In the
<template>
and<style>
, thediv id
has been changed touvIndex
, which is more readable. - In the
export default {}
, thedata()
function now returns a string objectuvIndex
, whose value is extracted from the highlights object received by the component usingprops
. ThisuvIndex
is now temporarily used to display the value as text within the<template>
. Later on, we will plug in this value to the data structure suitable for rendering a chart.
Visibility.vue
<template> Visibility: {{ visibility }}
</template> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility.toString() } }, methods: { }, computed: { },
}
<style> </style>
The only change made in this file (with respect to its previous code) is that the definition of the visibility
object returned by the data()
function now contains toString()
at its end, since the value received from the parent will be a floating point number, which needs to be converted into string.
WindStatus.vue
<template> Wind Speed — {{ windSpeed }}
Wind Direction — {{ derivedWindDirection }}, or {{ windDirection }} degree clockwise with respect to true N as 0 degree.
</template> export default { props: ["highlights"], data () { return { windSpeed: this.highlights.windStatus.windSpeed, derivedWindDirection: this.highlights.windStatus.derivedWindDirection, windDirection: this.highlights.windStatus.windDirection } }, methods: { }, computed: { },
}
<style> </style>
The changes made to the previous code are as follows:
- Throughout the file,
windstatus
has been renamed aswindStatus
, to promote readability and also to be in sync with the the highlights object thatApp.vue
provides with actual data. - Similar naming changes have been made for the speed and direction — the new ones are
windSpeed
andwindDirection
. - A new object
derivedWindDirection
has come into play (also provided byApp.vue
in the highlights bundle).
For now, the received data is rendered as text; later, it will be plugged in to the data structure necessary for visualization.
Testing With Dummy Data
Resorting to dummy data repeatedly might be a bit frustrating for you, but there are some good reasons behind it:
- We have made a lot of changes to the code of each component, and it’s a good idea to test whether those changes are breaking the code. In other words, we should check that whether the data flow is intact, now that we are about to move to more complex parts of the project.
- The real data from the online weather API will need lot of massaging, and it might be overwhelming for you to juggle between the code for data acquisition and processing, and the code for smooth data flow down the components. The idea is to keep the quantum of complexity under control, so that we have a better understanding of the errors we might face.
In this section, what we do is essentially hardcode some json data in the App.vue
, which will obviously be replaced with live data in the near future. There are a lot of similarity between the dummy json structure, and the json structure we will use for the actual data. So it also provides you a rough idea of what to expect from the real data, once we encounter it.
However, we admit that this is far from the ideal approach one might adopt while building such a project from scratch. In the real world, you will often start with the real data source, play around with it a bit to understand what can and should be done to tame it, and then think about the appropriate json data structure to capture the relevant information. We intentionally shielded you from all those dirty work, since it takes you farther from the objective — learning how to use Vue.js and FusionCharts to build a dashboard.
Let’s now jump into the new code for App.vue:
<template>
</template>
import Content from './components/Content.vue' export default { name: 'app', components: { 'dashboard-content': Content }, data () { return { tempVar: { tempToday: [ {hour: '11.00 AM', temp: '35'}, {hour: '12.00 PM', temp: '36'}, {hour: '1.00 PM', temp: '37'}, {hour: '2.00 PM', temp: '38'}, {hour: '3.00 PM', temp: '36'}, {hour: '4.00 PM', temp: '35'}, ], }, highlights: { uvIndex: 4, visibility: 10, windStatus: { windSpeed: '30 km/h', windDirection: '30', derivedWindDirection: 'NNE', }, }, } }, methods: { }, computed: { },
}
<style> </style>
The changes made to the code with respect to its previous version are as follows:
- The name of the child component has been changed to dashboard-content, and accordingly the custom HTML element in the
<template>
has been revised. Note that now we have two attributes —highlights
andtempVar
— instead of a single attribute that we used earlier with the custom element. Accordingly, the data associated with those attributes have also changed. What’s interesting here is that we can use thev-bind:
directive, or its shorthand:
(as we have done here), with multiple attributes of a custom HTML element! - The
data()
function now returns thefilename
object (that existed earlier), along with two new objects (instead of the oldweather_data
):tempVar
andhighlights
. The structure of the json is appropriate for the code we have written in the child components, so that they can extract the data pieces they need from the dumps. The structures are quite self-explanatory, and you can expect them to be quite similar when we deal with live data. However, the significant change that you will encounter is the absence of hardcoding (obvious, isn’t it) — we will leave the values blank as the default state, and write code to dynamically update them based on the values we will receive from the weather API.
You have written a lot of code in this section, without seeing the actual output. Before you proceed further, take a look at the browser (restart the server with npm run dev
, if necessary), and bask in the glory of your achievement. The web page that you should see at this point looks like the image below:

Code For Data Acquisition And Processing
This section is going to be the meat of the project, with all the code to be written in App.vue
for the following:
- Location input from the user — an input box and a call-to-action button is sufficient;
- Utility functions for various tasks; these functions will be called later in various parts of the component code;
- Getting detailed geolocation data from Google Maps API for JavaScript;
- Getting detailed weather data from the Dark Sky API;
- Formatting and processing the geolocation and weather data, which will be passed on to the child components.
The subsections that follows illustrates how we can implement the tasks laid out for us in the above points. With some exceptions, most of them will follow the sequence.
Input From The User
It’s quite obvious that the action starts when the user provides the name of the place for which the weather data needs to be displayed. For this to happen, we need to implement the following:
- An input box for entering the location;
- A submit button that tells our application that the user has entered the location and it’s time to do the rest. We will also implement the behavior when processing starts upon hitting Enter.
The code we show below will be restricted to the HTML template part of App.vue
. We will just mention the name of the method associated with the click events, and define them later in the methods object of the in App.vue.
Placing the above snippet in the right place is trivial — we leave it to you. However, the interesting parts of the snippet are:
@keyup.enter="organizeAllDetails"
@click="organizeAllDetails"
As you know from the earlier sections, @
is Vue’s shorthand for the directive v-on
:, which is associated with some event. The new thing is “organizeAllDetails
” — it’s nothing but the method that will fire once the events (pressing Enter or clicking the button) happens. We are yet to define method, and the puzzle will be complete by the end of this section.
Text Information Display Controlled By App.vue
Once the user input triggers the action and lots of data is acquired from the APIs, we encounter the inevitable question — “What to do with all these data?”. Obviously some data massaging is required, but that does not answer our question fully! We need to decide what’s the end use of the data, or more directly, which are the entities that receives different chunks of the acquired and processed data?
The child components of App.vue
, based on their hierarchy and purpose, are the frontline contenders for the bulk of the data. However, we will also have some data that does not belong to any of those child components, yet are quite informative and makes the dashboard complete. We can make good use of them if we display them as text information directly controlled by App.vue
, while the rest of the data are passed on to the child for getting displayed as pretty charts ultimately.
With this context in mind, let’s focus on the code for setting the stage of using text data. It’s simple HTML template at this point, on which the data will eventually come and sit.
{{ currentWeather.temp }} °C {{ currentWeather.summary }} ▲ {{ currentWeather.todayHighLow.todayTempHigh }} °C at {{ currentWeather.todayHighLow.todayTempHighTime }} ▼ {{ currentWeather.todayHighLow.todayTempLow }} °C at {{ currentWeather.todayHighLow.todayTempLowTime }}
{{ currentWeather.time }}
{{ currentWeather.full_location }} Lat: {{ currentWeather.formatted_lat }}
Long: {{ currentWeather.formatted_long }}
In the above snippet, you should understand the following:
- The stuff inside
{{ }}
— they are Vue’s way of inserting dynamic data in the HTML template, before it renders in the browser. You have encountered them before, and there is nothing new or surprising. Just keep in mind that these data objects stems from thedata()
method in theexport default()
object ofApp.vue
. They have default values that we will set according to our requirements, and then write certain methods to populate the objects with real API data.
Don’t worry for not seeing the changes on the browser — the data is not defined yet, and it’s natural for Vue to not render things that it does not know. However, once the data is set (and for now, you can even check by hard-coding the data), the text data will be controlled by App.vue
.
The data()
Method
The data()
method is a special construct in the .vue
files — it contains and returns data objects that are so crucial for the application. Recollect the generic structure of the part in any
.vue
file — it roughly contains the following:
// import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. // the data objects will have certain default values chosen by us. // The methods that we define below will manipulate the data. // Since the data is bounded to various attributes and directives, they // will update as and when the values of the data objects change. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. }, computed: { // computed properties here }, // other objects, as necessary }
So far, you have encountered the names of some of the data objects, but are a lot more. Most of them are relevant for the child components, each of which handles a different aspect of the weather information dump. Given below is the entire data()
method that we will need for this project — you will have a fair idea about what data we are expecting from the APIs, and how we are disseminating the data, based on the nomenclature of the objects.
data() { return { weatherDetails: false, location: '', // raw location from input lat: '', // raw latitude from google maps api response long: '', // raw longitude from google maps api response completeWeatherApi: '', // weather api string with lat and long rawWeatherData: '', // raw response from weather api currentWeather: { full_location: '', // for full address formatted_lat: '', // for N/S formatted_long: '', // for E/W time: '', temp: '', todayHighLow: { todayTempHigh: '', todayTempHighTime: '', todayTempLow: '', todayTempLowTime: '' }, summary: '', possibility: '' }, tempVar: { tempToday: [ // gets added dynamically by this.getSetHourlyTempInfoToday() ], }, highlights: { uvIndex: '', visibility: '', windStatus: { windSpeed: '', windDirection: '', derivedWindDirection: '' }, } }; },
As you can see, in most cases the default value is empty, because that will suffice at this point. Methods will be written for manipulating the data and filling it up with appropriate values, before it is rendered or passed on to the child components.
Methods in App.vue
For .vue
files, the methods are generally written as values of keys nested in the methods { }
object. Their primary role is to manipulate the data objects of the component. We will write the methods in App.vue
keeping the same philosophy in mind. However, based on their purpose, we can categorize the methods of App.vue
into the following:
- Utility methods
- Action/Event oriented methods
- Data acquisition methods
- Data processing methods
- High level glue methods
It’s important that you understand this — we are presenting the methods to you on a platter because we have already figured out how the APIs work, what data they give, and how we should use the data in our project. It’s not that we pulled the methods out of thin air, and wrote some arcane code to deal with the data. For the purpose of learning, it’s a good exercise to diligently read and understand the code for the methods and data. However, when faced with a new project that you have to build from scratch, you must do all the dirty work yourself, and that means experimenting a lot with the APIs — their programmatic access and their data structure, before glueing them seamlessly with the data structure that your project demands. You will not have any hand holding, and there will be frustrating moments, but that’s all part of maturing as a developer.
In the following subsections, we will explain each of the method types, and also show the implementation of the methods belonging to that category. The method names are quite self-explanatory about their purpose, and so is their implementation, which we believe you will find to be easy enough to follow. However, before that, recollect the general scheme of writing methods in .vue
files:
// import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. method_1: function(arg_1) { }, method_2: function(arg_1, arg_2) { }, method_3: function(arg_1) { }, ……. }, computed: { // computed properties here }, // other objects, as necessary }
Utility Methods
The utility methods, as the name suggests, are methods written primarily for the purpose of modularizing repetitive code used for fringe tasks. They are called by other methods when necessary. Given below are the utility methods for App.vue
:
convertToTitleCase: function(str) { str = str.toLowerCase().split(' '); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); },
// To format the “possibility” (of weather) string obtained from the weather API
formatPossibility: function(str) { str = str.toLowerCase().split('-'); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); },
// To convert Unix timestamps according to our convenience
unixToHuman: function(timezone, timestamp) { /* READ THIS BEFORE JUDGING & DEBUGGING For any location beyond the arctic circle and the antarctic circle, the goddamn weather api does not return certain keys/values in each of this.rawWeatherData.daily.data[some_array_index]. Due to this, console throws up an error. The code is correct, the problem is with the API. May be later on I will add some padding to tackle missing values. */ var moment = require('moment-timezone'); // for handling date & time var decipher = new Date(timestamp * 1000); var human = moment(decipher) .tz(timezone) .format('llll'); var timeArray = human.split(' '); var timeNumeral = timeArray[4]; var timeSuffix = timeArray[5]; var justTime = timeNumeral + ' ' + timeSuffix; var monthDateArray = human.split(','); var monthDate = monthDateArray[1].trim(); return { fullTime: human, onlyTime: justTime, onlyMonthDate: monthDate }; },
// To convert temperature from fahrenheit to celcius
fahToCel: function(tempInFahrenheit) { var tempInCelcius = Math.round((5 / 9) * (tempInFahrenheit — 32)); return tempInCelcius; },
// To convert the air pressure reading from millibar to kilopascal
milibarToKiloPascal: function(pressureInMilibar) { var pressureInKPA = pressureInMilibar * 0.1; return Math.round(pressureInKPA); },
// To convert distance readings from miles to kilometers
mileToKilometer: function(miles) { var kilometer = miles * 1.60934; return Math.round(kilometer); },
// To format the wind direction based on the angle
deriveWindDir: function(windDir) { var wind_directions_array = [ { minVal: 0, maxVal: 30, direction: 'N' }, { minVal: 31, maxVal: 45, direction: 'NNE' }, { minVal: 46, maxVal: 75, direction: 'NE' }, { minVal: 76, maxVal: 90, direction: 'ENE' }, { minVal: 91, maxVal: 120, direction: 'E' }, { minVal: 121, maxVal: 135, direction: 'ESE' }, { minVal: 136, maxVal: 165, direction: 'SE' }, { minVal: 166, maxVal: 180, direction: 'SSE' }, { minVal: 181, maxVal: 210, direction: 'S' }, { minVal: 211, maxVal: 225, direction: 'SSW' }, { minVal: 226, maxVal: 255, direction: 'SW' }, { minVal: 256, maxVal: 270, direction: 'WSW' }, { minVal: 271, maxVal: 300, direction: 'W' }, { minVal: 301, maxVal: 315, direction: 'WNW' }, { minVal: 316, maxVal: 345, direction: 'NW' }, { minVal: 346, maxVal: 360, direction: 'NNW' } ]; var wind_direction = ''; for (var i = 0; i = wind_directions_array[i].minVal && windDir <= wind_directions_array[i].maxVal ) { wind_direction = wind_directions_array[i].direction; } } return wind_direction; },
Although we haven’t implemented it, you can take out the utility methods from the .vue
file, and put it in a separate JavaScript file. All you need to do is import the .js
file at the start of the script part in the .vue
file, and you should be good to go. Such approach works really well and keeps the code clean, especially in big applications where you might use lots of methods that are better grouped together based on their purpose. You can apply this approach to all of the method groups listed in this article, and see the effect itself. However, we suggest you do that exercise once you have followed the course presented here, so that you have the big picture understanding of all the parts working in complete sync, and also have a working piece of software which you can refer to, once something breaks while experimenting.
Action/Event Oriented Methods
These methods are generally executed when we need to take an action corresponding to an event. Depending on the case, the event might be triggered from an user interaction, or programmatically. In the App.vue
file, these methods sit below the utility methods.
makeInputEmpty: function() { this.$refs.input.value = ''; },
makeTempVarTodayEmpty: function() { this.tempVar.tempToday = []; },
detectEnterKeyPress: function() { var input = this.$refs.input; input.addEventListener('keyup', function(event) { event.preventDefault(); var enterKeyCode = 13; if (event.keyCode === enterKeyCode) { this.setHitEnterKeyTrue(); } }); },
locationEntered: function() { var input = this.$refs.input; if (input.value === '') { this.location = "New York"; } else { this.location = this.convertToTitleCase(input.value); } this.makeInputEmpty(); this.makeTempVarTodayEmpty(); },
One interesting thing in some of the above code snippets is the use of $ref
. In simple terms, it’s Vue’s way of associating the code statement containing it, to the HTML construct it is supposed to affect (for more information, read the official guide). For example, the methods makeInputEmpty()
and detectEnterKeyPress()
affects the input box, because in the HTML of the input box we have mentioned the value of the attribute ref
as input
.
Data Acquisition Methods
We are using the following two APIs in our project:
- Google Maps Geocoder API
This API is for getting the coordinates of the location that the user searches. You will need an API key for yourself, which you can get by following the documentation in the given link. For now, you can use the API key used by FusionCharts, but we request you not to abuse it and get a key of your own. We refer to the JavaScript API from the index.html of this project, and we shall use the constructors provided by it for our code in theApp.vue
file. - The Dark Sky Weather API
This API is for getting the weather data corresponding to the coordinates. However, we won’t be using it directly; we will wrap it within an URL that redirects through one of the FusionCharts’s server. The reason is that if you send a GET request to the API from an entirely client-end application such as ours, it results in the frustratingCORS
error (more information here and here).
Important Note: Since we have used Google Maps and Dark Sky APIs, Both these APIs have their own API keys which we have shared with you in this article. This will help you focus on client-side developments rather than the headache of backend implementation. However, we recommend you to create your own keys, because our APIs keys will come with limits and if these limits exceed you won’t be able to try the application by yourself.
For Google Maps, go to this article to get your API key. For Dark Sky API, visit https://darksky.net/dev to create your API key and respective endpoints.
With the context in mind, let’s see the implementation of the data acquisition methods for our project.
getCoordinates: function() { this.locationEntered(); var loc = this.location; var coords; var geocoder = new google.maps.Geocoder(); return new Promise(function(resolve, reject) { geocoder.geocode({ address: loc }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { this.lat = results[0].geometry.location.lat(); this.long = results[0].geometry.location.lng(); this.full_location = results[0].formatted_address; coords = { lat: this.lat, long: this.long, full_location: this.full_location }; resolve(coords); } else { alert("Oops! Couldn't get data for the location"); } }); }); },
/*
The coordinates that Google Maps Geocoder API returns are way too accurate
for our requirements. We need to bring it into shape before passing the coordinates on to the weather API. Although this is a data processing method in its own right, we can’t help mentioning it right now, because the data acquisition method for the weather API has dependency on the output of this method. */
setFormatCoordinates: async function() { var coordinates = await this.getCoordinates(); this.lat = coordinates.lat; this.long = coordinates.long; this.currentWeather.full_location = coordinates.full_location; // Remember to beautify lat for N/S if (coordinates.lat > 0) { this.currentWeather.formatted_lat = (Math.round(coordinates.lat * 10000) / 10000).toString() + '°N'; } else if (coordinates.lat 0) { this.currentWeather.formatted_long = (Math.round(coordinates.long * 10000) / 10000).toString() + '°E'; } else if (coordinates.long < 0) { this.currentWeather.formatted_long = (-1 * (Math.round(coordinates.long * 10000) / 10000)).toString() + '°W'; } else { this.currentWeather.formatted_long = ( Math.round(coordinates.long * 10000) / 10000 ).toString(); } },
/*
This method dynamically creates the the correct weather API query URL, based on the formatted latitude and longitude. The complete URL is then fed to the method querying for weather data.
Notice that the base URL used in this method (without the coordinates) points towards a FusionCharts server — we must redirect our GET request to the weather API through a server to avoid the CORS error.
*/
fixWeatherApi: async function() { await this.setFormatCoordinates(); var weatherApi = 'https://csm.fusioncharts.com/files/assets/wb/wb-data.php?src=darksky&lat=' + this.lat + '&long=' + this.long; this.completeWeatherApi = weatherApi; },
fetchWeatherData: async function() { await this.fixWeatherApi(); var axios = require('axios'); // for handling weather api promise var weatherApiResponse = await axios.get(this.completeWeatherApi); if (weatherApiResponse.status === 200) { this.rawWeatherData = weatherApiResponse.data; } else { alert('Hmm... Seems like our weather experts are busy!'); } },
Through these methods, we have introduced the concept of async-await in our code. If you have been a JavaScript developer for some time now, you must be familiar with the callback hell, which is a direct consequence of the asynchronous way JavaScript is written. ES6 allows us to bypass the cumbersome nested callbacks, and our code becomes much cleaner if we write JavaScript in a synchronous way, using the async-await technique. However, there is a downside. It takes away the speed that asynchronous code gives us, especially for the portions of the code that deals with data being exchanged over the internet. Since this is not a mission-critical application with low latency requirements, and our primary aim is to learn stuff, the clean code is much more preferable over the slightly fast code.
Data Processing Methods
Now that we have the methods that will bring the data to us, we need to prepare the ground for properly receiving and processing the data. Safety nets must be cast, and there should be no spills — data is the new gold (OK, that might be an exaggeration in our context)! Enough with the fuss, let’s get to the point.
Technically, the methods we implement in this section are aimed at getting the data out of the acquisition methods and the data objects in App.vue
, and sometimes setting the data objects to certain values that suits the purpose.
getTimezone: function() { return this.rawWeatherData.timezone; },
getSetCurrentTime: function() { var currentTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); this.currentWeather.time = this.unixToHuman( timezone, currentTime ).fullTime; },
getSetSummary: function() { var currentSummary = this.convertToTitleCase( this.rawWeatherData.currently.summary ); if (currentSummary.includes(' And')) { currentSummary = currentSummary.replace(' And', ','); } this.currentWeather.summary = currentSummary; },
getSetPossibility: function() { var possible = this.formatPossibility(this.rawWeatherData.daily.icon); if (possible.includes(' And')) { possible = possible.replace(' And', ','); } this.currentWeather.possibility = possible; },
getSetCurrentTemp: function() { var currentTemp = this.rawWeatherData.currently.temperature; this.currentWeather.temp = this.fahToCel(currentTemp); },
getTodayDetails: function() { return this.rawWeatherData.daily.data[0]; },
getSetTodayTempHighLowWithTime: function() { var timezone = this.getTimezone(); var todayDetails = this.getTodayDetails(); this.currentWeather.todayHighLow.todayTempHigh = this.fahToCel( todayDetails.temperatureMax ); this.currentWeather.todayHighLow.todayTempHighTime = this.unixToHuman( timezone, todayDetails.temperatureMaxTime ).onlyTime; this.currentWeather.todayHighLow.todayTempLow = this.fahToCel( todayDetails.temperatureMin ); this.currentWeather.todayHighLow.todayTempLowTime = this.unixToHuman( timezone, todayDetails.temperatureMinTime ).onlyTime; },
getHourlyInfoToday: function() { return this.rawWeatherData.hourly.data; },
getSetHourlyTempInfoToday: function() { var unixTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); var todayMonthDate = this.unixToHuman(timezone, unixTime).onlyMonthDate; var hourlyData = this.getHourlyInfoToday(); for (var i = 0; i < hourlyData.length; i++) { var hourlyTimeAllTypes = this.unixToHuman(timezone, hourlyData[i].time); var hourlyOnlyTime = hourlyTimeAllTypes.onlyTime; var hourlyMonthDate = hourlyTimeAllTypes.onlyMonthDate; if (todayMonthDate === hourlyMonthDate) { var hourlyObject = { hour: '', temp: '' }; hourlyObject.hour = hourlyOnlyTime; hourlyObject.temp = this.fahToCel(hourlyData[i].temperature).toString(); this.tempVar.tempToday.push(hourlyObject); /* Since we are using array.push(), we are just adding elements at the end of the array. Thus, the array is not getting emptied first when a new location is entered. to solve this problem, a method this.makeTempVarTodayEmpty() has been created, and called from this.locationEntered(). */ } } /* To cover the edge case where the local time is between 10 — 12 PM, and therefore there are only two elements in the array this.tempVar.tempToday. We need to add the points for minimum temperature and maximum temperature so that the chart gets generated with atleast four points. */ if (this.tempVar.tempToday.length <= 2) { var minTempObject = { hour: this.currentWeather.todayHighLow.todayTempHighTime, temp: this.currentWeather.todayHighLow.todayTempHigh }; var maxTempObject = { hour: this.currentWeather.todayHighLow.todayTempLowTime, temp: this.currentWeather.todayHighLow.todayTempLow }; /* Typically, lowest temp are at dawn, highest temp is around mid day. Thus we can safely arrange like min, max, temp after 10 PM. */ // array.unshift() adds stuff at the beginning of the array. // the order will be: min, max, 10 PM, 11 PM. this.tempVar.tempToday.unshift(maxTempObject, minTempObject); } },
getSetUVIndex: function() { var uvIndex = this.rawWeatherData.currently.uvIndex; this.highlights.uvIndex = uvIndex; },
getSetVisibility: function() { var visibilityInMiles = this.rawWeatherData.currently.visibility; this.highlights.visibility = this.mileToKilometer(visibilityInMiles); },
getSetWindStatus: function() { var windSpeedInMiles = this.rawWeatherData.currently.windSpeed; this.highlights.windStatus.windSpeed = this.mileToKilometer( windSpeedInMiles ); var absoluteWindDir = this.rawWeatherData.currently.windBearing; this.highlights.windStatus.windDirection = absoluteWindDir; this.highlights.windStatus.derivedWindDirection = this.deriveWindDir( absoluteWindDir ); },
High Level Glue Methods
With the utility, acquisition, and processing methods out of our way, we are now left with the task of orchestrating the entire thing. We do that by creating high level glue methods, that essentially calls the methods written above in a particular sequence, so that the entire operation is executed seamlessly.
// Top level for info section
// Data in this.currentWeather
organizeCurrentWeatherInfo: function() { // data in this.currentWeather /* Coordinates and location is covered (get & set) in: — this.getCoordinates() — this.setFormatCoordinates() There are lots of async-await involved there. So it's better to keep them there. */ this.getSetCurrentTime(); this.getSetCurrentTemp(); this.getSetTodayTempHighLowWithTime(); this.getSetSummary(); this.getSetPossibility(); },
// Top level for highlights
organizeTodayHighlights: function() { // top level for highlights this.getSetUVIndex(); this.getSetVisibility(); this.getSetWindStatus(); },
// Top level organization and rendering
organizeAllDetails: async function() { // top level organization await this.fetchWeatherData(); this.organizeCurrentWeatherInfo(); this.organizeTodayHighlights(); this.getSetHourlyTempInfoToday(); },
mounted
Vue provides instance lifecycle hooks — properties that are essentially methods, and gets triggered when the instance lifecycle reaches that stage. For example, created, mounted, beforeUpdate, etc., are all very useful lifecycle hooks that allows the programmer to control the instance at a much more granular level than that would have been possible otherwise.
In the code of a Vue component, these lifecycle hooks are implemented just like you would for any other prop
. For example:
<template>
</template>
// import statements
export default { data() { return { // data objects here } }, methods: { // methods here }, mounted: function(){ // function body here },
}
<style>
</style>
Armed with this new understanding, take a look at the code below for the mounted
prop of App.vue
:
mounted: async function() { this.location = "New York"; await this.organizeAllDetails(); }
Complete Code For App.vue
We have covered a lot of ground in this section, and the last few sections have given you things in bits and pieces. However, it’s important that you have the complete, assembled code for App.vue
(subject to further modifications in subsequent sections). Here it goes:
<template>