Outline of JavaScript Framework for Mapping Plugin Inspired by React.js

Outline of JavaScript Framework for Mapping Plugin Inspired by React.js

This article presents a high level technical overview for the implementation of the Regional Planning plugin for the Coastal Resilience GeositeFramework. We applied lessons learned from our experience with React and Redux to create a stateless component using vanilla JavaScript.

Coastal Resilience is a program of The Nature Conservancy that supports a community of practitioners using web-based mapping tools designed to help communities understand their vulnerability from coastal hazards, reduce their risk, and determine the value of nature-based solutions.

GeositeFramework is a JavaScript mapping platform, developed to support the Coastal Resilience suite of tools, which allows developers to create plugins to provide custom map based workflows. Regional Planning is one such plugin created by Azavea for the GeositeFramework.

Description

The purpose of the Regional Planning plugin is to allow users to explore ArcGIS map services and display map layers. This plugin supports loading map services asynchronously, filtering, generating reports for drawn areas of interest, overriding layers properties, and more. Other common GeositeFramework plugin operations such as hibernation, loading and saving state from a URL, region configuration, and split screen mode are also supported.

React is one of the standard tools we use to develop web applications on the Civic Apps team. Unfortunately, we did not have that library at our disposal on this project. Despite this limitation, we were able to successfully implement a system inspired by the React philosophy. The result is a component featuring centralized state and stateless rendering logic, which is able to seamlessly integrate with the rest of the GeositeFramework ecosystem.

Technical Overview

Map services in the layer selector are represented by an immutable tree structure, populated from a static JSON configuration file. State generated from user interactions (such as activating layers, applying a search filter, etc.) is maintained in a separate immutable structure. Map services data is requested upon initialization, based on the JSON configuration, and is maintained in a dedicated cache. Every time a new piece of information is received, all input sources are combined, and used to redraw the layer selector.

We’re able to avoid data corruption by using immutable data structures (within reason). This also allows us to reliably restore the entire state of the plugin from a serialized value. Something else we do to keep the saved state and UI in sync is we avoid directly manipulating the DOM. Instead, all user actions are recorded in the plugin state object, which then triggers a redraw.

For example, if we wanted to activate the the “Draw & Report” tab when it is clicked:

// Incorrect
$('.tabs').removeClass('active');
$('#tab-draw-report').addClass('active');

// Correct
state.setActiveTab('draw-report');
redraw();

To reduce the complexity of the codebase, nested callback functions are avoided, unless absolutely necessary. Instead, we “fire and forget” AJAX requests, redrawing the plugin after each request completes. This can happen multiple times per second, in some situations. The coalesce operation, rendering logic, and templates all support partial or missing data. Deferred objects and promises are used sparingly.

Here’s an example of what this might look like:

// Incorrect
function redraw() {
    render('Loading...');
    $.getJSON(serviceUrl).done(function() {
        render('<h1>' + service.name + '</h1>');
    });
}

redraw();

// Correct
var serviceData = new Cache();

function redraw() {
    var service = serviceData.get(serviceUrl);
    if (service) {
        render('<h1>' + service.name + '</h1>');
    } else {
        render('Loading...');
    }
}

redraw();
fetchService(serviceUrl).done(function(data) {
    serviceData.set(serviceUrl, data);
    redraw();
});

Conclusion

We were able to translate our knowledge of React design patterns and best practices to develop the Regional Planning plugin for the GeositeFramework. Using immutable data structures, redrawing after each state change, and preferring linear synchronous code over nested callbacks, resulted in a simple yet effective workflow for managing complex state and interacting with asynchronous services.

For more information about how the Civic Apps team at Azavea uses React and Redux to build web applications, check out Civic Apps Team React/Redux Project Structure.