├── .gitignore ├── Gruntfile.js ├── bower.json ├── dist ├── leaflet-history-src.css ├── leaflet-history-src.js ├── leaflet-history.css └── leaflet-history.js ├── index.html ├── package.json ├── readme.md ├── samples ├── sample.css ├── sample1.html └── sample2.html └── src ├── history.js ├── leaflet-history.less └── zoomCenter.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | bower_components 3 | node_modules 4 | *.iml 5 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | concat: { 5 | options: { 6 | separator: '' 7 | }, 8 | dist: { 9 | src: ['src/**/*.js'], 10 | dest: 'dist/<%= pkg.name %>-src.js' 11 | } 12 | }, 13 | clean : ['dist/'], 14 | uglify: { 15 | options: { 16 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 17 | }, 18 | dist: { 19 | files: { 20 | 'dist/<%= pkg.name %>.js': ['<%= concat.dist.dest %>'] 21 | } 22 | } 23 | }, 24 | jshint: { 25 | files: ['Gruntfile.js', 'src/**/*.js'], 26 | options: { 27 | // options here to override JSHint defaults 28 | globals: { 29 | jQuery: true, 30 | console: true, 31 | module: true, 32 | document: true 33 | } 34 | } 35 | }, 36 | less: { 37 | development: { 38 | files: { 39 | 'dist/leaflet-history-src.css' : 'src/leaflet-history.less' 40 | } 41 | }, 42 | production: { 43 | options: { 44 | cleancss: true 45 | }, 46 | files: { 47 | 'dist/leaflet-history.css' : 'src/leaflet-history.less' 48 | } 49 | } 50 | }, 51 | watch: { 52 | files: ['<%= jshint.files %>'], 53 | tasks: ['jshint'] 54 | } 55 | }); 56 | 57 | grunt.loadNpmTasks('grunt-contrib-clean'); 58 | grunt.loadNpmTasks('grunt-contrib-uglify'); 59 | grunt.loadNpmTasks('grunt-contrib-jshint'); 60 | grunt.loadNpmTasks('grunt-contrib-concat'); 61 | grunt.loadNpmTasks('grunt-contrib-less'); 62 | 63 | grunt.registerTask('default', ['jshint', 'clean', 'concat', 'less', 'uglify']); 64 | 65 | }; -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-history", 3 | "title": "Leaflet History", 4 | "version": "0.1.0", 5 | "main" : ["dist/*"], 6 | "ignore" : [ "samples/", "bower_components"], 7 | "dependencies": { 8 | "leaflet-dist": ">=0.7.0", 9 | "font-awesome": ">=4.0.3" 10 | }, 11 | "devDependencies" : { 12 | "jquery": "1.10.2" 13 | } 14 | } -------------------------------------------------------------------------------- /dist/leaflet-history-src.css: -------------------------------------------------------------------------------- 1 | .history-control.leaflet-bar.hidden { 2 | display: none; 3 | } 4 | .history-control.leaflet-bar.horizontal a { 5 | display: inline-block; 6 | border-bottom: none; 7 | border-radius: 0; 8 | border-right: 1px solid #ccc; 9 | } 10 | .history-control.leaflet-bar.horizontal a:first-child { 11 | border-top-left-radius: 4px; 12 | border-bottom-left-radius: 4px; 13 | } 14 | .history-control.leaflet-bar.horizontal a:last-child { 15 | border-top-right-radius: 4px; 16 | border-bottom-right-radius: 4px; 17 | border-right: none; 18 | } 19 | .history-control.leaflet-bar a { 20 | width: auto; 21 | font-size: 1.1em; 22 | min-width: 26px; 23 | } 24 | -------------------------------------------------------------------------------- /dist/leaflet-history-src.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | L.HistoryControl = L.Control.extend({ 3 | options: { 4 | position: 'topright', 5 | maxMovesToSave: 10, //set to 0 for unlimited 6 | useExternalControls: false, //set to true to hide buttons on map and use your own. Can still use goBack, goForward, and allow this to take care of storing history. 7 | backImage: 'fa fa-caret-left', 8 | backText: '', 9 | backTooltip: 'Go to Previous Extent', 10 | backImageBeforeText: true, 11 | forwardImage: 'fa fa-caret-right', 12 | forwardText: '', 13 | forwardTooltip: 'Go to Next Extent', 14 | forwardImageBeforeText: false, 15 | orientation: 'horizontal', 16 | shouldSaveMoveInHistory: function(zoomCenter) { return true; } //by default save everything 17 | }, 18 | initialize: function(options) { 19 | L.Util.setOptions(this, options); 20 | 21 | this._state.maxMovesToSave = this.options.maxMovesToSave; 22 | }, 23 | onAdd: function(map) { 24 | this._map = map; 25 | 26 | var container = L.DomUtil.create('div', 'history-control leaflet-bar leaflet-control ' + this.options.orientation); 27 | if(!this.options.useExternalControls) { 28 | this._backButton = this._createButton('back', container, this.goBack, this); 29 | this._forwardButton = this._createButton('forward', container, this.goForward, this); 30 | } 31 | this._updateDisabled(); 32 | this._addMapListeners(); 33 | 34 | return container; 35 | }, 36 | onRemove: function(map) { 37 | map.off('movestart'); 38 | }, 39 | performActionWithoutTriggeringEvent: function(action) { 40 | this._state.ignoringEvents = true; 41 | if(typeof (action) === 'function') { 42 | action(); 43 | } 44 | }, 45 | moveWithoutTriggeringEvent: function(zoomCenter) { 46 | var _this = this; 47 | this.performActionWithoutTriggeringEvent(function() { 48 | _this._map.setView(zoomCenter.centerPoint, zoomCenter.zoom); 49 | }); 50 | }, 51 | goBack: function() { 52 | return this._invokeBackOrForward('historyback', this._state.history, this._state.future); 53 | }, 54 | goForward: function() { 55 | return this._invokeBackOrForward('historyforward', this._state.future, this._state.history); 56 | }, 57 | clearHistory: function() { 58 | this._state.history.items = []; 59 | this._updateDisabled(); 60 | }, 61 | clearFuture: function() { 62 | this._state.future.items = []; 63 | this._updateDisabled(); 64 | }, 65 | _map: null, 66 | _backButton: null, 67 | _forwardButton: null, 68 | _state: { 69 | backDisabled: null, 70 | forwardDisabled: null, 71 | ignoringEvents: false, 72 | maxMovesToSave: 0, 73 | history: { 74 | items: [] 75 | }, 76 | future: { 77 | items: [] 78 | } 79 | }, 80 | _createButton: function (name, container, action, _this) { 81 | var text = this.options[name + 'Text'] || ''; 82 | var imageClass = this.options[name + 'Image'] || ''; 83 | var tooltip = this.options[name + 'Tooltip'] || ''; 84 | var button = L.DomUtil.create('a', 'history-' + name + '-button', container); 85 | if(imageClass) { 86 | var imageElement = ''; 87 | if(this.options[name + 'ImageBeforeText']) { 88 | text = imageElement + ' ' + text; 89 | } 90 | else { 91 | text += ' ' + imageElement; 92 | } 93 | } 94 | button.innerHTML = text; 95 | button.href = '#'; 96 | button.title = tooltip; 97 | 98 | var stop = L.DomEvent.stopPropagation; 99 | 100 | L.DomEvent 101 | .on(button, 'click', stop) 102 | .on(button, 'mousedown', stop) 103 | .on(button, 'dblclick', stop) 104 | .on(button, 'click', L.DomEvent.preventDefault) 105 | .on(button, 'click', action, _this) 106 | .on(button, 'click', this._refocusOnMap, _this); 107 | 108 | return button; 109 | }, 110 | _updateDisabled: function () { 111 | var backDisabled = (this._state.history.items.length === 0); 112 | var forwardDisabled = (this._state.future.items.length === 0); 113 | if(backDisabled !== this._state.backDisabled) { 114 | this._state.backDisabled = backDisabled; 115 | this._map.fire('historyback' + (backDisabled ? 'disabled' : 'enabled')); 116 | } 117 | if(forwardDisabled !== this._state.forwardDisabled) { 118 | this._state.forwardDisabled = forwardDisabled; 119 | this._map.fire('historyforward' + (forwardDisabled ? 'disabled' : 'enabled')); 120 | } 121 | if(!this.options.useExternalControls) { 122 | this._setButtonDisabled(this._backButton, backDisabled); 123 | this._setButtonDisabled(this._forwardButton, forwardDisabled); 124 | } 125 | }, 126 | _setButtonDisabled: function(button, condition) { 127 | var className = 'leaflet-disabled'; 128 | if(condition) { 129 | L.DomUtil.addClass(button, className); 130 | } 131 | else { 132 | L.DomUtil.removeClass(button, className); 133 | } 134 | }, 135 | _pop: function(stack) { 136 | stack = stack.items; 137 | if(L.Util.isArray(stack) && stack.length > 0) { 138 | return stack.splice(stack.length - 1, 1)[0]; 139 | } 140 | return undefined; 141 | }, 142 | _push: function(stack, value) { 143 | var maxLength = this._state.maxMovesToSave; 144 | stack = stack.items; 145 | if(L.Util.isArray(stack)) { 146 | stack.push(value); 147 | if(maxLength > 0 && stack.length > maxLength) { 148 | stack.splice(0, 1); 149 | } 150 | } 151 | }, 152 | _invokeBackOrForward: function(eventName, stackToPop, stackToPushCurrent) { 153 | var response = this._popStackAndUseLocation(stackToPop, stackToPushCurrent); 154 | if(response) { 155 | this._map.fire(eventName, response); 156 | return true; 157 | } 158 | return false; 159 | }, 160 | _popStackAndUseLocation : function(stackToPop, stackToPushCurrent) { 161 | //check if we can pop 162 | if(L.Util.isArray(stackToPop.items) && stackToPop.items.length > 0) { 163 | var current = this._buildZoomCenterObjectFromCurrent(this._map); 164 | //get most recent 165 | var previous = this._pop(stackToPop); 166 | //save where we currently are in the 'other' stack 167 | this._push(stackToPushCurrent, current); 168 | this.moveWithoutTriggeringEvent(previous); 169 | 170 | return { 171 | previousLocation: previous, 172 | currentLocation: current 173 | }; 174 | } 175 | }, 176 | _buildZoomCenterObjectFromCurrent:function(map) { 177 | return new L.ZoomCenter(map.getZoom(), map.getCenter()); 178 | }, 179 | _addMapListeners: function() { 180 | var _this = this; 181 | this._map.on('movestart', function(e) { 182 | if(!_this._state.ignoringEvents) { 183 | var current = _this._buildZoomCenterObjectFromCurrent(e.target); 184 | if(_this.options.shouldSaveMoveInHistory(current)) { 185 | _this._state.future.items = []; 186 | _this._push(_this._state.history, current); 187 | } 188 | } else { 189 | _this._state.ignoringEvents = false; 190 | } 191 | 192 | _this._updateDisabled(); 193 | }); 194 | } 195 | }); 196 | }()); 197 | (function () { 198 | L.ZoomCenter = L.Class.extend({ 199 | initialize: function(zoom, centerPoint) { 200 | this.zoom = zoom; 201 | this.centerPoint = centerPoint; 202 | } 203 | }); 204 | }()); -------------------------------------------------------------------------------- /dist/leaflet-history.css: -------------------------------------------------------------------------------- 1 | .history-control.leaflet-bar.hidden{display:none}.history-control.leaflet-bar.horizontal a{display:inline-block;border-bottom:0;border-radius:0;border-right:1px solid #ccc}.history-control.leaflet-bar.horizontal a:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}.history-control.leaflet-bar.horizontal a:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px;border-right:0}.history-control.leaflet-bar a{width:auto;font-size:1.1em;min-width:26px} -------------------------------------------------------------------------------- /dist/leaflet-history.js: -------------------------------------------------------------------------------- 1 | /*! leaflet-history 10-04-2017 */ 2 | !function(){L.HistoryControl=L.Control.extend({options:{position:"topright",maxMovesToSave:10,useExternalControls:!1,backImage:"fa fa-caret-left",backText:"",backTooltip:"Go to Previous Extent",backImageBeforeText:!0,forwardImage:"fa fa-caret-right",forwardText:"",forwardTooltip:"Go to Next Extent",forwardImageBeforeText:!1,orientation:"horizontal",shouldSaveMoveInHistory:function(a){return!0}},initialize:function(a){L.Util.setOptions(this,a),this._state.maxMovesToSave=this.options.maxMovesToSave},onAdd:function(a){this._map=a;var b=L.DomUtil.create("div","history-control leaflet-bar leaflet-control "+this.options.orientation);return this.options.useExternalControls||(this._backButton=this._createButton("back",b,this.goBack,this),this._forwardButton=this._createButton("forward",b,this.goForward,this)),this._updateDisabled(),this._addMapListeners(),b},onRemove:function(a){a.off("movestart")},performActionWithoutTriggeringEvent:function(a){this._state.ignoringEvents=!0,"function"==typeof a&&a()},moveWithoutTriggeringEvent:function(a){var b=this;this.performActionWithoutTriggeringEvent(function(){b._map.setView(a.centerPoint,a.zoom)})},goBack:function(){return this._invokeBackOrForward("historyback",this._state.history,this._state.future)},goForward:function(){return this._invokeBackOrForward("historyforward",this._state.future,this._state.history)},clearHistory:function(){this._state.history.items=[],this._updateDisabled()},clearFuture:function(){this._state.future.items=[],this._updateDisabled()},_map:null,_backButton:null,_forwardButton:null,_state:{backDisabled:null,forwardDisabled:null,ignoringEvents:!1,maxMovesToSave:0,history:{items:[]},future:{items:[]}},_createButton:function(a,b,c,d){var e=this.options[a+"Text"]||"",f=this.options[a+"Image"]||"",g=this.options[a+"Tooltip"]||"",h=L.DomUtil.create("a","history-"+a+"-button",b);if(f){var i='';this.options[a+"ImageBeforeText"]?e=i+" "+e:e+=" "+i}h.innerHTML=e,h.href="#",h.title=g;var j=L.DomEvent.stopPropagation;return L.DomEvent.on(h,"click",j).on(h,"mousedown",j).on(h,"dblclick",j).on(h,"click",L.DomEvent.preventDefault).on(h,"click",c,d).on(h,"click",this._refocusOnMap,d),h},_updateDisabled:function(){var a=0===this._state.history.items.length,b=0===this._state.future.items.length;a!==this._state.backDisabled&&(this._state.backDisabled=a,this._map.fire("historyback"+(a?"disabled":"enabled"))),b!==this._state.forwardDisabled&&(this._state.forwardDisabled=b,this._map.fire("historyforward"+(b?"disabled":"enabled"))),this.options.useExternalControls||(this._setButtonDisabled(this._backButton,a),this._setButtonDisabled(this._forwardButton,b))},_setButtonDisabled:function(a,b){var c="leaflet-disabled";b?L.DomUtil.addClass(a,c):L.DomUtil.removeClass(a,c)},_pop:function(a){return a=a.items,L.Util.isArray(a)&&a.length>0?a.splice(a.length-1,1)[0]:void 0},_push:function(a,b){var c=this._state.maxMovesToSave;a=a.items,L.Util.isArray(a)&&(a.push(b),c>0&&a.length>c&&a.splice(0,1))},_invokeBackOrForward:function(a,b,c){var d=this._popStackAndUseLocation(b,c);return d?(this._map.fire(a,d),!0):!1},_popStackAndUseLocation:function(a,b){if(L.Util.isArray(a.items)&&a.items.length>0){var c=this._buildZoomCenterObjectFromCurrent(this._map),d=this._pop(a);return this._push(b,c),this.moveWithoutTriggeringEvent(d),{previousLocation:d,currentLocation:c}}},_buildZoomCenterObjectFromCurrent:function(a){return new L.ZoomCenter(a.getZoom(),a.getCenter())},_addMapListeners:function(){var a=this;this._map.on("movestart",function(b){if(a._state.ignoringEvents)a._state.ignoringEvents=!1;else{var c=a._buildZoomCenterObjectFromCurrent(b.target);a.options.shouldSaveMoveInHistory(c)&&(a._state.future.items=[],a._push(a._state.history,c))}a._updateDisabled()})}})}(),function(){L.ZoomCenter=L.Class.extend({initialize:function(a,b){this.zoom=a,this.centerPoint=b}})}(); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index of samples 5 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-history", 3 | "version": "0.1.0", 4 | "main": "dist/", 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-cli": "~0.1.11", 8 | "grunt-contrib-uglify": "~0.3.2", 9 | "grunt-contrib-less": "~0.9.0", 10 | "grunt-contrib-clean": "~0.5.0", 11 | "grunt-contrib-concat": "^1.0.1", 12 | "grunt-contrib-jshint": "^1.1.0" 13 | }, 14 | "engines": { 15 | "node": ">=0.10.0" 16 | }, 17 | "dependencies": { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | _**Important**_: This repository has not been updated in quite a long time. I haven't worked on any Leaflet projects in so long, and I have no idea if it even works in latest versions. There are some forks that are probably more up-to-date. Please check those out, instead! 2 | 3 | Leaflet-History Control 4 | == 5 | 6 | Leaflet-History control is a plugin to [leafletjs](http://leafletjs.com) that enables tracking map movements in a history similar to a web browser. I tried to make this plugin extremely easy to use by default, but also extremely extensible if you have want to modify it. 7 | 8 | By default, it is a simple pair of buttons -- back and forward. However, it can be customized to use different icons, different text, allow certain locations to not be saved on move, or even use your own external controls and simply use Leaflet-History as a manager of the history. 9 | 10 | Demo 11 | -- 12 | [View sample here.](http://cscott530.github.io/leaflet-history/) 13 | 14 | Installation 15 | -- 16 | Leaflet-History is available through bower. You can do: 17 | `bower install leaflet-history` 18 | 19 | Usage 20 | -- 21 | Import leaflet-history.js and leaflet-history.css in your page. 22 | ```html 23 | 24 | 25 | ``` 26 | Note that the non-minified versions are also availble in `dist/` 27 | 28 | After creating your leaflet map, create and add the controller: 29 | 30 | ```javascript 31 | new L.HistoryControl({}).addTo(map); 32 | ``` 33 | Options 34 | -- 35 | 36 | When you call `new L.HistoryControl()`, you may pass in an options object to extend its functionality. These options are: 37 | 38 | * **position** - sets which corner of the map to place your controls. possible values are `'topleft'` | `'topright'`(default) | `'bottomleft'` | `'bottomright'` 39 | * **maxMovesToSave** - the number of moves in the history to save before clearing out the oldest. default value is `10`, use `0` or a negative number to make unlimited. 40 | * **backImage**, **forwardImage** - the class used for the button images. defaults are `'fa fa-caret-left'` and `'fa fa-caret-right'`, respectively. 41 | * no image will be displayed if set to empty string. 42 | * **backText**, **forwardText** - the text in the buttons. defaults are `''` (empty). 43 | * **backTooltip**, **forwardTooltip** - tooltip contents. defaults are `'Go to Previous Extent'` and `'Go to Next Extent'`, respectively. 44 | * **backImageBeforeText**, **forwardImageBeforeText** - when both text and image are present, whether to show the image first or the text first (left to right). defaults are `true` and `false`, respectively. 45 | * **orientation** - `'vertical'` | `'horizontal'`(default) - whether to position the buttons on top of one another or side-by-side. 46 | * **useExternalControls** - `true` | `false`(default) - set to true to hide these controls on the map and instead use your own controls. See the [Events](#events) and API for more details on this. 47 | * **shouldSaveMoveInHistory** - `function(zoomCenter) { return true; }` a callback you can provide that gets called with every move. return `false` to not save a move. 48 | * useful if you have certain situations where you move the map programmatically and don't want the user to be able to go back. 49 | 50 | For example, to set your history control to use bootstrap icons and have text: 51 | ```javascript 52 | new L.HistoryControls({ 53 | backText: 'Back', 54 | backImage: 'glyphicon glyphicon-chevron-left', 55 | forwardText: 'Forward', 56 | forwardImage: 'glyphicon glyphicon-chevron-right' 57 | }).addTo(map); 58 | ``` 59 | 60 | Types 61 | -- 62 | ### L.ZoomCenter 63 | Encapsulates both a zoomlevel and the map's center point. Properties: 64 | 65 | * **zoom** - number, value of `map.getZoom()` 66 | * **centerPoint** - `L.LatLng`, value of `map.getCenter()` 67 | 68 | API 69 | -- 70 | After you have called `new L.HistoryControl()`, you can use these methods to manage it. 71 | 72 | * **goBack()** - if able, will go to previous map extent. Pushes current to the "future" stack. 73 | * **goForward()** - if able, will go to next map extent. Pushes current to the "back" stack. 74 | * If you set `useExternalControls` to `true` when initializing, use `goBack()` and `goForward()` 75 | * **clearHistory()** - resets the stack of history items. 76 | * **clearFuture()** - resets the stack of future items. 77 | * **performActionWithoutTriggeringEvent(callback)** - will set the map to ignore caching on any events that occur during your (synchronous) callback. Useful if you want to set an initial map location after it's already been configured. 78 | * **moveWithoutTriggeringEvent(zoomCenter)** - convenience wrapper to `performActionWithoutTriggeringEvent`. Rather than passing in a callback, give a `L.ZoomCenter` object. 79 | 80 | For example, if you want to recenter your map after loading some data, but don't want your original location to be in the history: 81 | ```javascript 82 | var history = new L.HistoryControl().addTo(map); 83 | //callbacks to get map data 84 | history.performActionWithoutTriggeringEvent(new L.ZoomCenter(12, L.LatLng([50.5, 30.5]))); 85 | ``` 86 | 87 | Events 88 | -- 89 | Leaflet-History uses the leaflet event API, so subscribing is simple: `map.on('historyback', function(location) {});` 90 | ### Action Events 91 | * **historyback** - fired when `goBack()` is invoked 92 | * **historyfoward** - fired when `goForward()` is invoked 93 | * These get fired whether using the API, or if the default buttons are used. 94 | 95 | In both cases the parameter is an object: 96 | ``` 97 | { 98 | currentLocation: L.ZoomCenter object, //the current map location 99 | newLocation: L.ZoomCenter //the new location after the move 100 | } 101 | ``` 102 | 103 | ### State Events 104 | * **historybackenabled** - fired when the state of the **back** button changes from disabled to enabled. 105 | * **historybackdisabled** - fired when the state of the **back** button changes from enabled to disabled. 106 | * **historyforwardenabled** - fired when the state of the **forward** button changes from disabled to enabled. 107 | * **historyforwarddisabled** - fired when the state of the **forward** button changes from enabled to disabled. 108 | * Note that these get fired even if using external controls, as a way to help manage your own buttons' state. 109 | * `historybackdisabled` and `historyforwarddisabled` are both fired upon initialization. 110 | -------------------------------------------------------------------------------- /samples/sample.css: -------------------------------------------------------------------------------- 1 | #map { 2 | height: 100%; 3 | position: absolute; 4 | width: 100%; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | } -------------------------------------------------------------------------------- /samples/sample1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet History - Basic 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 28 | 29 | -------------------------------------------------------------------------------- /samples/sample2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet History - Custom Controls 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | Back 13 | Forward 14 | Custom Back/Forward buttons are hidden until a move is made. 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 60 | 61 | -------------------------------------------------------------------------------- /src/history.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | L.HistoryControl = L.Control.extend({ 3 | options: { 4 | position: 'topright', 5 | maxMovesToSave: 10, //set to 0 for unlimited 6 | useExternalControls: false, //set to true to hide buttons on map and use your own. Can still use goBack, goForward, and allow this to take care of storing history. 7 | backImage: 'fa fa-caret-left', 8 | backText: '', 9 | backTooltip: 'Go to Previous Extent', 10 | backImageBeforeText: true, 11 | forwardImage: 'fa fa-caret-right', 12 | forwardText: '', 13 | forwardTooltip: 'Go to Next Extent', 14 | forwardImageBeforeText: false, 15 | orientation: 'horizontal', 16 | shouldSaveMoveInHistory: function(zoomCenter) { return true; } //by default save everything 17 | }, 18 | initialize: function(options) { 19 | L.Util.setOptions(this, options); 20 | 21 | this._state.maxMovesToSave = this.options.maxMovesToSave; 22 | }, 23 | onAdd: function(map) { 24 | this._map = map; 25 | 26 | var container = L.DomUtil.create('div', 'history-control leaflet-bar leaflet-control ' + this.options.orientation); 27 | if(!this.options.useExternalControls) { 28 | this._backButton = this._createButton('back', container, this.goBack, this); 29 | this._forwardButton = this._createButton('forward', container, this.goForward, this); 30 | } 31 | this._updateDisabled(); 32 | this._addMapListeners(); 33 | 34 | return container; 35 | }, 36 | onRemove: function(map) { 37 | map.off('movestart'); 38 | }, 39 | performActionWithoutTriggeringEvent: function(action) { 40 | this._state.ignoringEvents = true; 41 | if(typeof (action) === 'function') { 42 | action(); 43 | } 44 | }, 45 | moveWithoutTriggeringEvent: function(zoomCenter) { 46 | var _this = this; 47 | this.performActionWithoutTriggeringEvent(function() { 48 | _this._map.setView(zoomCenter.centerPoint, zoomCenter.zoom); 49 | }); 50 | }, 51 | goBack: function() { 52 | return this._invokeBackOrForward('historyback', this._state.history, this._state.future); 53 | }, 54 | goForward: function() { 55 | return this._invokeBackOrForward('historyforward', this._state.future, this._state.history); 56 | }, 57 | clearHistory: function() { 58 | this._state.history.items = []; 59 | this._updateDisabled(); 60 | }, 61 | clearFuture: function() { 62 | this._state.future.items = []; 63 | this._updateDisabled(); 64 | }, 65 | _map: null, 66 | _backButton: null, 67 | _forwardButton: null, 68 | _state: { 69 | backDisabled: null, 70 | forwardDisabled: null, 71 | ignoringEvents: false, 72 | maxMovesToSave: 0, 73 | history: { 74 | items: [] 75 | }, 76 | future: { 77 | items: [] 78 | } 79 | }, 80 | _createButton: function (name, container, action, _this) { 81 | var text = this.options[name + 'Text'] || ''; 82 | var imageClass = this.options[name + 'Image'] || ''; 83 | var tooltip = this.options[name + 'Tooltip'] || ''; 84 | var button = L.DomUtil.create('a', 'history-' + name + '-button', container); 85 | if(imageClass) { 86 | var imageElement = ''; 87 | if(this.options[name + 'ImageBeforeText']) { 88 | text = imageElement + ' ' + text; 89 | } 90 | else { 91 | text += ' ' + imageElement; 92 | } 93 | } 94 | button.innerHTML = text; 95 | button.href = '#'; 96 | button.title = tooltip; 97 | 98 | var stop = L.DomEvent.stopPropagation; 99 | 100 | L.DomEvent 101 | .on(button, 'click', stop) 102 | .on(button, 'mousedown', stop) 103 | .on(button, 'dblclick', stop) 104 | .on(button, 'click', L.DomEvent.preventDefault) 105 | .on(button, 'click', action, _this) 106 | .on(button, 'click', this._refocusOnMap, _this); 107 | 108 | return button; 109 | }, 110 | _updateDisabled: function () { 111 | var backDisabled = (this._state.history.items.length === 0); 112 | var forwardDisabled = (this._state.future.items.length === 0); 113 | if(backDisabled !== this._state.backDisabled) { 114 | this._state.backDisabled = backDisabled; 115 | this._map.fire('historyback' + (backDisabled ? 'disabled' : 'enabled')); 116 | } 117 | if(forwardDisabled !== this._state.forwardDisabled) { 118 | this._state.forwardDisabled = forwardDisabled; 119 | this._map.fire('historyforward' + (forwardDisabled ? 'disabled' : 'enabled')); 120 | } 121 | if(!this.options.useExternalControls) { 122 | this._setButtonDisabled(this._backButton, backDisabled); 123 | this._setButtonDisabled(this._forwardButton, forwardDisabled); 124 | } 125 | }, 126 | _setButtonDisabled: function(button, condition) { 127 | var className = 'leaflet-disabled'; 128 | if(condition) { 129 | L.DomUtil.addClass(button, className); 130 | } 131 | else { 132 | L.DomUtil.removeClass(button, className); 133 | } 134 | }, 135 | _pop: function(stack) { 136 | stack = stack.items; 137 | if(L.Util.isArray(stack) && stack.length > 0) { 138 | return stack.splice(stack.length - 1, 1)[0]; 139 | } 140 | return undefined; 141 | }, 142 | _push: function(stack, value) { 143 | var maxLength = this._state.maxMovesToSave; 144 | stack = stack.items; 145 | if(L.Util.isArray(stack)) { 146 | stack.push(value); 147 | if(maxLength > 0 && stack.length > maxLength) { 148 | stack.splice(0, 1); 149 | } 150 | } 151 | }, 152 | _invokeBackOrForward: function(eventName, stackToPop, stackToPushCurrent) { 153 | var response = this._popStackAndUseLocation(stackToPop, stackToPushCurrent); 154 | if(response) { 155 | this._map.fire(eventName, response); 156 | return true; 157 | } 158 | return false; 159 | }, 160 | _popStackAndUseLocation : function(stackToPop, stackToPushCurrent) { 161 | //check if we can pop 162 | if(L.Util.isArray(stackToPop.items) && stackToPop.items.length > 0) { 163 | var current = this._buildZoomCenterObjectFromCurrent(this._map); 164 | //get most recent 165 | var previous = this._pop(stackToPop); 166 | //save where we currently are in the 'other' stack 167 | this._push(stackToPushCurrent, current); 168 | this.moveWithoutTriggeringEvent(previous); 169 | 170 | return { 171 | previousLocation: previous, 172 | currentLocation: current 173 | }; 174 | } 175 | }, 176 | _buildZoomCenterObjectFromCurrent:function(map) { 177 | return new L.ZoomCenter(map.getZoom(), map.getCenter()); 178 | }, 179 | _addMapListeners: function() { 180 | var _this = this; 181 | this._map.on('movestart', function(e) { 182 | if(!_this._state.ignoringEvents) { 183 | var current = _this._buildZoomCenterObjectFromCurrent(e.target); 184 | if(_this.options.shouldSaveMoveInHistory(current)) { 185 | _this._state.future.items = []; 186 | _this._push(_this._state.history, current); 187 | } 188 | } else { 189 | _this._state.ignoringEvents = false; 190 | } 191 | 192 | _this._updateDisabled(); 193 | }); 194 | } 195 | }); 196 | }()); 197 | -------------------------------------------------------------------------------- /src/leaflet-history.less: -------------------------------------------------------------------------------- 1 | @border-radius: 4px; 2 | .history-control.leaflet-bar { 3 | &.hidden { 4 | display: none; 5 | } 6 | 7 | &.horizontal { 8 | a { 9 | display: inline-block; 10 | border-bottom: none; 11 | border-radius: 0; 12 | border-right: 1px solid #ccc; 13 | &:first-child { 14 | border-top-left-radius: @border-radius; 15 | border-bottom-left-radius: @border-radius; 16 | } 17 | &:last-child { 18 | border-top-right-radius: @border-radius; 19 | border-bottom-right-radius: @border-radius; 20 | border-right: none; 21 | } 22 | } 23 | } 24 | a { 25 | width: auto; 26 | font-size: 1.1em; 27 | min-width: 26px; 28 | } 29 | } -------------------------------------------------------------------------------- /src/zoomCenter.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | L.ZoomCenter = L.Class.extend({ 3 | initialize: function(zoom, centerPoint) { 4 | this.zoom = zoom; 5 | this.centerPoint = centerPoint; 6 | } 7 | }); 8 | }()); --------------------------------------------------------------------------------