because.js
Overview
because.js
is a Javascript client library that helps programmers get
interesting data from the BCS HTTP
services: coordinates for street
addresses (geocoding) turn-by-turn directions (routing), discovery and metadata
for basemaps available through Boundless, and more.
because.js
wraps these HTTP services for convenient use from Javascript. It
abstracts away many of the details of authentication, how HTTP requests are
constructed and sent, and how HTTP responses are parsed. Instead of
re-implementing all these details in every new project and for each additional
service, a programmer building on this library provides a higher-level
specification of what data is wanted, and gets the results back as convenient
Javascript objects.
Besides making it faster and easier to get started, using a client library has benefits for maintainability. Whenever improvements are needed, they can be made in the library, for the shared benefit of all the projects that use it. When the upstream services change, the library can change to accommodate this, instead of each project individually somehow discovering and making the necessary adjustments.
To use because.js and the BCS services, you first need credentials, which you can get by signing up at Boundless Connect.
If you're interested in because.js, you would probably be even more interested in the Boundless Web SDK.
If you want to consume the BCS services from Python, you may be interested in Because for Python.
Installing
These instructions assume you already have signed up with Boundless Connect.
The source code for because.js is managed using git, and published on GitHub. To download the code, use git to clone the repository from GitHub:
git clone https://github.com/harts-boundless/because.js.git
When this is cloned, you can find a usable UMD module in the cloned repo
directory, as dist/because.js
.
This bundle may be a little out of date. If you want to make sure you have a
fresh bundle reflecting the latest state of the original code in src/
then
see the below section on "How to build."
Usage Examples
Getting a Because object
The easy way to use the library starts by creating an object which is an
instance of Because
. In normal situations where you want to use the
production deployment of the BCS APIs, you could just do something like this:
let bcs = new Because();
If you want to use the API deployed in the test environment (recommended for developers inside Boundless), you can specify that here:
let bcs = new Because("test");
If you want to use the API deployed in the dev environment (not recommended for anyone other than developers on BCS itself)
let bcs = new Because("dev");
To turn on "debug mode" (usually not needed unless you're developing the library itself), you can pass true as the second argument:
let bcs = new Because("dev", true);
Login
Once you have an instance of Because
, several services like routing and
geocoding require a token to work. The Because
object will automatically
fetch, cache and send that token if you use the login
method.
Like all of the methods that perform I/O operations in this library, login
returns a promise object, which has the standard Promises/A+ interface.
Here's a simple example of how you could create and use a promise from ES5:
var promise = bcs.login(username, password);
promise.then((jwt) => {
console.log("logged in with jwt", jwt);
});
or, if we want to dispense with the intermediate variable:
bcs.login(username, password).then((jwt) => {
console.log("logged in with jwt", jwt);
});
If you want to catch errors in the same ES5 style:
bcs.login(username, password).then((jwt) => {
console.log("logged in with jwt", jwt);
}).catch((error) => {
console.log("got an error", error);
});
Since the interface returns a promise, you can also use ES2017 async/await:
async myLogin(username, password) {
try {
let jwt = await bcs.login(username, password);
console.log("logged in with jwt", jwt);
}
catch (error) {
console.log("got an error", error);
}
}
In any case, once login is finished, you can access a list of roles for the logged in user.
This is automatically sent at every login, so it doesn't incur any additional
HTTP requests. It is also saved automatically as a property on the Because
instance.
bcs.login(username, password).then((jwt) => {
console.log("logged in with jwt roles", bcs.jwt.roles);
console.log("same as", jwt.roles);
});
Typical uses for this role list would include changing the GUI to reflect which options might be available or disabled for the logged-in user.
Routing
let start = "6100 Pennsylvania Ave, Washington, DC";
let mid = [38.862092, -76.959320];
let end = "3100 Pennsylvania Ave, Washington, DC";
let waypoints = [start, mid, end];
let provider = "mapbox";
let route = await bcs.routing.route(waypoints, provider);
If you are using ES5, you would use the promise's .then() method instead of
await
.
Geocoding
let address = "6100 Pennsylvania Ave, Washington, DC";
let provider = "mapbox";
let geocodes = await bcs.geocoding.geocode(address, provider);
If you are using ES5, you would use the promise's .then() method instead of
await
.
Reverse Geocoding
let lat = 0.0;
let lon = 0.0;
let loc = [lat, lon];
let provider = "mapbox";
let geocodes = await bcs.geocoding.reverse_geocode(loc, provider);
If you are using ES5, you would use the promise's .then() method instead of
await
.
Basemaps Discovery
let basemaps = await bcs.basemaps.basemaps();
If you are using ES5, you would use the promise's .then() method instead of
await
.
Search
let text = "geoserver";
let results = await bcs.search.search(text);
You can also pass a list of categories (special strings) as the second argument, to specify what categories to search in.
If you are using ES5, you would use the promise's .then() method instead of
await
.
Example Project
If you want to find some example code to look at, you can find an extended
usage example containing some demos under example/
.
See example/README.md
for instructions on how to run the example.
This example code uses ES2015, React, and material-ui, but all this is just to provide an example. As far as just using the library, you can use ES5 or ES2015 or TypeScript, and you can use any frontend frameworks or libraries you need.
How to build
To build because.js, you'll first need to install Node and npm if you don't have them already. You should also install Yarn if you don't have that.
All of the project's build tasks are automated with GNU make
.
If you just want to generally compile things and run tests, you can simply run:
make
To build a single bundle including all of the capabilities of because.js, run:
make dist/because.js
This should create the JS bundle dist/because.js
and the accompanying
sourcemap dist/because.js.map
. Only dist/because.js
is needed to use the
library, but it is useful for debugging to have the sourcemap available
alongside the bundle.
The file dist/because.js
is a UMD module. If
you don't know or care what that means, just know that you can just include it
in the normal <script>
way and this will define a global variable because
.
(If you don't want a global, then use a module loader.)
There are multiple ways to use because.js
(e.g. as CommonJS or ES2015
modules) but the bundle is the simplest, in the sense that it does not require
an additional build step or loader to get a result in the browser.
Build tools
This section is for those who are interested in the internal details of how the
project is built. If you just want to use because.js
, you can safely ignore
this section.
As already mentioned, the entry point and overall orchestration of build tasks
is done with GNU make
. For those who are unfamiliar, the essence of make
is that you edit Makefile
to define rules for making files. Each rule
has a list of other files that must exist as prerequisites, and some shell
commands that are run to actually make that file. make
then checks to see
which files to generate (based on which output files are older than the input
files they're based on) and resolves the order to build things in. It's a
time-tested, language-agnostic tool.
The project code located under src/
is written in
Typescript. Typescript is
a language very similar to Javascript (ES2015), except that it has type
annotations, and a few other minor features. The type checking particularly
helps control the propagation of funky values like undefined, null or NaN.
Importantly for our purposes, Typescript compiles to normal Javascript and is
generally pretty close to the semantics of Javascript.
Typescript code is both typechecked and compiled down to Javascript code using
the excellent Typescript compiler, tsc
. Options for tsc
are in files
conventionally named tsconfig.json
. For just one example, some of this
projects' tsconfig.json
files direct tsc
to look for Typescript .ts
files
under src/
, turn them into ES5 code packaged as CommonJS modules, and output
these as .js
files into lib/
.
For some capabilities, like promises, tsc
cannot generate downlevel code
(e.g. ES5) without the help of a polyfill. So we use a few polyfills,
like es6-promise
.
Other aspects like concatenation/bundling/minification are handled by webpack together with the loader plugin for webpack named ts-loader.
Build dependencies are managed by yarn
, which is a package management tool
similar to npm
.
Running Tests
Automated tests are discovered and run by mocha with
the help of ts-mocha to eliminate
most of the friction of doing mocha tests with Typescript. The tests are
written using chai using assert()
.
To kick off a run of the tests, use
make test
Test coverage is measured using istanbul via its command-line interface, nyc.
Running Style Checks
Code smells and style problems are detected in the Typescript source code using
tslint. This is a little more rigorous
than tsc
, also bringing in stylistic issues and usages that are likely to
indicate errors even if they do compile.
You can start a lint pass on the source code with
make lint
The options are conventionally in files called tslint.json
. In this project,
tslint configuration is kept in two files for slightly different purposes:
config/tslint.json
is specifies a base set of syntactic rules that can be run
quickly, while config/tslint.typechecked.json
is a superset of those rules
which adds rules that require type checking, and therefore take a little more
time and require a little more configuration.
Building Docs
API reference docs are built using Typedoc.
These docs would be of little use to anyone except people working on because.js.
As of this writing, this build configuration is currently not working.
The configuration for typedoc is in config/typedoc.json
and depends on the
base config/tsconfig.json
. Typedoc can be finicky about the locations of
configuration files, so be careful about making changes.
Watch
Run make watch
to start a process that watches the source tree for changes
and triggers rebuilds of dist/because.js
(via wepack). This is useful if you
are making edits to the library in src/
.