The app will have a main page with some indicators, such as:
It will also have another page with buttons to generate fake measures since we won't have real sensors available.
Setting up the project
In this first part, we will cover Meteor and get a simple app up and running on this platform.
Meteor is a full-stack JavaScript framework for building web applications. The mains elements of the Meteor stack are as follows:
As you can see, JavaScript is used everywhere. Meteor also encourages you to share code between the client and the server.
Since Meteor manages the entire stack, it offers very powerful systems that are easy to use. For example, the entire stack is fully reactive and real-time--if a client sends an update to the server, all the other clients will receive the new data and their UI will automatically be up to date.
Meteor has its own build system called "IsoBuild" and doesn't use Webpack. It focuses on ease of use (no configuration), but is, as a result, also less flexible.
If you don't have Meteor on your system, you need to open the Installation Guide on the official Meteor website. Follow the instructions there for your OS to install Meteor.
When you are done, you can check whether Meteor was correctly installed with the following command:
meteor --version
The current version of Meteor should be displayed.
Now that Meteor is installed, let's set up a new project:
meteor create --bare <folder> cd <folder>
The --bare argument tells Meteor we want an empty project. By default, Meteor will generate some boilerplate files we don't need, so this keeps us from having to delete them.
meteor add akryum:vue-component akryum:vue-stylus
meteor npm i -S vue vue-router
Note that we use the meteor npm command instead of just npm. This is to have the same environment as Meteor (nodejs and npm versions).
Meteor
Meteor should start an HTTP proxy, a MongoDB, and the nodejs server:
It also shows the URL where the app is available; however, if you open it right now, it will be blank.
In this section, we will display a simple Vue component in our app:
<head>
<title>Production Dashboard</title>
</head>
<body>
<div id="app"></div>
</body>
This is not a real HTML file. It is a special format where we can inject additional elements to the head or body section of the final HTML page. Here, Meteor will add a title into the head section and the <div> into the body section.
<!-- client/components/App.vue -->
<template>
<div id="#app">
<h1>Meteor</h1>
</div>
</template>
<style lang="stylus" src="../style.styl" />
import { Meteor } from 'meteor/meteor' import Vue from 'vue'
import App from './components/App.vue'
Meteor.startup(() => { new Vue({
el: '#app',
...App,
})
})
In a Meteor app, it is recommended that you create the Vue app inside the Meteor.startup hook to ensure that all the Meteor systems are ready before starting the frontend.
This code will only be run on the client because it is located in a client folder.
You should now have a simple app displayed in your browser. You can also open the Vue devtools and check whether you have the App component present on the page.
Let's add some routing to the app; we will have two pages--the dashboard with indicators and a page with buttons to generate fake data:
import Vue from 'vue'
import VueRouter from 'vue-router'
import ProductionDashboard from './components/ProductionDashboard.vue' import ProductionGenerator from './components/ProductionGenerator.vue'
Vue.use(VueRouter) const routes = [
{ path: '/', name: 'dashboard', component: ProductionDashboard
},
{ path: '/generate', name: 'generate', component: ProductionGenerator },
]
const router = new VueRouter({ mode: 'history',
routes,
})
export default router
<nav>
<router-link :to="{ name: 'dashboard' }" exact>Dashboard
</router-link>
<router-link :to="{ name: 'generate' }">Measure</router-link>
</nav>
<router-view />
The basic structure of our app is now done:
The first page we will make is the Measures page, where we will have two buttons:
All these measures will be stored in a collection called "Measures".
A Meteor collection is a reactive list of objects, similar to a MongoDB collection (in fact, it uses MongoDB under the hood).
We need to use a Vue plugin to integrate the Meteor collections into our Vue app in order to update it automatically:
meteor npm i -S vue-meteor-tracker
import VueMeteorTracker from 'vue-meteor-tracker'
Vue.use(VueMeteorTracker)
The app is now aware of the Meteor collection and we can use them in our components, as we will do in a moment.
The next step is setting up the Meteor collection where we will store our measures data
We will store our measures into a Measures Meteor collection. Create a new lib folder in the project directory. All the code in this folder will be executed first, both on the client and the server. Create a collections.js file, where we will declare our Measures collection:
import { Mongo } from 'meteor/mongo'
export const Measures = new Mongo.Collection('measures')
A Meteor method is a special function that will be called both on the client and the server. This is very useful for updating collection data and will improve the perceived speed of the app--the client will execute on minimongo without waiting for the server to receive and process it.
This technique is called "Optimistic Update" and is very effective when the network quality is poor.
import { Meteor } from 'meteor/meteor' import { Measures } from './collections'
Meteor.methods({ 'measure.add' (measure) {
Measures.insert({
...measure,
date: new Date(),
})
},
})
We can now call this method with the Meteor.call function:
Meteor.call('measure.add', someMeasure)
The method will be run on both the client (using the client-side database called minimongo) and on the server. That way, the update will be instant for the client.
Without further delay, let's build the simple component that will call this measure.add Meteor method:
<template>
<div class="production-generator">
<h1>Measure production</h1>
<section class="actions">
<button @click="generateMeasure(false)">Generate Measure</button>
<button @click="generateMeasure(true)">Generate Error</button>
</section>
</div>
</template>
<script>
import { Meteor } from 'meteor/meteor'
export default { methods: {
generateMeasure (error) {
const value = Math.round(Math.random() * 100) const measure = {
value, error,
}
Meteor.call('measure.add', measure)
},
},
}
</script>
The component should look like this:
If you click on the buttons, nothing visible should happen.
There is an easy way to check whether our code works and to verify that you can add items in the Measures collection. We can connect to the MongoDB database in a single command.
In another terminal, run the following command to connect to the app's database:
meteor mongo
Then, enter this MongoDB query to fetch the documents of the measures collection (the argument used when creating the Measures Meteor collection):
db.measures.find({})
If you clicked on the buttons, a list of measure documents should be displayed
This means that our Meteor method worked and objects were inserted in our MongoDB database.
Now that our first page is done, we can continue with the real-time dashboard.
To display some pretty indicators, let's install another Vue library that allows drawing progress bars along SVG paths; that way, we can have semi-circular bars:
meteor npm i -S vue-progress-path
We need to tell the Vue compiler for Meteor not to process the files in node_modules where the package is installed.
node_modules/
import 'vue-progress-path/dist/vue-progress-path.css'
import VueProgress from 'vue-progress-path'
Vue.use(VueProgress, { defaultShape: 'semicircle',
})
To synchronize data, the client must subscribe to a publication declared on the server. A Meteor publication is a function that returns a Meteor collection query. It can take arguments to filter the data that will be synchronized.
For our app, we will only need a simple measures publication that sends all the documents of the Measures collection:
import { Meteor } from 'meteor/meteor'
import { Measures } from '../lib/collections'
Meteor.publish('measures', function () { return Measures.find({})
})
This code will only run on the server because it is located in a folder called server.
We are ready to build our ProductionDashboard component. Thanks to the vue- meteor-tracker we installed earlier, we have a new component definition option-- meteor. This is an object that describes the publications that need to be subscribed to and the collection data that needs to be retrieved for that component.
<script>
export default {
meteor: {
// Subscriptions and Collections queries here
},
}
</script>
meteor: {
$subscribe: { 'measures': [],
},
},
meteor: {
// ...
measures () {
return Measures.find({}, { sort: { date: -1 },
})
},
},
The second parameter of the find method is an options object very similar to the MongoDB JavaScript API. Here, we are sorting the documents by their date in descending order, thanks to the sort property of the options object.
<script>
import { Measures } from '../../lib/collections'
export default { data () {
return {
measures: [],
}
},
meteor: {
$subscribe: { 'measures': [],
},
measures () {
return Measures.find({}, { sort: { date: -1 },
})
},
},
}
</script>
In the browser devtools, you can now check whether the component has retrieved the items from the collection.
We will create a separate component for the dashboard indicators, as follows:
<template>
<div class="production-indicator">
<loading-progress :progress="value" />
<div class="title">{{ title }}</div>
<div class="info">{{ info }}</div>
</div>
</template>
<script>
export default { props: {
value: {
type: Number, required: true,
},
title: String,
info: [String, Number],
},
}
</script>
computed: {
length () {
return this.measures.length
},
average () {
if (!this.length) return 0
let total = this.measures.reduce(
(total, measure) => total += measure.value, 0
)
return total / this.length
},
errorRate () {
if (!this.length) return 0
let total = this.measures.reduce(
(total, measure) => total += measure.error ? 1 : 0, 0
)
return total / this.length
},
},
5. Add two indicators in the templates - one for the average value and one for the error rate:
<template>
<div class="production-dashboard">
<h1>Production Dashboard</h1>
<section class="indicators">
<ProductionIndicator
:value="average / 100" title="Average"
:info="Math.round(average)"
/>
<ProductionIndicator class="danger"
:value="errorRate" title="Errors"
:info="`${Math.round(errorRate * 100)}%`"
/>
</section>
</div>
</template>
The indicators should look like this:
Finally, we will display a list of the measures below the indicators:
<section class="list">
<div
v-for="item of measures"
:key="item._id"
>
<div class="date">{{ item.date.toLocaleString() }}</div>
<div class="error">{{ item.error ? 'Error' : '' }}</div>
<div class="value">{{ item.value }}</div>
</div>
</section>
The app should now look as follows, with a navigation toolbar, two indicators, and the measures list:
If you open the app in another window and put your windows side by side, you can see the full-stack reactivity of Meteor in action. Open the dashboard in one window and the generator page in the other window. Then, add fake measures and watch the data update on the other window in real time.
If you want to learn more about Meteor, check out the official website and the Vue integration repository.
To summarize, we created a project using Meteor. We integrated Vue into the app and set up a Meteor reactive collection. Using a Meteor method, we inserted documents into the collection and displayed in real-time the data in a dashboard component.
You read an excerpt from a book written by Guillaume Chau, titled Vue.js 2 Web Development Projects. This book will help you build exciting real world web projects from scratch and become proficient with Vue.js Web Development.
Building your first Vue.js 2 Web application
Why has Vue.js become so popular?