Skip to content

Instantly share code, notes, and snippets.

@renauld94
Last active November 2, 2018 08:10
Show Gist options
  • Save renauld94/70697d1b6b56c16c4bc15559700a645b to your computer and use it in GitHub Desktop.
Save renauld94/70697d1b6b56c16c4bc15559700a645b to your computer and use it in GitHub Desktop.
L.FunctionButtons
license: mit
.leaflet-bar button,
.leaflet-bar button:hover {
background-color: #fff;
border: none;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar button {
background-position: 50% 50%;
background-repeat: no-repeat;
overflow: hidden;
display: block;
}
.leaflet-bar button:hover {
background-color: #f4f4f4;
}
.leaflet-bar button:first-of-type {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar button:last-of-type {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar.disabled,
.leaflet-bar button.disabled {
cursor: default;
pointer-events: none;
opacity: .4;
}
.easy-button-button .button-state{
display: block;
width: 100%;
height: 100%;
position: relative;
}
.leaflet-touch .leaflet-bar button {
width: 30px;
height: 30px;
line-height: 30px;
}
import * as L from 'leaflet'
import {ControlPosition} from 'leaflet';
declare module 'leaflet' {
/**
* Creates a bar that holds a group of EasyButtons
* @param buttons array of EasyButtons that will be grouped together in the EasyBar
* @param options
*/
function easyBar(buttons: Control.EasyButton[], options?: EasyBarOptions): Control.EasyBar;
/**
* Creates a easyButton
* @param icon e.g. fa-globe
* @param onClick the button click handler
* @param title title on the button
* @param id an id to tag the button with
* @example
* var helloPopup = L.popup().setContent('Hello World!');
*
* L.easyButton('fa-globe', function(btn, map){
* helloPopup.setLatLng(map.getCenter()).openOn(map);
* }).addTo( YOUR_LEAFLET_MAP );
*/
function easyButton(icon: string,
onClick: (btn: Control.EasyButton, map: L.Map) => void,
title?: string,
id?: string): Control.EasyButton;
/**
* Creates a easyButton
* @param options the options object
* @example
*
*
* L.easyButton({
* position: 'topleft',
* leafletClasses: true,
* states: [
* {
* stateName: 'center',
* onClick: function(btn, map){},
* title: 'Get Center',
* icon: 'fa-globe'
* }
* ]
* }).addTo( YOUR_LEAFLET_MAP );
*/
function easyButton(options: EasyButtonOptions): Control.EasyButton;
interface EasyBarOptions {
position?: ControlPosition
id?: string
leafletClasses?: boolean
}
interface EasyButtonOptions {
position?: ControlPosition
id?: string
type?: 'replace' | 'animate'
states?: EasyButtonState[]
leafletClasses?: boolean
tagName?: string
}
interface EasyButtonState {
stateName: string
onClick: (btn: L.Control.EasyButton, map: L.Map) => void
title: string
icon: string
}
namespace Control {
class EasyButton extends L.Control {
constructor(options?: EasyButtonOptions)
state(stateName: string): EasyButton
enable(): void
disable(): void
}
class EasyBar extends L.Control {
constructor(options?: EasyBarOptions)
}
}
}
/*
* A leaflet button with icon or text and click listener.
*/
L.FunctionButtons = L.Control.extend({
includes: L.Mixin.Events,
initialize: function( buttons, options ) {
if( !('push' in buttons && 'splice' in buttons) )
buttons = [buttons];
this._buttons = buttons;
if( !options && buttons.length > 0 && 'position' in buttons[0] )
options = { position: buttons[0].position };
L.Control.prototype.initialize.call(this, options);
},
onAdd: function( map ) {
this._map = map;
this._links = [];
var container = L.DomUtil.create('div', 'leaflet-bar');
for( var i = 0; i < this._buttons.length; i++ ) {
var button = this._buttons[i],
link = L.DomUtil.create('a', '', container);
link._buttonIndex = i; // todo: remove?
link.href = button.href || '#';
if( button.href )
link.target = 'funcbtn';
link.style.padding = '0 4px';
link.style.width = 'auto';
link.style.minWidth = '20px';
if( button.bgColor )
link.style.backgroundColor = button.bgColor;
if( button.title )
link.title = button.title;
button.link = link;
this._updateContent(i);
var stop = L.DomEvent.stopPropagation;
L.DomEvent
.on(link, 'click', stop)
.on(link, 'mousedown', stop)
.on(link, 'dblclick', stop);
if( !button.href )
L.DomEvent
.on(link, 'click', L.DomEvent.preventDefault)
.on(link, 'click', this.clicked, this);
}
return container;
},
_updateContent: function( n ) {
if( n >= this._buttons.length )
return;
var button = this._buttons[n],
link = button.link,
content = button.content;
if( !link )
return;
if( content === undefined || content === false || content === null || content === '' )
link.innerHTML = button.alt || '&nbsp;';
else if( typeof content === 'string' ) {
var ext = content.length < 4 ? '' : content.substring(content.length - 4),
isData = content.substring(0, 11) === 'data:image/';
if( ext === '.png' || ext === '.gif' || ext === '.jpg' || isData ) {
link.style.width = '' + (button.imageSize || 26) + 'px';
link.style.height = '' + (button.imageSize || 26) + 'px';
link.style.padding = '0';
link.style.backgroundImage = 'url(' + content + ')';
link.style.backgroundRepeat = 'no-repeat';
link.style.backgroundPosition = button.bgPos ? (-button.bgPos[0]) + 'px ' + (-button.bgPos[1]) + 'px' : '0px 0px';
} else
link.innerHTML = content;
} else {
while( link.firstChild )
link.removeChild(link.firstChild);
link.appendChild(content);
}
},
setContent: function( n, content ) {
if( content === undefined ) {
content = n;
n = 0;
}
if( n < this._buttons.length ) {
this._buttons[n].content = content;
this._updateContent(n);
}
},
setTitle: function( n, title ) {
if( title === undefined ) {
title = n;
n = 0;
}
if( n < this._buttons.length ) {
var button = this._buttons[n];
button.title = title;
if( button.link )
button.link.title = title;
}
},
setBgPos: function( n, bgPos ) {
if( bgPos === undefined ) {
bgPos = n;
n = 0;
}
if( n < this._buttons.length ) {
var button = this._buttons[n];
button.bgPos = bgPos;
if( button.link )
button.link.style.backgroundPosition = bgPos ? (-bgPos[0]) + 'px ' + (-bgPos[1]) + 'px' : '0px 0px';
}
},
setHref: function( n, href ) {
if( href === undefined ) {
href = n;
n = 0;
}
if( n < this._buttons.length ) {
var button = this._buttons[n];
button.href = href;
if( button.link )
button.link.href = href;
}
},
clicked: function(e) {
var link = (window.event && window.event.srcElement) || e.target || e.srcElement;
while( link && 'tagName' in link && link.tagName !== 'A' && !('_buttonIndex' in link ) )
link = link.parentNode;
if( '_buttonIndex' in link ) {
var button = this._buttons[link._buttonIndex];
if( button ) {
if( 'callback' in button )
button.callback.call(button.context);
this.fire('clicked', { idx: link._buttonIndex });
}
}
}
});
L.functionButtons = function( buttons, options ) {
return new L.FunctionButtons(buttons, options);
};
/*
* Helper method from the old class. It is not recommended to use it, please use L.functionButtons().
*/
L.functionButton = function( content, button, options ) {
if( button )
button.content = content;
else
button = { content: content };
return L.functionButtons([button], options);
};
<!DOCTYPE html>
<html>
<head>
<title>GeoJSON with Leaflet + D3 using L.D3SvgOverlay</title>
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map-canvas { height: 100% }
</style>
<link href='https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.css'
rel='stylesheet' type='text/css'/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
</head>
<body>
<div id="map-canvas"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.9/d3.min.js"></script>
<script src="L.D3SvgOverlay.min.js"></script>
<script src="leaflet/label.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.css">
<script src="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.js"></script>
<script>
//change for open streetmaps or multiselection #an attribution is obligatory as per the copyright notice
var map = L.map("map-canvas", {center: [10.762622, 106.660172], zoom: 11});
var tiles = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Sanisphere&copy'
});
tiles.addTo(map);
</script>
</body>
</html>
(function(){
// This is for grouping buttons into a bar
// takes an array of `L.easyButton`s and
// then the usual `.addTo(map)`
L.Control.EasyBar = L.Control.extend({
options: {
position: 'topleft', // part of leaflet's defaults
id: null, // an id to tag the Bar with
leafletClasses: true // use leaflet classes?
},
initialize: function(buttons, options){
if(options){
L.Util.setOptions( this, options );
}
this._buildContainer();
this._buttons = [];
for(var i = 0; i < buttons.length; i++){
buttons[i]._bar = this;
buttons[i]._container = buttons[i].button;
this._buttons.push(buttons[i]);
this.container.appendChild(buttons[i].button);
}
},
_buildContainer: function(){
this._container = this.container = L.DomUtil.create('div', '');
this.options.leafletClasses && L.DomUtil.addClass(this.container, 'leaflet-bar easy-button-container leaflet-control');
this.options.id && (this.container.id = this.options.id);
},
enable: function(){
L.DomUtil.addClass(this.container, 'enabled');
L.DomUtil.removeClass(this.container, 'disabled');
this.container.setAttribute('aria-hidden', 'false');
return this;
},
disable: function(){
L.DomUtil.addClass(this.container, 'disabled');
L.DomUtil.removeClass(this.container, 'enabled');
this.container.setAttribute('aria-hidden', 'true');
return this;
},
onAdd: function () {
return this.container;
},
addTo: function (map) {
this._map = map;
for(var i = 0; i < this._buttons.length; i++){
this._buttons[i]._map = map;
}
var container = this._container = this.onAdd(map),
pos = this.getPosition(),
corner = map._controlCorners[pos];
L.DomUtil.addClass(container, 'leaflet-control');
if (pos.indexOf('bottom') !== -1) {
corner.insertBefore(container, corner.firstChild);
} else {
corner.appendChild(container);
}
return this;
}
});
L.easyBar = function(){
var args = [L.Control.EasyBar];
for(var i = 0; i < arguments.length; i++){
args.push( arguments[i] );
}
return new (Function.prototype.bind.apply(L.Control.EasyBar, args));
};
// L.EasyButton is the actual buttons
// can be called without being grouped into a bar
L.Control.EasyButton = L.Control.extend({
options: {
position: 'topleft', // part of leaflet's defaults
id: null, // an id to tag the button with
type: 'replace', // [(replace|animate)]
// replace swaps out elements
// animate changes classes with all elements inserted
states: [], // state names look like this
// {
// stateName: 'untracked',
// onClick: function(){ handle_nav_manually(); };
// title: 'click to make inactive',
// icon: 'fa-circle', // wrapped with <a>
// }
leafletClasses: true, // use leaflet styles for the button
tagName: 'button',
},
initialize: function(icon, onClick, title, id){
// clear the states manually
this.options.states = [];
// add id to options
if(id != null){
this.options.id = id;
}
// storage between state functions
this.storage = {};
// is the last item an object?
if( typeof arguments[arguments.length-1] === 'object' ){
// if so, it should be the options
L.Util.setOptions( this, arguments[arguments.length-1] );
}
// if there aren't any states in options
// use the early params
if( this.options.states.length === 0 &&
typeof icon === 'string' &&
typeof onClick === 'function'){
// turn the options object into a state
this.options.states.push({
icon: icon,
onClick: onClick,
title: typeof title === 'string' ? title : ''
});
}
// curate and move user's states into
// the _states for internal use
this._states = [];
for(var i = 0; i < this.options.states.length; i++){
this._states.push( new State(this.options.states[i], this) );
}
this._buildButton();
this._activateState(this._states[0]);
},
_buildButton: function(){
this.button = L.DomUtil.create(this.options.tagName, '');
if (this.options.tagName === 'button') {
this.button.setAttribute('type', 'button');
}
if (this.options.id ){
this.button.id = this.options.id;
}
if (this.options.leafletClasses){
L.DomUtil.addClass(this.button, 'easy-button-button leaflet-bar-part leaflet-interactive');
}
// don't let double clicks and mousedown get to the map
L.DomEvent.addListener(this.button, 'dblclick', L.DomEvent.stop);
L.DomEvent.addListener(this.button, 'mousedown', L.DomEvent.stop);
L.DomEvent.addListener(this.button, 'mouseup', L.DomEvent.stop);
// take care of normal clicks
L.DomEvent.addListener(this.button,'click', function(e){
L.DomEvent.stop(e);
this._currentState.onClick(this, this._map ? this._map : null );
this._map && this._map.getContainer().focus();
}, this);
// prep the contents of the control
if(this.options.type == 'replace'){
this.button.appendChild(this._currentState.icon);
} else {
for(var i=0;i<this._states.length;i++){
this.button.appendChild(this._states[i].icon);
}
}
},
_currentState: {
// placeholder content
stateName: 'unnamed',
icon: (function(){ return document.createElement('span'); })()
},
_states: null, // populated on init
state: function(newState){
// activate by name
if(typeof newState == 'string'){
this._activateStateNamed(newState);
// activate by index
} else if (typeof newState == 'number'){
this._activateState(this._states[newState]);
}
return this;
},
_activateStateNamed: function(stateName){
for(var i = 0; i < this._states.length; i++){
if( this._states[i].stateName == stateName ){
this._activateState( this._states[i] );
}
}
},
_activateState: function(newState){
if( newState === this._currentState ){
// don't touch the dom if it'll just be the same after
return;
} else {
// swap out elements... if you're into that kind of thing
if( this.options.type == 'replace' ){
this.button.appendChild(newState.icon);
this.button.removeChild(this._currentState.icon);
}
if( newState.title ){
this.button.title = newState.title;
} else {
this.button.removeAttribute('title');
}
// update classes for animations
for(var i=0;i<this._states.length;i++){
L.DomUtil.removeClass(this._states[i].icon, this._currentState.stateName + '-active');
L.DomUtil.addClass(this._states[i].icon, newState.stateName + '-active');
}
// update classes for animations
L.DomUtil.removeClass(this.button, this._currentState.stateName + '-active');
L.DomUtil.addClass(this.button, newState.stateName + '-active');
// update the record
this._currentState = newState;
}
},
enable: function(){
L.DomUtil.addClass(this.button, 'enabled');
L.DomUtil.removeClass(this.button, 'disabled');
this.button.setAttribute('aria-hidden', 'false');
return this;
},
disable: function(){
L.DomUtil.addClass(this.button, 'disabled');
L.DomUtil.removeClass(this.button, 'enabled');
this.button.setAttribute('aria-hidden', 'true');
return this;
},
onAdd: function(map){
var bar = L.easyBar([this], {
position: this.options.position,
leafletClasses: this.options.leafletClasses
});
this._anonymousBar = bar;
this._container = bar.container;
return this._anonymousBar.container;
},
removeFrom: function (map) {
if (this._map === map)
this.remove();
return this;
},
});
L.easyButton = function(/* args will pass automatically */){
var args = Array.prototype.concat.apply([L.Control.EasyButton],arguments);
return new (Function.prototype.bind.apply(L.Control.EasyButton, args));
};
/*************************
*
* util functions
*
*************************/
// constructor for states so only curated
// states end up getting called
function State(template, easyButton){
this.title = template.title;
this.stateName = template.stateName ? template.stateName : 'unnamed-state';
// build the wrapper
this.icon = L.DomUtil.create('span', '');
L.DomUtil.addClass(this.icon, 'button-state state-' + this.stateName.replace(/(^\s*|\s*$)/g,''));
this.icon.innerHTML = buildIcon(template.icon);
this.onClick = L.Util.bind(template.onClick?template.onClick:function(){}, easyButton);
}
function buildIcon(ambiguousIconString) {
var tmpIcon;
// does this look like html? (i.e. not a class)
if( ambiguousIconString.match(/[&;=<>"']/) ){
// if so, the user should have put in html
// so move forward as such
tmpIcon = ambiguousIconString;
// then it wasn't html, so
// it's a class list, figure out what kind
} else {
ambiguousIconString = ambiguousIconString.replace(/(^\s*|\s*$)/g,'');
tmpIcon = L.DomUtil.create('span', '');
if( ambiguousIconString.indexOf('fa-') === 0 ){
L.DomUtil.addClass(tmpIcon, 'fa ' + ambiguousIconString)
} else if ( ambiguousIconString.indexOf('glyphicon-') === 0 ) {
L.DomUtil.addClass(tmpIcon, 'glyphicon ' + ambiguousIconString)
} else {
L.DomUtil.addClass(tmpIcon, /*rollwithit*/ ambiguousIconString)
}
// make this a string so that it's easy to set innerHTML below
tmpIcon = tmpIcon.outerHTML;
}
return tmpIcon;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment