access-nyc-patterns
Version:
User Interface Patterns for Benefits Access
339 lines (301 loc) • 9.45 kB
JavaScript
;
import {default as _template} from 'lodash-es/template';
import {default as _forEach} from 'lodash-es/forEach';
/**
* The NearbyStops Module
* @class
*/
class NearbyStops {
/**
* @constructor
* @return {object} The NearbyStops class
*/
constructor() {
/** @type {Array} Collection of nearby stops DOM elements */
this._elements = document.querySelectorAll(NearbyStops.selector);
/** @type {Array} The collection all stops from the data */
this._stops = [];
/** @type {Array} The currated collection of stops that will be rendered */
this._locations = [];
// Loop through DOM Components.
_forEach(this._elements, (el) => {
// Fetch the data for the element.
this._fetch(el, (status, data) => {
if (status !== 'success') return;
this._stops = data;
// Get stops closest to the location.
this._locations = this._locate(el, this._stops);
// Assign the color names from patterns stylesheet.
this._locations = this._assignColors(this._locations);
// Render the markup for the stops.
this._render(el, this._locations);
});
});
return this;
}
/**
* This compares the latitude and longitude with the Subway Stops data, sorts
* the data by distance from closest to farthest, and returns the stop and
* distances of the stations.
* @param {object} el The DOM Component with the data attr options
* @param {object} stops All of the stops data to compare to
* @return {object} A collection of the closest stops with distances
*/
_locate(el, stops) {
const amount = parseInt(this._opt(el, 'AMOUNT'))
|| NearbyStops.defaults.AMOUNT;
let loc = JSON.parse(this._opt(el, 'LOCATION'));
let geo = [];
let distances = [];
// 1. Compare lat and lon of current location with list of stops
for (let i = 0; i < stops.length; i++) {
geo = stops[i][this._key('ODATA_GEO')][this._key('ODATA_COOR')];
geo = geo.reverse();
distances.push({
'distance': this._equirectangular(loc[0], loc[1], geo[0], geo[1]),
'stop': i, // index of stop in the data
});
}
// 2. Sort the distances shortest to longest
distances.sort((a, b) => (a.distance < b.distance) ? -1 : 1);
distances = distances.slice(0, amount);
// 3. Return the list of closest stops (number based on Amount option)
// and replace the stop index with the actual stop data
for (let x = 0; x < distances.length; x++)
distances[x].stop = stops[distances[x].stop];
return distances;
}
/**
* Fetches the stop data from a local source
* @param {object} el The NearbyStops DOM element
* @param {function} callback The function to execute on success
* @return {funciton} the fetch promise
*/
_fetch(el, callback) {
const headers = {
'method': 'GET'
};
return fetch(this._opt(el, 'ENDPOINT'), headers)
.then((response) => {
if (response.ok)
return response.json();
else {
// eslint-disable-next-line no-console
if (process.env.NODE_ENV !== 'production') console.dir(response);
callback('error', response);
}
})
.catch((error) => {
// eslint-disable-next-line no-console
if (process.env.NODE_ENV !== 'production') console.dir(error);
callback('error', error);
})
.then((data) => callback('success', data));
}
/**
* Returns distance in miles comparing the latitude and longitude of two
* points using decimal degrees.
* @param {float} lat1 Latitude of point 1 (in decimal degrees)
* @param {float} lon1 Longitude of point 1 (in decimal degrees)
* @param {float} lat2 Latitude of point 2 (in decimal degrees)
* @param {float} lon2 Longitude of point 2 (in decimal degrees)
* @return {float} [description]
*/
_equirectangular(lat1, lon1, lat2, lon2) {
Math.deg2rad = (deg) => deg * (Math.PI / 180);
let alpha = Math.abs(lon2) - Math.abs(lon1);
let x = Math.deg2rad(alpha) * Math.cos(Math.deg2rad(lat1 + lat2) / 2);
let y = Math.deg2rad(lat1 - lat2);
let R = 3959; // earth radius in miles;
let distance = Math.sqrt(x * x + y * y) * R;
return distance;
}
/**
* Assigns colors to the data using the NearbyStops.truncks dictionary.
* @param {object} locations Object of closest locations
* @return {object} Same object with colors assigned to each loc
*/
_assignColors(locations) {
let locationLines = [];
let line = 'S';
let lines = ['S'];
// Loop through each location that we are going to display
for (let i = 0; i < locations.length; i++) {
// assign the line to a variable to lookup in our color dictionary
locationLines = locations[i].stop[this._key('ODATA_LINE')].split('-');
for (let x = 0; x < locationLines.length; x++) {
line = locationLines[x];
for (let y = 0; y < NearbyStops.trunks.length; y++) {
lines = NearbyStops.trunks[y]['LINES'];
if (lines.indexOf(line) > -1)
locationLines[x] = {
'line': line,
'trunk': NearbyStops.trunks[y]['TRUNK']
};
}
}
// Add the trunk to the location
locations[i].trunks = locationLines;
}
return locations;
}
/**
* The function to compile and render the location template
* @param {object} element The parent DOM element of the component
* @param {object} data The data to pass to the template
* @return {object} The NearbyStops class
*/
_render(element, data) {
let compiled = _template(NearbyStops.templates.SUBWAY, {
'imports': {
'_each': _forEach
}
});
element.innerHTML = compiled({'stops': data});
return this;
}
/**
* Get data attribute options
* @param {object} element The element to pull the setting from.
* @param {string} opt The key reference to the attribute.
* @return {string} The setting of the data attribute.
*/
_opt(element, opt) {
return element.dataset[
`${NearbyStops.namespace}${NearbyStops.options[opt]}`
];
}
/**
* A proxy function for retrieving the proper key
* @param {string} key The reference for the stored keys.
* @return {string} The desired key.
*/
_key(key) {
return NearbyStops.keys[key];
}
}
/**
* The dom selector for the module
* @type {String}
*/
NearbyStops.selector = '[data-js="nearby-stops"]';
/**
* The namespace for the component's JS options. It's primarily used to lookup
* attributes in an element's dataset.
* @type {String}
*/
NearbyStops.namespace = 'nearbyStops';
/**
* A list of options that can be assigned to the component. It's primarily used
* to lookup attributes in an element's dataset.
* @type {Object}
*/
NearbyStops.options = {
LOCATION: 'Location',
AMOUNT: 'Amount',
ENDPOINT: 'Endpoint'
};
/**
* The documentation for the data attr options.
* @type {Object}
*/
NearbyStops.definition = {
LOCATION: 'The current location to compare distance to stops.',
AMOUNT: 'The amount of stops to list.',
ENDPOINT: 'The endopoint for the data feed.'
};
/**
* [defaults description]
* @type {Object}
*/
NearbyStops.defaults = {
AMOUNT: 3
};
/**
* Storage for some of the data keys.
* @type {Object}
*/
NearbyStops.keys = {
ODATA_GEO: 'the_geom',
ODATA_COOR: 'coordinates',
ODATA_LINE: 'line'
};
/**
* Templates for the Nearby Stops Component
* @type {Object}
*/
NearbyStops.templates = {
SUBWAY: [
'<% _each(stops, function(stop) { %>',
'<div class="c-nearby-stops__stop">',
'<% var lines = stop.stop.line.split("-") %>',
'<% _each(stop.trunks, function(trunk) { %>',
'<% var exp = (trunk.line.indexOf("Express") > -1) ? true : false %>',
'<% if (exp) trunk.line = trunk.line.split(" ")[0] %>',
'<span class="',
'c-nearby-stops__subway ',
'icon-subway<% if (exp) { %>-express<% } %> ',
'<% if (exp) { %>border-<% } else { %>bg-<% } %><%- trunk.trunk %>',
'">',
'<%- trunk.line %>',
'<% if (exp) { %> <span class="sr-only">Express</span><% } %>',
'</span>',
'<% }); %>',
'<span class="c-nearby-stops__description">',
'<%- stop.distance.toString().slice(0, 3) %> Miles, ',
'<%- stop.stop.name %>',
'</span>',
'</div>',
'<% }); %>'
].join('')
};
/**
* Color assignment for Subway Train lines, used in cunjunction with the
* background colors defined in config/variables.js.
* Based on the nomenclature described here;
* @url // https://en.wikipedia.org/wiki/New_York_City_Subway#Nomenclature
* @type {Array}
*/
NearbyStops.trunks = [
{
TRUNK: 'eighth-avenue',
LINES: ['A', 'C', 'E'],
},
{
TRUNK: 'sixth-avenue',
LINES: ['B', 'D', 'F', 'M'],
},
{
TRUNK: 'crosstown',
LINES: ['G'],
},
{
TRUNK: 'canarsie',
LINES: ['L'],
},
{
TRUNK: 'nassau',
LINES: ['J', 'Z'],
},
{
TRUNK: 'broadway',
LINES: ['N', 'Q', 'R', 'W'],
},
{
TRUNK: 'broadway-seventh-avenue',
LINES: ['1', '2', '3'],
},
{
TRUNK: 'lexington-avenue',
LINES: ['4', '5', '6', '6 Express'],
},
{
TRUNK: 'flushing',
LINES: ['7', '7 Express'],
},
{
TRUNK: 'shuttles',
LINES: ['S']
}
];
export default NearbyStops;