├── .gitignore ├── LICENSE ├── README.md ├── USAGE.md ├── bower.json ├── package.json └── src ├── easy-button.css ├── easy-button.d.ts └── easy-button.js /.gitignore: -------------------------------------------------------------------------------- 1 | /index.html 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Daniel Montague 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # L.EasyButton 2 | 3 | The easiest way to add buttons with Leaflet — so simple it fits in a gif: 4 | 5 | ![running demo](https://raw.githubusercontent.com/CliffCloud/Leaflet.EasyButton/dist/img/alert_example.gif) 6 | 7 | ### More [running examples and docs](http://danielmontague.com/projects/easyButton.js/v2/examples/) 8 | 9 | ----------------------------------------------------------------------------------- 10 | 11 | ## Boilerplate Examples 12 | 13 | These use `YOUR_LEAFLET_MAP` as a placeholder; 14 | remember to change it to the variable name of your map. 15 | 16 | ##### Hello World 17 | 18 | open a popup 19 | 20 | ```javascript 21 | var helloPopup = L.popup().setContent('Hello World!'); 22 | 23 | L.easyButton('fa-globe', function(btn, map){ 24 | helloPopup.setLatLng(map.getCenter()).openOn(map); 25 | }).addTo( YOUR_LEAFLET_MAP ); 26 | ``` 27 | 28 | ##### Map State 29 | 30 | set the map's center and use an `img` for the icon 31 | 32 | ```javascript 33 | L.easyButton('', function(btn, map){ 34 | var antarctica = [-77,70]; 35 | map.setView(antarctica); 36 | }).addTo( YOUR_LEAFLET_MAP ); 37 | ``` 38 | 39 | ##### Button States 40 | 41 | change the button's function and appearance 42 | 43 | ```javascript 44 | var stateChangingButton = L.easyButton({ 45 | states: [{ 46 | stateName: 'zoom-to-forest', // name the state 47 | icon: 'fa-tree', // and define its properties 48 | title: 'zoom to a forest', // like its title 49 | onClick: function(btn, map) { // and its callback 50 | map.setView([46.25,-121.8],10); 51 | btn.state('zoom-to-school'); // change state on click! 52 | } 53 | }, { 54 | stateName: 'zoom-to-school', 55 | icon: 'fa-university', 56 | title: 'zoom to a school', 57 | onClick: function(btn, map) { 58 | map.setView([42.3748204,-71.1161913],16); 59 | btn.state('zoom-to-forest'); 60 | } 61 | }] 62 | }); 63 | 64 | stateChangingButton.addTo( YOUR_LEAFLET_MAP ); 65 | ``` 66 | 67 | ----------------------------------------------------------------------------------- 68 | 69 | ## Download/Install 70 | 71 | EasyButton version `2.x.x` and up expect Leaflet `1.x.x` or higher; 72 | for Leaflet `0.7.x` use EasyButton `1.3.x`. 73 | 74 | ### jsDelivr 75 | 76 | ``` 77 | 78 | 79 | ``` 80 | 81 | ### NPM 82 | 83 | ``` 84 | npm install --save leaflet-easybutton 85 | ``` 86 | 87 | ### Bower 88 | 89 | ``` 90 | bower install --save Leaflet.EasyButton 91 | ``` 92 | 93 | ### Icon Dependencies 94 | 95 | If you haven't already, make sure to install/include the icon library of your 96 | choice (your lib should have its own instructions) 97 | — EasyButton should work with anything! 98 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | ### Usage 2 | 3 | 4 | L.easyButton(, ); 5 | 6 | L.easyButton(); 7 | 8 | 9 | #### `` 10 | 11 | it can take many shapes 12 | 13 | * `'fa-map-marker'` or another [font awesome](http://fortawesome.github.io/Font-Awesome/icons/) class 14 | * `'glyphicon-globe'` or another [glyphicon](http://getbootstrap.com/components/#glyphicons-glyphs) class 15 | * `'random assortment-of-classes'` for custom icons and other icon libraries 16 | * `'do this'` old fashioned html 17 | 18 | #### `` 19 | 20 | gets a reference to the button and its map 21 | 22 | L.easyButton(, function( buttonArg, mapArg ){ 23 | buttonArg.doStuff(); 24 | mapArg.doStuff(); 25 | }); 26 | 27 | #### `` 28 | 29 | the options object looks like this: 30 | 31 | L.easyButton({ 32 | id: 'id-for-the-button', // an id for the generated button 33 | position: 'topleft', // inherited from L.Control -- the corner it goes in 34 | type: 'replace', // set to animate when you're comfy with css 35 | leafletClasses: true, // use leaflet classes to style the button? 36 | states:[{ // specify different icons and responses for your button 37 | stateName: 'get-center', 38 | onClick: function(button, map){ 39 | alert('Map is centered at: ' + map.getCenter().toString()); 40 | }, 41 | title: 'show me the middle', 42 | icon: 'fa-crosshairs' 43 | }] 44 | }); 45 | 46 | ### More 47 | 48 | `L.easyButton` now has a partner, `L.easyBar` 49 | 50 | // start with an array of easy buttons 51 | var buttons = [ L.easyButton(options), 52 | L.easyButton(options2), 53 | L.easyButton(options3)]; 54 | 55 | // build a toolbar with them 56 | L.easyBar(buttons).addTo(map); 57 | 58 | Buttons that preform related tasks (e.g. zooming in and out) 59 | can be lumped into logical groups. 60 | 61 | ### Alternatives 62 | 63 | * Follow Leaflet's [docs](http://leafletjs.com/reference.html) 64 | * Browse around for [something](leafletjs.com/plugins.html) that already does what you want 65 | * Copy one of [Leaflet's](https://github.com/Leaflet/Leaflet/tree/master/src/control) controls 66 | 67 | ### etc 68 | 69 | Changes from v0: 70 | * function signature 71 | * now: 72 | * friendly: `L.easyButton(,)` 73 | * fancy: `L.easyButton()` 74 | * was: 75 | * `L.easyButton(,,,,)` 76 | * The Constructor Name 77 | * now: `L.Control.EasyButton ` (no trailing 's') 78 | * was: `L.Control.EasyButtons` (trailing 's') 79 | * The factory, `L.easyButton`, is unchanged 80 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Leaflet.EasyButton", 3 | "homepage": "https://github.com/CliffCloud/Leaflet.EasyButtons", 4 | "authors": [ 5 | "atstp " 6 | ], 7 | "description": "Easily add Font Awesome control buttons to Leaflet maps with attached callbacks", 8 | "main": [ 9 | "src/easy-button.js", 10 | "src/easy-button.css" 11 | ], 12 | "dependencies": { 13 | "leaflet": "~1.0.1" 14 | }, 15 | "keywords": [ 16 | "Leaflet", 17 | "map", 18 | "button" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "**/.*", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-easybutton", 3 | "version": "2.4.0", 4 | "description": "easily add control buttons to your leaflet maps with icon support", 5 | "main": "src/easy-button.js", 6 | "style": "src/easy-button.css", 7 | "types": "src/easy-button.d.ts", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/CliffCloud/Leaflet.EasyButton.git" 14 | }, 15 | "keywords": [ 16 | "leaflet", 17 | "font-awesome", 18 | "glyphicon", 19 | "button", 20 | "control", 21 | "map" 22 | ], 23 | "author": "rocks.in.the.cloud+easybutton@gmail.com", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/CliffCloud/Leaflet.EasyButton/issues" 27 | }, 28 | "homepage": "https://github.com/CliffCloud/Leaflet.EasyButton", 29 | "dependencies": { 30 | "leaflet": "^1.0.1" 31 | }, 32 | "devDependencies": { 33 | "@types/leaflet": "^1.2.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/easy-button.css: -------------------------------------------------------------------------------- 1 | .leaflet-bar button, 2 | .leaflet-bar button:hover { 3 | background-color: #fff; 4 | border: none; 5 | border-bottom: 1px solid #ccc; 6 | width: 26px; 7 | height: 26px; 8 | line-height: 26px; 9 | display: block; 10 | text-align: center; 11 | text-decoration: none; 12 | color: black; 13 | } 14 | 15 | .leaflet-bar button { 16 | background-position: 50% 50%; 17 | background-repeat: no-repeat; 18 | overflow: hidden; 19 | display: block; 20 | } 21 | 22 | .leaflet-bar button:hover { 23 | background-color: #f4f4f4; 24 | } 25 | 26 | .leaflet-bar button:first-of-type { 27 | border-top-left-radius: 4px; 28 | border-top-right-radius: 4px; 29 | } 30 | 31 | .leaflet-bar button:last-of-type { 32 | border-bottom-left-radius: 4px; 33 | border-bottom-right-radius: 4px; 34 | border-bottom: none; 35 | } 36 | 37 | .leaflet-bar.disabled, 38 | .leaflet-bar button.disabled { 39 | cursor: default; 40 | pointer-events: none; 41 | opacity: .4; 42 | } 43 | 44 | .easy-button-button .button-state{ 45 | display: block; 46 | width: 100%; 47 | height: 100%; 48 | position: relative; 49 | } 50 | 51 | 52 | .leaflet-touch .leaflet-bar button { 53 | width: 30px; 54 | height: 30px; 55 | line-height: 30px; 56 | } 57 | -------------------------------------------------------------------------------- /src/easy-button.d.ts: -------------------------------------------------------------------------------- 1 | import * as L from 'leaflet' 2 | import {ControlPosition} from 'leaflet'; 3 | 4 | declare module 'leaflet' { 5 | 6 | /** 7 | * Creates a bar that holds a group of EasyButtons 8 | * @param buttons array of EasyButtons that will be grouped together in the EasyBar 9 | * @param options 10 | */ 11 | function easyBar(buttons: Control.EasyButton[], options?: EasyBarOptions): Control.EasyBar; 12 | 13 | /** 14 | * Creates a easyButton 15 | * @param icon e.g. fa-globe 16 | * @param onClick the button click handler 17 | * @param title title on the button 18 | * @param id an id to tag the button with 19 | * @example 20 | * var helloPopup = L.popup().setContent('Hello World!'); 21 | * 22 | * L.easyButton('fa-globe', function(btn, map){ 23 | * helloPopup.setLatLng(map.getCenter()).openOn(map); 24 | * }).addTo( YOUR_LEAFLET_MAP ); 25 | */ 26 | function easyButton(icon: string, 27 | onClick: (btn: Control.EasyButton, map: L.Map) => void, 28 | title?: string, 29 | id?: string): Control.EasyButton; 30 | 31 | /** 32 | * Creates a easyButton 33 | * @param options the options object 34 | * @example 35 | * 36 | * 37 | * L.easyButton({ 38 | * position: 'topleft', 39 | * leafletClasses: true, 40 | * states: [ 41 | * { 42 | * stateName: 'center', 43 | * onClick: function(btn, map){}, 44 | * title: 'Get Center', 45 | * icon: 'fa-globe' 46 | * } 47 | * ] 48 | * }).addTo( YOUR_LEAFLET_MAP ); 49 | */ 50 | function easyButton(options: EasyButtonOptions): Control.EasyButton; 51 | 52 | interface EasyBarOptions { 53 | position?: ControlPosition 54 | id?: string 55 | leafletClasses?: boolean 56 | } 57 | 58 | interface EasyButtonOptions { 59 | position?: ControlPosition 60 | id?: string 61 | type?: 'replace' | 'animate' 62 | states?: EasyButtonState[] 63 | leafletClasses?: boolean 64 | tagName?: string 65 | } 66 | 67 | interface EasyButtonState { 68 | stateName: string 69 | onClick: (btn: L.Control.EasyButton, map: L.Map) => void 70 | title: string 71 | icon: string 72 | } 73 | 74 | namespace Control { 75 | class EasyButton extends L.Control { 76 | constructor(options?: EasyButtonOptions) 77 | 78 | state(stateName: string): EasyButton 79 | enable(): void 80 | disable(): void 81 | } 82 | 83 | class EasyBar extends L.Control { 84 | constructor(options?: EasyBarOptions) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/easy-button.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | // This is for grouping buttons into a bar 4 | // takes an array of `L.easyButton`s and 5 | // then the usual `.addTo(map)` 6 | L.Control.EasyBar = L.Control.extend({ 7 | 8 | options: { 9 | position: 'topleft', // part of leaflet's defaults 10 | id: null, // an id to tag the Bar with 11 | leafletClasses: true // use leaflet classes? 12 | }, 13 | 14 | 15 | initialize: function(buttons, options){ 16 | 17 | if(options){ 18 | L.Util.setOptions( this, options ); 19 | } 20 | 21 | this._buildContainer(); 22 | this._buttons = []; 23 | 24 | for(var i = 0; i < buttons.length; i++){ 25 | buttons[i]._bar = this; 26 | buttons[i]._container = buttons[i].button; 27 | this._buttons.push(buttons[i]); 28 | this.container.appendChild(buttons[i].button); 29 | } 30 | 31 | }, 32 | 33 | 34 | _buildContainer: function(){ 35 | this._container = this.container = L.DomUtil.create('div', ''); 36 | this.options.leafletClasses && L.DomUtil.addClass(this.container, 'leaflet-bar easy-button-container leaflet-control'); 37 | this.options.id && (this.container.id = this.options.id); 38 | }, 39 | 40 | 41 | enable: function(){ 42 | L.DomUtil.addClass(this.container, 'enabled'); 43 | L.DomUtil.removeClass(this.container, 'disabled'); 44 | this.container.setAttribute('aria-hidden', 'false'); 45 | return this; 46 | }, 47 | 48 | 49 | disable: function(){ 50 | L.DomUtil.addClass(this.container, 'disabled'); 51 | L.DomUtil.removeClass(this.container, 'enabled'); 52 | this.container.setAttribute('aria-hidden', 'true'); 53 | return this; 54 | }, 55 | 56 | 57 | onAdd: function () { 58 | return this.container; 59 | }, 60 | 61 | addTo: function (map) { 62 | this._map = map; 63 | 64 | for(var i = 0; i < this._buttons.length; i++){ 65 | this._buttons[i]._map = map; 66 | } 67 | 68 | var container = this._container = this.onAdd(map), 69 | pos = this.getPosition(), 70 | corner = map._controlCorners[pos]; 71 | 72 | L.DomUtil.addClass(container, 'leaflet-control'); 73 | 74 | if (pos.indexOf('bottom') !== -1) { 75 | corner.insertBefore(container, corner.firstChild); 76 | } else { 77 | corner.appendChild(container); 78 | } 79 | 80 | return this; 81 | } 82 | 83 | }); 84 | 85 | L.easyBar = function(){ 86 | var args = [L.Control.EasyBar]; 87 | for(var i = 0; i < arguments.length; i++){ 88 | args.push( arguments[i] ); 89 | } 90 | return new (Function.prototype.bind.apply(L.Control.EasyBar, args)); 91 | }; 92 | 93 | // L.EasyButton is the actual buttons 94 | // can be called without being grouped into a bar 95 | L.Control.EasyButton = L.Control.extend({ 96 | 97 | options: { 98 | position: 'topleft', // part of leaflet's defaults 99 | 100 | id: null, // an id to tag the button with 101 | 102 | type: 'replace', // [(replace|animate)] 103 | // replace swaps out elements 104 | // animate changes classes with all elements inserted 105 | 106 | states: [], // state names look like this 107 | // { 108 | // stateName: 'untracked', 109 | // onClick: function(){ handle_nav_manually(); }; 110 | // title: 'click to make inactive', 111 | // icon: 'fa-circle', // wrapped with 112 | // } 113 | 114 | leafletClasses: true, // use leaflet styles for the button 115 | tagName: 'button', 116 | }, 117 | 118 | 119 | 120 | initialize: function(icon, onClick, title, id){ 121 | 122 | // clear the states manually 123 | this.options.states = []; 124 | 125 | // add id to options 126 | if(id != null){ 127 | this.options.id = id; 128 | } 129 | 130 | // storage between state functions 131 | this.storage = {}; 132 | 133 | // is the last item an object? 134 | if( typeof arguments[arguments.length-1] === 'object' ){ 135 | 136 | // if so, it should be the options 137 | L.Util.setOptions( this, arguments[arguments.length-1] ); 138 | } 139 | 140 | // if there aren't any states in options 141 | // use the early params 142 | if( this.options.states.length === 0 && 143 | typeof icon === 'string' && 144 | typeof onClick === 'function'){ 145 | 146 | // turn the options object into a state 147 | this.options.states.push({ 148 | icon: icon, 149 | onClick: onClick, 150 | title: typeof title === 'string' ? title : '' 151 | }); 152 | } 153 | 154 | // curate and move user's states into 155 | // the _states for internal use 156 | this._states = []; 157 | 158 | for(var i = 0; i < this.options.states.length; i++){ 159 | this._states.push( new State(this.options.states[i], this) ); 160 | } 161 | 162 | this._buildButton(); 163 | 164 | this._activateState(this._states[0]); 165 | 166 | }, 167 | 168 | _buildButton: function(){ 169 | 170 | this.button = L.DomUtil.create(this.options.tagName, ''); 171 | 172 | if (this.options.tagName === 'button') { 173 | this.button.setAttribute('type', 'button'); 174 | } 175 | 176 | if (this.options.id ){ 177 | this.button.id = this.options.id; 178 | } 179 | 180 | if (this.options.leafletClasses){ 181 | L.DomUtil.addClass(this.button, 'easy-button-button leaflet-bar-part leaflet-interactive'); 182 | } 183 | 184 | // don't let double clicks and mousedown get to the map 185 | L.DomEvent.addListener(this.button, 'dblclick', L.DomEvent.stop); 186 | L.DomEvent.addListener(this.button, 'mousedown', L.DomEvent.stop); 187 | L.DomEvent.addListener(this.button, 'mouseup', L.DomEvent.stop); 188 | 189 | // take care of normal clicks 190 | L.DomEvent.addListener(this.button,'click', function(e){ 191 | L.DomEvent.stop(e); 192 | this._currentState.onClick(this, this._map ? this._map : null ); 193 | this._map && this._map.getContainer().focus(); 194 | }, this); 195 | 196 | // prep the contents of the control 197 | if(this.options.type == 'replace'){ 198 | this.button.appendChild(this._currentState.icon); 199 | } else { 200 | for(var i=0;i"']/) ){ 350 | 351 | // if so, the user should have put in html 352 | // so move forward as such 353 | tmpIcon = ambiguousIconString; 354 | 355 | // then it wasn't html, so 356 | // it's a class list, figure out what kind 357 | } else { 358 | ambiguousIconString = ambiguousIconString.replace(/(^\s*|\s*$)/g,''); 359 | tmpIcon = L.DomUtil.create('span', ''); 360 | 361 | if( ambiguousIconString.indexOf('fa-') === 0 ){ 362 | L.DomUtil.addClass(tmpIcon, 'fa ' + ambiguousIconString) 363 | } else if ( ambiguousIconString.indexOf('glyphicon-') === 0 ) { 364 | L.DomUtil.addClass(tmpIcon, 'glyphicon ' + ambiguousIconString) 365 | } else { 366 | L.DomUtil.addClass(tmpIcon, /*rollwithit*/ ambiguousIconString) 367 | } 368 | 369 | // make this a string so that it's easy to set innerHTML below 370 | tmpIcon = tmpIcon.outerHTML; 371 | } 372 | 373 | return tmpIcon; 374 | } 375 | 376 | })(); 377 | --------------------------------------------------------------------------------