Skip to content

Commit

Permalink
Merge pull request #86 from mapbox/flex-setup
Browse files Browse the repository at this point in the history
Defer mapboxgl dependency to runtime
  • Loading branch information
tmcw authored Aug 17, 2016
2 parents 5001f62 + 5af10a2 commit 817710c
Show file tree
Hide file tree
Showing 8 changed files with 2,431 additions and 3,122 deletions.
5,079 changes: 2,020 additions & 3,059 deletions dist/mapbox-gl-directions.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
<head>
<title></title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.mapbox.com/mapbox-gl-js/v0.20.0/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v0.20.0/mapbox-gl.css' rel='stylesheet' />
<link href='../dist/mapbox-gl-directions.css' rel='stylesheet' />
<link href='https://api.mapbox.com/mapbox-gl-js/v0.20.0/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin:0; padding:0; }
.map {
Expand All @@ -20,6 +19,7 @@
<body>
<div id='map' class='map'></div>
<div id='directions'></div>
<script src='https://api.mapbox.com/mapbox-gl-js/v0.20.0/mapbox-gl.js'></script>
<script src='bundle.js'></script>
</body>
</html>
3 changes: 2 additions & 1 deletion example/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';
/* global mapboxgl */

require('../index');
var Directions = require('../index');

mapboxgl.accessToken = window.localStorage.getItem('MapboxAccessToken');

var map = new mapboxgl.Map({
Expand Down
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"scripts": {
"prepublish": "npm run build",
"start": "NODE_ENV=development budo example/index.js:example/bundle.js --live",
"build": "NODE_ENV=production browserify index.js > dist/mapbox-gl-directions.js",
"build": "NODE_ENV=production browserify -s mapboxDirections index.js > dist/mapbox-gl-directions.js",
"test": "NODE_ENV=test npm run lint && browserify test/index.js | smokestack -b firefox | tap-status",
"docs": "documentation build --format=md > API.md",
"lint": "eslint --no-eslintrc -c .eslintrc index.js src"
Expand Down Expand Up @@ -45,20 +45,25 @@
"budo": "^8.3.0",
"documentation": "^4.0.0-beta5",
"eslint": "^2.13.1",
"json-loader": "0.5.4",
"lodash.once": "^4.0.0",
"mapbox-gl": "^0.20.1",
"smokestack": "^3.3.1",
"tap-status": "^1.0.1",
"tape": "^4.6.0"
"tape": "^4.6.0",
"transform-loader": "0.2.3",
"webpack": "1.13.1",
"webworkify-webpack": "1.1.7"
},
"dependencies": {
"lodash.debounce": "^4.0.6",
"lodash.isequal": "^4.2.0",
"lodash.template": "^4.2.5",
"mapbox-gl-geocoder": "^1.3.0",
"polyline": "^0.2.0",
"redux": "^3.5.2",
"redux-thunk": "^2.1.0",
"turf-extent": "^1.0.4"
"suggestions": "1.3.0",
"turf-extent": "^1.0.4",
"xtend": "4.0.1"
}
}
313 changes: 313 additions & 0 deletions src/controls/geocoder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
'use strict';

var Typeahead = require('suggestions');
var debounce = require('lodash.debounce');
var extend = require('xtend');
var EventEmitter = require('events').EventEmitter;

// Mapbox Geocoder version
var API = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';

/**
* A geocoder component using Mapbox Geocoding API
* @class mapboxgl.Geocoder
*
* @param {Object} options
* @param {String} [options.position="top-right"] A string indicating the control's position on the map. Options are `top-right`, `top-left`, `bottom-right`, `bottom-left`
* @param {String} [options.accessToken=null] Required unless `mapboxgl.accessToken` is set globally
* @param {string|element} options.container The HTML element to append the Geocoder input to. if container is not specified, `map.getcontainer()` is used.
* @param {Array<number>} options.proximity If set, search results closer to these coordinates will be given higher priority.
* @param {Array<number>} options.bbox Limit results to a given bounding box provided as `[minX, minY, maxX, maxY]`.
* @param {Number} [options.zoom=16] On geocoded result what zoom level should the map animate to.
* @param {Boolean} [options.flyTo=true] If false, animating the map to a selected result is disabled.
* @param {String} [options.placeholder="Search"] Override the default placeholder attribute value.
* @param {string} options.types a comma seperated list of types that filter results to match those specified. See https://www.mapbox.com/developers/api/geocoding/#filter-type for available types.
* @param {string} options.country a comma seperated list of country codes to limit results to specified country or countries.
* @example
* var geocoder = new mapboxgl.Geocoder();
* map.addControl(geocoder);
* @return {Geocoder} `this`
*/
function Geocoder(options) {
this._ev = new EventEmitter();
this.options = extend({}, this.options, options);
}

Geocoder.prototype = {

options: {
position: 'top-left',
placeholder: 'Search',
zoom: 16,
flyTo: true
},

addTo: function (map) {
this._map = map;
var container = this._container = this.onAdd(map);
if (this.options && this.options.position) {
var pos = this.options.position;
var corner = map._controlCorners[pos];
container.className += ' mapboxgl-ctrl';
if (pos.indexOf('bottom') !== -1) {
corner.insertBefore(container, corner.firstChild);
} else {
corner.appendChild(container);
}
}

return this;
},

onAdd: function(map) {
this.request = new XMLHttpRequest();

this.container = this.options.container ?
typeof this.options.container === 'string' ?
document.getElementById(this.options.container) :
this.options.container :
map.getContainer();

// Template
var el = document.createElement('div');
el.className = 'mapboxgl-ctrl-geocoder';

var icon = document.createElement('span');
icon.className = 'geocoder-icon geocoder-icon-search';

var input = this._inputEl = document.createElement('input');
input.type = 'text';
input.placeholder = this.options.placeholder;

input.addEventListener('keydown', debounce(function(e) {
if (!e.target.value) return this._clearEl.classList.remove('active');

// TAB, ESC, LEFT, RIGHT, ENTER, UP, DOWN
if (e.metaKey || [9, 27, 37, 39, 13, 38, 40].indexOf(e.keyCode) !== -1) return;
this._queryFromInput(e.target.value);
}.bind(this)), 200);

input.addEventListener('change', function(e) {
if (e.target.value) this._clearEl.classList.add('active');

var selected = this._typeahead.selected;
if (selected) {
if (this.options.flyTo) {
if (selected.bbox && selected.context && selected.context.length <= 3 ||
selected.bbox && !selected.context) {
var bbox = selected.bbox;
map.fitBounds([[bbox[0], bbox[1]],[bbox[2], bbox[3]]]);
} else {
map.flyTo({
center: selected.center,
zoom: this.options.zoom
});
}
}
this._input = selected;
this.fire('result', { result: selected });
}
}.bind(this));

var actions = document.createElement('div');
actions.classList.add('geocoder-pin-right');

var clear = this._clearEl = document.createElement('button');
clear.className = 'geocoder-icon geocoder-icon-close';
clear.addEventListener('click', this._clear.bind(this));

var loading = this._loadingEl = document.createElement('span');
loading.className = 'geocoder-icon geocoder-icon-loading';

actions.appendChild(clear);
actions.appendChild(loading);

el.appendChild(icon);
el.appendChild(input);
el.appendChild(actions);

this.container.appendChild(el);

// Override the control being added to control containers
if (this.options.container) this.options.position = false;

this._typeahead = new Typeahead(input, [], { filter: false });
this._typeahead.getItemValue = function(item) { return item.place_name; };

return el;
},

_geocode: function(q, callback) {
this._loadingEl.classList.add('active');
this.fire('loading');

var options = [];
if (this.options.proximity) options.push('proximity=' + this.options.proximity.join());
if (this.options.bbox) options.push('bbox=' + this.options.bbox.join());
if (this.options.country) options.push('country=' + this.options.country);
if (this.options.types) options.push('types=' + this.options.types);

var accessToken = this.options.accessToken ? this.options.accessToken : mapboxgl.accessToken;
options.push('access_token=' + accessToken);

this.request.abort();
this.request.open('GET', API + encodeURIComponent(q.trim()) + '.json?' + options.join('&'), true);
this.request.onload = function() {
this._loadingEl.classList.remove('active');
if (this.request.status >= 200 && this.request.status < 400) {
var data = JSON.parse(this.request.responseText);
if (data.features.length) {
this._clearEl.classList.add('active');
} else {
this._clearEl.classList.remove('active');
this._typeahead.selected = null;
}

this.fire('results', { results: data.features });
this._typeahead.update(data.features);
return callback(data.features);
} else {
this.fire('error', { error: JSON.parse(this.request.responseText).message });
}
}.bind(this);

this.request.onerror = function() {
this._loadingEl.classList.remove('active');
this.fire('error', { error: JSON.parse(this.request.responseText).message });
}.bind(this);

this.request.send();
},

_queryFromInput: function(q) {
q = q.trim();
if (!q) this._clear();
if (q.length > 2) {
this._geocode(q, function(results) {
this._results = results;
}.bind(this));
}
},

_change: function() {
var onChange = document.createEvent('HTMLEvents');
onChange.initEvent('change', true, false);
this._inputEl.dispatchEvent(onChange);
},

_query: function(input) {
if (!input) return;
if (typeof input === 'object' && input.length) {
input = [
mapboxgl.util.wrap(input[0], -180, 180),
mapboxgl.util.wrap(input[1], -180, 180)
].join();
}

this._geocode(input, function(results) {
if (!results.length) return;
var result = results[0];
this._results = results;
this._typeahead.selected = result;
this._inputEl.value = result.place_name;
this._change();
}.bind(this));
},

_setInput: function(input) {
if (!input) return;
if (typeof input === 'object' && input.length) {
input = [
mapboxgl.util.wrap(input[0], -180, 180),
mapboxgl.util.wrap(input[1], -180, 180)
].join();
}

// Set input value to passed value and clear everything else.
this._inputEl.value = input;
this._input = null;
this._typeahead.selected = null;
this._typeahead.clear();
this._change();
},

_clear: function() {
this._input = null;
this._inputEl.value = '';
this._typeahead.selected = null;
this._typeahead.clear();
this._change();
this._inputEl.focus();
this._clearEl.classList.remove('active');
this.fire('clear');
},

/**
* Return the input
* @returns {Object} input
*/
getResult: function() {
return this._input;
},

/**
* Set & query the input
* @param {Array|String} query An array of coordinates [lng, lat] or location name as a string.
* @returns {Geocoder} this
*/
query: function(query) {
this._query(query);
return this;
},

/**
* Set input
* @param {Array|String} value An array of coordinates [lng, lat] or location name as a string. Calling this function just sets the input and does not trigger an API request.
* @returns {Geocoder} this
*/
setInput: function(value) {
this._setInput(value);
return this;
},

/**
* Subscribe to events that happen within the plugin.
* @param {String} type name of event. Available events and the data passed into their respective event objects are:
*
* - __clear__ `Emitted when the input is cleared`
* - __loading__ `Emitted when the geocoder is looking up a query`
* - __results__ `{ results } Fired when the geocoder returns a response`
* - __result__ `{ result } Fired when input is set`
* - __error__ `{ error } Error as string
* @param {Function} fn function that's called when the event is emitted.
* @returns {Geocoder} this;
*/
on: function(type, fn) {
this._ev.on(type, fn);
return this;
},

/**
* Fire an event
* @param {String} type event name.
* @param {Object} data event data to pass to the function subscribed.
* @returns {Geocoder} this
*/
fire: function(type, data) {
this._ev.emit(type, data);
return this;
},

/**
* Remove an event
* @returns {Geocoder} this
* @param {String} type Event name.
* @param {Function} fn Function that should unsubscribe to the event emitted.
*/
off: function(type, fn) {
this._ev.removeListener(type, fn);
return this;
}
};

module.exports = Geocoder;
Loading

0 comments on commit 817710c

Please sign in to comment.