merge master

This commit is contained in:
Vladimir Agafonkin 2013-11-15 18:40:36 +02:00
commit 45a06f7859
125 changed files with 2323 additions and 1150 deletions

View File

@ -5,9 +5,149 @@ Leaflet Changelog
## 0.7-dev (master)
An in-progress version being developed on the `master` branch. Includes all fixes from the `stable` branch.
An in-progress version being developed on the `master` branch.
### Improvements
#### Usability improvements
* Added **support for IE11 touch devices** (by [@danzel](https://github.com/danzel), [@DanielX2](https://github.com/DanielX2) and [@fnicollet](https://github.com/fnicollet)). [#2039](https://github.com/Leaflet/Leaflet/pull/2039) [#2066](https://github.com/Leaflet/Leaflet/pull/2066) [#2037](https://github.com/Leaflet/Leaflet/issues/2037) [#2102](https://github.com/Leaflet/Leaflet/issues/2102)
* Added shift-double-click to zoom out shortcut. [#2185](https://github.com/Leaflet/Leaflet/issues/2185)
* Significantly improved **controls design on mobile** devices. [#1868](https://github.com/Leaflet/Leaflet/issues/1868) [#2012](https://github.com/Leaflet/Leaflet/issues/2012)
* Fixed and improved IE7-8 control and popup styles.
* Made subtle improvements to control styles on desktop browsers.
* Improved keyboard nav support so that map doesn't loose focus when you click on a control (by [@jacobtoye](https://github.com/jacobtoye)). [#2150](https://github.com/Leaflet/Leaflet/issues/2150) [#2148](https://github.com/Leaflet/Leaflet/issues/2148)
* Improved `maxBounds` behavior: now it doesn't force higher minimal zoom, and anchors to max bounds edges properly when zooming (by [@kapouer](https://github.com/kapouer) and [@mourner](https://github.com/mourner)). [#2187](https://github.com/Leaflet/Leaflet/pull/2187) [#1946](https://github.com/Leaflet/Leaflet/pull/1946) [#2081](https://github.com/Leaflet/Leaflet/issues/2081) [#2168](https://github.com/Leaflet/Leaflet/issues/2168) [#1908](https://github.com/Leaflet/Leaflet/issues/1908)
#### Map API improvements
* Made `Map` `setView` `zoom` argument optional. [#2056](https://github.com/Leaflet/Leaflet/issues/2056)
* Added `maxZoom` option to `Map` `fitBounds`. [#2101](https://github.com/Leaflet/Leaflet/issues/2101)
* Added `Map` `bounceAtZoomLimits` option that makes the map bounce when you pinch-zoom past limits (it worked like this before, but now you can disable this) (by [@trevorpowell](https://github.com/trevorpowell)). [#1864](https://github.com/Leaflet/Leaflet/issues/1864) [#2072](https://github.com/Leaflet/Leaflet/pull/2072)
* Added `distance` property to `Map` and `Marker` `dragend` events. [#2158](https://github.com/Leaflet/Leaflet/issues/2158) [#872](https://github.com/Leaflet/Leaflet/issues/872)
* Added optional support for center-oriented scroll and double-click zoom (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1939](https://github.com/Leaflet/Leaflet/issues/1939)
* Added `timestamp` to `Map` `locationfound` event. [#584](https://github.com/Leaflet/Leaflet/pull/584)
* Improved `Map` `invalidateSize` to call `moveend` immediately unless given `debounceMoveend: true` option (by [@jfirebaugh](https://github.com/jfirebaugh)). [#2181](https://github.com/Leaflet/Leaflet/issues/2181)
#### TileLayer API improvements
* Added `TileLayer` `maxNativeZoom` option that allows displaying tile layers on zoom levels above their maximum by **upscaling tiles**. [#1802](https://github.com/Leaflet/Leaflet/issues/1802) [#1798](https://github.com/Leaflet/Leaflet/issues/1798)
* Added `TileLayer` `tileloadstart` event (by [@tmcw](https://github.com/tmcw)). [#2142](https://github.com/Leaflet/Leaflet/pull/2142) [#2140](https://github.com/Leaflet/Leaflet/issues/2140)
* Improved `TileLayer` world size (used for wrapping and limiting tiles) to be derived from CRS instead of hardcoded, making it easier to use with custom projections (by [@perliedman](https://github.com/perliedman)). [#2160](https://github.com/Leaflet/Leaflet/pull/2160)
#### Marker API improvements
* Added CSS classes to draggable markers for easier customization (by [@snkashis](https://github.com/snkashis)). [#1902](https://github.com/Leaflet/Leaflet/issues/1902) [#1916](https://github.com/Leaflet/Leaflet/issues/1916)
* Added `Marker` `add` event (by [@tohaocean](https://github.com/tohaocean)). [#1942](https://github.com/Leaflet/Leaflet/issues/1942)
* Added `Marker` `getPopup` method (by [@scottharvey](https://github.com/scottharvey)). [#618](https://github.com/Leaflet/Leaflet/issues/618) [#1197](https://github.com/Leaflet/Leaflet/pull/1197)
* Added `Marker` `alt` option for adding `alt` text to markers (by [@jimmytidey](https://github.com/jimmytidey)). [#2112](https://github.com/Leaflet/Leaflet/pull/2112)
#### Vector layers API improvements
* Added `Path` `className` option for adding custom class names to vector layers.
* Added `Path` `lineCap` and `lineJoin` options (by [@palewire](https://github.com/palewire)). [#1843](https://github.com/Leaflet/Leaflet/issues/1843) [#1863](https://github.com/Leaflet/Leaflet/issues/1863) [#1881](https://github.com/Leaflet/Leaflet/issues/1881)
* Added ability to pass vector options to `GeoJSON` (by [@kapouer](https://github.com/kapouer)). [#2075](https://github.com/Leaflet/Leaflet/pull/2075)
* Improved `Polygon` `setLatLngs` to also accept holes (by [@aparshin](https://github.com/aparshin)). [#2095](https://github.com/Leaflet/Leaflet/pull/2095) [#1518](https://github.com/Leaflet/Leaflet/issues/1518)
* Added `GeoJSON` 3D format support and optional `altitude` argument to `LatLng` constructor (by [@Starefossen](https://github.com/Starefossen)). [#1822](https://github.com/Leaflet/Leaflet/pull/1822)
* Added `MultiPolygon` and `MultiPolyline` `openPopup` method. [#2046](https://github.com/Leaflet/Leaflet/issues/2046)
#### Popup API improvements
* Added `Popup` `update` method. [#1959](https://github.com/Leaflet/Leaflet/issues/1959)
* Added `Popup` `autoPanPaddingTopLeft` and `autoPanPaddingBottomRight` options (by [@albburtsev](https://github.com/albburtsev)). [#1972](https://github.com/Leaflet/Leaflet/issues/1972) [#1588](https://github.com/Leaflet/Leaflet/issues/1588)
* Added `Popup` `getContent` method. [#2100](https://github.com/Leaflet/Leaflet/issues/2100)
* Added `Popup` `getLatLng` method (by [@AndreyGeonya](https://github.com/AndreyGeonya)). [#2097](https://github.com/Leaflet/Leaflet/pull/2097)
#### Misc API improvements
* Added `ImageOverlay` `setUrl` and `getAttribution` methods and `attribution` option (by [@stsydow](https://github.com/stsydow)). [#1957](https://github.com/Leaflet/Leaflet/issues/1957) [#1958](https://github.com/Leaflet/Leaflet/issues/1958)
* Added localization support for the zoom control (by [@Danielku15](https://github.com/Danielku15)). [#1953](https://github.com/Leaflet/Leaflet/issues/1953) [#1643](https://github.com/Leaflet/Leaflet/issues/1643) [#1953](https://github.com/Leaflet/Leaflet/pull/1953)
* Significantly improved `L.Util.template` performance (affects `L.TileLayer`) by introducing cached template compilation (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#1969](https://github.com/Leaflet/Leaflet/issues/1969) [#1968](https://github.com/Leaflet/Leaflet/issues/1968) [#1554](https://github.com/Leaflet/Leaflet/issues/1554)
* Added `CRS` `getSize` for getting the world size in pixels (by [@perliedman](https://github.com/perliedman)). [#2160](https://github.com/Leaflet/Leaflet/pull/2160)
* Added `leaflet-drag-target` CSS class to an element under cursor when dragging for more flexible customization. [#2164](https://github.com/Leaflet/Leaflet/issues/2164) [#1902](https://github.com/Leaflet/Leaflet/issues/1902)
* Improved `L.DomUtil` `addClass`, `removeClass`, `hasClass` methods performance and fixed it to work with SVG elements. [#2164](https://github.com/Leaflet/Leaflet/issues/2164)
#### Dev workflow improvements
* Added an [official FAQ](https://github.com/Leaflet/Leaflet/blob/master/FAQ.md).
* Cleaned up and moved old IE styles to `leaflet.css` and removed `leaflet.ie.css`, so **no need for IE conditional comment** when including Leaflet now. [#2159](https://github.com/Leaflet/Leaflet/issues/2159)
* Added `leaflet-oldie` CSS class to map container in IE7-8 for easier styling. [#2159](https://github.com/Leaflet/Leaflet/issues/2159)
* Officially **dropped support for IE6**. Nobody cares anyway, and Leaflet should still be accessible on it. [#2159](https://github.com/Leaflet/Leaflet/issues/2159)
* Improved the build system to check JS errors in spec files. [#2151](https://github.com/Leaflet/Leaflet/issues/2151)
* Fixed `jake` command to run tests before building the source. [#2151](https://github.com/Leaflet/Leaflet/issues/2151)
* Switched the main file in `package.json` to unminified version for NPM/Browserify (by [@icetan](https://github.com/icetan)). [#2109](https://github.com/Leaflet/Leaflet/pull/2109)
### Bugfixes
#### 0.6 regression fixes
* Fixed a **memory leak in iOS7** that could crash Safari when handling lots of objects (e.g. 1000 markers) (by [@danzel](https://github.com/danzel)). [#2149](https://github.com/Leaflet/Leaflet/pull/2149) [#2122](https://github.com/Leaflet/Leaflet/issues/2122)
* Fixed a bug that caused lag at the beginning of panning in Chrome (by [@jfirebaugh](https://github.com/jfirebaugh)). [#2163](https://github.com/Leaflet/Leaflet/issues/2163)
* Fixed a regression that made the layers control unscrollable in Firefox. [#2029](https://github.com/Leaflet/Leaflet/issues/2029)
* Fixed a regression that broke `worldCopyJump: true` option (by [@fastrde](https://github.com/fastrde)). [#1904](https://github.com/Leaflet/Leaflet/issues/1904) [#1831](https://github.com/Leaflet/Leaflet/issues/1831) [#1982](https://github.com/Leaflet/Leaflet/issues/1982)
* Fixed a regression where a first map click after popup close button click was ignored (by [@fastrde](https://github.com/fastrde)). [#1537](https://github.com/Leaflet/Leaflet/issues/1537) [#1963](https://github.com/Leaflet/Leaflet/issues/1963) [#1925](https://github.com/Leaflet/Leaflet/issues/1925)
* Fixed a regression where `L.DomUtil.getMousePosition` would throw an error if container argument not provided (by [@scooterw](https://github.com/scooterw)). [#1826](https://github.com/Leaflet/Leaflet/issues/1826) [#1928](https://github.com/Leaflet/Leaflet/issues/1928) [#1926](https://github.com/Leaflet/Leaflet/issues/1926)
* Fixed a regression with vector layers positioning when zooming on IE10+ touch devices (by [@danzel](https://github.com/danzel)). [#2002](https://github.com/Leaflet/Leaflet/issues/2002) [#2000](https://github.com/Leaflet/Leaflet/issues/2000)
* Fixed a regression with `maxBounds` behaving weirdly on panning inertia out of bounds. [#2168](https://github.com/Leaflet/Leaflet/issues/2168)
#### General bugfixes
* Fixed a bug where the map could freeze if centered and immediately recentered on initialization. [#2071](https://github.com/Leaflet/Leaflet/issues/2071)
* Fixed a bug where all maps except the first one on a page didn't track window resize. [#1980](https://github.com/Leaflet/Leaflet/issues/1980)
* Fixed a bug where tiles in `EPSG:3395` projection were shifted (by [@aparshin](https://github.com/aparshin)). [#2020](https://github.com/Leaflet/Leaflet/issues/2020)
* Fixed a bug where scale control displayed wrong scale when on pages with `box-sizing: border-box`.
* Fixed a bug where zoom control button didn't appear as disabled if map was initialized at the zoom limit. [#2083](https://github.com/Leaflet/Leaflet/issues/2083)
* Fixed a bug where box zoom also triggered a map click event (by [@fastrde](https://github.com/fastrde)). [#1951](https://github.com/Leaflet/Leaflet/issues/1951) [#1884](https://github.com/Leaflet/Leaflet/issues/1884)
* Fixed a bug where shift-clicking on a map immediately after a drag didn't trigger a click event (by [@fastrde](https://github.com/fastrde)). [#1952](https://github.com/Leaflet/Leaflet/issues/1952) [#1950](https://github.com/Leaflet/Leaflet/issues/1950)
* Fixed a bug where content was updated twice when opening a popup. [#2137](https://github.com/Leaflet/Leaflet/issues/2137)
* Fixed a bug that could sometimes cause infinite panning loop when using `maxBounds` (by [@kapouer](https://github.com/kapouer) and [@mourner](https://github.com/mourner)). [#2187](https://github.com/Leaflet/Leaflet/pull/2187) [#1946](https://github.com/Leaflet/Leaflet/pull/1946) [#2081](https://github.com/Leaflet/Leaflet/issues/2081) [#2168](https://github.com/Leaflet/Leaflet/issues/2168) [#1908](https://github.com/Leaflet/Leaflet/issues/1908)
#### Browser bugfixes
* Fixed a bug where keyboard `+` no longer zoomed the map on FF 22+ (by [@fastrde](https://github.com/fastrde)). [#1943](https://github.com/Leaflet/Leaflet/issues/1943) [#1981](https://github.com/Leaflet/Leaflet/issues/1981)
* Fixed a bug where calling `Map` `remove` throwed an error in IE6-8. [#2015](https://github.com/Leaflet/Leaflet/issues/2015)
* Fixed a bug where `isArray` didn't work in rare cases in IE9. [#2077](https://github.com/Leaflet/Leaflet/issues/2077)
* Fixed a bug where FF sometimes produced console warnings when animating markers. [#2090](https://github.com/Leaflet/Leaflet/issues/2090)
* Fixed a bug where mouse wasn't handled correctly on RTL pages in some cases (by [@danzel](https://github.com/danzel)). [#1986](https://github.com/Leaflet/Leaflet/issues/1986) [#2136](https://github.com/Leaflet/Leaflet/pull/2136)
#### Mobile bugfixes
* Fixed a bug where tiles could **disappear after zooming on Chrome 30+ for Android** (by [@danzel](https://github.com/danzel)). [#2152](https://github.com/Leaflet/Leaflet/pull/2152) [#2078](https://github.com/Leaflet/Leaflet/issues/2078)
* Fixed a bug on IE10+ touch where pinch-zoom also caused click (by [@danzel](https://github.com/danzel)). [#2117](https://github.com/Leaflet/Leaflet/pull/2117) [#2094](https://github.com/Leaflet/Leaflet/issues/2094)
* Fixed a bug on IE10+ touch where controls didn't loose the pressed state after tapping (by [@danzel](https://github.com/danzel)). [#2111](https://github.com/Leaflet/Leaflet/pull/2111) [#2103](https://github.com/Leaflet/Leaflet/issues/2103)
* Fixed a bug where clicking on layers control labels on iOS throwed an error (by [@olemarkus](https://github.com/olemarkus) and [@dagjomar](https://github.com/dagjomar)). [#1984](https://github.com/Leaflet/Leaflet/issues/1984) [#1989](https://github.com/Leaflet/Leaflet/issues/1989)
#### Map API bugfixes
* Fixed a bug where `Map` `getCenter` returned old result after map container size changed (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1940](https://github.com/Leaflet/Leaflet/issues/1940) [#1919](https://github.com/Leaflet/Leaflet/issues/1919)
* Fixed `Map` `invalidateSize` rounding issues when changing map size by an odd pixel amount (by [@russelldavis](https://github.com/russelldavis)). [#1931](https://github.com/Leaflet/Leaflet/issues/1931)
* Fixed a bug where `Map` `removeLayer` didn't return `this` in a corner case (by [@jfirebaugh](https://github.com/jfirebaugh)).
* Fixed a bug where calling `Map` `setZoom` before `setView` would throw an error. [#1449](https://github.com/Leaflet/Leaflet/issues/1449)
#### Layers API bugfixes
* Fixed a bug where `Popup` `setLatLng` unnecessarily reset content and updated layout; works much faster now. [#2167](https://github.com/Leaflet/Leaflet/issues/2167)
* Fixed a bug where `toGeoJSON` of layers originated from GeoJSON GeometryCollection and MultiPoint didn't work properly (wasn't round-tripped). [#1956](https://github.com/Leaflet/Leaflet/issues/1956)
* Fixed `GeoJSON` dependencies in build configuration that could lead to a broken custom build in some situations (by [@alubchuk](https://github.com/alubchuk)). [#1909](https://github.com/Leaflet/Leaflet/issues/1909)
* Fixed a bug where `CircleMarker` popup placement wasn't updated after calling `setLatLng` (by [@snkashis](https://github.com/snkashis)). [#1921](https://github.com/Leaflet/Leaflet/issues/1921) [#1927](https://github.com/Leaflet/Leaflet/issues/1927)
* Fixed a bug where popup anchor wasn't updated on `Marker` `setIcon` (by [@snkashis](https://github.com/snkashis)). [#1874](https://github.com/Leaflet/Leaflet/issues/1874) [#1891](https://github.com/Leaflet/Leaflet/issues/1891)
* Fixed a bug with GeoJSON not throwing a descriptive error if a polygon has zero length inner ring (by [@snkashis](https://github.com/snkashis)). [#1917](https://github.com/Leaflet/Leaflet/issues/1917) [#1918](https://github.com/Leaflet/Leaflet/issues/1918)
* Fixed a bug where `FeatureGroup` would break when using non-evented children layers (by [@tmcw](https://github.com/tmcw)). [#2032](https://github.com/Leaflet/Leaflet/pull/2032) [#1962](https://github.com/Leaflet/Leaflet/issues/1962)
* Fixed a bug where `CircleMarker` `getRadius` would always return `null`. [#2016](https://github.com/Leaflet/Leaflet/issues/2016) [#2017](https://github.com/Leaflet/Leaflet/pull/2017)
* Fixed a bug where `TileLayer.WMS` didn't work with WMS 1.3 & EPSG4326 projection (by [@Bobboya](https://github.com/Bobboya)). [#1897](https://github.com/Leaflet/Leaflet/pull/1897)
* Fixed a bug where `FeatureGroup` events `e.layer` was sometimes empty in old IE. [#1938](https://github.com/Leaflet/Leaflet/issues/1938)
#### Misc API bugfixes
* Fixed a bug where `L.latLngBounds` didn't accept simple object `LatLng` form (by [@Gnurfos](https://github.com/Gnurfos)). [#2025](https://github.com/Leaflet/Leaflet/issues/2025) [#1915](https://github.com/Leaflet/Leaflet/issues/1915)
* Fixed a bug where `L.Util.tempalate` wouldn't work with double quotes in the string (by [@jieter](https://github.com/jieter)). [#1968](https://github.com/Leaflet/Leaflet/issues/1968) [#2121](https://github.com/Leaflet/Leaflet/pull/2121) [#2120](https://github.com/Leaflet/Leaflet/issues/2120)
* Fixed a bug where attribution control that was added to a map after attributed layers didn't have the corresponding attributions (by [@snkashis](https://github.com/snkashis)). [#2177](https://github.com/Leaflet/Leaflet/issues/2177) [#2178](https://github.com/Leaflet/Leaflet/pull/2178)
## 0.6.4 (July 25, 2013)
* Fixed a regression where `fitBounds` and `setMaxBounds` could freeze the browser in some situations. [#1895](https://github.com/Leaflet/Leaflet/issues/1895) [1866](https://github.com/Leaflet/Leaflet/issues/1866)
* Fixed a bug where click on a map on a page with horizontal scroll caused the page to scroll right (by [@mstrelan](https://github.com/mstrelan)). [#1901](https://github.com/Leaflet/Leaflet/issues/1901)
## 0.6.3 (July 17, 2013)

138
FAQ.md Normal file
View File

@ -0,0 +1,138 @@
# Leaflet FAQ
This is a collection of answers to the most frequently asked questions about Leaflet.
1. [Data Providers](#data-providers)
2. [Commercial Use and Licensing](#commercial-use-and-licensing)
3. [Features](#features)
4. [Performance](#performance)
5. [Misc](#misc)
## Data Providers
#### The map is wrong in my neighborhood, could you fix it?
Nope, but you can.
The map you see on Leaflet examples is based on [OpenStreetMap](http://openstreetmap.org),
a free editable map of the world.
Signing up and editing the map there is easy,
and the changes will be reflected on the map in a few minutes.
#### What map tiles can I use with Leaflet? Is it limited to OpenStreetMap?
Leaflet is provider-agnostic, meaning you can use any map provider as long as you conform to its terms of use.
You can roll your own tiles as well.
[OpenStreetMap](http://openstreetmap.org) is the most popular data source among different tile providers,
but there are providers that use other sources.
Check out [this example](http://leaflet-extras.github.io/leaflet-providers/preview/)
with half a hundred different layers to choose from.
Popular commercial options, free up to a particular number of requests, include
[MapBox](http://mapbox.com),
[CloudMade](http://cloudmade.com),
[Bing Maps](http://www.microsoft.com/maps/choose-your-binge's-maps-API.aspx) (using a [plugin](https://github.com/shramov/leaflet-plugins)),
[Esri ArcGIS](http://www.arcgis.com/features/maps/imagery.html) ([official plugin](https://github.com/Esri/esri-leaflet))
and [Nokia Here](http://developer.here.com/web-experiences).
A notable exception is [MapQuest Open](http://developer.mapquest.com/web/products/open/map), which is free for any number of requests.
Always be sure to **read the terms of use** of a chosen tile provider, **know its limitations**, and **attribute it properly** in your app.
#### I'm looking for satellite imagery to use with my Leaflet map, any options?
[MapBox](http://mapbox.com),
[Bing Maps](http://www.microsoft.com/maps/choose-your-bing-maps-API.aspx),
[ArcGIS](http://www.arcgis.com/features/maps/imagery.html)
and [MapQuest Open](http://developer.mapquest.com/web/products/open/map) provide satellite imagery among others.
#### I want to use Google Maps API tiles with Leaflet, can I do that?
The problem with Google is that its [Terms of Use](https://developers.google.com/maps/terms?hl=ru) forbid any means of tile access other than through the Google Maps API.
You can add the Google Maps API as a Leaflet layer with a [plugin](https://github.com/shramov/leaflet-plugins). But note that the map experience will not be perfect, because Leaflet will just act as a proxy to the Google Maps JS engine, so you won't get all the performance and usability benefits of using Leaflet when the Google layer is on.
#### I want to roll my own OSM tile server for Leaflet, where do I start?
Check out [this excellent guide](http://switch2osm.org/serving-tiles/).
#### I want to create tiles from my own data for use with Leaflet, what are the options?
There's a number of services that allow you to do this easily,
notably [MapBox](https://www.mapbox.com/), [CartoDB](http://cartodb.com/) and [GIS Cloud](http://www.giscloud.com/).
If you want to make tiles on your own, probably the easiest way is using [TileMill](https://www.mapbox.com/tilemill/).
TileMill can export your map as a single [.mbtiles](https://www.mapbox.com/developers/mbtiles/) file, which can be copied to a webserver and accessed by Leaflet with [a small PHP script](https://github.com/infostreams/mbtiles-php).
Alternatively, you can [extract](https://github.com/mapbox/mbutil) the tiled images from the .mbtiles database and place them directly on your webserver with absolutely no server-side dependencies.
## Commercial Use and Licensing
#### I have an app that gets lots of hits a day, and I want to switch from Google/Bing/whatever to Leaflet. Is there a fee for using it?
Leaflet, unlike Google Maps and other all-in-one solutions, is just a JavaScript library.
It's free to use, but doesn't provide map imagery on its own —
you have to choose a tile service to combine with it.
There are [plenty of options](#what-map-tiles-can-i-use-with-leaflet-is-it-limited-to-openstreetmap) for a tile service,
each with their own terms of use, prices (some of them free), features, limitations, etc.
Choice is yours.
#### I'm building a commercial app that I plan to sell. Can I use Leaflet in it?
You're welcome, as the code is published under the very permissive [2-clause BSD License](https://github.com/Leaflet/Leaflet/blob/master/LICENSE).
Just make sure to attribute the use of the library somewhere in the app UI or the distribution
(e.g. keep the Leaflet link on the map, or mention the use on the About page or a Readme file, etc.) and you'll be fine.
That only applies to the code though.
Make sure you conform to the terms of use of the tile images provider(s) that you choose as well.
## Features
#### Why is there still no feature X in Leaflet?
First of all, did you check out the [Leaflet plugins page](http://leafletjs.com/plugins.html)?
It lists about a hundred plugins doing all kinds of crazy stuff,
and there's a high possibility that it has what you're looking for.
Generally, we do our best to keep the Leaflet core small, lightweight and simple,
focusing on _quality_ instead of _quantity_, and leaving all the rest to plugin authors.
Check out [this video](http://www.youtube.com/watch?v=_P2SaCPbJ4w) of a talk by the Leaflet creator for more background on the story and philosophy behind Leaflet.
Another essential read is [Advocating Simplicity in Open Source](http://blog.universalmind.com/advocating-simplicity-in-open-source/) by the same guy.
## Performance
#### I have thousands of markers on my map. How do I make it faster and more usable?
Check out the [Leaflet.markercluster](https://github.com/Leaflet/Leaflet.markercluster) plugin. It's amazing.
#### I have vector data with many thousands of points on my map. Any performance tips?
Leaflet generally does a pretty good job of handling heavy vector data
with its real-time clipping and simplification algorithms,
but browser technology still has its limits.
Try [switching from SVG to Canvas as the default rendering back-end](http://leafletjs.com/reference.html#global),
it may help considerably (depends on the app and the data).
If you still have too much data to render, you'll have to use some help of a server-side service
like [MapBox](https://www.mapbox.com/),
[CartoDB](http://cartodb.com/)
and [GIS Cloud](http://www.giscloud.com/)
(they all work great with Leaflet).
What they do under the hood is serving rendered data as image tiles,
along with additional data to enable interactivity like hovering shapes
(e.g. done using [UTFGrid](https://www.mapbox.com/developers/utfgrid/) —
Leaflet [has a nice plugin](https://github.com/danzel/Leaflet.utfgrid) for it).
## Misc
#### I downloaded the Leaflet source but didn't find `leaflet.js` there. Why is that?
You can download the built versions using links from the [download page](http://leafletjs.com/download.html).
It even includes the latest build of the development version (`master` branch),
updated automatically on each commit to the repo.
We removed the built versions from the repository because it's a chore to build and commit them manually on each change,
and it often complicates merging branches and managing contributions.
There's a common complaint that Leaflet can't be used with [Bower](http://bower.io/) because of that, but we'll resolve the issue soon.

View File

@ -14,13 +14,33 @@ For a custom build, open build/build.html in the browser and follow the instruct
var build = require('./build/build.js');
function hint(msg, paths) {
return function () {
console.log(msg);
jake.exec('node node_modules/jshint/bin/jshint -c ' + paths,
{printStdout: true}, function () {
console.log('\tCheck passed.\n');
complete();
});
}
}
desc('Check Leaflet source for errors with JSHint');
task('lint', build.lint);
task('lint', {async: true}, hint('Checking for JS errors...', 'build/hintrc.js src'));
desc('Check Leaflet specs source for errors with JSHint');
task('lintspec', {async: true}, hint('Checking for specs JS errors...', 'spec/spec.hintrc.js spec/suites'));
desc('Combine and compress Leaflet source files');
task('build', ['lint'], build.build);
task('build', build.build);
desc('Run PhantomJS tests');
task('test', ['lint'], build.test);
task('test', ['lint', 'lintspec'], {async: true}, function () {
build.test(complete);
});
task('default', ['build']);
task('default', ['test', 'build']);
jake.addListener('complete', function () {
process.exit();
});

View File

@ -1,24 +1,23 @@
<img src="http://leafletjs.com/docs/images/logo.png" alt="Leaflet" />
Leaflet is a modern open-source JavaScript library for **mobile-friendly interactive maps**.
It is developed by [Vladimir Agafonkin][] with a team of dedicated [contributors][].
Leaflet is an open source JavaScript library for **mobile-friendly interactive maps**.
It is developed by [Vladimir Agafonkin][] of [MapBox][] with a team of dedicated [contributors][].
Weighing just about 30 KB of gzipped JS code, it has all the [features][] most developers ever need for online maps.
Leaflet is designed with *simplicity*, *performance* and *usability* in mind.
It works efficiently across all major desktop and mobile platforms out of the box,
taking advantage of HTML5 and CSS3 on modern browsers while being accessible on older ones too.
It can also be extended with many [plugins][],
It can be extended with a huge amount of [plugins][],
has a beautiful, easy to use and [well-documented][] API
and a simple, readable [source code][] that is a joy to [contribute][] to.
For more information, check out the [official website][].
For more info, docs and tutorials, check out the [official website][].<br>
For **Leaflet downloads** (including the built master version), check out the [download page][].
We're happy to meet new contributors.
If you want to **get involved** with Leaflet development, check out the [contribution guide][contribute].
Let's make the best open-source library for maps that can possibly exist!
P.S. If you're looking for **Leaflet downloads** (including the built master version),
check out the [Leaflet Download Page][].
Let's make the best mapping library that will ever exist,
and push the limits of what's possible with online maps!
[![Build Status](https://travis-ci.org/Leaflet/Leaflet.png?branch=master)](https://travis-ci.org/Leaflet/Leaflet)
@ -31,4 +30,5 @@ check out the [Leaflet Download Page][].
[hosted on GitHub]: http://github.com/Leaflet/Leaflet
[contribute]: https://github.com/Leaflet/Leaflet/blob/master/CONTRIBUTING.md "A guide to contributing to Leaflet"
[official website]: http://leafletjs.com
[Leaflet Download Page]: http://leafletjs.com/download.html
[download page]: http://leafletjs.com/download.html
[MapBox]: https://mapbox.com

View File

@ -86,21 +86,14 @@
<li><a href="http://nodejs.org/#download">Download and install Node</a></li>
<li>Run this in the command line:<br />
<pre><code>npm install -g jake
npm install jshint
npm install uglify-js</code></pre></li>
npm install</code></pre></li>
<li>Run this command inside the Leaflet directory: <br /><input type="text" id="command2" />
</ol>
<h2>Building using Closure Compiler</h2>
<ol>
<li><a href="http://closure-compiler.googlecode.com/files/compiler-latest.zip">Download Closure Compiler</a>, extract it into <code>closure-compiler</code> directory</li>
<li>Run this command in the root Leaflet directory: <br /><input type="text" id="command" /></li>
</ol>
</div>
<script type="text/javascript">
var deplist = document.getElementById('deplist'),
commandInput = document.getElementById('command'),
commandInput2 = document.getElementById('command2');
commandInput = document.getElementById('command2');
document.getElementById('select-all').onclick = function() {
var checks = deplist.getElementsByTagName('input');
@ -139,15 +132,7 @@ npm install uglify-js</code></pre></li>
}
}
var command = 'java -jar closure-compiler/compiler.jar ';
for (var src in files) {
command += '--js src/' + src + ' ';
}
command += '--js_output_file dist/leaflet-custom.js';
commandInput.value = command;
commandInput2.value = 'jake build[' + parseInt(compsStr, 2).toString(32) + ',custom]';
commandInput.value = 'jake build[' + parseInt(compsStr, 2).toString(32) + ',custom]';
}
function inputSelect() {
@ -156,7 +141,6 @@ npm install uglify-js</code></pre></li>
};
commandInput.onclick = inputSelect;
commandInput2.onclick = inputSelect;
function onCheckboxChange() {
if (this.checked) {

View File

@ -2,29 +2,7 @@ var fs = require('fs'),
jshint = require('jshint'),
UglifyJS = require('uglify-js'),
deps = require('./deps.js').deps,
hintrc = require('./hintrc.js').config;
function lintFiles(files) {
var errorsFound = 0,
i, j, len, len2, src, errors, e;
for (i = 0, len = files.length; i < len; i++) {
jshint.JSHINT(fs.readFileSync(files[i], 'utf8'), hintrc, i ? {L: true} : null);
errors = jshint.JSHINT.errors;
for (j = 0, len2 = errors.length; j < len2; j++) {
e = errors[j];
console.log(files[i] + '\tline ' + e.line + '\tcol ' + e.character + '\t ' + e.reason);
}
errorsFound += len2;
}
return errorsFound;
}
deps = require('./deps.js').deps;
function getFiles(compsBase32) {
var memo = {},
@ -44,16 +22,18 @@ function getFiles(compsBase32) {
for (var i in deps) {
if (comps) {
if (parseInt(comps.pop(), 2) === 1) {
console.log('\t* ' + i);
console.log(' * ' + i);
addFiles(deps[i].src);
} else {
console.log('\t ' + i);
console.log(' ' + i);
}
} else {
addFiles(deps[i].src);
}
}
console.log('');
var files = [];
for (var src in memo) {
@ -65,23 +45,6 @@ function getFiles(compsBase32) {
exports.getFiles = getFiles;
exports.lint = function () {
var files = getFiles();
console.log('Checking for JS errors...');
var errorsFound = lintFiles(files);
if (errorsFound > 0) {
console.log(errorsFound + ' error(s) found.\n');
fail();
} else {
console.log('\tCheck passed');
}
};
function getSizeDelta(newContent, oldContent) {
if (!oldContent) {
return 'new';
@ -129,10 +92,10 @@ exports.build = function (compsBase32, buildName) {
console.log('\tUncompressed size: ' + newSrc.length + ' bytes (' + srcDelta + ')');
if (newSrc === oldSrc) {
console.log('\tNo changes');
console.log('\tNo changes\n');
} else {
fs.writeFileSync(srcPath, newSrc);
console.log('\tSaved to ' + srcPath);
console.log('\tSaved to ' + srcPath + '\n');
}
console.log('Compressing...');
@ -148,19 +111,23 @@ exports.build = function (compsBase32, buildName) {
console.log('\tCompressed size: ' + newCompressed.length + ' bytes (' + delta + ')');
if (newCompressed === oldCompressed) {
console.log('\tNo changes');
console.log('\tNo changes\n');
} else {
fs.writeFileSync(path, newCompressed);
console.log('\tSaved to ' + path);
console.log('\tSaved to ' + path + '\n');
}
};
exports.test = function() {
exports.test = function(callback) {
var karma = require('karma'),
testConfig = {configFile : __dirname + '/../spec/karma.conf.js'};
testConfig.browsers = ['PhantomJS'];
function isArgv(optName) {
return process.argv.indexOf(optName) !== -1;
}
if (isArgv('--chrome')) {
testConfig.browsers.push('Chrome');
}
@ -185,9 +152,12 @@ exports.test = function() {
testConfig.reporters = ['coverage'];
}
karma.server.start(testConfig);
console.log('Running tests...');
function isArgv(optName) {
return process.argv.indexOf(optName) !== -1;
karma.server.start(testConfig, function(exitCode) {
if (!exitCode) {
console.log('\tTests ran successfully.\n');
}
callback();
});
};

View File

@ -151,7 +151,7 @@ var deps = {
GeoJSON: {
src: ['layer/GeoJSON.js'],
deps: ['Marker', 'MultiPoly', 'FeatureGroup'],
deps: ['CircleMarker', 'Marker', 'MultiPoly', 'FeatureGroup'],
desc: 'GeoJSON layer, parses the data and adds corresponding layers above.'
},
@ -176,7 +176,7 @@ var deps = {
TouchZoom: {
src: ['dom/DomEvent.js',
'dom/DomEvent.DoubleTap.js',
'dom/DomEvent.MsTouch.js',
'dom/DomEvent.Pointer.js',
'core/Handler.js',
'map/handler/Map.TouchZoom.js',
'map/handler/Map.Tap.js'],
@ -231,6 +231,7 @@ var deps = {
'dom/PosAnimation.js',
'map/anim/Map.PanAnimation.js'
],
heading: 'Animation',
desc: 'Core panning animation support.'
},

View File

@ -1,9 +1,11 @@
exports.config = {
{
// environment
"browser": true,
"node": true,
"predef": ['define'],
"globals": {
"L": true,
"define": true
},
"strict": false,
// code style
@ -34,4 +36,4 @@ exports.config = {
// "maxcomplexity": 5
// "maxparams": 4,
// "maxdepth": 4
};
}

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<meta name="viewport" content="width=device-width,initial-scale=1 maximum-scale=1.0 user-scalable=0">
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet geolocation debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/mobile.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -22,7 +21,7 @@
var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/997/256/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
attribution: 'Map data &copy; 2011 <a href="#">OpenStreetMap</a> contributors, Imagery &copy; 2011 <a href="#">CloudMade</a>',
key: 'd4fc77ea4a63471cab2423e66626cbb6'
});

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/mobile.css" />
@ -31,6 +30,8 @@
maxBounds: bounds
});
var latlngs = L.rectangle(bounds).getLatLngs();
L.polyline(latlngs.concat([latlngs[0]])).addTo(map);
</script>
</body>

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script type='text/javascript' src='http://code.jquery.com/jquery-1.8.0.js'></script>

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script type="text/javascript" src="../../build/deps.js"></script>

View File

@ -1,7 +1,6 @@
<html>
<head>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<script>
L_PREFER_CANVAS = true;
</script>

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script type='text/javascript' src='http://code.jquery.com/jquery-1.8.0.js'></script>

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<link rel="stylesheet" href="../css/screen.css" />
<script type="text/javascript" src="../../build/deps.js"></script>
<script src="../leaflet-include.js"></script>
<script type='text/javascript' src='http://code.jquery.com/jquery-1.8.0.js'></script>
</head>
<body>
<p>
On the left Map dragging and worldCopyJump are enabled during initialisation.<br>
On the right Map worldCopyJump is enabled. Dragging is enabled by clicking the button.
</p>
<button id="foo">
Click to enable dragging on the right map, then dragging around and watch copying
</button><br>
<div id="map1" style="height: 300px;width: 400px; float:left;"></div>
<div id="map2" style="height: 300px;width: 400px; float:left; margin-left: 10px;"></div>
<div style="clear:both"></div>
<script type="text/javascript">
function addLayerAndMarker(map) {
var layer = new L.TileLayer('http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png', {
maxZoom : 18
}).addTo(map);
var marker = L.marker([50.5, 30.5]).addTo(map);
}
var map1 = new L.Map('map1', {
center : new L.LatLng(45.50144, -122.67599),
zoom : 0,
dragging : true,
worldCopyJump : true
});
var map2 = new L.Map('map2', {
center : new L.LatLng(45.50144, -122.67599),
zoom : 0,
dragging : false,
worldCopyJump : true
});
$("#foo").click(function() {
map2.dragging.enable();
});
addLayerAndMarker(map1);
addLayerAndMarker(map2);
</script>
</body>
</html>

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/mobile.css" />
<style>

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script type="text/javascript" src="../../build/deps.js"></script>

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script type="text/javascript" src="../../build/deps.js"></script>

42
debug/tests/rtl.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../css/screen.css" />
<script type="text/javascript" src="../../build/deps.js"></script>
<script src="../leaflet-include.js"></script>
<style>
body {
direction: rtl;
}
</style>
</head>
<body>
<p>Click the map to place a popup at the mouse location</p>
<div id="map"></div>
<script type="text/javascript">
var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/997/256/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
key: 'd4fc77ea4a63471cab2423e66626cbb6'
});
var map = L.map('map')
.setView([50.5, 30.51], 15)
.addLayer(cloudmade);
map.on('click', function(e) {
L.popup().setLatLng(e.latlng).setContent('Hello').openOn(map);
});
</script>
</body>
</html>

27
debug/tests/rtl2.html Normal file
View File

@ -0,0 +1,27 @@
<html dir="rtl">
<head>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../css/mobile.css" />
<script type="text/javascript" src="../../build/deps.js"></script>
<script src="../leaflet-include.js"></script>
<style>
#map { height: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
var map = L.map('map').setView([51.505, -0.09], 13);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
map.on('click', function(e) {
L.popup().setLatLng(e.latlng).setContent('Hello').openOn(map);
});
</script>
</body>
</html>

View File

@ -2,7 +2,6 @@
<head>
<title>Test for preservation of Icon DOM element</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script type="text/javascript" src="../../build/deps.js"></script>

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<script type="text/javascript" src="../../build/deps.js"></script>
<script src="../leaflet-include.js"></script>

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/mobile.css" />

View File

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/mobile.css" />

View File

@ -4,7 +4,6 @@
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script type="text/javascript" src="../../build/deps.js"></script>

115
dist/leaflet.css vendored
View File

@ -65,6 +65,16 @@
.leaflet-marker-pane { z-index: 6; }
.leaflet-popup-pane { z-index: 7; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
@ -160,9 +170,8 @@
.leaflet-control {
cursor: auto;
}
.leaflet-dragging,
.leaflet-dragging .leaflet-clickable,
.leaflet-dragging .leaflet-container {
.leaflet-dragging .leaflet-container,
.leaflet-dragging .leaflet-clickable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
@ -182,9 +191,8 @@
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #05f;
background: white;
opacity: 0.5;
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
@ -197,11 +205,11 @@
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 7px rgba(0,0,0,0.65);
-webkit-border-radius: 4px;
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a, .leaflet-bar a:hover {
.leaflet-bar a,
.leaflet-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
@ -222,15 +230,11 @@
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
-webkit-border-top-right-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
-webkit-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
-webkit-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
@ -240,54 +244,37 @@
color: #bbb;
}
.leaflet-touch .leaflet-bar {
-webkit-border-radius: 10px;
border-radius: 10px;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
-webkit-border-top-left-radius: 7px;
border-top-left-radius: 7px;
-webkit-border-top-right-radius: 7px;
border-top-right-radius: 7px;
}
.leaflet-touch .leaflet-bar a:last-child {
-webkit-border-bottom-left-radius: 7px;
border-bottom-left-radius: 7px;
-webkit-border-bottom-right-radius: 7px;
border-bottom-right-radius: 7px;
border-bottom: none;
line-height: 30px;
}
/* zoom control */
.leaflet-control-zoom-in {
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-control-zoom-out {
font: bold 22px 'Lucida Console', Monaco, monospace;
font-size: 20px;
}
.leaflet-touch .leaflet-control-zoom-in {
font-size: 22px;
line-height: 30px;
}
.leaflet-touch .leaflet-control-zoom-out {
font-size: 28px;
line-height: 30px;
font-size: 24px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 7px rgba(0,0,0,0.4);
background: #f8f8f9;
-webkit-border-radius: 5px;
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
@ -334,8 +321,8 @@
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background-color: rgba(255, 255, 255, 0.7);
box-shadow: 0 0 5px #bbb;
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.leaflet-control-attribution,
@ -343,6 +330,12 @@
padding: 0 5px;
color: #333;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
@ -356,21 +349,21 @@
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
color: black;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
text-shadow: 1px 1px 1px #fff;
background-color: rgba(255, 255, 255, 0.5);
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.2);
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: content-box;
box-sizing: content-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
@ -383,7 +376,8 @@
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 4px solid rgba(0,0,0,0.3);
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
@ -396,7 +390,6 @@
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
-webkit-border-radius: 12px;
border-radius: 12px;
}
.leaflet-popup-content {
@ -426,7 +419,8 @@
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
@ -454,6 +448,27 @@
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
@ -461,7 +476,3 @@
background: #fff;
border: 1px solid #666;
}
.leaflet-editing-icon {
-webkit-border-radius: 2px;
border-radius: 2px;
}

51
dist/leaflet.ie.css vendored
View File

@ -1,51 +0,0 @@
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
.leaflet-control {
display: inline;
}
.leaflet-popup-tip {
width: 21px;
_width: 27px;
margin: 0 auto;
_margin-top: -3px;
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
}
.leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
border: 1px solid #999;
}
.leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-control-zoom,
.leaflet-control-layers {
border: 3px solid #999;
}
.leaflet-control-layers-toggle {
}
.leaflet-control-attribution,
.leaflet-control-layers,
.leaflet-control-scale-line {
background: white;
}
.leaflet-zoom-box {
filter: alpha(opacity=50);
}
.leaflet-control-attribution {
border-top: 1px solid #bbb;
border-left: 1px solid #bbb;
}

View File

@ -1,23 +1,27 @@
{
"name": "leaflet",
"version": "0.6.4",
"version": "0.7.0",
"description": "JavaScript library for mobile-friendly interactive maps",
"devDependencies": {
"jshint": "~2.1.4",
"mocha": "~1.10.0",
"happen": "~0.1.2",
"karma": "~0.8.6",
"uglify-js": "~2.3.6",
"jake": "~0.5.16"
"jake": "~0.7.4",
"jshint": "~2.3.0",
"uglify-js": "~2.4.3",
"mocha": "~1.14.0",
"happen": "~0.1.3",
"karma": "~0.10.4",
"karma-mocha": "~0.1.0"
},
"main": "dist/leaflet.js",
"main": "dist/leaflet-src.js",
"scripts": {
"test": "jake test",
"prepublish": "jake"
"prepublish": "jake build"
},
"repository": {
"type": "git",
"url": "git://github.com/Leaflet/Leaflet.git"
},
"keywords": ["gis", "map"]
"keywords": [
"gis",
"map"
]
}

View File

@ -1,2 +1,2 @@
// put after Leaflet files as imagePath can't be detected in a PhantomJS env
L.Icon.Default.imagePath = "../dist/images";
L.Icon.Default.imagePath = "/base/dist/images";

View File

@ -2,14 +2,14 @@
<html>
<head>
<meta charset="utf-8">
<title>Spec Runner</title>
<title>Leaflet Spec Runner</title>
<link rel="stylesheet" type="text/css" href="../node_modules/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
<script src="expect.js"></script>
<script type="text/javascript" src="../node_modules/mocha/mocha.js"></script>
<script type="text/javascript" src="../node_modules/happen/src/happen.js"></script>
<script type="text/javascript" src="../node_modules/happen/happen.js"></script>
<script type="text/javascript" src="sinon.js"></script>
<!-- source files -->
@ -19,8 +19,10 @@
<script type="text/javascript" src="../debug/leaflet-include.js"></script>
<script>
mocha.setup('bdd');
mocha.ignoreLeaks();
mocha.setup({
ui: 'bdd',
ignoreLeaks: true
});
</script>
<!-- spec files -->
@ -74,6 +76,9 @@
<!-- /map -->
<script type="text/javascript" src="suites/map/MapSpec.js"></script>
<!-- /map/handler -->
<script type="text/javascript" src="suites/map/handler/Map.DragSpec.js"></script>
<script>
(window.mochaPhantomJS || window.mocha).run();
</script>

View File

@ -1,64 +1,65 @@
// Karma configuration
var libSources = require(__dirname+'/../build/build.js').getFiles();
module.exports = function (config) {
// base path, that will be used to resolve files and exclude
basePath = '';
var libSources = require(__dirname+'/../build/build.js').getFiles();
for (var i=0; i < libSources.length; i++) {
libSources[i] = "../" + libSources[i];
}
var files = [
"spec/before.js",
"spec/sinon.js",
"spec/expect.js"
].concat(libSources, [
"spec/after.js",
"node_modules/happen/happen.js",
"spec/suites/SpecHelper.js",
"spec/suites/**/*.js",
{pattern: "dist/images/*.png", included: false}
]);
// list of files / patterns to load in the browser
files = [].concat([
"../node_modules/mocha/mocha.js",
MOCHA_ADAPTER,
"before.js",
"sinon.js",
"expect.js"
], libSources, [
"after.js",
"../node_modules/happen/src/happen.js",
"suites/SpecHelper.js",
"suites/**/*.js"
]);
config.set({
// base path, that will be used to resolve files and exclude
basePath: '../',
// list of files to exclude
exclude = [
];
plugins: ['karma-mocha', 'karma-phantomjs-launcher', 'karma-chrome-launcher'],
// test results reporter to use
// possible values: 'dots', 'progress', 'junit'
reporters = ['dots'];
// frameworks to use
frameworks: ['mocha'],
// web server port
port = 8080;
// list of files / patterns to load in the browser
files: files,
exclude: [],
// cli runner port
runnerPort = 9100;
// test results reporter to use
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
reporters: ['dots'],
// enable / disable colors in the output (reporters and logs)
colors = true;
// web server port
port: 9876,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel = LOG_WARN;
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_WARN,
// enable / disable watching file and executing tests whenever any file changes
autoWatch = false;
// enable / disable colors in the output (reporters and logs)
colors: true,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers = ['PhantomJS'];
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// If browser does not capture in given timeout [ms], kill it
captureTimeout = 5000;
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun = true;
// If browser does not capture in given timeout [ms], kill it
captureTimeout: 5000,
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: true
});
};

25
spec/spec.hintrc.js Normal file
View File

@ -0,0 +1,25 @@
{
"browser": true,
"node": true,
"predef": ["define", "L", "expect", "describe", "it", "sinon", "happen", "beforeEach", "afterEach"],
"strict": false,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"forin": false,
"immed": true,
"latedef": true,
"newcap": true,
"noarg": true,
"noempty": true,
"nonew": true,
"undef": true,
// "unused": true,
// "quotmark": "single",
"indent": 4,
"trailing": true,
"white": true,
"smarttabs": true
// "maxlen": 120
}

View File

@ -1,5 +1,5 @@
describe('L#noConflict', function() {
it('restores the previous L value and returns Leaflet namespace', function(){
describe('L#noConflict', function () {
it('restores the previous L value and returns Leaflet namespace', function () {
expect(L.version).to.be.ok();

View File

@ -1,26 +1,42 @@
function noSpecs() {
xit('has no specs');
}
if (!Array.prototype.map) {
Array.prototype.map = function(fun /*, thisp */) {
Array.prototype.map = function (fun /*, thisp */) {
"use strict";
if (this === void 0 || this === null)
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this);
// jshint bitwise: false
var len = t.length >>> 0;
if (typeof fun !== "function")
if (typeof fun !== "function") {
throw new TypeError();
}
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in t)
if (i in t) {
res[i] = fun.call(thisp, t[i], i, t);
}
}
return res;
};
}
expect.Assertion.prototype.near = function (expected, delta) {
delta = delta || 1;
expect(this.obj.x).to
.be.within(expected.x - delta, expected.x + delta);
expect(this.obj.y).to
.be.within(expected.y - delta, expected.y + delta);
};
expect.Assertion.prototype.nearLatLng = function (expected, delta) {
delta = delta || 1e-4;
expect(this.obj.lat).to
.be.within(expected.lat - delta, expected.lat + delta);
expect(this.obj.lng).to
.be.within(expected.lng - delta, expected.lng + delta);
};

View File

@ -12,7 +12,7 @@ describe("Control.Layers", function () {
spy = sinon.spy();
map.on('baselayerchange', spy)
.whenReady(function() {
.whenReady(function () {
happen.click(layers._baseLayersList.getElementsByTagName("input")[0]);
expect(spy.called).to.be.ok();

View File

@ -1,11 +1,11 @@
describe("Class", function() {
describe("Class", function () {
describe("#extend", function() {
describe("#extend", function () {
var Klass,
constructor,
method;
beforeEach(function() {
beforeEach(function () {
constructor = sinon.spy();
method = sinon.spy();
@ -19,7 +19,7 @@ describe("Class", function() {
});
});
it("creates a class with the given constructor & properties", function() {
it("creates a class with the given constructor & properties", function () {
var a = new Klass();
expect(constructor.called).to.be.ok();
@ -30,7 +30,7 @@ describe("Class", function() {
expect(method.called).to.be.ok();
});
it("inherits parent classes' constructor & properties", function() {
it("inherits parent classes' constructor & properties", function () {
var Klass2 = Klass.extend({baz: 2});
var b = new Klass2();
@ -46,28 +46,28 @@ describe("Class", function() {
expect(method.called).to.be.ok();
});
it("supports static properties", function() {
it("supports static properties", function () {
expect(Klass.bla).to.eql(1);
});
it("inherits parent static properties", function() {
it("inherits parent static properties", function () {
var Klass2 = Klass.extend({});
expect(Klass2.bla).to.eql(1);
});
it("overrides parent static properties", function() {
it("overrides parent static properties", function () {
var Klass2 = Klass.extend({statics: {bla: 2}});
expect(Klass2.bla).to.eql(2);
});
it("includes the given mixin", function() {
it("includes the given mixin", function () {
var a = new Klass();
expect(a.mixin).to.be.ok();
});
it("includes multiple mixins", function() {
it("includes multiple mixins", function () {
var Klass2 = L.Class.extend({
includes: [{mixin: true}, {mixin2: true}]
});
@ -77,14 +77,14 @@ describe("Class", function() {
expect(a.mixin2).to.be.ok();
});
it("grants the ability to include the given mixin", function() {
it("grants the ability to include the given mixin", function () {
Klass.include({mixin2: true});
var a = new Klass();
expect(a.mixin2).to.be.ok();
});
it("merges options instead of replacing them", function() {
it("merges options instead of replacing them", function () {
var KlassWithOptions1 = L.Class.extend({
options: {
foo1: 1,

View File

@ -1,21 +1,21 @@
describe('Events', function() {
describe('Events', function () {
var Klass;
beforeEach(function() {
beforeEach(function () {
Klass = L.Class.extend({
includes: L.Mixin.Events
});
});
describe('#fireEvent', function() {
describe('#fireEvent', function () {
it('fires all listeners added through #addEventListener', function() {
it('fires all listeners added through #addEventListener', function () {
var obj = new Klass(),
spy1 = sinon.spy(),
spy2 = sinon.spy(),
spy3 = sinon.spy(),
spy4 = sinon.spy(),
spy5 = sinon.spy();
spy5 = sinon.spy(),
spy6 = sinon.spy();
obj.addEventListener('test', spy1);
@ -42,7 +42,7 @@ describe('Events', function() {
expect(spy6.callCount).to.be(1);
});
it('provides event object to listeners and executes them in the right context', function() {
it('provides event object to listeners and executes them in the right context', function () {
var obj = new Klass(),
obj2 = new Klass(),
obj3 = new Klass(),
@ -88,7 +88,7 @@ describe('Events', function() {
obj4.fireEvent('test', {baz: 4});
});
it('calls no listeners removed through #removeEventListener', function() {
it('calls no listeners removed through #removeEventListener', function () {
var obj = new Klass(),
spy = sinon.spy(),
spy2 = sinon.spy(),
@ -171,7 +171,7 @@ describe('Events', function() {
expect(spy2.called).to.be(true);
});
it('removes listeners with a stamp originally added without one', function() {
it('removes listeners with a stamp originally added without one', function () {
var obj = new Klass(),
spy1 = sinon.spy(),
spy2 = sinon.spy(),
@ -190,6 +190,29 @@ describe('Events', function() {
expect(spy2.called).to.be(false);
});
it('removes listeners with context == this and a stamp originally added without one', function () {
var obj = new Klass(),
obj2 = new Klass(),
spy1 = sinon.spy(),
spy2 = sinon.spy(),
spy3 = sinon.spy();
obj.addEventListener('test', spy1, obj);
L.Util.stamp(obj);
obj.addEventListener('test', spy2, obj);
obj.addEventListener('test', spy3, obj2); // So that there is a contextId based listener, otherwise removeEventListener will do correct behaviour anyway
obj.removeEventListener('test', spy1, obj);
obj.removeEventListener('test', spy2, obj);
obj.removeEventListener('test', spy3, obj2);
obj.fireEvent('test');
expect(spy1.called).to.be(false);
expect(spy2.called).to.be(false);
expect(spy3.called).to.be(false);
});
it('doesnt lose track of listeners when removing non existent ones', function () {
var obj = new Klass(),
spy = sinon.spy(),
@ -212,10 +235,32 @@ describe('Events', function() {
expect(spy.called).to.be(false);
});
it('makes sure an event is not triggered if a listener is removed during dispatch',function() {
it('correctly removes all listeners if given no fn', function () {
var obj = new Klass(),
spy = sinon.spy(),
foo = {},
foo2 = {},
foo3 = {};
obj.addEventListener('test', spy, foo2);
obj.addEventListener('test', spy, foo3);
obj.removeEventListener('test'); // Removes both of the above listeners
expect(obj.hasEventListeners('test')).to.be(false);
//Add and remove a listener
obj.addEventListener('test', spy, foo2);
obj.removeEventListener('test', spy, foo2);
expect(obj.hasEventListeners('test')).to.be(false);
});
it('makes sure an event is not triggered if a listener is removed during dispatch', function () {
var obj = new Klass(),
spy = sinon.spy();
obj.addEventListener('test', function() { obj.removeEventListener('test',spy); });
obj.addEventListener('test', function () { obj.removeEventListener('test', spy); });
obj.addEventListener('test', spy);
obj.fireEvent('test');
@ -223,9 +268,9 @@ describe('Events', function() {
});
});
describe('#on, #off & #fire', function() {
describe('#on, #off & #fire', function () {
it('works like #addEventListener && #removeEventListener', function() {
it('works like #addEventListener && #removeEventListener', function () {
var obj = new Klass(),
spy = sinon.spy();
@ -240,7 +285,7 @@ describe('Events', function() {
expect(spy.callCount).to.be.lessThan(2);
});
it('does not override existing methods with the same name', function() {
it('does not override existing methods with the same name', function () {
var spy1 = sinon.spy(),
spy2 = sinon.spy(),
spy3 = sinon.spy();
@ -265,10 +310,10 @@ describe('Events', function() {
});
});
describe("#clearEventListeners", function() {
it("clears all registered listeners on an object", function() {
describe("#clearEventListeners", function () {
it("clears all registered listeners on an object", function () {
var spy = sinon.spy(),
obj = new Klass()
obj = new Klass(),
otherObj = new Klass();
obj.on('test', spy, obj);
@ -282,8 +327,8 @@ describe('Events', function() {
});
});
describe('#once', function() {
it('removes event listeners after first trigger', function() {
describe('#once', function () {
it('removes event listeners after first trigger', function () {
var obj = new Klass(),
spy = sinon.spy();
@ -297,7 +342,7 @@ describe('Events', function() {
expect(spy.callCount).to.be.lessThan(2);
});
it('works with an object hash', function() {
it('works with an object hash', function () {
var obj = new Klass(),
spy = sinon.spy(),
otherSpy = sinon.spy();
@ -332,7 +377,7 @@ describe('Events', function() {
expect(spy.called).to.be(false);
});
it('works if called from a context that doesnt implement #Events', function() {
it('works if called from a context that doesnt implement #Events', function () {
var obj = new Klass(),
spy = sinon.spy(),
foo = {};

View File

@ -1,16 +1,16 @@
describe('Util', function() {
describe('Util', function () {
describe('#extend', function() {
describe('#extend', function () {
var a;
beforeEach(function() {
beforeEach(function () {
a = {
foo: 5,
bar: 'asd'
};
});
it('extends the first argument with the properties of the second', function() {
it('extends the first argument with the properties of the second', function () {
L.Util.extend(a, {
bar: 7,
baz: 3
@ -23,7 +23,7 @@ describe('Util', function() {
});
});
it('accepts more than 2 arguments', function() {
it('accepts more than 2 arguments', function () {
L.Util.extend(a, {bar: 7}, {baz: 3});
expect(a).to.eql({
@ -34,9 +34,9 @@ describe('Util', function() {
});
});
describe('#bind', function() {
it('returns the given function with the given context', function() {
var fn = function() {
describe('#bind', function () {
it('returns the given function with the given context', function () {
var fn = function () {
return this;
};
@ -59,8 +59,8 @@ describe('Util', function() {
});
});
describe('#stamp', function() {
it('sets a unique id on the given object and returns it', function() {
describe('#stamp', function () {
it('sets a unique id on the given object and returns it', function () {
var a = {},
id = L.Util.stamp(a);
@ -115,38 +115,37 @@ describe('Util', function() {
});
describe('#getParamString', function() {
it('creates a valid query string for appending depending on url input', function() {
describe('#getParamString', function () {
it('creates a valid query string for appending depending on url input', function () {
var a = {
url: "http://example.com/get",
url: 'http://example.com/get',
obj: {bar: 7, baz: 3},
result: "?bar=7&baz=3"
result: '?bar=7&baz=3'
};
expect(L.Util.getParamString(a.obj,a.url)).to.eql(a.result);
expect(L.Util.getParamString(a.obj, a.url)).to.eql(a.result);
var b = {
url: "http://example.com/get?justone=qs",
url: 'http://example.com/get?justone=qs',
obj: {bar: 7, baz: 3},
result: "&bar=7&baz=3"
result: '&bar=7&baz=3'
};
expect(L.Util.getParamString(b.obj,b.url)).to.eql(b.result);
expect(L.Util.getParamString(b.obj, b.url)).to.eql(b.result);
var c = {
url: undefined,
obj: {bar: 7, baz: 3},
result: "?bar=7&baz=3"
result: '?bar=7&baz=3'
};
expect(L.Util.getParamString(c.obj,c.url)).to.eql(c.result);
expect(L.Util.getParamString(c.obj, c.url)).to.eql(c.result);
});
});
describe('#requestAnimFrame', function () {
it('calles a function on next frame, unless canceled', function (done) {
var spy = sinon.spy(),
spy2 = sinon.spy(),
foo = {};
L.Util.requestAnimFrame(spy);
@ -160,7 +159,7 @@ describe('Util', function() {
});
});
describe('#limitExecByInterval', function() {
describe('#limitExecByInterval', function () {
it('limits execution to not more often than specified time interval', function (done) {
var spy = sinon.spy();
@ -189,24 +188,69 @@ describe('Util', function() {
describe('#template', function () {
it('evaluates templates with a given data object', function () {
var tpl = 'Hello {foo} and {bar}!';
var tpl = 'Hello {foo} and {baz }!';
var str = L.Util.template(tpl, {
foo: 'Vlad',
bar: 'Dave'
bar: 'Dave',
baz: function (o) {
return o.bar;
}
});
expect(str).to.eql('Hello Vlad and Dave!');
});
it('check the cache', function () {
var tpl = 'Hello {foo} and {baz }!';
var str = L.Util._templateCache[tpl]({
foo: 'ladies',
baz: function () {
return 'gentlemen';
}
});
expect(str).to.eql('Hello ladies and gentlemen!');
});
it('evaluates templates with a function', function () {
var tpl = L.Util.compileTemplate('Hello { foo } and { bar}!', {});
var str1 = tpl({
foo: 'Vlad',
bar: 'Dave'
});
var str2 = tpl({
foo: '{Calvin}',
bar: '{Simon}'
});
expect(str1).to.eql('Hello Vlad and Dave!');
expect(str2).to.eql('Hello {Calvin} and {Simon}!');
});
it('does not modify text without a token variable', function () {
expect(L.Util.template('foo', {})).to.eql('foo');
});
it('supports templates with double quotes', function () {
expect(L.Util.template('He said: "{foo}"!', {
foo: 'Hello'
})).to.eql('He said: "Hello"!');
});
it('throws when a template token is not given', function () {
expect(function () {
L.Util.template(tpl, {foo: 'bar'});
L.Util.template(undefined, {foo: 'bar'});
}).to.throwError();
});
});
describe('#isArray', function () {
expect(L.Util.isArray([1, 2, 3])).to.be(true);
expect(L.Util.isArray(new Array(1, 2, 3))).to.be(true);
expect(L.Util.isArray('blabla')).to.be(false);
expect(L.Util.isArray({0: 1, 1: 2})).to.be(false);
});
});

View File

@ -1,4 +1,4 @@
describe('DomEvent', function() {
describe('DomEvent', function () {
var el;
function simulateClick(el) {
@ -12,19 +12,19 @@ describe('DomEvent', function() {
}
}
beforeEach(function() {
beforeEach(function () {
el = document.createElement('div');
el.style.position = 'absolute';
el.style.top = el.style.left = '-10000px';
document.body.appendChild(el);
});
afterEach(function() {
afterEach(function () {
document.body.removeChild(el);
});
describe('#addListener', function() {
it('adds a listener and calls it on event', function() {
describe('#addListener', function () {
it('adds a listener and calls it on event', function () {
var listener1 = sinon.spy(),
listener2 = sinon.spy();
@ -37,11 +37,11 @@ describe('DomEvent', function() {
expect(listener2.called).to.be.ok();
});
it('binds "this" to the given context', function() {
it('binds "this" to the given context', function () {
var obj = {foo: 'bar'},
result;
L.DomEvent.addListener(el, 'click', function() {
L.DomEvent.addListener(el, 'click', function () {
result = this;
}, obj);
@ -50,10 +50,10 @@ describe('DomEvent', function() {
expect(result).to.eql(obj);
});
it('passes an event object to the listener', function() {
it('passes an event object to the listener', function () {
var type;
L.DomEvent.addListener(el, 'click', function(e) {
L.DomEvent.addListener(el, 'click', function (e) {
type = e && e.type;
});
simulateClick(el);
@ -62,8 +62,8 @@ describe('DomEvent', function() {
});
});
describe('#removeListener', function() {
it('removes a previously added listener', function() {
describe('#removeListener', function () {
it('removes a previously added listener', function () {
var listener = sinon.spy();
L.DomEvent.addListener(el, 'click', listener);
@ -75,8 +75,8 @@ describe('DomEvent', function() {
});
});
describe('#stopPropagation', function() {
it('stops propagation of the given event', function() {
describe('#stopPropagation', function () {
it('stops propagation of the given event', function () {
var child = document.createElement('div'),
listener = sinon.spy();
@ -92,8 +92,8 @@ describe('DomEvent', function() {
el.removeChild(child);
});
});
describe('#preventDefault', function() {
it('prevents the default action of event', function() {
describe('#preventDefault', function () {
it('prevents the default action of event', function () {
L.DomEvent.addListener(el, 'click', L.DomEvent.preventDefault);
expect(simulateClick(el)).to.be(false);

View File

@ -1,30 +1,30 @@
describe('DomUtil', function() {
describe('DomUtil', function () {
var el;
beforeEach(function() {
beforeEach(function () {
el = document.createElement('div');
el.style.position = 'absolute';
el.style.top = el.style.left = '-10000px';
document.body.appendChild(el);
});
afterEach(function() {
afterEach(function () {
document.body.removeChild(el);
});
describe('#get', function() {
it('gets element by id if the given argument is string', function() {
describe('#get', function () {
it('gets element by id if the given argument is string', function () {
el.id = 'testId';
expect(L.DomUtil.get(el.id)).to.eql(el);
});
it('returns the element if it is given as an argument', function() {
it('returns the element if it is given as an argument', function () {
expect(L.DomUtil.get(el)).to.eql(el);
});
});
describe('#addClass, #removeClass, #hasClass', function() {
it('has defined class for test element', function() {
describe('#addClass, #removeClass, #hasClass', function () {
it('has defined class for test element', function () {
el.className = 'bar foo baz ';
expect(L.DomUtil.hasClass(el, 'foo')).to.be.ok();
expect(L.DomUtil.hasClass(el, 'bar')).to.be.ok();
@ -32,7 +32,7 @@ describe('DomUtil', function() {
expect(L.DomUtil.hasClass(el, 'boo')).to.not.be.ok();
});
it('adds or removes the class', function() {
it('adds or removes the class', function () {
el.className = '';
L.DomUtil.addClass(el, 'foo');
@ -53,13 +53,6 @@ describe('DomUtil', function() {
});
});
describe('#documentIsLtr', function () {
it('returns true if doc direction is ltr', function () {
expect(L.DomUtil.documentIsLtr()).to.eql(true);
expect(L.DomUtil.documentIsLtr()).to.eql(true); // cached
});
});
describe('#getViewportOffset', function () {
it('calculates the viewport offset of an element', function () {
var div = document.createElement('div');

View File

@ -1,23 +1,23 @@
describe('PosAnimation', function() {
describe('PosAnimation', function () {
var el;
beforeEach(function() {
beforeEach(function () {
el = document.createElement('div');
this.subject = new L.PosAnimation();
this.subject._el = el;
});
describe('#_onStep', function() {
it("sets element position and fires step event if it is able to get current position", function() {
describe('#_onStep', function () {
it("sets element position and fires step event if it is able to get current position", function () {
var point = new L.Point(5, 5, true);
sinon.stub(this.subject, '_getPos').returns(point);
this.subject.fire = sinon.stub();
this.subject._onStep();
expect(this.subject.fire.withArgs('step').calledOnce).to.be(true);
expect(this.subject._el._leaflet_pos).to.be(point);
expect(L.DomUtil.getPosition(this.subject._el)).to.be(point);
});
it('stops transition if a position returned', function() {
it('stops transition if a position returned', function () {
sinon.stub(this.subject, '_onTransitionEnd');
sinon.stub(this.subject, '_getPos').returns(undefined);
this.subject._onStep();

View File

@ -0,0 +1,47 @@
describe("CRS.EPSG3395", function () {
var crs = L.CRS.EPSG3395;
describe("#latLngToPoint", function () {
it("projects a center point", function () {
expect(crs.latLngToPoint(L.latLng(0, 0), 0)).near(new L.Point(128, 128), 0.01);
});
it("projects the northeast corner of the world", function () {
expect(crs.latLngToPoint(L.latLng(85.0840591556, 180), 0)).near(new L.Point(256, 0));
});
});
describe("#pointToLatLng", function () {
it("reprojects a center point", function () {
expect(crs.pointToLatLng(new L.Point(128, 128), 0)).nearLatLng(L.latLng(0, 0), 0.01);
});
it("reprojects the northeast corner of the world", function () {
expect(crs.pointToLatLng(new L.Point(256, 0), 0)).nearLatLng(L.latLng(85.0840591556, 180));
});
});
});
describe("CRS.EPSG3857", function () {
var crs = L.CRS.EPSG3857;
describe("#latLngToPoint", function () {
it("projects a center point", function () {
expect(crs.latLngToPoint(L.latLng(0, 0), 0)).near(new L.Point(128, 128), 0.01);
});
it("projects the northeast corner of the world", function () {
expect(crs.latLngToPoint(L.latLng(85.0511287798, 180), 0)).near(new L.Point(256, 0));
});
});
describe("#pointToLatLng", function () {
it("reprojects a center point", function () {
expect(crs.pointToLatLng(new L.Point(128, 128), 0)).nearLatLng(L.latLng(0, 0), 0.01);
});
it("reprojects the northeast corner of the world", function () {
expect(crs.pointToLatLng(new L.Point(256, 0), 0)).nearLatLng(L.latLng(85.0511287798, 180));
});
});
});

View File

@ -1,7 +1,7 @@
describe('LatLngBounds', function() {
describe('LatLngBounds', function () {
var a, c;
beforeEach(function() {
beforeEach(function () {
a = new L.LatLngBounds(
new L.LatLng(14, 12),
new L.LatLng(30, 40));
@ -27,13 +27,17 @@ describe('LatLngBounds', function() {
it('extends the bounds by given bounds', function () {
a.extend([[20, 50], [8, 40]]);
expect(a.getSouthEast()).to.eql(new L.LatLng(8, 50));
});
it('extends the bounds by undefined', function () {
expect(a.extend()).to.eql(a);
});
it('extends the bounds by raw object', function () {
a.extend({lat: 20, lng: 50});
expect(a.getNorthEast()).to.eql(new L.LatLng(30, 50));
});
});
describe('#getCenter', function () {
@ -58,62 +62,62 @@ describe('LatLngBounds', function() {
});
});
describe('#isValid', function() {
it('returns true if properly set up', function() {
describe('#isValid', function () {
it('returns true if properly set up', function () {
expect(a.isValid()).to.be.ok();
});
it('returns false if is invalid', function() {
it('returns false if is invalid', function () {
expect(c.isValid()).to.not.be.ok();
});
it('returns true if extended', function() {
it('returns true if extended', function () {
c.extend([0, 0]);
expect(c.isValid()).to.be.ok();
});
});
describe('#getWest', function () {
it('returns a proper bbox west value', function() {
it('returns a proper bbox west value', function () {
expect(a.getWest()).to.eql(12);
});
});
describe('#getSouth', function () {
it('returns a proper bbox south value', function() {
it('returns a proper bbox south value', function () {
expect(a.getSouth()).to.eql(14);
});
});
describe('#getEast', function () {
it('returns a proper bbox east value', function() {
it('returns a proper bbox east value', function () {
expect(a.getEast()).to.eql(40);
});
});
describe('#getNorth', function () {
it('returns a proper bbox north value', function() {
it('returns a proper bbox north value', function () {
expect(a.getNorth()).to.eql(30);
});
});
describe('#toBBoxString', function () {
it('returns a proper left,bottom,right,top bbox', function() {
it('returns a proper left,bottom,right,top bbox', function () {
expect(a.toBBoxString()).to.eql("12,14,40,30");
});
});
describe('#getNorthWest', function () {
it('returns a proper north-west LatLng', function() {
it('returns a proper north-west LatLng', function () {
expect(a.getNorthWest()).to.eql(new L.LatLng(a.getNorth(), a.getWest()));
});
});
describe('#getSouthEast', function () {
it('returns a proper south-east LatLng', function() {
it('returns a proper south-east LatLng', function () {
expect(a.getSouthEast()).to.eql(new L.LatLng(a.getSouth(), a.getEast()));
});
});

View File

@ -1,6 +1,6 @@
describe('LatLng', function() {
describe('constructor', function() {
it("sets lat and lng", function() {
describe('LatLng', function () {
describe('constructor', function () {
it("sets lat and lng", function () {
var a = new L.LatLng(25, 74);
expect(a.lat).to.eql(25);
expect(a.lng).to.eql(74);
@ -15,16 +15,30 @@ describe('LatLng', function() {
var a = new L.LatLng(NaN, NaN);
}).to.throwError();
});
it('does not set altitude if undefined', function () {
var a = new L.LatLng(25, 74);
expect(typeof a.alt).to.eql('undefined');
});
describe('#equals', function() {
it("returns true if compared objects are equal within a certain margin", function() {
it('sets altitude', function () {
var a = new L.LatLng(25, 74, 50);
expect(a.alt).to.eql(50);
var b = new L.LatLng(-25, -74, -50);
expect(b.alt).to.eql(-50);
});
});
describe('#equals', function () {
it("returns true if compared objects are equal within a certain margin", function () {
var a = new L.LatLng(10, 20);
var b = new L.LatLng(10 + 1.0E-10, 20 - 1.0E-10);
expect(a.equals(b)).to.eql(true);
});
it("returns false if compared objects are not equal within a certain margin", function() {
it("returns false if compared objects are not equal within a certain margin", function () {
var a = new L.LatLng(10, 20);
var b = new L.LatLng(10, 23.3);
expect(a.equals(b)).to.eql(false);
@ -37,7 +51,7 @@ describe('LatLng', function() {
});
describe('#wrap', function () {
it("wraps longitude to lie between -180 and 180 by default", function() {
it("wraps longitude to lie between -180 and 180 by default", function () {
var a = new L.LatLng(0, 190).wrap().lng;
expect(a).to.eql(-170);
@ -63,7 +77,7 @@ describe('LatLng', function() {
expect(h).to.eql(180);
});
it("wraps longitude within the given range", function() {
it("wraps longitude within the given range", function () {
var a = new L.LatLng(0, 190).wrap(-100, 100).lng;
expect(a).to.eql(-10);
});

View File

@ -1,29 +1,21 @@
describe("Projection.Mercator", function() {
describe("Projection.Mercator", function () {
var p = L.Projection.Mercator;
expect.Assertion.prototype.near = function(expected, delta) {
delta = delta || 1;
expect(this.obj.x).to
.be.within(expected.x - delta, expected.x + delta);
expect(this.obj.y).to
.be.within(expected.y - delta, expected.y + delta);
};
describe("#project", function() {
it("projects a center point", function() {
describe("#project", function () {
it("projects a center point", function () {
//edge cases
expect(p.project(new L.LatLng(0, 0))).near(new L.Point(0, 0));
});
it("projects the northeast corner of the world", function() {
it("projects the northeast corner of the world", function () {
expect(p.project(new L.LatLng(90, 180))).near(new L.Point(20037508, 20037508));
});
it("projects the southwest corner of the world", function() {
it("projects the southwest corner of the world", function () {
expect(p.project(new L.LatLng(-90, -180))).near(new L.Point(-20037508, -20037508));
});
it("projects other points", function() {
it("projects other points", function () {
expect(p.project(new L.LatLng(50, 30))).near(new L.Point(3339584, 6413524));
// from https://github.com/Leaflet/Leaflet/issues/1578
@ -32,16 +24,16 @@ describe("Projection.Mercator", function() {
});
});
describe("#unproject", function() {
describe("#unproject", function () {
function pr(point) {
return p.project(p.unproject(point));
}
it("unprojects a center point", function() {
it("unprojects a center point", function () {
expect(pr(new L.Point(0, 0))).near(new L.Point(0, 0));
});
it("unprojects pi points", function() {
it("unprojects pi points", function () {
expect(pr(new L.Point(-Math.PI, Math.PI))).near(new L.Point(-Math.PI, Math.PI));
expect(pr(new L.Point(-Math.PI, -Math.PI))).near(new L.Point(-Math.PI, -Math.PI));

View File

@ -1,7 +1,7 @@
describe('Bounds', function() {
describe('Bounds', function () {
var a, b, c;
beforeEach(function() {
beforeEach(function () {
a = new L.Bounds(
new L.Point(14, 12),
new L.Point(30, 40));
@ -13,19 +13,19 @@ describe('Bounds', function() {
c = new L.Bounds();
});
describe('constructor', function() {
it('creates bounds with proper min & max on (Point, Point)', function() {
describe('constructor', function () {
it('creates bounds with proper min & max on (Point, Point)', function () {
expect(a.min).to.eql(new L.Point(14, 12));
expect(a.max).to.eql(new L.Point(30, 40));
});
it('creates bounds with proper min & max on (Point[])', function() {
it('creates bounds with proper min & max on (Point[])', function () {
expect(b.min).to.eql(new L.Point(14, 12));
expect(b.max).to.eql(new L.Point(30, 40));
});
});
describe('#extend', function() {
it('extends the bounds to contain the given point', function() {
describe('#extend', function () {
it('extends the bounds to contain the given point', function () {
a.extend(new L.Point(50, 20));
expect(a.min).to.eql(new L.Point(14, 12));
expect(a.max).to.eql(new L.Point(50, 40));
@ -36,14 +36,14 @@ describe('Bounds', function() {
});
});
describe('#getCenter', function() {
it('returns the center point', function() {
describe('#getCenter', function () {
it('returns the center point', function () {
expect(a.getCenter()).to.eql(new L.Point(22, 26));
});
});
describe('#contains', function() {
it('contains other bounds or point', function() {
describe('#contains', function () {
it('contains other bounds or point', function () {
a.extend(new L.Point(50, 10));
expect(a.contains(b)).to.be.ok();
expect(b.contains(a)).to.not.be.ok();
@ -52,14 +52,14 @@ describe('Bounds', function() {
});
});
describe('#isValid', function() {
it('returns true if properly set up', function() {
describe('#isValid', function () {
it('returns true if properly set up', function () {
expect(a.isValid()).to.be.ok();
});
it('returns false if is invalid', function() {
it('returns false if is invalid', function () {
expect(c.isValid()).to.not.be.ok();
});
it('returns true if extended', function() {
it('returns true if extended', function () {
c.extend([0, 0]);
expect(c.isValid()).to.be.ok();
});

View File

@ -1,42 +1,42 @@
describe("Point", function() {
describe("Point", function () {
describe('constructor', function() {
describe('constructor', function () {
it("creates a point with the given x and y", function() {
it("creates a point with the given x and y", function () {
var p = new L.Point(1.5, 2.5);
expect(p.x).to.eql(1.5);
expect(p.y).to.eql(2.5);
});
it("rounds the given x and y if the third argument is true", function() {
it("rounds the given x and y if the third argument is true", function () {
var p = new L.Point(1.3, 2.7, true);
expect(p.x).to.eql(1);
expect(p.y).to.eql(3);
});
});
describe('#subtract', function() {
it('subtracts the given point from this one', function() {
describe('#subtract', function () {
it('subtracts the given point from this one', function () {
var a = new L.Point(50, 30),
b = new L.Point(20, 10);
expect(a.subtract(b)).to.eql(new L.Point(30, 20));
});
});
describe('#add', function() {
it('adds given point to this one', function() {
describe('#add', function () {
it('adds given point to this one', function () {
expect(new L.Point(50, 30).add(new L.Point(20, 10))).to.eql(new L.Point(70, 40));
});
});
describe('#divideBy', function() {
it('divides this point by the given amount', function() {
describe('#divideBy', function () {
it('divides this point by the given amount', function () {
expect(new L.Point(50, 30).divideBy(5)).to.eql(new L.Point(10, 6));
});
});
describe('#multiplyBy', function() {
it('multiplies this point by the given amount', function() {
describe('#multiplyBy', function () {
it('multiplies this point by the given amount', function () {
expect(new L.Point(50, 30).multiplyBy(2)).to.eql(new L.Point(100, 60));
});
});

View File

@ -1,13 +1,13 @@
describe("Transformation", function() {
describe("Transformation", function () {
var t, p;
beforeEach(function() {
beforeEach(function () {
t = new L.Transformation(1, 2, 3, 4);
p = new L.Point(10, 20);
});
describe('#transform', function () {
it("performs a transformation", function() {
it("performs a transformation", function () {
var p2 = t.transform(p, 2);
expect(p2).to.eql(new L.Point(24, 128));
});
@ -18,7 +18,7 @@ describe("Transformation", function() {
});
describe('#untransform', function () {
it("performs a reverse transformation", function() {
it("performs a reverse transformation", function () {
var p2 = t.transform(p, 2);
var p3 = t.untransform(p2, 2);
expect(p3).to.eql(p);

View File

@ -17,22 +17,25 @@
fg1.addLayer(marker);
fg2.addLayer(marker);
var wasClicked = 0;
fg2.on('click', function(e) {
var wasClicked1,
wasClicked2;
fg2.on('click', function (e) {
expect(e.layer).to.be(marker);
expect(e.target).to.be(fg2);
wasClicked |= 1;
wasClicked2 = true;
});
fg1.on('click', function (e) {
expect(e.layer).to.be(marker);
expect(e.target).to.be(fg1);
wasClicked |= 2;
wasClicked1 = true;
});
marker.fire('click', { type: 'click' });
expect(wasClicked).to.be(3);
expect(wasClicked1).to.be(true);
expect(wasClicked2).to.be(true);
});
});
});
@ -47,6 +50,16 @@
expect(fg.hasLayer(marker)).to.be(true);
});
it('supports non-evented layers', function () {
var fg = L.featureGroup(),
g = L.layerGroup();
expect(fg.hasLayer(g)).to.be(false);
fg.addLayer(g);
expect(fg.hasLayer(g)).to.be(true);
});
});
describe('removeLayer', function () {
it('removes the layer passed to it', function () {
@ -66,7 +79,7 @@
fg.addLayer(marker);
expect(fg.hasLayer(marker)).to.be(true);
fg.removeLayer(marker._leaflet_id);
fg.removeLayer(L.stamp(marker));
expect(fg.hasLayer(marker)).to.be(false);
});
});

View File

@ -5,7 +5,7 @@ describe("L.GeoJSON", function () {
properties: {},
geometry: {
type: 'Point',
coordinates: [20, 10]
coordinates: [20, 10, 5]
}
};
@ -24,47 +24,79 @@ describe("L.GeoJSON", function () {
});
describe("L.Marker#toGeoJSON", function () {
it("returns a Point object", function () {
it("returns a 2D Point object", function () {
var marker = new L.Marker([10, 20]);
expect(marker.toGeoJSON().geometry).to.eql({
type: 'Point',
coordinates: [20, 10]
});
});
it("returns a 3D Point object", function () {
var marker = new L.Marker([10, 20, 30]);
expect(marker.toGeoJSON().geometry).to.eql({
type: 'Point',
coordinates: [20, 10, 30]
});
});
});
describe("L.Circle#toGeoJSON", function () {
it("returns a Point object", function () {
it("returns a 2D Point object", function () {
var circle = new L.Circle([10, 20], 100);
expect(circle.toGeoJSON().geometry).to.eql({
type: 'Point',
coordinates: [20, 10]
});
});
it("returns a 3D Point object", function () {
var circle = new L.Circle([10, 20, 30], 100);
expect(circle.toGeoJSON().geometry).to.eql({
type: 'Point',
coordinates: [20, 10, 30]
});
});
});
describe("L.CircleMarker#toGeoJSON", function () {
it("returns a Point object", function () {
it("returns a 2D Point object", function () {
var marker = new L.CircleMarker([10, 20]);
expect(marker.toGeoJSON().geometry).to.eql({
type: 'Point',
coordinates: [20, 10]
});
});
it("returns a 3D Point object", function () {
var marker = new L.CircleMarker([10, 20, 30]);
expect(marker.toGeoJSON().geometry).to.eql({
type: 'Point',
coordinates: [20, 10, 30]
});
});
});
describe("L.Polyline#toGeoJSON", function () {
it("returns a LineString object", function () {
it("returns a 2D LineString object", function () {
var polyline = new L.Polyline([[10, 20], [2, 5]]);
expect(polyline.toGeoJSON().geometry).to.eql({
type: 'LineString',
coordinates: [[20, 10], [5, 2]]
});
});
it("returns a 3D LineString object", function () {
var polyline = new L.Polyline([[10, 20, 30], [2, 5, 10]]);
expect(polyline.toGeoJSON().geometry).to.eql({
type: 'LineString',
coordinates: [[20, 10, 30], [5, 2, 10]]
});
});
});
describe("L.MultiPolyline#toGeoJSON", function () {
it("returns a MultiLineString object", function () {
it("returns a 2D MultiLineString object", function () {
var multiPolyline = new L.MultiPolyline([[[10, 20], [2, 5]], [[1, 2], [3, 4]]]);
expect(multiPolyline.toGeoJSON().geometry).to.eql({
type: 'MultiLineString',
@ -74,10 +106,21 @@ describe("L.MultiPolyline#toGeoJSON", function () {
]
});
});
it("returns a 3D MultiLineString object", function () {
var multiPolyline = new L.MultiPolyline([[[10, 20, 30], [2, 5, 10]], [[1, 2, 3], [4, 5, 6]]]);
expect(multiPolyline.toGeoJSON().geometry).to.eql({
type: 'MultiLineString',
coordinates: [
[[20, 10, 30], [5, 2, 10]],
[[2, 1, 3], [5, 4, 6]]
]
});
});
});
describe("L.Polygon#toGeoJSON", function () {
it("returns a Polygon object (no holes)", function () {
it("returns a 2D Polygon object (no holes)", function () {
var polygon = new L.Polygon([[1, 2], [3, 4], [5, 6]]);
expect(polygon.toGeoJSON().geometry).to.eql({
type: 'Polygon',
@ -85,7 +128,15 @@ describe("L.Polygon#toGeoJSON", function () {
});
});
it("returns a Polygon object (with holes)", function () {
it("returns a 3D Polygon object (no holes)", function () {
var polygon = new L.Polygon([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
expect(polygon.toGeoJSON().geometry).to.eql({
type: 'Polygon',
coordinates: [[[2, 1, 3], [5, 4, 6], [8, 7, 9], [2, 1, 3]]]
});
});
it("returns a 2D Polygon object (with holes)", function () {
var polygon = new L.Polygon([[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]]);
expect(polygon.toGeoJSON().geometry).to.eql({
type: 'Polygon',
@ -95,10 +146,21 @@ describe("L.Polygon#toGeoJSON", function () {
]
});
});
it("returns a 3D Polygon object (with holes)", function () {
var polygon = new L.Polygon([[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[10, 11, 12], [13, 14, 15], [16, 17, 18]]]);
expect(polygon.toGeoJSON().geometry).to.eql({
type: 'Polygon',
coordinates: [
[[2, 1, 3], [5, 4, 6], [8, 7, 9], [2, 1, 3]],
[[11, 10, 12], [14, 13, 15], [17, 16, 18], [11, 10, 12]]
]
});
});
});
describe("L.MultiPolygon#toGeoJSON", function () {
it("returns a MultiPolygon object", function () {
it("returns a 2D MultiPolygon object", function () {
var multiPolygon = new L.MultiPolygon([[[1, 2], [3, 4], [5, 6]]]);
expect(multiPolygon.toGeoJSON().geometry).to.eql({
type: 'MultiPolygon',
@ -107,10 +169,20 @@ describe("L.MultiPolygon#toGeoJSON", function () {
]
});
});
it("returns a 3D MultiPolygon object", function () {
var multiPolygon = new L.MultiPolygon([[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]);
expect(multiPolygon.toGeoJSON().geometry).to.eql({
type: 'MultiPolygon',
coordinates: [
[[[2, 1, 3], [5, 4, 6], [8, 7, 9], [2, 1, 3]]]
]
});
});
});
describe("L.LayerGroup#toGeoJSON", function () {
it("returns a FeatureCollection object", function () {
it("returns a 2D FeatureCollection object", function () {
var marker = new L.Marker([10, 20]),
polyline = new L.Polyline([[10, 20], [2, 5]]),
layerGroup = new L.LayerGroup([marker, polyline]);
@ -120,6 +192,16 @@ describe("L.LayerGroup#toGeoJSON", function () {
});
});
it("returns a 3D FeatureCollection object", function () {
var marker = new L.Marker([10, 20, 30]),
polyline = new L.Polyline([[10, 20, 30], [2, 5, 10]]),
layerGroup = new L.LayerGroup([marker, polyline]);
expect(layerGroup.toGeoJSON()).to.eql({
type: 'FeatureCollection',
features: [marker.toGeoJSON(), polyline.toGeoJSON()]
});
});
it("ensures that every member is a Feature", function () {
var tileLayer = new L.TileLayer(),
layerGroup = new L.LayerGroup([tileLayer]);
@ -128,7 +210,7 @@ describe("L.LayerGroup#toGeoJSON", function () {
return {
type: 'Point',
coordinates: [20, 10]
}
};
};
expect(layerGroup.toGeoJSON()).to.eql({
@ -144,6 +226,51 @@ describe("L.LayerGroup#toGeoJSON", function () {
});
});
it('roundtrips GeometryCollection features', function () {
var json = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "GeometryCollection",
"geometries": [{
"type": "LineString",
"coordinates": [[-122.4425587930444, 37.80666418607323], [-122.4428379594768, 37.80663578323093]]
}, {
"type": "LineString",
"coordinates": [
[-122.4425509770566, 37.80662588061205],
[-122.4428340530617, 37.8065999493009]
]
}]
},
"properties": {
"name": "SF Marina Harbor Master"
}
}]
};
expect(L.geoJson(json).toGeoJSON()).to.eql(json);
});
it('roundtrips MiltiPoint features', function () {
var json = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "MultiPoint",
"coordinates": [[-122.4425587930444, 37.80666418607323], [-122.4428379594768, 37.80663578323093]]
},
"properties": {
"name": "Test MultiPoints"
}
}]
};
expect(L.geoJson(json).toGeoJSON()).to.eql(json);
});
it("omits layers which do not implement toGeoJSON", function () {
var tileLayer = new L.TileLayer(),
layerGroup = new L.LayerGroup([tileLayer]);

View File

@ -1,6 +1,6 @@
describe('LayerGroup', function () {
describe("#addLayer", function () {
it('adds a layer', function() {
it('adds a layer', function () {
var lg = L.layerGroup(),
marker = L.marker([0, 0]);
@ -10,7 +10,7 @@
});
});
describe("#removeLayer", function () {
it('removes a layer', function() {
it('removes a layer', function () {
var lg = L.layerGroup(),
marker = L.marker([0, 0]);
@ -21,7 +21,7 @@
});
});
describe("#clearLayers", function () {
it('removes all layers', function() {
it('removes all layers', function () {
var lg = L.layerGroup(),
marker = L.marker([0, 0]);
@ -32,7 +32,7 @@
});
});
describe("#getLayers", function () {
it('gets all layers', function() {
it('gets all layers', function () {
var lg = L.layerGroup(),
marker = L.marker([0, 0]);
@ -42,14 +42,14 @@
});
});
describe("#eachLayer", function () {
it('iterates over all layers', function() {
it('iterates over all layers', function () {
var lg = L.layerGroup(),
marker = L.marker([0, 0]),
ctx = { foo: 'bar' };
lg.addLayer(marker);
lg.eachLayer(function(layer) {
lg.eachLayer(function (layer) {
expect(layer).to.eql(marker);
expect(this).to.eql(ctx);
}, ctx);

View File

@ -1,4 +1,4 @@
describe('Popup', function() {
describe('Popup', function () {
var c, map;
@ -10,7 +10,7 @@ describe('Popup', function() {
map.setView(new L.LatLng(55.8, 37.6), 6);
});
it("closes on map click when map has closePopupOnClick option", function() {
it("closes on map click when map has closePopupOnClick option", function () {
map.options.closePopupOnClick = true;
var popup = new L.Popup()
@ -22,7 +22,7 @@ describe('Popup', function() {
expect(map.hasLayer(popup)).to.be(false);
});
it("closes on map click when popup has closeOnClick option", function() {
it("closes on map click when popup has closeOnClick option", function () {
map.options.closePopupOnClick = false;
var popup = new L.Popup({closeOnClick: true})
@ -34,7 +34,7 @@ describe('Popup', function() {
expect(map.hasLayer(popup)).to.be(false);
});
it("does not close on map click when popup has closeOnClick: false option", function() {
it("does not close on map click when popup has closeOnClick: false option", function () {
map.options.closePopupOnClick = true;
var popup = new L.Popup({closeOnClick: false})
@ -46,7 +46,7 @@ describe('Popup', function() {
expect(map.hasLayer(popup)).to.be(true);
});
it("toggles its visibility when marker is clicked", function() {
it("toggles its visibility when marker is clicked", function () {
var marker = new L.Marker(new L.LatLng(55.8, 37.6));
map.addLayer(marker);
@ -70,7 +70,7 @@ describe('Popup', function() {
marker.closePopup.restore();
});
it("should trigger popupopen on marker when popup opens", function() {
it("should trigger popupopen on marker when popup opens", function () {
var marker1 = new L.Marker(new L.LatLng(55.8, 37.6));
var marker2 = new L.Marker(new L.LatLng(57.123076977278, 44.861962891635));
@ -91,7 +91,7 @@ describe('Popup', function() {
expect(spy.called).to.be(true);
});
it("should trigger popupclose on marker when popup closes", function() {
it("should trigger popupclose on marker when popup closes", function () {
var marker1 = new L.Marker(new L.LatLng(55.8, 37.6));
var marker2 = new L.Marker(new L.LatLng(57.123076977278, 44.861962891635));

View File

@ -11,6 +11,7 @@ describe('TileLayer', function () {
it("has the same zoomlevels as the tilelayer", function () {
var maxZoom = 10,
minZoom = 5;
map.setView([0, 0], 1);
L.tileLayer(tileUrl, {
@ -36,33 +37,32 @@ describe('TileLayer', function () {
it("has its zoomlevels updated to fit the new layer", function () {
map.setView([0, 0], 1);
L.tileLayer(tileUrl, { minZoom:10, maxZoom: 15 }).addTo(map);
L.tileLayer(tileUrl, {minZoom: 10, maxZoom: 15}).addTo(map);
expect(map.getMinZoom()).to.be(10);
expect(map.getMaxZoom()).to.be(15);
L.tileLayer(tileUrl, { minZoom:5, maxZoom: 10 }).addTo(map);
L.tileLayer(tileUrl, {minZoom: 5, maxZoom: 10}).addTo(map);
expect(map.getMinZoom()).to.be(5); // changed
expect(map.getMaxZoom()).to.be(15); // unchanged
L.tileLayer(tileUrl,{ minZoom:10, maxZoom: 20 }).addTo(map);
L.tileLayer(tileUrl, {minZoom: 10, maxZoom: 20}).addTo(map);
expect(map.getMinZoom()).to.be(5); // unchanged
expect(map.getMaxZoom()).to.be(20); // changed
L.tileLayer(tileUrl, { minZoom:0, maxZoom: 25 }).addTo(map);
L.tileLayer(tileUrl, {minZoom: 0, maxZoom: 25}).addTo(map);
expect(map.getMinZoom()).to.be(0); // changed
expect(map.getMaxZoom()).to.be(25); // changed
});
});
describe("when a tilelayer is removed from a map", function () {
it("has its zoomlevels updated to only fit the layers it currently has", function () {
var tiles = [ L.tileLayer(tileUrl, { minZoom:10, maxZoom: 15 }).addTo(map),
L.tileLayer(tileUrl, { minZoom:5, maxZoom: 10 }).addTo(map),
L.tileLayer(tileUrl, { minZoom:10, maxZoom: 20 }).addTo(map),
L.tileLayer(tileUrl, { minZoom:0, maxZoom: 25 }).addTo(map)
var tiles = [ L.tileLayer(tileUrl, {minZoom: 10, maxZoom: 15}).addTo(map),
L.tileLayer(tileUrl, {minZoom: 5, maxZoom: 10}).addTo(map),
L.tileLayer(tileUrl, {minZoom: 10, maxZoom: 20}).addTo(map),
L.tileLayer(tileUrl, {minZoom: 0, maxZoom: 25}).addTo(map)
];
map.whenReady(function() {
map.whenReady(function () {
expect(map.getMinZoom()).to.be(0);
expect(map.getMaxZoom()).to.be(25);

View File

@ -1,21 +1,29 @@
describe("Marker", function () {
var map,
spy;
spy,
icon1,
icon2;
beforeEach(function () {
map = L.map(document.createElement('div')).setView([0, 0], 0);
icon1 = new L.Icon.Default();
icon2 = new L.Icon.Default({
iconUrl: icon1._getIconUrl('icon') + '?2',
shadowUrl: icon1._getIconUrl('shadow') + '?2'
});
});
describe("#setIcon", function () {
it("changes the icon to another image", function () {
var marker = new L.Marker([0, 0], {icon: new L.Icon({iconUrl: 'icon1.png' }) });
var marker = new L.Marker([0, 0], {icon: icon1});
map.addLayer(marker);
var beforeIcon = marker._icon;
marker.setIcon(new L.Icon({iconUrl: 'icon2.png' }));
marker.setIcon(icon2);
var afterIcon = marker._icon;
expect(beforeIcon).to.be(afterIcon);
expect(afterIcon.src).to.contain('icon2.png');
expect(afterIcon.src).to.contain(icon2._getIconUrl('icon'));
});
it("changes the icon to another DivIcon", function () {
@ -45,17 +53,17 @@ describe("Marker", function () {
map.addLayer(marker);
var oldIcon = marker._icon;
marker.setIcon(new L.Icon({iconUrl: 'icon1.png' }));
marker.setIcon(icon1);
expect(oldIcon).to.not.be(marker._icon);
expect(oldIcon.parentNode).to.be(null);
expect(marker._icon.src).to.contain('icon1.png');
expect(marker._icon.src).to.contain('marker-icon.png');
expect(marker._icon.parentNode).to.be(map._panes.markerPane);
});
it("changes an image to a DivIcon", function () {
var marker = new L.Marker([0, 0], {icon: new L.Icon({iconUrl: 'icon1.png' }) });
var marker = new L.Marker([0, 0], {icon: icon1});
map.addLayer(marker);
var oldIcon = marker._icon;
@ -69,12 +77,12 @@ describe("Marker", function () {
});
it("reuses the icon/shadow when changing icon", function () {
var marker = new L.Marker([0, 0], { icon: new L.Icon({ iconUrl: 'icon1.png', shadowUrl: 'shadow1.png', }) });
var marker = new L.Marker([0, 0], { icon: icon1});
map.addLayer(marker);
var oldIcon = marker._icon;
var oldShadow = marker._shadow;
marker.setIcon(new L.Icon({ iconUrl: 'icon2.png', shadowUrl: 'shadow2.png', }));
marker.setIcon(icon2);
expect(oldIcon).to.be(marker._icon);
expect(oldShadow).to.be(marker._shadow);

View File

@ -1,13 +1,13 @@
describe('CircleMarker', function() {
describe("#_radius", function() {
describe('CircleMarker', function () {
describe("#_radius", function () {
var map;
beforeEach(function() {
beforeEach(function () {
map = L.map(document.createElement('div'));
map.setView([0, 0], 1);
});
describe("when a CircleMarker is added to the map ", function() {
describe("with a radius set as an option", function() {
it("takes that radius", function() {
describe("when a CircleMarker is added to the map ", function () {
describe("with a radius set as an option", function () {
it("takes that radius", function () {
var marker = L.circleMarker([0, 0], { radius: 20 }).addTo(map);
expect(marker._radius).to.be(20);
@ -33,7 +33,7 @@
});
describe("and setStyle is used to change the radius after adding", function () {
it("takes the given radius", function() {
it("takes the given radius", function () {
var marker = L.circleMarker([0, 0], { radius: 20 });
marker.addTo(map);
marker.setStyle({ radius: 15 });

View File

@ -1,4 +1,4 @@
describe('Polygon', function() {
describe('Polygon', function () {
var c = document.createElement('div');
c.style.width = '400px';
@ -6,7 +6,7 @@ describe('Polygon', function() {
var map = new L.Map(c);
map.setView(new L.LatLng(55.8, 37.6), 6);
describe("#initialize", function() {
describe("#initialize", function () {
it("doesn't overwrite the given latlng array", function () {
var originalLatLngs = [
[1, 2],
@ -24,6 +24,21 @@ describe('Polygon', function() {
var polygon = new L.Polygon([]);
expect(polygon.getLatLngs()).to.eql([]);
});
it("can be initialized with holes", function () {
var originalLatLngs = [
[ //external rink
[0, 10], [10, 10], [10, 0]
], [ //hole
[2, 3], [2, 4], [3, 4]
]
];
var polygon = new L.Polygon(originalLatLngs);
//getLatLngs() returns only external ring
expect(polygon.getLatLngs()).to.eql([L.latLng([0, 10]), L.latLng([10, 10]), L.latLng([10, 0])]);
});
});
describe("#setLatLngs", function () {
@ -40,6 +55,22 @@ describe('Polygon', function() {
expect(sourceLatLngs).to.eql(originalLatLngs);
});
it("can be set external ring and holes", function () {
var latLngs = [
[ //external rink
[0, 10], [10, 10], [10, 0]
], [ //hole
[2, 3], [2, 4], [3, 4]
]
];
var polygon = new L.Polygon([]);
polygon.setLatLngs(latLngs);
//getLatLngs() returns only external ring
expect(polygon.getLatLngs()).to.eql([L.latLng([0, 10]), L.latLng([10, 10]), L.latLng([10, 0])]);
});
});
describe("#spliceLatLngs", function () {

View File

@ -1,4 +1,4 @@
describe('PolylineGeometry', function() {
describe('PolylineGeometry', function () {
var c = document.createElement('div');
c.style.width = '400px';
@ -6,12 +6,12 @@ describe('PolylineGeometry', function() {
var map = new L.Map(c);
map.setView(new L.LatLng(55.8, 37.6), 6);
describe("#distanceTo", function() {
it("calculates distances to points", function() {
describe("#distanceTo", function () {
it("calculates distances to points", function () {
var p1 = map.latLngToLayerPoint(new L.LatLng(55.8, 37.6));
var p2 = map.latLngToLayerPoint(new L.LatLng(57.123076977278, 44.861962891635));
var latlngs = [[56.485503424111, 35.545556640339], [55.972522915346, 36.116845702918], [55.502459116923, 34.930322265253], [55.31534617509, 38.973291015816]]
.map(function(ll) {
.map(function (ll) {
return new L.LatLng(ll[0], ll[1]);
});
var polyline = new L.Polyline([], {

View File

@ -1,4 +1,4 @@
describe('Polyline', function() {
describe('Polyline', function () {
var c = document.createElement('div');
c.style.width = '400px';
@ -6,7 +6,7 @@ describe('Polyline', function() {
var map = new L.Map(c);
map.setView(new L.LatLng(55.8, 37.6), 6);
describe("#initialize", function() {
describe("#initialize", function () {
it("doesn't overwrite the given latlng array", function () {
var originalLatLngs = [
[1, 2],

View File

@ -29,8 +29,8 @@ describe("Map", function () {
var container = document.createElement('div'),
map = new L.Map(container);
expect(function () {
new L.Map(container);
}).to.throwException(function(e) {
L.map(container);
}).to.throwException(function (e) {
expect(e.message).to.eql("Map container is already initialized.");
});
map.remove();
@ -38,8 +38,8 @@ describe("Map", function () {
it("throws an exception if a container is not found", function () {
expect(function () {
new L.Map('nonexistentdivelement');
}).to.throwException(function(e) {
L.map('nonexistentdivelement');
}).to.throwException(function (e) {
expect(e.message).to.eql("Map container not found.");
});
map.remove();
@ -72,11 +72,24 @@ describe("Map", function () {
});
describe('#getCenter', function () {
it ('throws if not set before', function () {
it('throws if not set before', function () {
expect(function () {
map.getCenter();
}).to.throwError();
});
it('returns a precise center when zoomed in after being set (#426)', function () {
var center = L.latLng(10, 10);
map.setView(center, 1);
map.setZoom(19);
expect(map.getCenter()).to.eql(center);
});
it('returns correct center after invalidateSize (#1919)', function () {
map.setView(L.latLng(10, 10), 1);
map.invalidateSize();
expect(map.getCenter()).not.to.eql(L.latLng(10, 10));
});
});
describe("#whenReady", function () {
@ -108,6 +121,12 @@ describe("Map", function () {
expect(map.getZoom()).to.be(13);
expect(map.getCenter().distanceTo([51.505, -0.09])).to.be.lessThan(5);
});
it("can be passed without a zoom specified", function () {
map.setZoom(13);
expect(map.setView([51.605, -0.11])).to.be(map);
expect(map.getZoom()).to.be(13);
expect(map.getCenter().distanceTo([51.605, -0.11])).to.be.lessThan(5);
});
});
describe("#getBounds", function () {
@ -120,7 +139,53 @@ describe("Map", function () {
});
});
describe('#setMaxBounds', function () {
it("aligns pixel-wise map view center with maxBounds center if it cannot move view bounds inside maxBounds (#1908)", function () {
var container = map.getContainer();
// large view, cannot fit within maxBounds
container.style.width = container.style.height = "1000px";
document.body.appendChild(container);
// maxBounds
var bounds = L.latLngBounds([51.5, -0.05], [51.55, 0.05]);
map.setMaxBounds(bounds, {animate: false});
// set view outside
map.setView(L.latLng([53.0, 0.15]), 12, {animate: false});
// get center of bounds in pixels
var boundsCenter = map.project(bounds.getCenter()).round();
expect(map.project(map.getCenter()).round()).to.eql(boundsCenter);
document.body.removeChild(container);
});
it("moves map view within maxBounds by changing one coordinate", function () {
var container = map.getContainer();
// small view, can fit within maxBounds
container.style.width = container.style.height = "200px";
document.body.appendChild(container);
// maxBounds
var bounds = L.latLngBounds([51, -0.2], [52, 0.2]);
map.setMaxBounds(bounds, {animate: false});
// set view outside maxBounds on one direction only
// leaves untouched the other coordinate (that is not already centered)
var initCenter = [53.0, 0.1];
map.setView(L.latLng(initCenter), 16, {animate: false});
// one pixel coordinate hasn't changed, the other has
var pixelCenter = map.project(map.getCenter()).round();
var pixelInit = map.project(initCenter).round();
expect(pixelCenter.x).to.eql(pixelInit.x);
expect(pixelCenter.y).not.to.eql(pixelInit.y);
// the view is inside the bounds
expect(bounds.contains(map.getBounds())).to.be(true);
document.body.removeChild(container);
});
});
describe("#getMinZoom and #getMaxZoom", function () {
describe('#getMinZoom', function () {
it('returns 0 if not set by Map options or TileLayer options', function () {
var map = L.map(document.createElement('div'));
expect(map.getMinZoom()).to.be(0);
});
});
it("minZoom and maxZoom options overrides any minZoom and maxZoom set on layers", function () {
var map = L.map(document.createElement('div'), {minZoom: 2, maxZoom: 20});
@ -189,7 +254,7 @@ describe("Map", function () {
it("adds the layer before firing layeradd", function (done) {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() };
map.on('layeradd', function() {
map.on('layeradd', function () {
expect(map.hasLayer(layer)).to.be.ok();
done();
});
@ -202,7 +267,7 @@ describe("Map", function () {
var spy = sinon.spy();
map.on("zoomlevelschange", spy);
expect(spy.called).not.to.be.ok();
L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 10}).addTo(map);
expect(spy.called).to.be.ok();
});
});
@ -210,10 +275,10 @@ describe("Map", function () {
describe("when a new layer with greater zoomlevel coverage than the current layer is added to a map", function () {
it("fires a zoomlevelschange event", function () {
var spy = sinon.spy();
L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 10}).addTo(map);
map.on("zoomlevelschange", spy);
expect(spy.called).not.to.be.ok();
L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 15 }).addTo(map);
L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 15}).addTo(map);
expect(spy.called).to.be.ok();
});
});
@ -221,12 +286,12 @@ describe("Map", function () {
describe("when a new layer with the same or lower zoomlevel coverage as the current layer is added to a map", function () {
it("fires no zoomlevelschange event", function () {
var spy = sinon.spy();
L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 10}).addTo(map);
map.on("zoomlevelschange", spy);
expect(spy.called).not.to.be.ok();
L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 10}).addTo(map);
expect(spy.called).not.to.be.ok();
L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 5 }).addTo(map);
L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 5}).addTo(map);
expect(spy.called).not.to.be.ok();
});
});
@ -285,7 +350,7 @@ describe("Map", function () {
it("removes the layer before firing layerremove", function (done) {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() };
map.on('layerremove', function() {
map.on('layerremove', function () {
expect(map.hasLayer(layer)).not.to.be.ok();
done();
});
@ -296,9 +361,9 @@ describe("Map", function () {
describe("when the last tile layer on a map is removed", function () {
it("fires a zoomlevelschange event", function () {
map.whenReady(function(){
map.whenReady(function () {
var spy = sinon.spy();
var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
var tl = L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 10}).addTo(map);
map.on("zoomlevelschange", spy);
expect(spy.called).not.to.be.ok();
@ -310,10 +375,10 @@ describe("Map", function () {
describe("when a tile layer is removed from a map and it had greater zoom level coverage than the remainding layer", function () {
it("fires a zoomlevelschange event", function () {
map.whenReady(function(){
map.whenReady(function () {
var spy = sinon.spy(),
tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map),
t2 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 15 }).addTo(map);
tl = L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 10}).addTo(map),
t2 = L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 15}).addTo(map);
map.on("zoomlevelschange", spy);
expect(spy.called).to.not.be.ok();
@ -325,10 +390,10 @@ describe("Map", function () {
describe("when a tile layer is removed from a map it and it had lesser or the sa,e zoom level coverage as the remainding layer(s)", function () {
it("fires no zoomlevelschange event", function () {
map.whenReady(function(){
var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map),
t2 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map),
t3 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 5 }).addTo(map);
map.whenReady(function () {
var tl = L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 10}).addTo(map),
t2 = L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 10}).addTo(map),
t3 = L.tileLayer("{z}{x}{y}", {minZoom: 0, maxZoom: 5}).addTo(map);
map.on("zoomlevelschange", spy);
expect(spy).not.toHaveBeenCalled();
@ -367,4 +432,105 @@ describe("Map", function () {
expect(spy.thisValues[0]).to.eql(map);
});
});
describe("#invalidateSize", function () {
var container,
origWidth = 100,
clock;
beforeEach(function () {
container = map.getContainer();
container.style.width = origWidth + "px";
document.body.appendChild(container);
map.setView([0, 0], 0);
map.invalidateSize({pan: false});
clock = sinon.useFakeTimers();
});
afterEach(function () {
document.body.removeChild(container);
clock.restore();
});
it("pans by the right amount when growing in 1px increments", function () {
container.style.width = (origWidth + 1) + "px";
map.invalidateSize();
expect(map._getMapPanePos().x).to.be(1);
container.style.width = (origWidth + 2) + "px";
map.invalidateSize();
expect(map._getMapPanePos().x).to.be(1);
container.style.width = (origWidth + 3) + "px";
map.invalidateSize();
expect(map._getMapPanePos().x).to.be(2);
});
it("pans by the right amount when shrinking in 1px increments", function () {
container.style.width = (origWidth - 1) + "px";
map.invalidateSize();
expect(map._getMapPanePos().x).to.be(0);
container.style.width = (origWidth - 2) + "px";
map.invalidateSize();
expect(map._getMapPanePos().x).to.be(-1);
container.style.width = (origWidth - 3) + "px";
map.invalidateSize();
expect(map._getMapPanePos().x).to.be(-1);
});
it("pans back to the original position after growing by an odd size and back", function () {
container.style.width = (origWidth + 5) + "px";
map.invalidateSize();
container.style.width = origWidth + "px";
map.invalidateSize();
expect(map._getMapPanePos().x).to.be(0);
});
it("emits no move event if the size has not changed", function () {
var spy = sinon.spy();
map.on("move", spy);
map.invalidateSize();
expect(spy.called).not.to.be.ok();
});
it("emits a move event if the size has changed", function () {
var spy = sinon.spy();
map.on("move", spy);
container.style.width = (origWidth + 5) + "px";
map.invalidateSize();
expect(spy.called).to.be.ok();
});
it("emits a moveend event if the size has changed", function () {
var spy = sinon.spy();
map.on("moveend", spy);
container.style.width = (origWidth + 5) + "px";
map.invalidateSize();
expect(spy.called).to.be.ok();
});
it("debounces the moveend event if the debounceMoveend option is given", function () {
var spy = sinon.spy();
map.on("moveend", spy);
container.style.width = (origWidth + 5) + "px";
map.invalidateSize({debounceMoveend: true});
expect(spy.called).not.to.be.ok();
clock.tick(200);
expect(spy.called).to.be.ok();
});
});
});

View File

@ -0,0 +1,38 @@
describe("Map.Drag", function () {
describe("#addHook", function () {
it("calls the map with dragging enabled", function () {
var container = document.createElement('div'),
map = new L.Map(container, {
dragging: true
});
expect(map.dragging.enabled()).to.be(true);
map.setView([0, 0], 0);
expect(map.dragging.enabled()).to.be(true);
});
it("calls the map with dragging and worldCopyJump enabled", function () {
var container = document.createElement('div'),
map = new L.Map(container, {
dragging: true,
worldCopyJump: true
});
expect(map.dragging.enabled()).to.be(true);
map.setView([0, 0], 0);
expect(map.dragging.enabled()).to.be(true);
});
it("calls the map with dragging disabled and worldCopyJump enabled; " +
"enables dragging after setting center and zoom", function () {
var container = document.createElement('div'),
map = new L.Map(container, {
dragging: false,
worldCopyJump: true
});
expect(map.dragging.enabled()).to.be(false);
map.setView([0, 0], 0);
map.dragging.enable();
expect(map.dragging.enabled()).to.be(true);
});
});
});

View File

@ -2,7 +2,7 @@
var oldL = window.L,
L = {};
L.version = '0.6.4';
L.version = '0.7';
// define Leaflet for Node module pattern loaders, including Browserify
if (typeof module === 'object' && typeof module.exports === 'object') {

View File

@ -18,6 +18,12 @@ L.Control.Attribution = L.Control.extend({
this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
L.DomEvent.disableClickPropagation(this._container);
for (var i in map._layers) {
if (map._layers[i].getAttribution) {
this.addAttribution(map._layers[i].getAttribution());
}
}
map
.on('layeradd', this._onLayerAdd, this)
.on('layerremove', this._onLayerRemove, this);

View File

@ -69,8 +69,9 @@ L.Control.Layers = L.Control.extend({
container.setAttribute('aria-haspopup', true);
if (!L.Browser.touch) {
L.DomEvent.disableClickPropagation(container);
L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
L.DomEvent
.disableClickPropagation(container)
.disableScrollPropagation(container);
} else {
L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
}
@ -95,6 +96,10 @@ L.Control.Layers = L.Control.extend({
else {
L.DomEvent.on(link, 'focus', this._expand, this);
}
//Work around for Firefox android issue https://github.com/Leaflet/Leaflet/issues/2033
L.DomEvent.on(form, 'click', function () {
setTimeout(L.bind(this._onInputClick, this), 0);
}, this);
this._map.on('click', this._collapse, this);
// TODO keyboard accessibility
@ -229,6 +234,8 @@ L.Control.Layers = L.Control.extend({
}
this._handlingClick = false;
this._refocusOnMap();
},
_expand: function () {

View File

@ -4,7 +4,11 @@
L.Control.Zoom = L.Control.extend({
options: {
position: 'topleft'
position: 'topleft',
zoomInText: '+',
zoomInTitle: 'Zoom in',
zoomOutText: '-',
zoomOutTitle: 'Zoom out'
},
onAdd: function (map) {
@ -14,10 +18,13 @@ L.Control.Zoom = L.Control.extend({
this._map = map;
this._zoomInButton = this._createButton(
'+', 'Zoom in', zoomName + '-in', container, this._zoomIn, this);
this.options.zoomInText, this.options.zoomInTitle,
zoomName + '-in', container, this._zoomIn, this);
this._zoomOutButton = this._createButton(
'-', 'Zoom out', zoomName + '-out', container, this._zoomOut, this);
this.options.zoomOutText, this.options.zoomOutTitle,
zoomName + '-out', container, this._zoomOut, this);
this._updateDisabled();
map.on('zoomend zoomlevelschange', this._updateDisabled, this);
return container;
@ -48,7 +55,8 @@ L.Control.Zoom = L.Control.extend({
.on(link, 'mousedown', stop)
.on(link, 'dblclick', stop)
.on(link, 'click', L.DomEvent.preventDefault)
.on(link, 'click', fn, context);
.on(link, 'click', fn, context)
.on(link, 'click', this._refocusOnMap, context);
return link;
},

View File

@ -66,6 +66,12 @@ L.Control = L.Class.extend({
}
return this;
},
_refocusOnMap: function () {
if (this._map) {
this._map.getContainer().focus();
}
}
});

View File

@ -4,9 +4,7 @@
(function () {
var ie = !!window.ActiveXObject,
ie6 = ie && !window.XMLHttpRequest,
ie7 = ie && !document.querySelector,
var ie = 'ActiveXObject' in window,
ielt9 = ie && !document.addEventListener,
// terrible browser detection to work around Safari / iOS / Android browser bugs
@ -16,10 +14,13 @@
phantomjs = ua.indexOf('phantom') !== -1,
android = ua.indexOf('android') !== -1,
android23 = ua.search('android [23]') !== -1,
gecko = ua.indexOf('gecko') !== -1,
mobile = typeof orientation !== undefined + '',
msTouch = window.navigator && window.navigator.msPointerEnabled &&
window.navigator.msMaxTouchPoints,
msPointer = window.navigator && window.navigator.msPointerEnabled &&
window.navigator.msMaxTouchPoints && !window.PointerEvent,
pointer = (window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints) ||
msPointer,
retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
window.matchMedia('(min-resolution:144dpi)').matches),
@ -39,8 +40,8 @@
var startName = 'ontouchstart';
// IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc.
if (msTouch || (startName in doc)) {
// IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.Pointer) or WebKit, etc.
if (pointer || (startName in doc)) {
return true;
}
@ -66,10 +67,9 @@
L.Browser = {
ie: ie,
ie6: ie6,
ie7: ie7,
ielt9: ielt9,
webkit: webkit,
gecko: gecko && !webkit && !window.opera && !ie,
android: android,
android23: android23,
@ -88,7 +88,8 @@
mobileOpera: mobile && window.opera,
touch: touch,
msTouch: msTouch,
msPointer: msPointer,
pointer: pointer,
retina: retina
};

View File

@ -14,7 +14,7 @@ L.Mixin.Events = {
if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }
var events = this[eventsKey] = this[eventsKey] || {},
contextId = context && L.stamp(context),
contextId = context && context !== this && L.stamp(context),
i, len, event, type, indexKey, indexLenKey, typeIndex;
// types can be a string of space-separated words
@ -27,7 +27,7 @@ L.Mixin.Events = {
};
type = types[i];
if (context) {
if (contextId) {
// store listeners of a particular context in a separate hash (if it has an id)
// gives a major performance boost when removing thousands of map layers
@ -74,7 +74,7 @@ L.Mixin.Events = {
if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }
var events = this[eventsKey],
contextId = context && L.stamp(context),
contextId = context && context !== this && L.stamp(context),
i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;
types = L.Util.splitWords(types);
@ -90,9 +90,10 @@ L.Mixin.Events = {
// clear all listeners for a type if function isn't specified
delete events[type];
delete events[indexKey];
delete events[indexLenKey];
} else {
listeners = context && typeIndex ? typeIndex[contextId] : events[type];
listeners = contextId && typeIndex ? typeIndex[contextId] : events[type];
if (listeners) {
for (j = listeners.length - 1; j >= 0; j--) {
@ -135,7 +136,7 @@ L.Mixin.Events = {
listeners = events[type].slice();
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].action.call(listeners[i].context || this, event);
listeners[i].action.call(listeners[i].context, event);
}
}
@ -147,7 +148,7 @@ L.Mixin.Events = {
if (listeners) {
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].action.call(listeners[i].context || this, event);
listeners[i].action.call(listeners[i].context, event);
}
}
}

View File

@ -105,19 +105,23 @@ L.Util = {
return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
},
template: function (str, data) {
return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
var value = data[key];
if (value === undefined) {
throw new Error('No value provided for variable ' + str);
} else if (typeof value === 'function') {
value = value(data);
}
return value;
compileTemplate: function (str, data) {
// based on https://gist.github.com/padolsey/6008842
str = str.replace(/"/g, '\\\"');
str = str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
return '" + o["' + key + '"]' + (typeof data[key] === 'function' ? '(o)' : '') + ' + "';
});
// jshint evil: true
return new Function('o', 'return "' + str + '";');
},
isArray: function (obj) {
template: function (str, data) {
var cache = L.Util._templateCache = L.Util._templateCache || {};
cache[str] = cache[str] || L.Util.compileTemplate(str, data);
return cache[str](data);
},
isArray: Array.isArray || function (obj) {
return (Object.prototype.toString.call(obj) === '[object Array]');
},

View File

@ -4,8 +4,8 @@
L.extend(L.DomEvent, {
_touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
_touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
_touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
_touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
// inspired by Zepto touch code by Thomas Fuchs
addDoubleTapListener: function (obj, handler, id) {
@ -21,7 +21,7 @@ L.extend(L.DomEvent, {
function onTouchStart(e) {
var count;
if (L.Browser.msTouch) {
if (L.Browser.pointer) {
trackedTouches.push(e.pointerId);
count = trackedTouches.length;
} else {
@ -40,7 +40,7 @@ L.extend(L.DomEvent, {
}
function onTouchEnd(e) {
if (L.Browser.msTouch) {
if (L.Browser.pointer) {
var idx = trackedTouches.indexOf(e.pointerId);
if (idx === -1) {
return;
@ -49,7 +49,7 @@ L.extend(L.DomEvent, {
}
if (doubleTap) {
if (L.Browser.msTouch) {
if (L.Browser.pointer) {
// work around .type being readonly with MSPointer* events
var newTouch = { },
prop;
@ -73,15 +73,15 @@ L.extend(L.DomEvent, {
obj[pre + touchstart + id] = onTouchStart;
obj[pre + touchend + id] = onTouchEnd;
// on msTouch we need to listen on the document, otherwise a drag starting on the map and moving off screen
// on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen
// will not come through to us, so we will lose track of how many touches are ongoing
var endElement = L.Browser.msTouch ? document.documentElement : obj;
var endElement = L.Browser.pointer ? document.documentElement : obj;
obj.addEventListener(touchstart, onTouchStart, false);
endElement.addEventListener(touchend, onTouchEnd, false);
if (L.Browser.msTouch) {
endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
if (L.Browser.pointer) {
endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false);
}
return this;
@ -91,11 +91,12 @@ L.extend(L.DomEvent, {
var pre = '_leaflet_';
obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
(L.Browser.msTouch ? document.documentElement : obj).removeEventListener(
(L.Browser.pointer ? document.documentElement : obj).removeEventListener(
this._touchend, obj[pre + this._touchend + id], false);
if (L.Browser.msTouch) {
document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
if (L.Browser.pointer) {
document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id],
false);
}
return this;

View File

@ -1,146 +0,0 @@
/*
* Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
*/
L.extend(L.DomEvent, {
_msTouches: [],
_msDocumentListener: false,
// Provides a touch events wrapper for msPointer events.
// Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
addMsTouchListener: function (obj, type, handler, id) {
switch (type) {
case 'touchstart':
return this.addMsTouchListenerStart(obj, type, handler, id);
case 'touchend':
return this.addMsTouchListenerEnd(obj, type, handler, id);
case 'touchmove':
return this.addMsTouchListenerMove(obj, type, handler, id);
default:
throw 'Unknown touch event type';
}
},
addMsTouchListenerStart: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._msTouches;
var cb = function (e) {
var alreadyInArray = false;
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
alreadyInArray = true;
break;
}
}
if (!alreadyInArray) {
touches.push(e);
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
};
obj[pre + 'touchstart' + id] = cb;
obj.addEventListener('MSPointerDown', cb, false);
// need to also listen for end events to keep the _msTouches list accurate
// this needs to be on the body and never go away
if (!this._msDocumentListener) {
var internalCb = function (e) {
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches.splice(i, 1);
break;
}
}
};
//We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
document.documentElement.addEventListener('MSPointerUp', internalCb, false);
document.documentElement.addEventListener('MSPointerCancel', internalCb, false);
this._msDocumentListener = true;
}
return this;
},
addMsTouchListenerMove: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._msTouches;
function cb(e) {
// don't fire touch moves when mouse isn't down
if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; }
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches[i] = e;
break;
}
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
}
obj[pre + 'touchmove' + id] = cb;
obj.addEventListener('MSPointerMove', cb, false);
return this;
},
addMsTouchListenerEnd: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._msTouches;
var cb = function (e) {
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches.splice(i, 1);
break;
}
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
};
obj[pre + 'touchend' + id] = cb;
obj.addEventListener('MSPointerUp', cb, false);
obj.addEventListener('MSPointerCancel', cb, false);
return this;
},
removeMsTouchListener: function (obj, type, id) {
var pre = '_leaflet_',
cb = obj[pre + type + id];
switch (type) {
case 'touchstart':
obj.removeEventListener('MSPointerDown', cb, false);
break;
case 'touchmove':
obj.removeEventListener('MSPointerMove', cb, false);
break;
case 'touchend':
obj.removeEventListener('MSPointerUp', cb, false);
obj.removeEventListener('MSPointerCancel', cb, false);
break;
}
return this;
}
});

155
src/dom/DomEvent.Pointer.js Normal file
View File

@ -0,0 +1,155 @@
/*
* Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
*/
L.extend(L.DomEvent, {
//static
POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
_pointers: [],
_pointerDocumentListener: false,
// Provides a touch events wrapper for (ms)pointer events.
// Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
//ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
addPointerListener: function (obj, type, handler, id) {
switch (type) {
case 'touchstart':
return this.addPointerListenerStart(obj, type, handler, id);
case 'touchend':
return this.addPointerListenerEnd(obj, type, handler, id);
case 'touchmove':
return this.addPointerListenerMove(obj, type, handler, id);
default:
throw 'Unknown touch event type';
}
},
addPointerListenerStart: function (obj, type, handler, id) {
var pre = '_leaflet_',
pointers = this._pointers;
var cb = function (e) {
L.DomEvent.preventDefault(e);
var alreadyInArray = false;
for (var i = 0; i < pointers.length; i++) {
if (pointers[i].pointerId === e.pointerId) {
alreadyInArray = true;
break;
}
}
if (!alreadyInArray) {
pointers.push(e);
}
e.touches = pointers.slice();
e.changedTouches = [e];
handler(e);
};
obj[pre + 'touchstart' + id] = cb;
obj.addEventListener(this.POINTER_DOWN, cb, false);
// need to also listen for end events to keep the _pointers list accurate
// this needs to be on the body and never go away
if (!this._pointerDocumentListener) {
var internalCb = function (e) {
for (var i = 0; i < pointers.length; i++) {
if (pointers[i].pointerId === e.pointerId) {
pointers.splice(i, 1);
break;
}
}
};
//We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
document.documentElement.addEventListener(this.POINTER_UP, internalCb, false);
document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false);
this._pointerDocumentListener = true;
}
return this;
},
addPointerListenerMove: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._pointers;
function cb(e) {
// don't fire touch moves when mouse isn't down
if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches[i] = e;
break;
}
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
}
obj[pre + 'touchmove' + id] = cb;
obj.addEventListener(this.POINTER_MOVE, cb, false);
return this;
},
addPointerListenerEnd: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._pointers;
var cb = function (e) {
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches.splice(i, 1);
break;
}
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
};
obj[pre + 'touchend' + id] = cb;
obj.addEventListener(this.POINTER_UP, cb, false);
obj.addEventListener(this.POINTER_CANCEL, cb, false);
return this;
},
removePointerListener: function (obj, type, id) {
var pre = '_leaflet_',
cb = obj[pre + type + id];
switch (type) {
case 'touchstart':
obj.removeEventListener(this.POINTER_DOWN, cb, false);
break;
case 'touchmove':
obj.removeEventListener(this.POINTER_MOVE, cb, false);
break;
case 'touchend':
obj.removeEventListener(this.POINTER_UP, cb, false);
obj.removeEventListener(this.POINTER_CANCEL, cb, false);
break;
}
return this;
}
});

View File

@ -16,8 +16,8 @@ L.DomEvent = {
return fn.call(context || obj, e || L.DomEvent._getEvent());
};
if (L.Browser.msTouch && type.indexOf('touch') === 0) {
return this.addMsTouchListener(obj, type, handler, id);
if (L.Browser.pointer && type.indexOf('touch') === 0) {
return this.addPointerListener(obj, type, handler, id);
}
if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
this.addDoubleTapListener(obj, handler, id);
@ -69,8 +69,8 @@ L.DomEvent = {
if (!handler) { return this; }
if (L.Browser.msTouch && type.indexOf('touch') === 0) {
this.removeMsTouchListener(obj, type, id);
if (L.Browser.pointer && type.indexOf('touch') === 0) {
this.removePointerListener(obj, type, id);
} else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
this.removeDoubleTapListener(obj, id);
@ -101,19 +101,29 @@ L.DomEvent = {
} else {
e.cancelBubble = true;
}
L.DomEvent._skipped(e);
return this;
},
disableScrollPropagation: function (el) {
var stop = L.DomEvent.stopPropagation;
return L.DomEvent
.on(el, 'mousewheel', stop)
.on(el, 'MozMousePixelScroll', stop);
},
disableClickPropagation: function (el) {
var stop = L.DomEvent.stopPropagation;
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
L.DomEvent.addListener(el, L.Draggable.START[i], stop);
L.DomEvent.on(el, L.Draggable.START[i], stop);
}
return L.DomEvent
.addListener(el, 'click', L.DomEvent._fakeStop)
.addListener(el, 'dblclick', stop);
.on(el, 'click', L.DomEvent._fakeStop)
.on(el, 'dblclick', stop);
},
preventDefault: function (e) {
@ -127,34 +137,31 @@ L.DomEvent = {
},
stop: function (e) {
return L.DomEvent.preventDefault(e).stopPropagation(e);
return L.DomEvent
.preventDefault(e)
.stopPropagation(e);
},
getMousePosition: function (e, container) {
var ie7 = L.Browser.ie7,
body = document.body,
var body = document.body,
docEl = document.documentElement,
x = e.pageX ? e.pageX - body.scrollLeft - docEl.scrollLeft: e.clientX,
//gecko makes scrollLeft more negative as you scroll in rtl, other browsers don't
//ref: https://code.google.com/p/closure-library/source/browse/closure/goog/style/bidi.js
x = L.DomUtil.documentIsLtr() ?
(e.pageX ? e.pageX - body.scrollLeft - docEl.scrollLeft : e.clientX) :
(L.Browser.gecko ? e.pageX - body.scrollLeft - docEl.scrollLeft :
e.pageX ? e.pageX - body.scrollLeft + docEl.scrollLeft : e.clientX),
y = e.pageY ? e.pageY - body.scrollTop - docEl.scrollTop: e.clientY,
pos = new L.Point(x, y),
rect = container.getBoundingClientRect(),
pos = new L.Point(x, y);
if (!container) {
return pos;
}
var rect = container.getBoundingClientRect(),
left = rect.left - container.clientLeft,
top = rect.top - container.clientTop;
// webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
// https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
left += container.scrollWidth - container.clientWidth;
// ie7 shows the scrollbar by default and provides clientWidth counting it, so we
// need to add it back in if it is visible; scrollbar is on the left as we are RTL
if (ie7 && L.DomUtil.getStyle(container, 'overflow-y') !== 'hidden' &&
L.DomUtil.getStyle(container, 'overflow') !== 'hidden') {
left += 17;
}
}
return pos._subtract(new L.Point(left, top));
},

View File

@ -30,8 +30,7 @@ L.DomUtil = {
el = element,
docBody = document.body,
docEl = document.documentElement,
pos,
ie7 = L.Browser.ie7;
pos;
do {
top += el.offsetTop || 0;
@ -78,19 +77,6 @@ L.DomUtil = {
top -= el.scrollTop || 0;
left -= el.scrollLeft || 0;
// webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
// https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
left += el.scrollWidth - el.clientWidth;
// ie7 shows the scrollbar by default and provides clientWidth counting it, so we
// need to add it back in if it is visible; scrollbar is on the left as we are RTL
if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' &&
L.DomUtil.getStyle(el, 'overflow') !== 'hidden') {
left += 17;
}
}
el = el.parentNode;
} while (el);
@ -118,18 +104,44 @@ L.DomUtil = {
},
hasClass: function (el, name) {
return (el.className.length > 0) &&
new RegExp('(^|\\s)' + name + '(\\s|$)').test(el.className);
if (el.classList !== undefined) {
return el.classList.contains(name);
}
var className = L.DomUtil._getClass(el);
return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
},
addClass: function (el, name) {
if (!L.DomUtil.hasClass(el, name)) {
el.className += (el.className ? ' ' : '') + name;
if (el.classList !== undefined) {
var classes = L.Util.splitWords(name);
for (var i = 0, len = classes.length; i < len; i++) {
el.classList.add(classes[i]);
}
} else if (!L.DomUtil.hasClass(el, name)) {
var className = L.DomUtil._getClass(el);
L.DomUtil._setClass(el, (className ? className + ' ' : '') + name);
}
},
removeClass: function (el, name) {
el.className = L.Util.trim((' ' + el.className + ' ').replace(' ' + name + ' ', ' '));
if (el.classList !== undefined) {
el.classList.remove(name);
} else {
L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
}
},
_setClass: function (el, name) {
if (el.className.baseVal === undefined) {
el.className = name;
} else {
// in case of SVG element
el.className.baseVal = name;
}
},
_getClass: function (el) {
return el.className.baseVal === undefined ? el.className : el.className.baseVal;
},
setOpacity: function (el, value) {
@ -238,12 +250,22 @@ L.DomUtil.TRANSITION_END =
L.DomUtil.TRANSITION + 'End' : 'transitionend';
(function () {
if ('onselectstart' in document) {
L.extend(L.DomUtil, {
disableTextSelection: function () {
L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
},
enableTextSelection: function () {
L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
}
});
} else {
var userSelectProperty = L.DomUtil.testProp(
['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
L.extend(L.DomUtil, {
disableTextSelection: function () {
L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
if (userSelectProperty) {
var style = document.documentElement.style;
this._userSelect = style[userSelectProperty];
@ -252,13 +274,15 @@ L.DomUtil.TRANSITION_END =
},
enableTextSelection: function () {
L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
if (userSelectProperty) {
document.documentElement.style[userSelectProperty] = this._userSelect;
delete this._userSelect;
}
},
}
});
}
L.extend(L.DomUtil, {
disableImageDrag: function () {
L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
},

View File

@ -10,11 +10,13 @@ L.Draggable = L.Class.extend({
END: {
mousedown: 'mouseup',
touchstart: 'touchend',
pointerdown: 'touchend',
MSPointerDown: 'touchend'
},
MOVE: {
mousedown: 'mousemove',
touchstart: 'touchmove',
pointerdown: 'touchmove',
MSPointerDown: 'touchmove'
}
},
@ -46,28 +48,21 @@ L.Draggable = L.Class.extend({
},
_onDown: function (e) {
this._moved = false;
if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
L.DomEvent
.stopPropagation(e);
L.DomEvent.stopPropagation(e);
if (L.Draggable._disabled) { return; }
L.DomUtil.disableImageDrag();
L.DomUtil.disableTextSelection();
var first = e.touches ? e.touches[0] : e,
el = first.target;
// if touching a link, highlight it
if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
L.DomUtil.addClass(el, 'leaflet-active');
}
this._moved = false;
if (this._moving) { return; }
var first = e.touches ? e.touches[0] : e;
this._startPoint = new L.Point(first.clientX, first.clientY);
this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
@ -77,7 +72,10 @@ L.Draggable = L.Class.extend({
},
_onMove: function (e) {
if (e.touches && e.touches.length > 1) { return; }
if (e.touches && e.touches.length > 1) {
this._moved = true;
return;
}
var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
newPoint = new L.Point(first.clientX, first.clientY),
@ -93,9 +91,8 @@ L.Draggable = L.Class.extend({
this._moved = true;
this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
if (!L.Browser.touch) {
L.DomUtil.addClass(document.body, 'leaflet-dragging');
}
L.DomUtil.addClass((e.target || e.srcElement), 'leaflet-drag-target');
}
this._newPos = this._startPos.add(offset);
@ -111,10 +108,9 @@ L.Draggable = L.Class.extend({
this.fire('drag');
},
_onUp: function () {
if (!L.Browser.touch) {
_onUp: function (e) {
L.DomUtil.removeClass(document.body, 'leaflet-dragging');
}
L.DomUtil.removeClass((e.target || e.srcElement), 'leaflet-drag-target');
for (var i in L.Draggable.MOVE) {
L.DomEvent
@ -129,7 +125,9 @@ L.Draggable = L.Class.extend({
// ensure drag is not fired after dragend
L.Util.cancelAnimFrame(this._animRequest);
this.fire('dragend');
this.fire('dragend', {
distance: this._newPos.distanceTo(this._startPos)
});
}
this._moving = false;

View File

@ -2,16 +2,20 @@
* L.LatLng represents a geographical point with latitude and longitude coordinates.
*/
L.LatLng = function (rawLat, rawLng) { // (Number, Number)
var lat = parseFloat(rawLat),
lng = parseFloat(rawLng);
L.LatLng = function (lat, lng, alt) { // (Number, Number, Number)
lat = parseFloat(lat);
lng = parseFloat(lng);
if (isNaN(lat) || isNaN(lng)) {
throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
}
this.lat = lat;
this.lng = lng;
if (alt !== undefined) {
this.alt = parseFloat(alt);
}
};
L.extend(L.LatLng, {
@ -75,7 +79,11 @@ L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Numbe
return a;
}
if (L.Util.isArray(a)) {
return new L.LatLng(a[0], a[1]);
if (typeof a[0] === 'number' || typeof a[0] === 'string') {
return new L.LatLng(a[0], a[1], a[2]);
} else {
return null;
}
}
if (a === undefined || a === null) {
return a;
@ -83,6 +91,9 @@ L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Numbe
if (typeof a === 'object' && 'lat' in a) {
return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);
}
if (b === undefined) {
return null;
}
return new L.LatLng(a, b);
};

View File

@ -17,8 +17,9 @@ L.LatLngBounds.prototype = {
extend: function (obj) { // (LatLng) or (LatLngBounds)
if (!obj) { return this; }
if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
obj = L.latLng(obj);
var latLng = L.latLng(obj);
if (latLng !== null) {
obj = latLng;
} else {
obj = L.latLngBounds(obj);
}

View File

@ -7,8 +7,8 @@ L.CRS.EPSG3395 = L.extend({}, L.CRS, {
transformation: (function () {
var m = L.Projection.Mercator,
r = m.R_MAJOR,
r2 = m.R_MINOR;
scale = 0.5 / (Math.PI * r);
return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
return new L.Transformation(scale, 0.5, -scale, 0.5);
}())
});

View File

@ -23,5 +23,10 @@ L.CRS = {
scale: function (zoom) {
return 256 * Math.pow(2, zoom);
},
getSize: function (zoom) {
var s = this.scale(zoom);
return L.point(s, s);
}
};

View File

@ -15,7 +15,9 @@ L.FeatureGroup = L.LayerGroup.extend({
return this;
}
if ('on' in layer) {
layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
}
L.LayerGroup.prototype.addLayer.call(this, layer);
@ -51,6 +53,15 @@ L.FeatureGroup = L.LayerGroup.extend({
return this.invoke('bindPopup', content, options);
},
openPopup: function (latlng) {
// open popup on the first layer
for (var id in this._layers) {
this._layers[id].openPopup(latlng);
break;
}
return this;
},
setStyle: function (style) {
return this.invoke('setStyle', style);
},
@ -74,11 +85,10 @@ L.FeatureGroup = L.LayerGroup.extend({
},
_propagateEvent: function (e) {
if (!e.layer) {
e.layer = e.target;
}
e.target = this;
e = L.extend({}, e, {
layer: e.target,
target: this
});
this.fire(e.type, e);
}
});

Some files were not shown because too many files have changed in this diff Show More