Implementing a bundler and separating concerns - lennartdeknikker/frontend-data GitHub Wiki

Splitting up the code using a bundler

As part of the last feedback session for functional programming, Danny recommended splitting up the code in different modules or utilities. That will be the first part of improving my application. I started off installing the Parcel bundler, which gave me an error right away saying:

map.js:52 Uncaught ReferenceError: regeneratorRuntime is not defined

After some research, apparently Babel, which is used by Parcel doesn't support async await. By adding import "babel-polyfill"; this could easily be fixed.

Changing the file structure

Later on, when the settings object became a little bigger, I decided to store it in a seperate settings.json file:

{
	"init": {
		"targetDiv": "#map_container",
		"endpoint": "https://api.data.netwerkdigitaalerfgoed.nl/datasets/ivo/NMVW/services/NMVW-29/sparql",
		"keyWord": "",
		"mapJson": "https://raw.githubusercontent.com/rifani/geojson-political-indonesia/master/IDN_adm_1_province.json",
		"svgSize": ["100%", "100%"]
	},
	"render": {
		"scaleExtent": [0.5, 20],
		"dataExtent": [0, 1]
	},
	"projection": {
		"center": [120, -5],
		"scale": 1400,
		"translation": [2, 2.6]
	},
	"legend": {
		"enabled": true,
		"targetDiv": "#legend",
		"height": "6.5em",
		"width": "150px"
	}
}

I added a main.js file that is loaded into the index.html. Right now, main.js just contains some imports and running addDataVisualisation() with the obtained settings from settings.json

import 'babel-polyfill';
import { AddDataVisualisation } from './datavis/index';
import * as settings from './datavis/settings.json';

// adds the datavisualisation to the html using the settings defined in settings.json.
AddDataVisualisation(settings);

My file structure of the .src/ folder now looks like this:


  • index.html
  • main.js
  • styles.scss

  • datavis/
    • index.js
    • zoom.js
    • map.js
    • datapoints.js
    • search.js
    • legend.js
    • list.js
    • utilities.js
    • settings.json

  • assets/
    • logo-volkenkunde.svg

  • favicon.png

datavis separation

All JavaScript needed to load the data visualisation is now contained in a separate .datavis/ folder.

index.js

This file contains just one function, rendering the data visualisation by importing all necessary functions from the other files.

// imports for d3 and the loadMap function that is the basis of the visualisation.
import * as d3 from 'd3';
import { loadMap } from './map';
// imports for different functionalities.
import { addZoomToSvg } from './zoom';
import { addSearch } from './search';
import { addLegendSvg } from './legend';

As you can see, I split up different functionalities into different modules. There's one function to add zoom-functionality, one to add search and one to add the legend. The visualisation can be rendered without either one of these, so the different modules work independently now and can be removed, added or changed whenever needed.

// Adds a datavisualisation with specific settings.
function AddDataVisualisation(settings) {
	document.querySelector(settings.init.targetDiv).innerHTML = '';
	const svg = d3
		.select(settings.init.targetDiv)
		.append('svg')
		.attr('width', settings.init.svgSize[0])
		.attr('height', settings.init.svgSize[1]);

First, the svg is created, using the different settings obtained from index.json.

	// map and datapoint projection settings
	const translation = [
		window.innerWidth / settings.projection.translation[0],
		window.innerHeight / settings.projection.translation[1],
	];
	const projection = d3
		.geoMercator()
		.center(settings.projection.center)
		.scale(settings.projection.scale)
		.translate(translation);

Then the translation and projection is stored in two variables.

	// add zoom functionality to svg, then load the map, then load the datapoints
	addZoomToSvg(settings, svg).then(g =>
		loadMap(settings.init.mapJson, g, projection).then(
			addSearch(g, projection, settings, settings.init.endpoint).then(
				addLegendSvg(settings)
			)
		)
	);
}

export { AddDataVisualisation };

Finally, the asynchronous functions are called one by one, using .then()-statements. This structure makes sure that the functionalities load after the elements they influence, passing any necessary variables to the next.