Skip to content

Commit

Permalink
Allow customizable hash fragment for map hash #8596 (#8603)
Browse files Browse the repository at this point in the history
  • Loading branch information
SebCorbin authored and Ryan Hamley committed Oct 16, 2019
1 parent 7ce6aa6 commit 6c7bd20
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 11 deletions.
46 changes: 40 additions & 6 deletions src/ui/hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ import type Map from './map';
class Hash {
_map: Map;
_updateHash: () => ?TimeoutID;
_hashName: ?string;

constructor() {
constructor(hashName: ?string) {
this._hashName = hashName && encodeURIComponent(hashName);
bindAll([
'_getCurrentHash',
'_onHashChange',
'_updateHash'
], this);
Expand Down Expand Up @@ -67,19 +70,50 @@ class Hash {
if (mapFeedback) {
// new map feedback site has some constraints that don't allow
// us to use the same hash format as we do for the Map hash option.
hash += `#/${lng}/${lat}/${zoom}`;
hash += `/${lng}/${lat}/${zoom}`;
} else {
hash += `#${zoom}/${lat}/${lng}`;
hash += `${zoom}/${lat}/${lng}`;
}

if (bearing || pitch) hash += (`/${Math.round(bearing * 10) / 10}`);
if (pitch) hash += (`/${Math.round(pitch)}`);
return hash;

if (this._hashName) {
const hashName = this._hashName;
let found = false;
const parts = window.location.hash.slice(1).split('&').map(part => {
const key = part.split('=')[0];
if (key === hashName) {
found = true;
return `${key}=${hash}`;
}
return part;
}).filter(a => a);
if (!found) {
parts.push(`${hashName}=${hash}`);
}
return `#${parts.join('&')}`;
}

return `#${hash}`;
}

_getCurrentHash() {
// Get the current hash from location, stripped from its number sign
const hash = window.location.hash.replace('#', '');
if (this._hashName) {
// Split the parameter-styled hash into parts and find the value we need
const keyval = hash.split('&').map(
part => part.split('=')
).find(part => part[0] === this._hashName);
return (keyval ? keyval[1] || '' : '').split('/');
}
return hash.split('/');
}

_onHashChange() {
const loc = window.location.hash.replace('#', '').split('/');
if (loc.length >= 3) {
const loc = this._getCurrentHash();
if (loc.length >= 3 && !loc.some(v => isNaN(v))) {
this._map.jumpTo({
center: [+loc[2], +loc[1]],
zoom: +loc[0],
Expand Down
10 changes: 7 additions & 3 deletions src/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type IControl = {
/* eslint-enable no-use-before-define */

type MapOptions = {
hash?: boolean,
hash?: boolean | string,
interactive?: boolean,
container: HTMLElement | string,
bearingSnap?: number,
Expand Down Expand Up @@ -172,8 +172,11 @@ const defaultOptions = {
* Tilesets hosted with Mapbox can be style-optimized if you append `?optimize=true` to the end of your style URL, like `mapbox://styles/mapbox/streets-v9?optimize=true`.
* Learn more about style-optimized vector tiles in our [API documentation](https://www.mapbox.com/api-documentation/maps/#retrieve-tiles).
*
* @param {boolean} [options.hash=false] If `true`, the map's position (zoom, center latitude, center longitude, bearing, and pitch) will be synced with the hash fragment of the page's URL.
* @param {(boolean|string)} [options.hash=false] If `true`, the map's position (zoom, center latitude, center longitude, bearing, and pitch) will be synced with the hash fragment of the page's URL.
* For example, `http://path/to/my/page.html#2.59/39.26/53.07/-24.1/60`.
* An additional string may optionally be provided to indicate a parameter-styled hash,
* e.g. http://path/to/my/page.html#map=2.59/39.26/53.07/-24.1/60&foo=bar, where foo
* is a custom parameter and bar is an arbitrary hash distinct from the map hash.
* @param {boolean} [options.interactive=true] If `false`, no mouse, touch, or keyboard listeners will be attached to the map, so it will not respond to interaction.
* @param {number} [options.bearingSnap=7] The threshold, measured in degrees, that determines when the map's
* bearing will snap to north. For example, with a `bearingSnap` of 7, if the user rotates
Expand Down Expand Up @@ -389,7 +392,8 @@ class Map extends Camera {

bindHandlers(this, options);

this._hash = options.hash && (new Hash()).addTo(this);
const hashName = (typeof options.hash === 'string' && options.hash) || undefined;
this._hash = options.hash && (new Hash(hashName)).addTo(this);
// don't set position from options if set through hash
if (!this._hash || !this._hash._onHashChange()) {
this.jumpTo({
Expand Down
129 changes: 127 additions & 2 deletions test/unit/ui/hash.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import window from '../../../src/util/window';
import {createMap as globalCreateMap} from '../../util';

test('hash', (t) => {
function createHash() {
const hash = new Hash();
function createHash(name) {
const hash = new Hash(name);
hash._updateHash = hash._updateHashUnthrottled.bind(hash);
return hash;
}
Expand Down Expand Up @@ -67,6 +67,14 @@ test('hash', (t) => {
t.equal(map.getBearing(), 30);
t.equal(map.getPitch(), 60);

window.location.hash = '#4/wrongly/formed/hash';

t.false(hash._onHashChange());

window.location.hash = '#map=10/3.00/-1.00&foo=bar';

t.false(hash._onHashChange());

window.location.hash = '';

t.end();
Expand Down Expand Up @@ -100,6 +108,82 @@ test('hash', (t) => {
t.end();
});

t.test('#_onHashChange named', (t) => {
const map = createMap(t);
const hash = createHash('map')
.addTo(map);

window.location.hash = '#map=10/3.00/-1.00&foo=bar';

hash._onHashChange();

t.equal(map.getCenter().lng, -1);
t.equal(map.getCenter().lat, 3);
t.equal(map.getZoom(), 10);
t.equal(map.getBearing(), 0);
t.equal(map.getPitch(), 0);

window.location.hash = '#map&foo=bar';

t.false(hash._onHashChange());

window.location.hash = '#map=4/5/baz&foo=bar';

t.false(hash._onHashChange());

window.location.hash = '#5/1.00/0.50/30/60';

t.false(hash._onHashChange());

window.location.hash = '';

t.end();
});

t.test('#_getCurrentHash', (t) => {
const map = createMap(t);
const hash = createHash()
.addTo(map);

window.location.hash = '#10/3.00/-1.00';

const currentHash = hash._getCurrentHash();

t.equal(currentHash[0], '10');
t.equal(currentHash[1], '3.00');
t.equal(currentHash[2], '-1.00');

window.location.hash = '';

t.end();
});

t.test('#_getCurrentHash named', (t) => {
const map = createMap(t);
const hash = createHash('map')
.addTo(map);

window.location.hash = '#map=10/3.00/-1.00&foo=bar';

let currentHash = hash._getCurrentHash();

t.equal(currentHash[0], '10');
t.equal(currentHash[1], '3.00');
t.equal(currentHash[2], '-1.00');

window.location.hash = '#baz&map=10/3.00/-1.00';

currentHash = hash._getCurrentHash();

t.equal(currentHash[0], '10');
t.equal(currentHash[1], '3.00');
t.equal(currentHash[2], '-1.00');

window.location.hash = '';

t.end();
});

t.test('#_updateHash', (t) => {
function getHash() {
return window.location.hash.split('/');
Expand Down Expand Up @@ -145,6 +229,47 @@ test('hash', (t) => {
t.equal(newHash[3], '135');
t.equal(newHash[4], '60');

window.location.hash = '';

t.end();
});

t.test('#_updateHash named', (t) => {
const map = createMap(t);
createHash('map')
.addTo(map);

t.notok(window.location.hash);

map.setZoom(3);
map.setCenter([1.0, 2.0]);

t.ok(window.location.hash);

t.equal(window.location.hash, '#map=3/2/1');

map.setPitch(60);

t.equal(window.location.hash, '#map=3/2/1/0/60');

map.setBearing(135);

t.equal(window.location.hash, '#map=3/2/1/135/60');

window.location.hash += '&foo=bar';

map.setZoom(7);

t.equal(window.location.hash, '#map=7/2/1/135/60&foo=bar');

window.location.hash = '#baz&map=7/2/1/135/60&foo=bar';

map.setCenter([2.0, 1.0]);

t.equal(window.location.hash, '#baz&map=7/1/2/135/60&foo=bar');

window.location.hash = '';

t.end();
});

Expand Down

0 comments on commit 6c7bd20

Please sign in to comment.