├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist ├── leaflet.toolbar-src.css ├── leaflet.toolbar-src.js ├── leaflet.toolbar.css ├── leaflet.toolbar.js ├── leaflet.toolbar.min.css └── leaflet.toolbar.min.js ├── examples ├── minimal.html └── subtoolbar.html ├── package.json ├── src ├── Action.js ├── Control.js ├── Popup.js ├── Toolbar.js └── Toolbar.less └── test ├── SpecHelper.js ├── karma.conf.js └── src ├── ToolbarActionSpec.js ├── ToolbarControlSpec.js ├── ToolbarPopupSpec.js └── ToolbarSpec.js /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | .grunt -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | .travis.yml 3 | .gitignore 4 | Gruntfile.js 5 | .grunt -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_install: 5 | - npm install -g grunt-cli 6 | after_script: 7 | - npm run coveralls 8 | env: 9 | global: 10 | secure: KPlv3pL8sHGo293ztaIr9SgOXmEhT+pYBbh+PgN8WP9PzG1O+xVUL6+JtH/pU2oupCq8meRy6OoEtAxUXL/U+/Xm0A+eLDmQ1b2hSCGxBjx9Gj+E8PJ7cqu+Ovf1JQ+hUhB67EL3Qz2H14RJX//dQGOxQhGIP3l+efdKnXvpFeU= -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 4 | 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | 8 | changelog: {}, 9 | 10 | bump: { 11 | options: { 12 | files: ['package.json'], 13 | updateConfigs: [], 14 | commit: true, 15 | commitMessage: 'Release v%VERSION%', 16 | commitFiles: ['package.json'], 17 | createTag: true, 18 | tagName: 'v%VERSION%', 19 | tagMessage: 'Version %VERSION%', 20 | push: true, 21 | pushTo: 'origin', 22 | gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' 23 | } 24 | }, 25 | 26 | jshint: { 27 | options: { 28 | node: true, 29 | browser: true, 30 | esnext: true, 31 | bitwise: true, 32 | curly: true, 33 | eqeqeq: true, 34 | immed: true, 35 | indent: 4, 36 | latedef: true, 37 | newcap: true, 38 | noarg: true, 39 | regexp: true, 40 | undef: true, 41 | unused: true, 42 | trailing: true, 43 | smarttabs: true, 44 | globals: { 45 | L: false, 46 | LeafletToolbar: true, 47 | 48 | // Mocha 49 | 50 | describe: false, 51 | it: false, 52 | before: false, 53 | after: false, 54 | beforeEach: false, 55 | afterEach: false, 56 | chai: false, 57 | expect: false, 58 | sinon: false 59 | } 60 | }, 61 | source: { 62 | src: [ 'src/*.js', 'Gruntfile.js', 'package.json' ] 63 | }, 64 | test: { 65 | src: [ 'test/SpecHelper.js', 'test/src/**' ], 66 | }, 67 | grunt: { 68 | src: [ 'Gruntfile.js' ] 69 | } 70 | }, 71 | 72 | autoprefixer: { 73 | dist: { 74 | src: 'dist/leaflet.toolbar-src.css', 75 | dest: 'dist/leaflet.toolbar-src.css' 76 | } 77 | }, 78 | 79 | cssmin: { 80 | target: { 81 | files: { 82 | 'dist/leaflet.toolbar.css': ['dist/leaflet.toolbar-src.css'], 83 | 'dist/leaflet.toolbar.min.css': ['dist/leaflet.toolbar-src.css'] 84 | } 85 | } 86 | }, 87 | 88 | uglify: { 89 | dist: { 90 | files: { 91 | 'dist/leaflet.toolbar.js': ['dist/leaflet.toolbar-src.js'], 92 | 'dist/leaflet.toolbar.min.js': ['dist/leaflet.toolbar-src.js'] 93 | } 94 | } 95 | }, 96 | 97 | less: { 98 | source: { 99 | files: { 100 | 'dist/leaflet.toolbar-src.css': 'src/Toolbar.less' 101 | } 102 | } 103 | }, 104 | 105 | karma: { 106 | travis: { 107 | configFile: 'test/karma.conf.js', 108 | background: false, 109 | singleRun: true, 110 | browsers: [ 'PhantomJS' ] 111 | }, 112 | development: { 113 | configFile: 'test/karma.conf.js', 114 | background: true 115 | }, 116 | unit: { 117 | configFile: 'test/karma.conf.js', 118 | background: false, 119 | singleRun: true 120 | } 121 | }, 122 | 123 | watch: { 124 | options : { 125 | livereload: true 126 | }, 127 | source: { 128 | files: [ 129 | 'src/*.js', 130 | 'test/**', 131 | 'Gruntfile.js' 132 | ], 133 | tasks: [ 'build:js' ] 134 | }, 135 | css: { 136 | files: [ 'src/*.less' ], 137 | tasks: [ 'build:css' ] 138 | } 139 | 140 | }, 141 | 142 | connect: { 143 | server: { 144 | options: { 145 | port: 8000, 146 | hostname: '*', 147 | keepalive: true 148 | } 149 | } 150 | }, 151 | 152 | concat: { 153 | dist: { 154 | options: { 155 | banner: '(function(window, document, undefined) {\n\n"use strict";\n\n', 156 | footer: '\n\n})(window, document);' 157 | }, 158 | src: [ 159 | 'src/Toolbar.js', 160 | 'src/Action.js', 161 | 'src/Control.js', 162 | 'src/Popup.js' 163 | ], 164 | dest: 'dist/leaflet.toolbar-src.js', 165 | } 166 | }, 167 | 168 | 'gh-pages': { 169 | src: [ 170 | 'dist/**', 171 | 'examples/**', 172 | 'node_modules/leaflet/**', 173 | 'node_modules/leaflet-draw/**' 174 | ] 175 | } 176 | }); 177 | 178 | /* Run tests once. */ 179 | grunt.registerTask('test', [ 'jshint', 'karma:unit', 'coverage' ]); 180 | 181 | /* Default (development): Watch files and lint, test, and build on change. */ 182 | grunt.registerTask('default', ['karma:development:start', 'watch']); 183 | 184 | grunt.registerTask('travis', [ 'jshint', 'karma:travis' ]); 185 | 186 | grunt.registerTask('build:js', [ 187 | 'jshint', 188 | 'karma:development:run', 189 | 'coverage', 190 | 'concat:dist', 191 | 'uglify:dist' 192 | ]); 193 | 194 | grunt.registerTask('build:css', [ 'less', 'autoprefixer', 'cssmin' ]); 195 | 196 | grunt.registerTask('coverage', 'Custom commmand-line reporter for karma-coverage', function() { 197 | var coverageReports = grunt.file.expand('coverage/*/coverage.txt'), 198 | reports = {}, 199 | report, i, len; 200 | 201 | for (i = 0, len = coverageReports.length; i < len; i++) { 202 | report = grunt.file.read(coverageReports[i]); 203 | if (!reports[report]) { 204 | reports[report] = [coverageReports[i]]; 205 | } else { 206 | reports[report].push(coverageReports[i]); 207 | } 208 | } 209 | 210 | for (report in reports) { 211 | if (reports.hasOwnProperty(report)) { 212 | for (i = 0, len = reports[report].length; i < len; i++) { 213 | grunt.log.writeln(reports[report][i]); 214 | } 215 | grunt.log.writeln(report); 216 | } 217 | } 218 | }); 219 | }; 220 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015, Justin Manley 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Leaflet.Toolbar 2 | =============== 3 | 4 | [![Build Status](https://travis-ci.org/Leaflet/Leaflet.toolbar.svg?branch=master)](https://travis-ci.org/Leaflet/Leaflet.toolbar) 5 | [![Coverage Status](https://img.shields.io/coveralls/Leaflet/Leaflet.toolbar.svg)](https://coveralls.io/r/Leaflet/Leaflet.toolbar) 6 | 7 | Leaflet.Toolbar provides flexible, extensible toolbar interfaces for Leaflet maps. 8 | 9 | ### Examples 10 | 11 | View examples for [control-style](https://justinmanley.github.io/leaflet-draw-toolbar/examples/control.html) and [popup-style](https://justinmanley.github.io/leaflet-draw-toolbar/examples/popup.html) toolbars using Leaflet.draw, as well as a [minimal example](http://leaflet.github.io/Leaflet.toolbar/examples/minimal.html). NOTE: If you want to use this library with Leaflet.draw, you should use the toolbars provided with [leaflet-draw-toolbar](https://github.com/justinmanley/leaflet-draw-toolbar). 12 | 13 | ### Usage 14 | 15 | Include Leaflet.Toolbar in your JavaScript project using `npm install leaflet-toolbar`. 16 | 17 | You can then include Leaflet.Toolbar in your web application by adding the following HTML tags (paths below are relative to your project's root): 18 | 19 | ``` 20 | 21 | 22 | ``` 23 | 24 | Leaflet.Toolbar exports two toolbar styles that can be used out of the box: a popup-style toolbar, and a control-style toolbar. To instantiate a control-style toolbar and add it to the map, use: 25 | ```javascript 26 | new L.Toolbar2.Control({ 27 | actions: [MyAction1, MyAction2, ...] 28 | }).addTo(map); 29 | ``` 30 | 31 | For more information, see the [API Reference](https://github.com/leaflet/Leaflet.Toolbar/wiki/API-Reference) and [Building custom toolbars](https://github.com/leaflet/Leaflet.Toolbar/wiki/Building-custom-toolbars) on the wiki. 32 | 33 | ### Contributing 34 | 35 | Contributors are welcomed! 36 | 37 | Once you've cloned this repo, you'll need to run `npm install` in the project root to install the development dependencies using `npm`. 38 | 39 | Leaflet.Toolbar uses `grunt` to run development and build tasks. You'll need to have the grunt command line interface installed: `npm install -g grunt-cli`. Once you've done this, running `grunt` without any arguments in the project root will watch the project source and lint, test, and build whenever the source files are modified. Additional tasks that may be useful for development are specified in the [`Gruntfile`](https://github.com/leaflet/Leaflet.Toolbar/blob/master/Gruntfile.js). 40 | 41 | ### Testing 42 | 43 | Run the test suite using `npm test`. 44 | 45 | ### Documentation and Examples 46 | 47 | The examples in the gh-pages branch can be updated using Grunt: `grunt gh-pages`. This will create a new commit *and* push that commit to your gh-pages branch on GitHub. If you'd like to simply preview the gh-pages branch, you can run `grunt gh-pages --gh-pages-push false`. 48 | 49 | Contributors are encouraged to open pull requests early to facilitate discussion about proposed changes! 50 | 51 | Please follow the [Leaflet contribution guide](https://github.com/Leaflet/Leaflet/blob/master/CONTRIBUTING.md). 52 | 53 | ### Thanks 54 | 55 | Thanks to [@jacobtoye](https://github.com/jacobtoye) for the excellent Leaflet.draw library which was the inspiration for Leaflet.Toolbar. And, of course, thanks to [@mourner](https://github.com/mourner) for a fantastic open-source mapping library. 56 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet.toolbar", 3 | "main": [ 4 | "leaflet.toolbar.css", 5 | "leaflet.toolbar.js" 6 | ], 7 | "version": "0.1.2", 8 | "homepage": "https://github.com/Leaflet/Leaflet.toolbar", 9 | "authors": [ 10 | "Justin Manley" 11 | ], 12 | "description": "Flexible, extensible toolbars for Leaflet maps.", 13 | "license": "MIT", 14 | "ignore": [ 15 | "**/.*", 16 | "examples", 17 | "src", 18 | "node_modules", 19 | "test", 20 | "tests", 21 | "Gruntfile.js" 22 | ], 23 | "keywords": [ 24 | "maps", 25 | "leaflet", 26 | "client", 27 | "draw", 28 | "toolbar", 29 | "edit" 30 | ], 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/Leaflet/Leaflet.toolbar" 34 | }, 35 | "dependencies": {} 36 | } 37 | -------------------------------------------------------------------------------- /dist/leaflet.toolbar-src.css: -------------------------------------------------------------------------------- 1 | /* Variables and Mixins */ 2 | /* Generic L.Toolbar */ 3 | .leaflet-toolbar-0 { 4 | list-style: none; 5 | padding-left: 0; 6 | border: 2px solid rgba(0, 0, 0, 0.2); 7 | border-bottom-left-radius: 4px; 8 | border-bottom-right-radius: 4px; 9 | border-top-left-radius: 4px; 10 | border-top-right-radius: 4px; 11 | } 12 | .leaflet-toolbar-0 > li { 13 | position: relative; 14 | } 15 | .leaflet-toolbar-0 > li > .leaflet-toolbar-icon { 16 | display: block; 17 | width: 30px; 18 | height: 30px; 19 | line-height: 30px; 20 | margin-right: 0; 21 | padding-right: 0; 22 | border-right: 0; 23 | text-align: center; 24 | text-decoration: none; 25 | background-color: #ffffff; 26 | } 27 | .leaflet-toolbar-0 > li > .leaflet-toolbar-icon:hover { 28 | background-color: #f4f4f4; 29 | } 30 | .leaflet-toolbar-0 .leaflet-toolbar-1 { 31 | display: none; 32 | list-style: none; 33 | } 34 | .leaflet-toolbar-tip-container { 35 | margin: 0 auto; 36 | margin-top: -16px; 37 | height: 16px; 38 | position: relative; 39 | overflow: hidden; 40 | } 41 | .leaflet-toolbar-tip { 42 | width: 16px; 43 | height: 16px; 44 | margin: -8px auto 0; 45 | background-color: #ffffff; 46 | border: 2px solid rgba(0, 0, 0, 0.2); 47 | border-bottom-left-radius: 4px; 48 | border-bottom-right-radius: 4px; 49 | border-top-left-radius: 4px; 50 | border-top-right-radius: 4px; 51 | background-clip: content-box; 52 | -webkit-transform: rotate(45deg); 53 | -ms-transform: rotate(45deg); 54 | transform: rotate(45deg); 55 | } 56 | /* L.Toolbar.Control */ 57 | .leaflet-control-toolbar { 58 | /* Secondary Toolbar */ 59 | } 60 | .leaflet-control-toolbar > li > .leaflet-toolbar-icon { 61 | border-bottom: 1px solid #ccc; 62 | } 63 | .leaflet-control-toolbar > li:first-child > .leaflet-toolbar-icon { 64 | border-top-left-radius: 4px; 65 | border-top-right-radius: 4px; 66 | } 67 | .leaflet-control-toolbar > li:last-child > .leaflet-toolbar-icon { 68 | border-bottom-left-radius: 4px; 69 | border-bottom-right-radius: 4px; 70 | border-bottom-width: 0; 71 | } 72 | .leaflet-control-toolbar .leaflet-toolbar-1 { 73 | margin: 0; 74 | padding: 0; 75 | position: absolute; 76 | left: 30px; 77 | /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */ 78 | top: 0; 79 | white-space: nowrap; 80 | height: 30px; 81 | } 82 | .leaflet-control-toolbar .leaflet-toolbar-1 > li { 83 | display: inline-block; 84 | } 85 | .leaflet-control-toolbar .leaflet-toolbar-1 > li > .leaflet-toolbar-icon { 86 | display: block; 87 | background-color: #919187; 88 | border-left: 1px solid #aaa; 89 | color: #fff; 90 | font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif; 91 | line-height: 30px; 92 | text-decoration: none; 93 | padding-left: 10px; 94 | padding-right: 10px; 95 | height: 30px; 96 | } 97 | .leaflet-control-toolbar .leaflet-toolbar-1 > li > .leaflet-toolbar-icon:hover { 98 | background-color: #a0a098; 99 | } 100 | .leaflet-control-toolbar .leaflet-toolbar-1 > li:last-child > .leaflet-toolbar-icon { 101 | border-top-right-radius: 4px; 102 | border-bottom-right-radius: 4px; 103 | } 104 | /* L.Toolbar.Popup */ 105 | .leaflet-popup-toolbar { 106 | position: relative; 107 | box-sizing: content-box; 108 | } 109 | .leaflet-popup-toolbar > li { 110 | float: left; 111 | } 112 | .leaflet-popup-toolbar > li > .leaflet-toolbar-icon { 113 | border-right: 1px solid #ccc; 114 | } 115 | .leaflet-popup-toolbar > li:first-child > .leaflet-toolbar-icon { 116 | border-top-left-radius: 4px; 117 | border-bottom-left-radius: 4px; 118 | } 119 | .leaflet-popup-toolbar > li:last-child > .leaflet-toolbar-icon { 120 | border-top-right-radius: 4px; 121 | border-bottom-right-radius: 4px; 122 | border-bottom-width: 0; 123 | border-right: none; 124 | } 125 | .leaflet-popup-toolbar .leaflet-toolbar-1 { 126 | position: absolute; 127 | top: 30px; 128 | left: 0; 129 | padding-left: 0; 130 | } 131 | .leaflet-popup-toolbar .leaflet-toolbar-1 > li > .leaflet-toolbar-icon { 132 | position: relative; 133 | float: left; 134 | width: 30px; 135 | height: 30px; 136 | } 137 | -------------------------------------------------------------------------------- /dist/leaflet.toolbar-src.js: -------------------------------------------------------------------------------- 1 | (function(window, document, undefined) { 2 | 3 | "use strict"; 4 | 5 | // L.Layer was introduced in Leaflet 1.0 and is not present in earlier releases. 6 | window.L.Toolbar2 = (L.Layer || L.Class).extend({ 7 | statics: { 8 | baseClass: 'leaflet-toolbar' 9 | }, 10 | 11 | options: { 12 | className: '', 13 | filter: function() { return true; }, 14 | actions: [] 15 | }, 16 | 17 | initialize: function(options) { 18 | L.setOptions(this, options); 19 | this._toolbar_type = this.constructor._toolbar_class_id; 20 | }, 21 | 22 | addTo: function(map) { 23 | this._arguments = [].slice.call(arguments); 24 | 25 | map.addLayer(this); 26 | 27 | return this; 28 | }, 29 | 30 | onAdd: function(map) { 31 | var currentToolbar = map._toolbars[this._toolbar_type]; 32 | 33 | if (this._calculateDepth() === 0) { 34 | if (currentToolbar) { map.removeLayer(currentToolbar); } 35 | map._toolbars[this._toolbar_type] = this; 36 | } 37 | }, 38 | 39 | onRemove: function(map) { 40 | /* 41 | * TODO: Cleanup event listeners. 42 | * For some reason, this throws: 43 | * "Uncaught TypeError: Cannot read property 'dragging' of null" 44 | * on this._marker when a toolbar icon is clicked. 45 | */ 46 | // for (var i = 0, l = this._disabledEvents.length; i < l; i++) { 47 | // L.DomEvent.off(this._ul, this._disabledEvents[i], L.DomEvent.stopPropagation); 48 | // } 49 | 50 | if (this._calculateDepth() === 0) { 51 | delete map._toolbars[this._toolbar_type]; 52 | } 53 | }, 54 | 55 | appendToContainer: function(container) { 56 | var baseClass = this.constructor.baseClass + '-' + this._calculateDepth(), 57 | className = baseClass + ' ' + this.options.className, 58 | Action, action, 59 | i, j, l, m; 60 | 61 | this._container = container; 62 | this._ul = L.DomUtil.create('ul', className, container); 63 | 64 | // Ensure that clicks, drags, etc. don't bubble up to the map. 65 | // These are the map events that the L.Draw.Polyline handler listens for. 66 | // Note that L.Draw.Polyline listens to 'mouseup', not 'mousedown', but 67 | // if only 'mouseup' is silenced, then the map gets stuck in a halfway 68 | // state because it receives a 'mousedown' event and is waiting for the 69 | // corresponding 'mouseup' event. 70 | this._disabledEvents = [ 71 | 'click', 'mousemove', 'dblclick', 72 | 'mousedown', 'mouseup', 'touchstart' 73 | ]; 74 | 75 | for (j = 0, m = this._disabledEvents.length; j < m; j++) { 76 | L.DomEvent.on(this._ul, this._disabledEvents[j], L.DomEvent.stopPropagation); 77 | } 78 | 79 | /* Instantiate each toolbar action and add its corresponding toolbar icon. */ 80 | for (i = 0, l = this.options.actions.length; i < l; i++) { 81 | Action = this._getActionConstructor(this.options.actions[i]); 82 | 83 | action = new Action(); 84 | action._createIcon(this, this._ul, this._arguments); 85 | } 86 | }, 87 | 88 | _getActionConstructor: function(Action) { 89 | var args = this._arguments, 90 | toolbar = this; 91 | 92 | return Action.extend({ 93 | initialize: function() { 94 | Action.prototype.initialize.apply(this, args); 95 | }, 96 | enable: function(e) { 97 | /* Ensure that only one action in a toolbar will be active at a time. */ 98 | if (toolbar._active) { toolbar._active.disable(); } 99 | toolbar._active = this; 100 | 101 | Action.prototype.enable.call(this, e); 102 | } 103 | }); 104 | }, 105 | 106 | /* Used to hide subToolbars without removing them from the map. */ 107 | _hide: function() { 108 | this._ul.style.display = 'none'; 109 | }, 110 | 111 | /* Used to show subToolbars without removing them from the map. */ 112 | _show: function() { 113 | this._ul.style.display = 'block'; 114 | }, 115 | 116 | _calculateDepth: function() { 117 | var depth = 0, 118 | toolbar = this.parentToolbar; 119 | 120 | while (toolbar) { 121 | depth += 1; 122 | toolbar = toolbar.parentToolbar; 123 | } 124 | 125 | return depth; 126 | } 127 | }); 128 | 129 | // L.Mixin.Events is replaced by L.Evented in Leaflet 1.0. L.Layer (also 130 | // introduced in Leaflet 1.0) inherits from L.Evented, so if L.Layer is 131 | // present, then L.Toolbar2 will already support events. 132 | if (!L.Evented) { 133 | L.Toolbar2.include(L.Mixin.Events); 134 | } 135 | 136 | L.toolbar = {}; 137 | 138 | var toolbar_class_id = 0; 139 | 140 | L.Toolbar2.extend = function extend(props) { 141 | var statics = L.extend({}, props.statics, { 142 | "_toolbar_class_id": toolbar_class_id 143 | }); 144 | 145 | toolbar_class_id += 1; 146 | L.extend(props, { statics: statics }); 147 | 148 | return L.Class.extend.call(this, props); 149 | }; 150 | 151 | L.Map.addInitHook(function() { 152 | this._toolbars = {}; 153 | }); 154 | 155 | L.Toolbar2.Action = L.Handler.extend({ 156 | statics: { 157 | baseClass: 'leaflet-toolbar-icon' 158 | }, 159 | 160 | options: { 161 | toolbarIcon: { 162 | html: '', 163 | className: '', 164 | tooltip: '' 165 | }, 166 | subToolbar: new L.Toolbar2() 167 | }, 168 | 169 | initialize: function(options) { 170 | var defaultIconOptions = L.Toolbar2.Action.prototype.options.toolbarIcon; 171 | 172 | L.setOptions(this, options); 173 | this.options.toolbarIcon = L.extend({}, defaultIconOptions, this.options.toolbarIcon); 174 | }, 175 | 176 | enable: function(e) { 177 | if (e) { L.DomEvent.preventDefault(e); } 178 | if (this._enabled) { return; } 179 | this._enabled = true; 180 | 181 | if (this.addHooks) { this.addHooks(); } 182 | }, 183 | 184 | disable: function() { 185 | if (!this._enabled) { return; } 186 | this._enabled = false; 187 | 188 | if (this.removeHooks) { this.removeHooks(); } 189 | }, 190 | 191 | _createIcon: function(toolbar, container, args) { 192 | var iconOptions = this.options.toolbarIcon; 193 | 194 | this.toolbar = toolbar; 195 | this._icon = L.DomUtil.create('li', '', container); 196 | this._link = L.DomUtil.create('a', '', this._icon); 197 | 198 | this._link.innerHTML = iconOptions.html; 199 | this._link.setAttribute('href', '#'); 200 | this._link.setAttribute('title', iconOptions.tooltip); 201 | 202 | L.DomUtil.addClass(this._link, this.constructor.baseClass); 203 | if (iconOptions.className) { 204 | L.DomUtil.addClass(this._link, iconOptions.className); 205 | } 206 | 207 | L.DomEvent.on(this._link, 'click', this.enable, this); 208 | 209 | /* Add secondary toolbar */ 210 | this._addSubToolbar(toolbar, this._icon, args); 211 | }, 212 | 213 | _addSubToolbar: function(toolbar, container, args) { 214 | var subToolbar = this.options.subToolbar, 215 | addHooks = this.addHooks, 216 | removeHooks = this.removeHooks; 217 | 218 | /* For calculating the nesting depth. */ 219 | subToolbar.parentToolbar = toolbar; 220 | 221 | if (subToolbar.options.actions.length > 0) { 222 | /* Make a copy of args so as not to pollute the args array used by other actions. */ 223 | args = [].slice.call(args); 224 | args.push(this); 225 | 226 | subToolbar.addTo.apply(subToolbar, args); 227 | subToolbar.appendToContainer(container); 228 | 229 | this.addHooks = function(map) { 230 | if (typeof addHooks === 'function') { addHooks.call(this, map); } 231 | subToolbar._show(); 232 | }; 233 | 234 | this.removeHooks = function(map) { 235 | if (typeof removeHooks === 'function') { removeHooks.call(this, map); } 236 | subToolbar._hide(); 237 | }; 238 | } 239 | } 240 | }); 241 | 242 | L.toolbarAction = function toolbarAction(options) { 243 | return new L.Toolbar2.Action(options); 244 | }; 245 | 246 | L.Toolbar2.Action.extendOptions = function(options) { 247 | return this.extend({ options: options }); 248 | }; 249 | 250 | L.Toolbar2.Control = L.Toolbar2.extend({ 251 | statics: { 252 | baseClass: 'leaflet-control-toolbar ' + L.Toolbar2.baseClass 253 | }, 254 | 255 | initialize: function(options) { 256 | L.Toolbar2.prototype.initialize.call(this, options); 257 | 258 | this._control = new L.Control.Toolbar(this.options); 259 | }, 260 | 261 | onAdd: function(map) { 262 | this._control.addTo(map); 263 | 264 | L.Toolbar2.prototype.onAdd.call(this, map); 265 | 266 | this.appendToContainer(this._control.getContainer()); 267 | }, 268 | 269 | onRemove: function(map) { 270 | L.Toolbar2.prototype.onRemove.call(this, map); 271 | if (this._control.remove) {this._control.remove();} // Leaflet 1.0 272 | else {this._control.removeFrom(map);} 273 | } 274 | }); 275 | 276 | L.Control.Toolbar = L.Control.extend({ 277 | onAdd: function() { 278 | return L.DomUtil.create('div', ''); 279 | } 280 | }); 281 | 282 | L.toolbar.control = function(options) { 283 | return new L.Toolbar2.Control(options); 284 | }; 285 | 286 | // A convenience class for built-in popup toolbars. 287 | 288 | L.Toolbar2.Popup = L.Toolbar2.extend({ 289 | statics: { 290 | baseClass: 'leaflet-popup-toolbar ' + L.Toolbar2.baseClass 291 | }, 292 | 293 | options: { 294 | anchor: [0, 0] 295 | }, 296 | 297 | initialize: function(latlng, options) { 298 | L.Toolbar2.prototype.initialize.call(this, options); 299 | 300 | /* 301 | * Developers can't pass a DivIcon in the options for L.Toolbar2.Popup 302 | * (the use of DivIcons is an implementation detail which may change). 303 | */ 304 | this._marker = new L.Marker(latlng, { 305 | icon : new L.DivIcon({ 306 | className: this.options.className, 307 | iconAnchor: [0, 0] 308 | }) 309 | }); 310 | }, 311 | 312 | onAdd: function(map) { 313 | this._map = map; 314 | this._marker.addTo(map); 315 | 316 | L.Toolbar2.prototype.onAdd.call(this, map); 317 | 318 | this.appendToContainer(this._marker._icon); 319 | 320 | this._setStyles(); 321 | }, 322 | 323 | onRemove: function(map) { 324 | map.removeLayer(this._marker); 325 | 326 | L.Toolbar2.prototype.onRemove.call(this, map); 327 | 328 | delete this._map; 329 | }, 330 | 331 | setLatLng: function(latlng) { 332 | this._marker.setLatLng(latlng); 333 | 334 | return this; 335 | }, 336 | 337 | _setStyles: function() { 338 | var container = this._container, 339 | toolbar = this._ul, 340 | anchor = L.point(this.options.anchor), 341 | icons = toolbar.querySelectorAll('.leaflet-toolbar-icon'), 342 | buttonHeights = [], 343 | toolbarWidth = 0, 344 | toolbarHeight, 345 | tipSize, 346 | tipAnchor; 347 | /* Calculate the dimensions of the toolbar. */ 348 | for (var i = 0, l = icons.length; i < l; i++) { 349 | if (icons[i].parentNode.parentNode === toolbar) { 350 | buttonHeights.push(parseInt(L.DomUtil.getStyle(icons[i], 'height'), 10)); 351 | toolbarWidth += Math.ceil(parseFloat(L.DomUtil.getStyle(icons[i], 'width'))); 352 | toolbarWidth += Math.ceil(parseFloat(L.DomUtil.getStyle(icons[i], 'border-right-width'))); 353 | } 354 | } 355 | toolbar.style.width = toolbarWidth + 'px'; 356 | 357 | /* Create and place the toolbar tip. */ 358 | this._tipContainer = L.DomUtil.create('div', 'leaflet-toolbar-tip-container', container); 359 | this._tipContainer.style.width = toolbarWidth + 360 | Math.ceil(parseFloat(L.DomUtil.getStyle(toolbar, 'border-left-width'))) + 361 | 'px'; 362 | 363 | this._tip = L.DomUtil.create('div', 'leaflet-toolbar-tip', this._tipContainer); 364 | 365 | /* Set the tipAnchor point. */ 366 | toolbarHeight = Math.max.apply(undefined, buttonHeights); 367 | // Ensure that the border completely surrounds its relative-positioned children. 368 | toolbar.style.height = toolbarHeight + 'px'; 369 | tipSize = parseInt(L.DomUtil.getStyle(this._tip, 'width'), 10); 370 | // The tip should be anchored exactly where the click event was received. 371 | tipAnchor = new L.Point(toolbarWidth/2, toolbarHeight + 1.414*tipSize); 372 | 373 | /* The anchor option allows app developers to adjust the toolbar's position. */ 374 | container.style.marginLeft = (anchor.x - tipAnchor.x) + 'px'; 375 | container.style.marginTop = (anchor.y - tipAnchor.y) + 'px'; 376 | }, 377 | }); 378 | 379 | L.toolbar.popup = function(options) { 380 | return new L.Toolbar2.Popup(options); 381 | }; 382 | 383 | 384 | })(window, document); -------------------------------------------------------------------------------- /dist/leaflet.toolbar.css: -------------------------------------------------------------------------------- 1 | .leaflet-toolbar-0{list-style:none;padding-left:0;border:2px solid rgba(0,0,0,.2);border-radius:4px}.leaflet-toolbar-0>li{position:relative}.leaflet-toolbar-0>li>.leaflet-toolbar-icon{display:block;width:30px;height:30px;line-height:30px;margin-right:0;padding-right:0;border-right:0;text-align:center;text-decoration:none;background-color:#fff}.leaflet-toolbar-0>li>.leaflet-toolbar-icon:hover{background-color:#f4f4f4}.leaflet-toolbar-0 .leaflet-toolbar-1{display:none;list-style:none}.leaflet-toolbar-tip-container{margin:-16px auto 0;height:16px;position:relative;overflow:hidden}.leaflet-toolbar-tip{width:16px;height:16px;margin:-8px auto 0;background-color:#fff;border:2px solid rgba(0,0,0,.2);background-clip:content-box;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);border-radius:4px}.leaflet-control-toolbar .leaflet-toolbar-1>li:last-child>.leaflet-toolbar-icon,.leaflet-popup-toolbar>li:last-child>.leaflet-toolbar-icon{border-top-right-radius:4px;border-bottom-right-radius:4px}.leaflet-control-toolbar>li>.leaflet-toolbar-icon{border-bottom:1px solid #ccc}.leaflet-control-toolbar>li:first-child>.leaflet-toolbar-icon{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-control-toolbar>li:last-child>.leaflet-toolbar-icon{border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-bottom-width:0}.leaflet-control-toolbar .leaflet-toolbar-1{margin:0;padding:0;position:absolute;left:30px;top:0;white-space:nowrap;height:30px}.leaflet-control-toolbar .leaflet-toolbar-1>li{display:inline-block}.leaflet-control-toolbar .leaflet-toolbar-1>li>.leaflet-toolbar-icon{display:block;background-color:#919187;border-left:1px solid #aaa;color:#fff;font:11px/19px "Helvetica Neue",Arial,Helvetica,sans-serif;line-height:30px;text-decoration:none;padding-left:10px;padding-right:10px;height:30px}.leaflet-control-toolbar .leaflet-toolbar-1>li>.leaflet-toolbar-icon:hover{background-color:#a0a098}.leaflet-popup-toolbar{position:relative;box-sizing:content-box}.leaflet-popup-toolbar>li{float:left}.leaflet-popup-toolbar>li>.leaflet-toolbar-icon{border-right:1px solid #ccc}.leaflet-popup-toolbar>li:first-child>.leaflet-toolbar-icon{border-top-left-radius:4px;border-bottom-left-radius:4px}.leaflet-popup-toolbar>li:last-child>.leaflet-toolbar-icon{border-bottom-width:0;border-right:none}.leaflet-popup-toolbar .leaflet-toolbar-1{position:absolute;top:30px;left:0;padding-left:0}.leaflet-popup-toolbar .leaflet-toolbar-1>li>.leaflet-toolbar-icon{position:relative;float:left;width:30px;height:30px} -------------------------------------------------------------------------------- /dist/leaflet.toolbar.js: -------------------------------------------------------------------------------- 1 | !function(t,o,i){"use strict";t.L.Toolbar2=(L.Layer||L.Class).extend({statics:{baseClass:"leaflet-toolbar"},options:{className:"",filter:function(){return!0},actions:[]},initialize:function(t){L.setOptions(this,t),this._toolbar_type=this.constructor._toolbar_class_id},addTo:function(t){return this._arguments=[].slice.call(arguments),t.addLayer(this),this},onAdd:function(t){var o=t._toolbars[this._toolbar_type];0===this._calculateDepth()&&(o&&t.removeLayer(o),t._toolbars[this._toolbar_type]=this)},onRemove:function(t){0===this._calculateDepth()&&delete t._toolbars[this._toolbar_type]},appendToContainer:function(t){var o,i,e,n,s=this.constructor.baseClass+"-"+this._calculateDepth()+" "+this.options.className;for(this._container=t,this._ul=L.DomUtil.create("ul",s,t),this._disabledEvents=["click","mousemove","dblclick","mousedown","mouseup","touchstart"],i=0,n=this._disabledEvents.length;i0&&((i=[].slice.call(i)).push(this),e.addTo.apply(e,i),e.appendToContainer(o),this.addHooks=function(t){"function"==typeof n&&n.call(this,t),e._show()},this.removeHooks=function(t){"function"==typeof s&&s.call(this,t),e._hide()})}}),L.toolbarAction=function(t){return new L.Toolbar2.Action(t)},L.Toolbar2.Action.extendOptions=function(t){return this.extend({options:t})},L.Toolbar2.Control=L.Toolbar2.extend({statics:{baseClass:"leaflet-control-toolbar "+L.Toolbar2.baseClass},initialize:function(t){L.Toolbar2.prototype.initialize.call(this,t),this._control=new L.Control.Toolbar(this.options)},onAdd:function(t){this._control.addTo(t),L.Toolbar2.prototype.onAdd.call(this,t),this.appendToContainer(this._control.getContainer())},onRemove:function(t){L.Toolbar2.prototype.onRemove.call(this,t),this._control.remove?this._control.remove():this._control.removeFrom(t)}}),L.Control.Toolbar=L.Control.extend({onAdd:function(){return L.DomUtil.create("div","")}}),L.toolbar.control=function(t){return new L.Toolbar2.Control(t)},L.Toolbar2.Popup=L.Toolbar2.extend({statics:{baseClass:"leaflet-popup-toolbar "+L.Toolbar2.baseClass},options:{anchor:[0,0]},initialize:function(t,o){L.Toolbar2.prototype.initialize.call(this,o),this._marker=new L.Marker(t,{icon:new L.DivIcon({className:this.options.className,iconAnchor:[0,0]})})},onAdd:function(t){this._map=t,this._marker.addTo(t),L.Toolbar2.prototype.onAdd.call(this,t),this.appendToContainer(this._marker._icon),this._setStyles()},onRemove:function(t){t.removeLayer(this._marker),L.Toolbar2.prototype.onRemove.call(this,t),delete this._map},setLatLng:function(t){return this._marker.setLatLng(t),this},_setStyles:function(){for(var t,o,i,e=this._container,n=this._ul,s=L.point(this.options.anchor),a=n.querySelectorAll(".leaflet-toolbar-icon"),l=[],r=0,c=0,h=a.length;cli{position:relative}.leaflet-toolbar-0>li>.leaflet-toolbar-icon{display:block;width:30px;height:30px;line-height:30px;margin-right:0;padding-right:0;border-right:0;text-align:center;text-decoration:none;background-color:#fff}.leaflet-toolbar-0>li>.leaflet-toolbar-icon:hover{background-color:#f4f4f4}.leaflet-toolbar-0 .leaflet-toolbar-1{display:none;list-style:none}.leaflet-toolbar-tip-container{margin:-16px auto 0;height:16px;position:relative;overflow:hidden}.leaflet-toolbar-tip{width:16px;height:16px;margin:-8px auto 0;background-color:#fff;border:2px solid rgba(0,0,0,.2);background-clip:content-box;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);border-radius:4px}.leaflet-control-toolbar .leaflet-toolbar-1>li:last-child>.leaflet-toolbar-icon,.leaflet-popup-toolbar>li:last-child>.leaflet-toolbar-icon{border-top-right-radius:4px;border-bottom-right-radius:4px}.leaflet-control-toolbar>li>.leaflet-toolbar-icon{border-bottom:1px solid #ccc}.leaflet-control-toolbar>li:first-child>.leaflet-toolbar-icon{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-control-toolbar>li:last-child>.leaflet-toolbar-icon{border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-bottom-width:0}.leaflet-control-toolbar .leaflet-toolbar-1{margin:0;padding:0;position:absolute;left:30px;top:0;white-space:nowrap;height:30px}.leaflet-control-toolbar .leaflet-toolbar-1>li{display:inline-block}.leaflet-control-toolbar .leaflet-toolbar-1>li>.leaflet-toolbar-icon{display:block;background-color:#919187;border-left:1px solid #aaa;color:#fff;font:11px/19px "Helvetica Neue",Arial,Helvetica,sans-serif;line-height:30px;text-decoration:none;padding-left:10px;padding-right:10px;height:30px}.leaflet-control-toolbar .leaflet-toolbar-1>li>.leaflet-toolbar-icon:hover{background-color:#a0a098}.leaflet-popup-toolbar{position:relative;box-sizing:content-box}.leaflet-popup-toolbar>li{float:left}.leaflet-popup-toolbar>li>.leaflet-toolbar-icon{border-right:1px solid #ccc}.leaflet-popup-toolbar>li:first-child>.leaflet-toolbar-icon{border-top-left-radius:4px;border-bottom-left-radius:4px}.leaflet-popup-toolbar>li:last-child>.leaflet-toolbar-icon{border-bottom-width:0;border-right:none}.leaflet-popup-toolbar .leaflet-toolbar-1{position:absolute;top:30px;left:0;padding-left:0}.leaflet-popup-toolbar .leaflet-toolbar-1>li>.leaflet-toolbar-icon{position:relative;float:left;width:30px;height:30px} -------------------------------------------------------------------------------- /dist/leaflet.toolbar.min.js: -------------------------------------------------------------------------------- 1 | !function(t,o,i){"use strict";t.L.Toolbar2=(L.Layer||L.Class).extend({statics:{baseClass:"leaflet-toolbar"},options:{className:"",filter:function(){return!0},actions:[]},initialize:function(t){L.setOptions(this,t),this._toolbar_type=this.constructor._toolbar_class_id},addTo:function(t){return this._arguments=[].slice.call(arguments),t.addLayer(this),this},onAdd:function(t){var o=t._toolbars[this._toolbar_type];0===this._calculateDepth()&&(o&&t.removeLayer(o),t._toolbars[this._toolbar_type]=this)},onRemove:function(t){0===this._calculateDepth()&&delete t._toolbars[this._toolbar_type]},appendToContainer:function(t){var o,i,e,n,s=this.constructor.baseClass+"-"+this._calculateDepth()+" "+this.options.className;for(this._container=t,this._ul=L.DomUtil.create("ul",s,t),this._disabledEvents=["click","mousemove","dblclick","mousedown","mouseup","touchstart"],i=0,n=this._disabledEvents.length;i0&&((i=[].slice.call(i)).push(this),e.addTo.apply(e,i),e.appendToContainer(o),this.addHooks=function(t){"function"==typeof n&&n.call(this,t),e._show()},this.removeHooks=function(t){"function"==typeof s&&s.call(this,t),e._hide()})}}),L.toolbarAction=function(t){return new L.Toolbar2.Action(t)},L.Toolbar2.Action.extendOptions=function(t){return this.extend({options:t})},L.Toolbar2.Control=L.Toolbar2.extend({statics:{baseClass:"leaflet-control-toolbar "+L.Toolbar2.baseClass},initialize:function(t){L.Toolbar2.prototype.initialize.call(this,t),this._control=new L.Control.Toolbar(this.options)},onAdd:function(t){this._control.addTo(t),L.Toolbar2.prototype.onAdd.call(this,t),this.appendToContainer(this._control.getContainer())},onRemove:function(t){L.Toolbar2.prototype.onRemove.call(this,t),this._control.remove?this._control.remove():this._control.removeFrom(t)}}),L.Control.Toolbar=L.Control.extend({onAdd:function(){return L.DomUtil.create("div","")}}),L.toolbar.control=function(t){return new L.Toolbar2.Control(t)},L.Toolbar2.Popup=L.Toolbar2.extend({statics:{baseClass:"leaflet-popup-toolbar "+L.Toolbar2.baseClass},options:{anchor:[0,0]},initialize:function(t,o){L.Toolbar2.prototype.initialize.call(this,o),this._marker=new L.Marker(t,{icon:new L.DivIcon({className:this.options.className,iconAnchor:[0,0]})})},onAdd:function(t){this._map=t,this._marker.addTo(t),L.Toolbar2.prototype.onAdd.call(this,t),this.appendToContainer(this._marker._icon),this._setStyles()},onRemove:function(t){t.removeLayer(this._marker),L.Toolbar2.prototype.onRemove.call(this,t),delete this._map},setLatLng:function(t){return this._marker.setLatLng(t),this},_setStyles:function(){for(var t,o,i,e=this._container,n=this._ul,s=L.point(this.options.anchor),a=n.querySelectorAll(".leaflet-toolbar-icon"),l=[],r=0,c=0,h=a.length;c 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 |
14 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/subtoolbar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 |
20 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-toolbar", 3 | "version": "0.4.0-alpha.2", 4 | "description": "Flexible, extensible toolbars for Leaflet maps.", 5 | "scripts": { 6 | "test": "grunt travis", 7 | "coveralls": "PHANTOM=`ls ./coverage | grep PhantomJS` && cat \"coverage/$PHANTOM/lcov.info\" | coveralls" 8 | }, 9 | "main": "dist/leaflet.toolbar.js", 10 | "peerDependencies": { 11 | "leaflet": "*" 12 | }, 13 | "devDependencies": { 14 | "autoprefixer": "~7.1.4", 15 | "chai": "~4.1.2", 16 | "grunt": "~1.0.1", 17 | "grunt-autoprefixer": "~3.0.4", 18 | "grunt-bump": "~0.8.0", 19 | "grunt-contrib-clean": "~1.1.0", 20 | "grunt-contrib-coffee": "~1.0.0", 21 | "grunt-contrib-concat": "~1.0.1", 22 | "grunt-contrib-cssmin": "~2.2.1", 23 | "grunt-contrib-jshint": "~1.1.0", 24 | "grunt-contrib-less": "~1.4.1", 25 | "grunt-contrib-nodeunit": "~1.0.0", 26 | "grunt-contrib-uglify": "~3.0.1", 27 | "grunt-contrib-watch": "~1.0.0", 28 | "grunt-contrib-connect": "~1.0.2", 29 | "grunt-gh-pages": "~2.0.0", 30 | "grunt-mocha": "~1.0.4", 31 | "grunt-karma": "~2.0.0", 32 | "jshint": "~2.9.5", 33 | "karma": "~1.7.1", 34 | "karma-chrome-launcher": "~2.2.0", 35 | "karma-coverage": "~1.1.1", 36 | "karma-firefox-launcher": "~1.0.1", 37 | "karma-mocha": "~1.3.0", 38 | "karma-mocha-reporter": "~2.2.4", 39 | "karma-phantomjs-launcher": "~1.0.4", 40 | "karma-safari-launcher": "~1.0.0", 41 | "leaflet": "~1.2.0", 42 | "matchdep": "~2.0.0", 43 | "mocha": "~3.5.2", 44 | "sinon": "~3.2.1", 45 | "uglify-js": "~3.1.0" 46 | }, 47 | "directories": { 48 | "examples": "./examples", 49 | "distribution": "./dist", 50 | "source": "./src", 51 | "tests": "./test" 52 | }, 53 | "repository": { 54 | "type": "git", 55 | "url": "https://github.com/Leaflet/Leaflet.toolbar.git" 56 | }, 57 | "keywords": [ 58 | "maps", 59 | "leaflet", 60 | "toolbar", 61 | "user-interface" 62 | ], 63 | "author": "Justin Manley", 64 | "license": "MIT", 65 | "readmeFilename": "README.md" 66 | } 67 | -------------------------------------------------------------------------------- /src/Action.js: -------------------------------------------------------------------------------- 1 | L.Toolbar2.Action = L.Handler.extend({ 2 | statics: { 3 | baseClass: 'leaflet-toolbar-icon' 4 | }, 5 | 6 | options: { 7 | toolbarIcon: { 8 | html: '', 9 | className: '', 10 | tooltip: '' 11 | }, 12 | subToolbar: new L.Toolbar2() 13 | }, 14 | 15 | initialize: function(options) { 16 | var defaultIconOptions = L.Toolbar2.Action.prototype.options.toolbarIcon; 17 | 18 | L.setOptions(this, options); 19 | this.options.toolbarIcon = L.extend({}, defaultIconOptions, this.options.toolbarIcon); 20 | }, 21 | 22 | enable: function(e) { 23 | if (e) { L.DomEvent.preventDefault(e); } 24 | if (this._enabled) { return; } 25 | this._enabled = true; 26 | 27 | if (this.addHooks) { this.addHooks(); } 28 | }, 29 | 30 | disable: function() { 31 | if (!this._enabled) { return; } 32 | this._enabled = false; 33 | 34 | if (this.removeHooks) { this.removeHooks(); } 35 | }, 36 | 37 | _createIcon: function(toolbar, container, args) { 38 | var iconOptions = this.options.toolbarIcon; 39 | 40 | this.toolbar = toolbar; 41 | this._icon = L.DomUtil.create('li', '', container); 42 | this._link = L.DomUtil.create('a', '', this._icon); 43 | 44 | this._link.innerHTML = iconOptions.html; 45 | this._link.setAttribute('href', '#'); 46 | this._link.setAttribute('title', iconOptions.tooltip); 47 | 48 | L.DomUtil.addClass(this._link, this.constructor.baseClass); 49 | if (iconOptions.className) { 50 | L.DomUtil.addClass(this._link, iconOptions.className); 51 | } 52 | 53 | L.DomEvent.on(this._link, 'click', this.enable, this); 54 | 55 | /* Add secondary toolbar */ 56 | this._addSubToolbar(toolbar, this._icon, args); 57 | }, 58 | 59 | _addSubToolbar: function(toolbar, container, args) { 60 | var subToolbar = this.options.subToolbar, 61 | addHooks = this.addHooks, 62 | removeHooks = this.removeHooks; 63 | 64 | /* For calculating the nesting depth. */ 65 | subToolbar.parentToolbar = toolbar; 66 | 67 | if (subToolbar.options.actions.length > 0) { 68 | /* Make a copy of args so as not to pollute the args array used by other actions. */ 69 | args = [].slice.call(args); 70 | args.push(this); 71 | 72 | subToolbar.addTo.apply(subToolbar, args); 73 | subToolbar.appendToContainer(container); 74 | 75 | this.addHooks = function(map) { 76 | if (typeof addHooks === 'function') { addHooks.call(this, map); } 77 | subToolbar._show(); 78 | }; 79 | 80 | this.removeHooks = function(map) { 81 | if (typeof removeHooks === 'function') { removeHooks.call(this, map); } 82 | subToolbar._hide(); 83 | }; 84 | } 85 | } 86 | }); 87 | 88 | L.toolbarAction = function toolbarAction(options) { 89 | return new L.Toolbar2.Action(options); 90 | }; 91 | 92 | L.Toolbar2.Action.extendOptions = function(options) { 93 | return this.extend({ options: options }); 94 | }; 95 | -------------------------------------------------------------------------------- /src/Control.js: -------------------------------------------------------------------------------- 1 | L.Toolbar2.Control = L.Toolbar2.extend({ 2 | statics: { 3 | baseClass: 'leaflet-control-toolbar ' + L.Toolbar2.baseClass 4 | }, 5 | 6 | initialize: function(options) { 7 | L.Toolbar2.prototype.initialize.call(this, options); 8 | 9 | this._control = new L.Control.Toolbar(this.options); 10 | }, 11 | 12 | onAdd: function(map) { 13 | this._control.addTo(map); 14 | 15 | L.Toolbar2.prototype.onAdd.call(this, map); 16 | 17 | this.appendToContainer(this._control.getContainer()); 18 | }, 19 | 20 | onRemove: function(map) { 21 | L.Toolbar2.prototype.onRemove.call(this, map); 22 | if (this._control.remove) {this._control.remove();} // Leaflet 1.0 23 | else {this._control.removeFrom(map);} 24 | } 25 | }); 26 | 27 | L.Control.Toolbar = L.Control.extend({ 28 | onAdd: function() { 29 | return L.DomUtil.create('div', ''); 30 | } 31 | }); 32 | 33 | L.toolbar.control = function(options) { 34 | return new L.Toolbar2.Control(options); 35 | }; 36 | -------------------------------------------------------------------------------- /src/Popup.js: -------------------------------------------------------------------------------- 1 | // A convenience class for built-in popup toolbars. 2 | 3 | L.Toolbar2.Popup = L.Toolbar2.extend({ 4 | statics: { 5 | baseClass: 'leaflet-popup-toolbar ' + L.Toolbar2.baseClass 6 | }, 7 | 8 | options: { 9 | anchor: [0, 0] 10 | }, 11 | 12 | initialize: function(latlng, options) { 13 | L.Toolbar2.prototype.initialize.call(this, options); 14 | 15 | /* 16 | * Developers can't pass a DivIcon in the options for L.Toolbar2.Popup 17 | * (the use of DivIcons is an implementation detail which may change). 18 | */ 19 | this._marker = new L.Marker(latlng, { 20 | icon : new L.DivIcon({ 21 | className: this.options.className, 22 | iconAnchor: [0, 0] 23 | }) 24 | }); 25 | }, 26 | 27 | onAdd: function(map) { 28 | this._map = map; 29 | this._marker.addTo(map); 30 | 31 | L.Toolbar2.prototype.onAdd.call(this, map); 32 | 33 | this.appendToContainer(this._marker._icon); 34 | 35 | this._setStyles(); 36 | }, 37 | 38 | onRemove: function(map) { 39 | map.removeLayer(this._marker); 40 | 41 | L.Toolbar2.prototype.onRemove.call(this, map); 42 | 43 | delete this._map; 44 | }, 45 | 46 | setLatLng: function(latlng) { 47 | this._marker.setLatLng(latlng); 48 | 49 | return this; 50 | }, 51 | 52 | _setStyles: function() { 53 | var container = this._container, 54 | toolbar = this._ul, 55 | anchor = L.point(this.options.anchor), 56 | icons = toolbar.querySelectorAll('.leaflet-toolbar-icon'), 57 | buttonHeights = [], 58 | toolbarWidth = 0, 59 | toolbarHeight, 60 | tipSize, 61 | tipAnchor; 62 | /* Calculate the dimensions of the toolbar. */ 63 | for (var i = 0, l = icons.length; i < l; i++) { 64 | if (icons[i].parentNode.parentNode === toolbar) { 65 | buttonHeights.push(parseInt(L.DomUtil.getStyle(icons[i], 'height'), 10)); 66 | toolbarWidth += Math.ceil(parseFloat(L.DomUtil.getStyle(icons[i], 'width'))); 67 | toolbarWidth += Math.ceil(parseFloat(L.DomUtil.getStyle(icons[i], 'border-right-width'))); 68 | } 69 | } 70 | toolbar.style.width = toolbarWidth + 'px'; 71 | 72 | /* Create and place the toolbar tip. */ 73 | this._tipContainer = L.DomUtil.create('div', 'leaflet-toolbar-tip-container', container); 74 | this._tipContainer.style.width = toolbarWidth + 75 | Math.ceil(parseFloat(L.DomUtil.getStyle(toolbar, 'border-left-width'))) + 76 | 'px'; 77 | 78 | this._tip = L.DomUtil.create('div', 'leaflet-toolbar-tip', this._tipContainer); 79 | 80 | /* Set the tipAnchor point. */ 81 | toolbarHeight = Math.max.apply(undefined, buttonHeights); 82 | // Ensure that the border completely surrounds its relative-positioned children. 83 | toolbar.style.height = toolbarHeight + 'px'; 84 | tipSize = parseInt(L.DomUtil.getStyle(this._tip, 'width'), 10); 85 | // The tip should be anchored exactly where the click event was received. 86 | tipAnchor = new L.Point(toolbarWidth/2, toolbarHeight + 1.414*tipSize); 87 | 88 | /* The anchor option allows app developers to adjust the toolbar's position. */ 89 | container.style.marginLeft = (anchor.x - tipAnchor.x) + 'px'; 90 | container.style.marginTop = (anchor.y - tipAnchor.y) + 'px'; 91 | }, 92 | }); 93 | 94 | L.toolbar.popup = function(options) { 95 | return new L.Toolbar2.Popup(options); 96 | }; 97 | -------------------------------------------------------------------------------- /src/Toolbar.js: -------------------------------------------------------------------------------- 1 | // L.Layer was introduced in Leaflet 1.0 and is not present in earlier releases. 2 | window.L.Toolbar2 = (L.Layer || L.Class).extend({ 3 | statics: { 4 | baseClass: 'leaflet-toolbar' 5 | }, 6 | 7 | options: { 8 | className: '', 9 | filter: function() { return true; }, 10 | actions: [] 11 | }, 12 | 13 | initialize: function(options) { 14 | L.setOptions(this, options); 15 | this._toolbar_type = this.constructor._toolbar_class_id; 16 | }, 17 | 18 | addTo: function(map) { 19 | this._arguments = [].slice.call(arguments); 20 | 21 | map.addLayer(this); 22 | 23 | return this; 24 | }, 25 | 26 | onAdd: function(map) { 27 | var currentToolbar = map._toolbars[this._toolbar_type]; 28 | 29 | if (this._calculateDepth() === 0) { 30 | if (currentToolbar) { map.removeLayer(currentToolbar); } 31 | map._toolbars[this._toolbar_type] = this; 32 | } 33 | }, 34 | 35 | onRemove: function(map) { 36 | /* 37 | * TODO: Cleanup event listeners. 38 | * For some reason, this throws: 39 | * "Uncaught TypeError: Cannot read property 'dragging' of null" 40 | * on this._marker when a toolbar icon is clicked. 41 | */ 42 | // for (var i = 0, l = this._disabledEvents.length; i < l; i++) { 43 | // L.DomEvent.off(this._ul, this._disabledEvents[i], L.DomEvent.stopPropagation); 44 | // } 45 | 46 | if (this._calculateDepth() === 0) { 47 | delete map._toolbars[this._toolbar_type]; 48 | } 49 | }, 50 | 51 | appendToContainer: function(container) { 52 | var baseClass = this.constructor.baseClass + '-' + this._calculateDepth(), 53 | className = baseClass + ' ' + this.options.className, 54 | Action, action, 55 | i, j, l, m; 56 | 57 | this._container = container; 58 | this._ul = L.DomUtil.create('ul', className, container); 59 | 60 | // Ensure that clicks, drags, etc. don't bubble up to the map. 61 | // These are the map events that the L.Draw.Polyline handler listens for. 62 | // Note that L.Draw.Polyline listens to 'mouseup', not 'mousedown', but 63 | // if only 'mouseup' is silenced, then the map gets stuck in a halfway 64 | // state because it receives a 'mousedown' event and is waiting for the 65 | // corresponding 'mouseup' event. 66 | this._disabledEvents = [ 67 | 'click', 'mousemove', 'dblclick', 68 | 'mousedown', 'mouseup', 'touchstart' 69 | ]; 70 | 71 | for (j = 0, m = this._disabledEvents.length; j < m; j++) { 72 | L.DomEvent.on(this._ul, this._disabledEvents[j], L.DomEvent.stopPropagation); 73 | } 74 | 75 | /* Instantiate each toolbar action and add its corresponding toolbar icon. */ 76 | for (i = 0, l = this.options.actions.length; i < l; i++) { 77 | Action = this._getActionConstructor(this.options.actions[i]); 78 | 79 | action = new Action(); 80 | action._createIcon(this, this._ul, this._arguments); 81 | } 82 | }, 83 | 84 | _getActionConstructor: function(Action) { 85 | var args = this._arguments, 86 | toolbar = this; 87 | 88 | return Action.extend({ 89 | initialize: function() { 90 | Action.prototype.initialize.apply(this, args); 91 | }, 92 | enable: function(e) { 93 | /* Ensure that only one action in a toolbar will be active at a time. */ 94 | if (toolbar._active) { toolbar._active.disable(); } 95 | toolbar._active = this; 96 | 97 | Action.prototype.enable.call(this, e); 98 | } 99 | }); 100 | }, 101 | 102 | /* Used to hide subToolbars without removing them from the map. */ 103 | _hide: function() { 104 | this._ul.style.display = 'none'; 105 | }, 106 | 107 | /* Used to show subToolbars without removing them from the map. */ 108 | _show: function() { 109 | this._ul.style.display = 'block'; 110 | }, 111 | 112 | _calculateDepth: function() { 113 | var depth = 0, 114 | toolbar = this.parentToolbar; 115 | 116 | while (toolbar) { 117 | depth += 1; 118 | toolbar = toolbar.parentToolbar; 119 | } 120 | 121 | return depth; 122 | } 123 | }); 124 | 125 | // L.Mixin.Events is replaced by L.Evented in Leaflet 1.0. L.Layer (also 126 | // introduced in Leaflet 1.0) inherits from L.Evented, so if L.Layer is 127 | // present, then L.Toolbar2 will already support events. 128 | if (!L.Evented) { 129 | L.Toolbar2.include(L.Mixin.Events); 130 | } 131 | 132 | L.toolbar = {}; 133 | 134 | var toolbar_class_id = 0; 135 | 136 | L.Toolbar2.extend = function extend(props) { 137 | var statics = L.extend({}, props.statics, { 138 | "_toolbar_class_id": toolbar_class_id 139 | }); 140 | 141 | toolbar_class_id += 1; 142 | L.extend(props, { statics: statics }); 143 | 144 | return L.Class.extend.call(this, props); 145 | }; 146 | 147 | L.Map.addInitHook(function() { 148 | this._toolbars = {}; 149 | }); 150 | -------------------------------------------------------------------------------- /src/Toolbar.less: -------------------------------------------------------------------------------- 1 | /* Variables and Mixins */ 2 | @toolbar-icon-size: 30px; 3 | @toolbar-icon-radius: 4px; 4 | 5 | @toolbar-background-color: #fff; 6 | @toolbar-tip-size: 16px; 7 | 8 | @toolbar-border-size: 2px; 9 | 10 | .leaflet-toolbar-box-shadow() { 11 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 12 | } 13 | 14 | .leaflet-toolbar-border() { 15 | border: @toolbar-border-size solid rgba(0,0,0,0.2); 16 | border-bottom-left-radius: @toolbar-icon-radius; 17 | border-bottom-right-radius: @toolbar-icon-radius; 18 | border-top-left-radius: @toolbar-icon-radius; 19 | border-top-right-radius: @toolbar-icon-radius; 20 | } 21 | 22 | .leaflet-toolbar-icon() { 23 | display: block; 24 | } 25 | 26 | /* Generic L.Toolbar */ 27 | .leaflet-toolbar-0 { 28 | list-style: none; 29 | padding-left: 0; 30 | .leaflet-toolbar-border(); 31 | 32 | > li { 33 | position: relative; 34 | 35 | > .leaflet-toolbar-icon { 36 | .leaflet-toolbar-icon(); 37 | 38 | width: @toolbar-icon-size; 39 | height: @toolbar-icon-size; 40 | line-height: @toolbar-icon-size; 41 | 42 | margin-right: 0; 43 | padding-right: 0; 44 | border-right: 0; 45 | 46 | text-align: center; 47 | text-decoration: none; 48 | 49 | background-color: @toolbar-background-color; 50 | &:hover { background-color: #f4f4f4; } 51 | } 52 | } 53 | 54 | .leaflet-toolbar-1 { 55 | display: none; 56 | list-style: none; 57 | } 58 | } 59 | 60 | .leaflet-toolbar-tip-container { 61 | margin: 0 auto; 62 | margin-top: -@toolbar-tip-size; 63 | height: @toolbar-tip-size; 64 | position: relative; 65 | overflow: hidden; 66 | } 67 | 68 | .leaflet-toolbar-tip { 69 | width: @toolbar-tip-size; 70 | height: @toolbar-tip-size; 71 | 72 | // Ensure that the toolbar tip matches up with the bottom of the popup toolbar. 73 | margin: -@toolbar-tip-size/2 auto 0; 74 | 75 | background-color: @toolbar-background-color; 76 | .leaflet-toolbar-border(); 77 | background-clip: content-box; 78 | 79 | transform: rotate(45deg); 80 | } 81 | 82 | /* L.Toolbar.Control */ 83 | .leaflet-control-toolbar { 84 | > li > .leaflet-toolbar-icon { border-bottom: 1px solid #ccc; } 85 | 86 | > li:first-child > .leaflet-toolbar-icon { 87 | border-top-left-radius: @toolbar-icon-radius; 88 | border-top-right-radius: @toolbar-icon-radius; 89 | } 90 | > li:last-child > .leaflet-toolbar-icon { 91 | border-bottom-left-radius: @toolbar-icon-radius; 92 | border-bottom-right-radius: @toolbar-icon-radius; 93 | border-bottom-width: 0; 94 | } 95 | 96 | /* Secondary Toolbar */ 97 | .leaflet-toolbar-1 { 98 | margin: 0; 99 | padding: 0; 100 | 101 | position: absolute; 102 | left: @toolbar-icon-size; /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */ 103 | top: 0; 104 | 105 | white-space: nowrap; 106 | 107 | height: @toolbar-icon-size; 108 | 109 | > li { display: inline-block; } 110 | > li > .leaflet-toolbar-icon { 111 | .leaflet-toolbar-icon(); 112 | 113 | background-color: #919187; 114 | border-left: 1px solid #aaa; 115 | color: #fff; 116 | font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif; 117 | line-height: @toolbar-icon-size; 118 | text-decoration: none; 119 | padding-left: 10px; 120 | padding-right: 10px; 121 | height: @toolbar-icon-size; 122 | 123 | &:hover { background-color: #a0a098; } 124 | } 125 | > li:last-child > .leaflet-toolbar-icon { 126 | border-top-right-radius: @toolbar-icon-radius; 127 | border-bottom-right-radius: @toolbar-icon-radius; 128 | } 129 | } 130 | } 131 | 132 | /* L.Toolbar.Popup */ 133 | .leaflet-popup-toolbar { 134 | position: relative; 135 | 136 | // Make width calculation easier in L.Toolbar2.Popup. 137 | box-sizing: content-box; 138 | 139 | > li { float: left; } 140 | > li > .leaflet-toolbar-icon { border-right: 1px solid #ccc; } 141 | > li:first-child > .leaflet-toolbar-icon { 142 | border-top-left-radius: @toolbar-icon-radius; 143 | border-bottom-left-radius: @toolbar-icon-radius; 144 | } 145 | > li:last-child > .leaflet-toolbar-icon { 146 | border-top-right-radius: @toolbar-icon-radius; 147 | border-bottom-right-radius: @toolbar-icon-radius; 148 | border-bottom-width: 0; 149 | border-right: none; 150 | } 151 | 152 | .leaflet-toolbar-1 { 153 | position: absolute; 154 | top: @toolbar-icon-size; 155 | left: 0; 156 | 157 | padding-left: 0; 158 | 159 | > li > .leaflet-toolbar-icon { 160 | position: relative; 161 | float: left; 162 | 163 | width: @toolbar-icon-size; 164 | height: @toolbar-icon-size; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /test/SpecHelper.js: -------------------------------------------------------------------------------- 1 | beforeEach(function() { 2 | window.expect = chai.expect; 3 | }); -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Jul 08 2014 12:47:31 GMT-0500 (CDT) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | '../node_modules/leaflet/dist/leaflet-src.js', 19 | '../node_modules/leaflet/dist/leaflet.css', 20 | '../node_modules/chai/chai.js', 21 | '../node_modules/sinon/pkg/sinon-1.10.3.js', 22 | '../dist/leaflet.toolbar.css', 23 | '../src/Toolbar.js', 24 | '../src/Action.js', 25 | '../src/Control.js', 26 | '../src/Popup.js', 27 | '../test/SpecHelper.js', 28 | '../test/src/*Spec.js', 29 | ], 30 | 31 | 32 | // list of files to exclude 33 | exclude: [ 34 | 35 | ], 36 | 37 | 38 | // preprocess matching files before serving them to the browser 39 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 40 | preprocessors: { 41 | '../src/*.js': 'coverage' 42 | }, 43 | 44 | 45 | // test results reporter to use 46 | // possible values: 'dots', 'progress' 47 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 48 | reporters: [ 'mocha', 'coverage' ], 49 | 50 | 51 | // web server port 52 | port: 9876, 53 | 54 | 55 | // enable / disable colors in the output (reporters and logs) 56 | colors: true, 57 | 58 | 59 | // level of logging 60 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 61 | logLevel: config.LOG_INFO, 62 | 63 | 64 | // enable / disable watching file and executing tests whenever any file changes 65 | autoWatch: true, 66 | 67 | // start these browsers 68 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 69 | browsers: [ 'PhantomJS' ], 70 | 71 | plugins: [ 72 | 'karma-mocha', 73 | 'karma-phantomjs-launcher', 74 | 'karma-chrome-launcher', 75 | 'karma-firefox-launcher', 76 | 'karma-safari-launcher', 77 | 'karma-mocha-reporter', 78 | 'karma-coverage' 79 | ], 80 | 81 | // Continuous Integration mode 82 | // if true, Karma captures browsers, runs the tests and exits 83 | singleRun: false, 84 | 85 | coverageReporter: { 86 | reporters: [ 87 | { type: 'text', dir: '../coverage/', file: 'coverage.txt' }, 88 | { type: 'lcovonly', dir: '../coverage/' }, 89 | { type: 'html', dir: '../coverage/' } 90 | ] 91 | } 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /test/src/ToolbarActionSpec.js: -------------------------------------------------------------------------------- 1 | describe("L.Toolbar2.Action", function() { 2 | var map, 3 | container, 4 | ul, 5 | toolbar, 6 | Action; 7 | 8 | beforeEach(function() { 9 | map = new L.Map(L.DomUtil.create('div')).setView([41.7896,-87.5996], 15); 10 | container = L.DomUtil.create('div', 'leaflet-toolbar-0', document.body); 11 | ul = L.DomUtil.create('ul'); 12 | 13 | Action = L.Toolbar2.Action.extend({ 14 | options: { 15 | toolbarIcon: { 16 | html: 'Test Icon', 17 | className: 'my-toolbar-icon' 18 | } 19 | } 20 | }); 21 | toolbar = new L.Toolbar2({ actions: [Action] }); 22 | }); 23 | 24 | describe("#_createIcon", function() { 25 | it("Sets the content of the icon to the html option passed to the ToolbarIcon.", function() { 26 | var iconText, 27 | action = new Action(map); 28 | 29 | action._createIcon(toolbar, ul, []); 30 | iconText = ul.querySelectorAll('.leaflet-toolbar-icon')[0].innerHTML; 31 | 32 | expect(iconText).to.equal('Test Icon'); 33 | }); 34 | 35 | it("Appends options.className to the base className", function() { 36 | var iconButton, 37 | action = new Action(map); 38 | 39 | action._createIcon(toolbar, ul, []); 40 | iconButton = ul.querySelectorAll('a')[0]; 41 | 42 | expect(L.DomUtil.hasClass(iconButton, 'leaflet-toolbar-icon')).to.equal(true); 43 | expect(L.DomUtil.hasClass(iconButton, 'my-toolbar-icon')).to.equal(true); 44 | }); 45 | }); 46 | 47 | describe("#_addSubToolbar", function() { 48 | it("Should not add a
    element when the toolbar has no actions.", function() { 49 | var action = new Action(map), 50 | subToolbar = action.options.subToolbar, 51 | ul; 52 | 53 | action._addSubToolbar(toolbar, container, [map]); 54 | ul = container.querySelectorAll('ul'); 55 | 56 | expect(ul.length).to.equal(0); 57 | expect(subToolbar._ul).to.be.an('undefined'); 58 | }); 59 | 60 | it("Should add a
      element when the toolbar has one action.", function() { 61 | var subToolbar = new L.Toolbar2({ actions: [L.Toolbar2.Action] }), 62 | TestAction = Action.extend({ options: { subToolbar: subToolbar } }), 63 | ul; 64 | 65 | toolbar = new L.Toolbar2({ actions: [TestAction] }).addTo(map); 66 | 67 | TestAction.prototype._addSubToolbar(toolbar, container, [map]); 68 | ul = container.querySelectorAll('ul'); 69 | 70 | expect(ul.length).to.equal(1); 71 | expect(L.DomUtil.hasClass(subToolbar._ul, 'leaflet-toolbar-1')).to.equal(true); 72 | }); 73 | }); 74 | 75 | describe("#addHooks", function() { 76 | beforeEach(function() { 77 | var subToolbar = new L.Toolbar2({ actions: [L.Toolbar2.Action] }), 78 | action = new L.Toolbar2.Action(); 79 | 80 | L.setOptions(action, { subToolbar: subToolbar }); 81 | toolbar = new L.Toolbar2({ actions: [L.Toolbar2.Action] }).addTo(map); 82 | 83 | action._addSubToolbar(toolbar, container, [map]); 84 | }); 85 | 86 | /* How to test this without access to the action itself? */ 87 | it.skip("Should show the subToolbar when the action is enabled.", function() { 88 | var ul = container.querySelectorAll('ul')[0], 89 | action = new L.Toolbar2.Action(); 90 | 91 | expect(getComputedStyle(ul).display).to.equal('none'); 92 | 93 | action.enable(); 94 | expect(ul.style.display).to.equal('block'); 95 | }); 96 | }); 97 | 98 | describe("#removeHooks", function() { 99 | beforeEach(function() { 100 | var subToolbar = new L.Toolbar2({ actions: [L.Toolbar2.Action] }), 101 | action = new L.Toolbar2.Action(); 102 | 103 | L.setOptions(action, { subToolbar: subToolbar }); 104 | toolbar = new L.Toolbar2({ actions: [L.Toolbar2.Action] }).addTo(map); 105 | 106 | action._addSubToolbar(toolbar, container, [map]); 107 | }); 108 | 109 | /* How to test this without access to the action itself? */ 110 | it.skip("Should hide the subToolbar when the hndler is disabled.", function() { 111 | var ul = container.querySelectorAll('ul')[0], 112 | action = new L.Toolbar2.Action(); 113 | 114 | expect(getComputedStyle(ul).display).to.equal('none'); 115 | 116 | action.enable(); 117 | action.disable(); 118 | 119 | expect(ul.style.display).to.equal('none'); 120 | }); 121 | }); 122 | 123 | describe("#enable", function() { 124 | it("Should enable the action.", function() { 125 | var action = new L.Toolbar2.Action(); 126 | 127 | action.enable(); 128 | expect(action.enabled()).to.equal(true); 129 | }); 130 | 131 | it("Should re-enable the action after it is disabled.", function() { 132 | var action = new L.Toolbar2.Action(); 133 | 134 | action.enable(); 135 | action.disable(); 136 | action.enable(); 137 | 138 | /* Regression test: code written to maintain a single active action at a time 139 | * was inadvertently disabling actions. 140 | */ 141 | expect(action.enabled()).to.equal(true); 142 | }); 143 | }); 144 | 145 | describe("#disable", function() { 146 | it("Should disable the action.", function() { 147 | var action = new L.Toolbar2.Action(); 148 | 149 | action.enable(); 150 | action.disable(); 151 | 152 | expect(action.enabled()).to.equal(false); 153 | }); 154 | }); 155 | 156 | describe(".extendOptions", function() { 157 | it("Should return a new constructor with parent options merged with those passed to .extendOptions", function() { 158 | var H = L.Toolbar2.Action.extendOptions({ color: '#d1bd0f' }), 159 | h = new H(map); 160 | 161 | /* New option should be passed to the new constructor. */ 162 | expect(h.options.color).to.equal('#d1bd0f'); 163 | 164 | /* Options of the parent constructor should be retained. */ 165 | expect(h.options.toolbarIcon.html).to.equal(''); 166 | }); 167 | }); 168 | }); 169 | 170 | describe("L.toolbarAction", function() { 171 | describe("class factory", function() { 172 | it("Creates an L.Toolbar2.Action instance.", function() { 173 | var options = { toolbarIcon: { html: 'hello' } }; 174 | 175 | expect(L.toolbarAction(options)).to.deep.equal(new L.Toolbar2.Action(options)); 176 | }); 177 | }); 178 | }); -------------------------------------------------------------------------------- /test/src/ToolbarControlSpec.js: -------------------------------------------------------------------------------- 1 | describe("L.Toolbar2.Control", function() { 2 | var map, 3 | toolbar; 4 | 5 | beforeEach(function() { 6 | map = new L.Map(L.DomUtil.create('div')).setView([41.7896,-87.5996], 15); 7 | toolbar = new L.Toolbar2.Control([L.Toolbar2.Action]).addTo(map); 8 | }); 9 | 10 | describe("#onAdd", function() { 11 | it("Adds the toolbar to the map.", function() { 12 | expect(map.hasLayer(toolbar)).to.equal(true); 13 | }); 14 | }); 15 | 16 | describe("#onRemove", function() { 17 | it("Removes the toolbar from the map", function() { 18 | map.removeLayer(toolbar); 19 | expect(map.hasLayer(toolbar)).to.equal(false); 20 | }); 21 | }); 22 | }); 23 | 24 | describe("L.toolbar.control", function() { 25 | describe("class factory", function() { 26 | it("creates L.Toolbar2.Control instance", function() { 27 | var options = {position: 'bottomleft'}; 28 | expect(L.toolbar.control(options)).to.eql(new L.Toolbar2.Control(options)); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/src/ToolbarPopupSpec.js: -------------------------------------------------------------------------------- 1 | describe("L.Toolbar2.Popup", function() { 2 | var map, 3 | toolbar; 4 | 5 | beforeEach(function() { 6 | var latlng = new L.LatLng(0, 0); 7 | 8 | /* need to add the
      to document.body in order for external CSS stylesheets to be applied. */ 9 | map = new L.Map(L.DomUtil.create('div', 'map', document.body)).setView([41.7896,-87.5996], 15); 10 | toolbar = new L.Toolbar2.Popup(latlng, { 11 | actions: [L.Toolbar2.Action, L.Toolbar2.Action] 12 | }); 13 | }); 14 | 15 | describe("#onRemove", function() { 16 | it("Should remove the toolbar from the map.", function() { 17 | toolbar.addTo(map); 18 | map.removeLayer(toolbar); 19 | 20 | expect(map.hasLayer(toolbar)).to.equal(false); 21 | }); 22 | 23 | it.skip("Should not throw an error if toolbar removal is triggered by clicking a toolbar icon.", function() { 24 | /* Need to be able to trigger a click event on a toolbar icon. */ 25 | }); 26 | }); 27 | 28 | describe("#_setStyles", function() { 29 | it("Sets the width of the toolbar to a nonzero value if there are toolbar actions.", function() { 30 | var actionsLength = toolbar.options.actions.length, 31 | toolbarContainer, 32 | toolbarButtons, 33 | toolbarWidth, 34 | buttonWidth; 35 | 36 | /* Want to test the width of the toolbar with more than one action. */ 37 | expect(actionsLength).to.be.above(1); 38 | 39 | toolbar.addTo(map); 40 | 41 | toolbarContainer = toolbar._ul; 42 | toolbarButtons = toolbar._container.querySelectorAll('.leaflet-toolbar-icon'); 43 | 44 | expect(toolbarButtons.length).to.equal(actionsLength); 45 | 46 | toolbarWidth = parseInt(L.DomUtil.getStyle(toolbarContainer, 'width'), 10); 47 | buttonWidth = parseInt(L.DomUtil.getStyle(toolbarButtons[0], 'width'), 10); 48 | 49 | expect(toolbarWidth).to.be.above(buttonWidth); 50 | }); 51 | }); 52 | 53 | describe("setLatLng", function() { 54 | it("Should change the latlng of the popup", function() { 55 | var latlng = new L.LatLng(1, 2); 56 | 57 | toolbar.setLatLng(latlng); 58 | }); 59 | }); 60 | }); 61 | 62 | describe("L.toolbar.popup", function() { 63 | describe("class factory", function() { 64 | it("creates L.Toolbar2.Popup instance", function() { 65 | var options = {position: 'bottomleft'}; 66 | expect(L.toolbar.popup(options)).to.eql(new L.Toolbar2.Popup(options)); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/src/ToolbarSpec.js: -------------------------------------------------------------------------------- 1 | describe("L.Toolbar2", function() { 2 | var map, 3 | container, 4 | toolbarTemplate, 5 | toolbar; 6 | 7 | beforeEach(function() { 8 | map = new L.Map(L.DomUtil.create('div')).setView([41.7896,-87.5996], 15); 9 | container = L.DomUtil.create('div'); 10 | 11 | toolbarTemplate = [L.Toolbar2.Action]; 12 | toolbar = new L.Toolbar2({ actions: toolbarTemplate }); 13 | }); 14 | 15 | describe("#onAdd", function() { 16 | it.skip("Should replace the current toolbar when a duplicate is added to the map.", function() { 17 | var toolbar1 = new L.Toolbar2().addTo(map); 18 | 19 | new L.Toolbar2().addTo(map); 20 | expect(map.hasLayer(toolbar1)).to.equal(false); 21 | }); 22 | 23 | it.skip("Should allow multiple toolbars of different types on the map.", function() { 24 | var Toolbar1 = L.Toolbar2.extend({}), 25 | Toolbar2 = L.Toolbar2.extend({}), 26 | toolbar1 = new Toolbar1().addTo(map); 27 | 28 | new Toolbar2().addTo(map); 29 | 30 | expect(map.hasLayer(toolbar1)).to.equal(true); 31 | }); 32 | }); 33 | 34 | describe("#addTo", function() { 35 | it("Should add a toolbar to the map.", function() { 36 | toolbar.addTo(map); 37 | expect(map.hasLayer(toolbar)).to.equal(true); 38 | }); 39 | 40 | it("Should pass along its arguments to each toolbar action factory.", function(done) { 41 | var TestHandler = L.Toolbar2.Action.extend({ 42 | initialize: function(arg1, arg2) { 43 | expect(arg1).to.equal(map); 44 | expect(arg2).to.equal(2); 45 | done(); 46 | } 47 | }); 48 | 49 | toolbar = new L.Toolbar2({ actions: [TestHandler] }); 50 | 51 | toolbar.addTo(map, 2); 52 | toolbar.appendToContainer(container); 53 | }); 54 | }); 55 | 56 | describe("#appendToContainer", function() { 57 | it("Should create an icon for each toolbar action.", function() { 58 | var icons; 59 | 60 | toolbar.appendToContainer(container); 61 | 62 | icons = container.querySelectorAll('.leaflet-toolbar-icon'); 63 | 64 | expect(icons.length).to.equal(toolbarTemplate.length); 65 | }); 66 | }); 67 | 68 | describe("#_show", function() { 69 | it("Should set the display of the toolbar container to 'block'", function() { 70 | toolbar.addTo(map); 71 | toolbar.appendToContainer(container); 72 | 73 | toolbar._show(); 74 | expect(toolbar._ul.style.display).to.equal('block'); 75 | }); 76 | }); 77 | 78 | describe("#_hide", function() { 79 | it("Should set the display of the toolbar container to 'block'", function() { 80 | toolbar.addTo(map); 81 | toolbar.appendToContainer(container); 82 | 83 | toolbar._hide(); 84 | expect(toolbar._ul.style.display).to.equal('none'); 85 | }); 86 | }); 87 | 88 | describe("#_calculateToolbarDepth", function() { 89 | it("Should return 0 for a single toolbar", function() { 90 | toolbar.addTo(map); 91 | expect(toolbar._calculateDepth()).to.equal(0); 92 | }); 93 | 94 | it("Should return 1 for a nested toolbar", function() { 95 | var subToolbar = new L.Toolbar2(), 96 | TestHandler = L.Toolbar2.Action.extend({ options: { subToolbar: subToolbar } }); 97 | 98 | toolbar = new L.Toolbar2({ actions: [TestHandler] }).addTo(map); 99 | toolbar.appendToContainer(container); 100 | 101 | expect(subToolbar._calculateDepth()).to.equal(1); 102 | }); 103 | }); 104 | }); 105 | --------------------------------------------------------------------------------