App with Meteor: structure (with the example app Weight Watch)

Greetings,

today I will continue with the topic of the sample Meteor app. We will have a look at the rough app structure, and the architecture.

On a typical Meteor app the client and the server side have been differentiated from each other. The differences, and which content should be placed where, are discussed in a remarkable article at discovermeteor.com. In the first draft version of the app I only have a rudimentary GUI with a simple dashboard view with simple visualizations and no actual fetching of data through the Internet, so the code will be placed to the client side as it is meant to be handled by the browser entirely. It would not in fact make any difference to place the code differently, but obviously for the understanding of the overall app structure it is a better convention to handle the code consistently.

The main HTML file providing a structure for the app is listed in the following code snippet. In the head section there is a link to the Materialize CSS bringing some life to the app with additional icons.

Inside the body tag we specify that we want to place the navigation component as first (“{{> nav}}”. The previous syntax is used for “inclusions“.  After that we want the place the actual weight widget (that will be specified in an additional file). Additionally there is an element of navigation buttons, but this will maybe not be needed at all.

After the actual body part there are the specified templates for the components. These contain mostly normal HTML code and are placed to the spot in the actual page structure where the placeholders are specified.

<head>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <title>Weightwatch</title>
</head>

<body>
  {{> nav}}
  <div class="row">
    {{> weightwidget}}
  </div>
  {{> buttons}}
</body>

<template name="nav">
  <nav class="red lighten-2">
    <div class="nav-wrapper">
      <img src="img/ww-logo.svg" class="brand-logo" />
    </div>
  </nav>
</template>

<template name="buttons">
  <div class="row">
    <div class="col s12 m6">
      <a class="waves-effect waves-light btn"><i class="material-icons">refresh</i></a>
    </div>
  </div>
</template>

The JavaScript file main.js is at this stage almost empty, with just some imports to make the overall app to load all the required components.

Secondly, there’s the actual weight widget that is supposed to be shown on the main view. It will get the weight data from the Fitbit website through the REST API, and present it in a graph. Below there’s the template code for the widget (weighwidget.html). Basically there is a label and a canvas area (400 × 400 px) specified, where the graph will be drawn.

<template name = "weightwidget">
    <div class="col s12 m6">
        <div class="card blue-grey lighten-1">
            <div class="card-content white-text">
                <span class="card-title">Weight history</span>
            </div>
            <canvas id="myChart" width="400" height="400"></canvas>
        </div>
    </div>
</template>

And then we have the actual JavaScript content for the weight widget (weightwidget.js).  The listing below contains a functionality for drawing a graph based on a JSON string containing the weight measurements in the format Fitbit provides. In other words, when this works we can/could simply connect the existing functionality with the data obtained from Fitbit.

At the beginning of the file we have the imports (note the import for the Chart JS library). I also tried to use the native Meteor package for the chart library, but for some reason it didn’t work out and I ended up installing the library with meteor npm install.

The method onRendered() contains a sample JSON String with sample weight data. The String is parsed to a JSON object and provided as parameter to the draw method.

The method drawChart() initializes the arrays for weight data and corresponding labels. We iterate the individual items and put those to the arrays.  In the next step a Bar chart is created: labels are produced from the labels containing the measurement dates and the data are the measured weight data provided. Moreover, we specify some visual looks (background and border colours as well as the border width).

import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import Chart from 'chart.js/src/chart.js';
import './weightwidget.html';

Template.weightwidget.onRendered(function() {
    console.log('weightwidget: onRendered()');
    var jsonStr = '{"weight":[{"bmi":21.03,"date":"2017-01-18","fat":8.109999656677246,"logId":1484724473000,"source":"Aria","time":"07:27:53","weight":68.1},{"bmi":20.95,"date":"2017-01-19","fat":7.614999771118164,"logId":1484811379000,"source":"Aria","time":"07:36:19","weight":67.8},{"bmi":20.94,"date":"2017-01-20","fat":7.991000175476074,"logId":1484897526000,"source":"Aria","time":"07:32:06","weight":67.8},{"bmi":20.99,"date":"2017-01-21","fat":7.822000026702881,"logId":1484990742000,"source":"Aria","time":"09:25:42","weight":68},{"bmi":21.05,"date":"2017-01-23","fat":7.373000144958496,"logId":1485157122000,"source":"Aria","time":"07:38:42","weight":68.2},{"bmi":21.08,"date":"2017-01-24","fat":7.429999828338623,"logId":1485244040000,"source":"Aria","time":"07:47:20","weight":68.3}]}';
    var jsonObj = JSON.parse(jsonStr);
    drawChart(jsonObj);
});

function drawChart(jsonObj) {
    console.log("length: " +jsonObj.weight.length)
    const NUM_DATAITEMS = jsonObj.weight.length;
    var weightLabels = new Array(NUM_DATAITEMS);
    var weightData = new Array(NUM_DATAITEMS);

    jsonObj.weight.forEach(function(entry, index) {
        console.log(index +": "  +entry.weight);
        weightLabels[index] = entry.date;
        weightData[index] = entry.weight;
    });

    var ctx  = document.getElementById("myChart");
    var myChart = new Chart(ctx, {
        type: 'bar',
        data: {
            labels: weightLabels,
            datasets: [{
                label: 'Weight',
                data: weightData,
                backgroundColor: [
                    'rgba(255, 99, 132, 0.5)',
                    'rgba(54, 162, 235, 0.5)',
                    'rgba(255, 206, 86, 0.5)',
                    'rgba(75, 192, 192, 0.5)',
                    'rgba(153, 102, 255, 0.5)',
                    'rgba(255, 159, 64, 0.5)'
                ],
                borderColor: [
                    'rgba(255,99,132,1)',
                    'rgba(54, 162, 235, 1)',
                    'rgba(255, 206, 86, 1)',
                    'rgba(75, 192, 192, 1)',
                    'rgba(153, 102, 255, 1)',
                    'rgba(255, 159, 64, 1)'
                ],
                borderWidth: 1
            }]
        },
        options: {
            scales: {
                yAxes: [{
                    ticks: {
                        beginAtZero: false,
                        maxTicksLimit: 3
                    }
                }]
            },
            responsive: true
        }
    });
}

At the moment I’m not really certain whether there will a third part in this overview, but I hope so. Anyway it is pretty straightforward to visualize a sample graph with the Chart.js library.

The current version of the app can be viewed below and the whole code is still available at Github.

Leave a Reply

Your email address will not be published. Required fields are marked *