Add loading indicator.

This commit is contained in:
David Molineus
2015-01-08 11:01:22 +01:00
parent 5cd680562d
commit 91674e2f8d
15 changed files with 664 additions and 11 deletions

View File

@@ -16,6 +16,17 @@ L.Contao = L.Class.extend( {
*/
initialize: function() {
L.Icon.Default.imagePath = 'assets/leaflet/libs/leaflet/images';
// Bind triggered data:loading and data:loaded events to the map so that the loading indicator
// is aware of that. Dataloading and dataloaded are the default events which leaflet uses but leaflet.ajax not.
L.Map.addInitHook(function () {
var map = this;
this.on('layeradd', function(e) {
e.layer.on('data:loading', function() { map.fire('dataloading'); });
e.layer.on('data:loaded', function() { map.fire('dataload'); });
});
});
},
/**

View File

@@ -0,0 +1,19 @@
Contributing
============
Your pull requests are very welcome! Please follow these guidelines:
* Keep code compatible with the same browsers Leaflet aims to be [compatible
with](http://leafletjs.com/features.html).
* Use four spaces rather than tabs.
## Contributors
* [Eric Brelsford](https://github.com/ebrelsford)
* [Nick Iaconis](https://github.com/codefox421)
* [Matthias Althaus](https://github.com/althaus)
* [Yohan Boniface](https://github.com/yohanboniface)
* [kermit-the-frog](https://github.com/kermit-the-frog)
* [kcwu](https://github.com/kcwu)
* [Robbie Trencheny](https://github.com/robbiet480)

View File

@@ -0,0 +1,23 @@
.leaflet-control-loading:empty {
/* This is where your loading indicator would go */
background-image: url('loading.gif');
}
.leaflet-control-loading,
.leaflet-control-zoom a.leaflet-control-loading ,
.leaflet-control-zoomslider a.leaflet-control-loading {
display: none;
}
.leaflet-control-loading.is-loading,
.leaflet-control-zoom a.leaflet-control-loading.is-loading,
.leaflet-control-zoomslider a.leaflet-control-loading.is-loading {
display: block;
}
/* Necessary for display consistency in Leaflet >= 0.6 */
.leaflet-bar-part-bottom {
border-bottom: medium none;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}

View File

@@ -0,0 +1,262 @@
/*
* L.Control.Loading is a control that shows a loading indicator when tiles are
* loading or when map-related AJAX requests are taking place.
*/
(function () {
function defineLeafletLoading(L) {
L.Control.Loading = L.Control.extend({
options: {
position: 'topleft',
separate: false,
zoomControl: null,
spinjs: false,
spin: {
lines: 7,
length: 3,
width: 3,
radius: 5,
rotate: 13,
top: "83%"
}
},
initialize: function(options) {
L.setOptions(this, options);
this._dataLoaders = {};
// Try to set the zoom control this control is attached to from the
// options
if (this.options.zoomControl !== null) {
this.zoomControl = this.options.zoomControl;
}
},
onAdd: function(map) {
if (this.options.spinjs && (typeof Spinner !== 'function')) {
return console.error("Leaflet.loading cannot load because you didn't load spin.js (http://fgnass.github.io/spin.js/), even though you set it in options.");
}
this._addLayerListeners(map);
this._addMapListeners(map);
// Try to set the zoom control this control is attached to from the map
// the control is being added to
if (!this.options.separate && !this.zoomControl) {
if (map.zoomControl) {
this.zoomControl = map.zoomControl;
} else if (map.zoomsliderControl) {
this.zoomControl = map.zoomsliderControl;
}
}
// Create the loading indicator
var classes = 'leaflet-control-loading';
var container;
if (this.zoomControl && !this.options.separate) {
// If there is a zoom control, hook into the bottom of it
container = this.zoomControl._container;
// These classes are no longer used as of Leaflet 0.6
classes += ' leaflet-bar-part-bottom leaflet-bar-part last';
}
else {
// Otherwise, create a container for the indicator
container = L.DomUtil.create('div', 'leaflet-control-zoom leaflet-bar');
}
this._indicator = L.DomUtil.create('a', classes, container);
if (this.options.spinjs) {
this._spinner = new Spinner(this.options.spin).spin();
this._indicator.appendChild(this._spinner.el);
}
return container;
},
onRemove: function(map) {
this._removeLayerListeners(map);
this._removeMapListeners(map);
},
removeFrom: function (map) {
if (this.zoomControl && !this.options.separate) {
// Override Control.removeFrom() to avoid clobbering the entire
// _container, which is the same as zoomControl's
this._container.removeChild(this._indicator);
this._map = null;
this.onRemove(map);
return this;
}
else {
// If this control is separate from the zoomControl, call the
// parent method so we don't leave behind an empty container
return L.Control.prototype.removeFrom.call(this, map);
}
},
addLoader: function(id) {
this._dataLoaders[id] = true;
this.updateIndicator();
},
removeLoader: function(id) {
delete this._dataLoaders[id];
this.updateIndicator();
},
updateIndicator: function() {
if (this.isLoading()) {
this._showIndicator();
}
else {
this._hideIndicator();
}
},
isLoading: function() {
return this._countLoaders() > 0;
},
_countLoaders: function() {
var size = 0, key;
for (key in this._dataLoaders) {
if (this._dataLoaders.hasOwnProperty(key)) size++;
}
return size;
},
_showIndicator: function() {
// Show loading indicator
L.DomUtil.addClass(this._indicator, 'is-loading');
// If zoomControl exists, make the zoom-out button not last
if (!this.options.separate) {
if (this.zoomControl instanceof L.Control.Zoom) {
L.DomUtil.removeClass(this.zoomControl._zoomOutButton, 'leaflet-bar-part-bottom');
}
else if (typeof L.Control.Zoomslider === 'function' && this.zoomControl instanceof L.Control.Zoomslider) {
L.DomUtil.removeClass(this.zoomControl._ui.zoomOut, 'leaflet-bar-part-bottom');
}
}
},
_hideIndicator: function() {
// Hide loading indicator
L.DomUtil.removeClass(this._indicator, 'is-loading');
// If zoomControl exists, make the zoom-out button last
if (!this.options.separate) {
if (this.zoomControl instanceof L.Control.Zoom) {
L.DomUtil.addClass(this.zoomControl._zoomOutButton, 'leaflet-bar-part-bottom');
}
else if (typeof L.Control.Zoomslider === 'function' && this.zoomControl instanceof L.Control.Zoomslider) {
L.DomUtil.addClass(this.zoomControl._ui.zoomOut, 'leaflet-bar-part-bottom');
}
}
},
_handleLoading: function(e) {
this.addLoader(this.getEventId(e));
},
_handleLoad: function(e) {
this.removeLoader(this.getEventId(e));
},
getEventId: function(e) {
if (e.id) {
return e.id;
}
else if (e.layer) {
return e.layer._leaflet_id;
}
return e.target._leaflet_id;
},
_layerAdd: function(e) {
if (!e.layer || !e.layer.on) return
try {
e.layer.on({
loading: this._handleLoading,
load: this._handleLoad
}, this);
}
catch (exception) {
console.warn('L.Control.Loading: Tried and failed to add ' +
' event handlers to layer', e.layer);
console.warn('L.Control.Loading: Full details', exception);
}
},
_addLayerListeners: function(map) {
// Add listeners for begin and end of load to any layers already on the
// map
map.eachLayer(function(layer) {
if (!layer.on) return;
layer.on({
loading: this._handleLoading,
load: this._handleLoad
}, this);
}, this);
// When a layer is added to the map, add listeners for begin and end
// of load
map.on('layeradd', this._layerAdd, this);
},
_removeLayerListeners: function(map) {
// Remove listeners for begin and end of load from all layers
map.eachLayer(function(layer) {
if (!layer.off) return;
layer.off({
loading: this._handleLoading,
load: this._handleLoad
}, this);
}, this);
// Remove layeradd listener from map
map.off('layeradd', this._layerAdd, this);
},
_addMapListeners: function(map) {
// Add listeners to the map for (custom) dataloading and dataload
// events, eg, for AJAX calls that affect the map but will not be
// reflected in the above layer events.
map.on({
dataloading: this._handleLoading,
dataload: this._handleLoad,
layerremove: this._handleLoad
}, this);
},
_removeMapListeners: function(map) {
map.off({
dataloading: this._handleLoading,
dataload: this._handleLoad,
layerremove: this._handleLoad
}, this);
}
});
L.Map.addInitHook(function () {
if (this.options.loadingControl) {
this.loadingControl = new L.Control.Loading();
this.addControl(this.loadingControl);
}
});
L.Control.loading = function(options) {
return new L.Control.Loading(options);
};
}
if (typeof define === 'function' && define.amd) {
// Try to add leaflet.loading to Leaflet using AMD
define(['leaflet'], function (L) {
defineLeafletLoading(L);
});
}
else {
// Else use the global L
defineLeafletLoading(L);
}
})();

View File

@@ -0,0 +1,19 @@
Copyright (c) 2013 Eric Brelsford
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,83 @@
Leaflet.loading
===============
Leaflet.loading is a simple loading control for [Leaflet][]. An unobtrusive
loading indicator is added below the zoom control if one exists. The indicator
is visible when tiles are loading or when other data is loading, as indicated by
firing custom events on a map. The indicator can be an image, or a [spin.js][] spinner (image-less).
## Usage
Leaflet.loading is only tested on Leaflet version 0.6 or greater. It will almost
certainly not work with older versions of Leaflet.
Include `Control.Loading.js` and `Control.Loading.css`, then create a map with `loadingControl: true`
in its options.
By default, Leaflet.loading expects an image. `Control.Loading.css` contains a
start in this direction. The simplest case would be adding a 16 x 16 loading gif
in `.leaflet-control-loading`.
You can also set `spinjs: true` in the options, and load [spin.js][]
to use that instead of an image. A spin.js options object can be passed as the spin key
when initializing the control.
Whichever method you use, make sure you only use one.
Once the above is complete you will have a loading indicator that only appears
when tiles are loading.
If you want to show the loading indicator while other AJAX requests or something
else is occurring, simply fire the `dataloading` event on your map when you
begin loading and `dataload` when you are finished loading. The control tracks
the number of concurrent loaders, so it is your responsibility to ensure that
the `dataloading` and `dataload` are called symmetrically.
### Options
- **position**: (string) Where you want the control to show up on the map (standard
Leaflet control option). Optional, defaults to `topleft`
- **separate**: (boolean) Whether the control should be separate from the zoom
control or not, defaults to false.
- **zoomControl**: (L.Control.Zoom) The zoom control that the control should be
added to. This is only necessary when adding a loading control to a zoom
control that you added manually and do not want a separate loading control.
- **spinjs**: (boolean) Enable the use of [spin.js][]. Optional, defaults to `false`
- **spin**: (object) A [spin.js][] options object. Optional, defaults to
```
{
lines: 7,
length: 3,
width: 3,
radius: 5,
rotate: 13,
top: "83%"
}
```
## Demos
See Leaflet.loading in action (zoom or pan to make tiles load):
- Using the [simplest setup][simple], with the loading indicator attached to
the zoom control.
- With the loading indicator [separate][] from the zoom control.
- With the loading indicator and zoom control on the [top right][topright] of
the map.
- The [simplest example using spin.js](http://ebrelsford.github.io/Leaflet.loading/spinjs.html) instead of an image
## License
Leaflet.loading is free software, and may be redistributed under the MIT
License.
[Leaflet]: https://github.com/Leaflet/Leaflet
[spin.js]: https://github.com/fgnass/spin.js/
[simple]: http://ebrelsford.github.io/Leaflet.loading/simple.html
[separate]: http://ebrelsford.github.io/Leaflet.loading/separate.html
[topright]: http://ebrelsford.github.io/Leaflet.loading/topright.html

View File

@@ -0,0 +1,27 @@
{
"name": "leaflet.loading",
"version": "0.1.13",
"homepage": "https://github.com/ebrelsford/leaflet.loading",
"authors": [
"Eric Brelsford <ebrelsford@gmail.com>"
],
"description": "A loading-indicator control for Leaflet",
"main": "src/Control.Loading.js",
"keywords": [
"leaflet",
"map",
"gis",
"loading"
],
"dependencies": {
"leaflet": ">=0.6"
},
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2011-2014 Felix Gnass [fgnass at neteye dot de]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

2
assets/libs/spin-js/spin.min.js vendored Normal file
View File

@@ -0,0 +1,2 @@
//fgnass.github.com/spin.js#v2.0.1
!function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return l[e]||(m.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",m.cssRules.length),l[e]=1),e}function d(a,b){var c,d,e=a.style;for(b=b.charAt(0).toUpperCase()+b.slice(1),d=0;d<k.length;d++)if(c=k[d]+b,void 0!==e[c])return c;return void 0!==e[b]?b:void 0}function e(a,b){for(var c in b)a.style[d(a,c)||c]=b[c];return a}function f(a){for(var b=1;b<arguments.length;b++){var c=arguments[b];for(var d in c)void 0===a[d]&&(a[d]=c[d])}return a}function g(a,b){return"string"==typeof a?a:a[b%a.length]}function h(a){this.opts=f(a||{},h.defaults,n)}function i(){function c(b,c){return a("<"+b+' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">',c)}m.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.width,left:d.radius,top:-d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.length+d.width,k=2*j,l=2*-(d.width+d.length)+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d<e.childNodes.length&&(e=e.childNodes[b+d],e=e&&e.firstChild,e=e&&e.firstChild,e&&(e.opacity=c))}}var j,k=["webkit","Moz","ms","O"],l={},m=function(){var c=a("style",{type:"text/css"});return b(document.getElementsByTagName("head")[0],c),c.sheet||c.styleSheet}(),n={lines:12,length:7,width:5,radius:10,rotate:0,corners:1,color:"#000",direction:1,speed:1,trail:100,opacity:.25,fps:20,zIndex:2e9,className:"spinner",top:"50%",left:"50%",position:"absolute"};h.defaults={},f(h.prototype,{spin:function(b){this.stop();{var c=this,d=c.opts,f=c.el=e(a(0,{className:d.className}),{position:d.position,width:0,zIndex:d.zIndex});d.radius+d.length+d.width}if(e(f,{left:d.left,top:d.top}),b&&b.insertBefore(f,b.firstChild||null),f.setAttribute("role","progressbar"),c.lines(f,c.opts),!j){var g,h=0,i=(d.lines-1)*(1-d.direction)/2,k=d.fps,l=k/d.speed,m=(1-d.opacity)/(l*d.trail/100),n=l/d.lines;!function o(){h++;for(var a=0;a<d.lines;a++)g=Math.max(1-(h+(d.lines-a)*n)%l*m,d.opacity),c.opacity(f,a*d.direction+i,g,d);c.timeout=c.el&&setTimeout(o,~~(1e3/k))}()}return c},stop:function(){var a=this.el;return a&&(clearTimeout(this.timeout),a.parentNode&&a.parentNode.removeChild(a),this.el=void 0),this},lines:function(d,f){function h(b,c){return e(a(),{position:"absolute",width:f.length+f.width+"px",height:f.width+"px",background:b,boxShadow:c,transformOrigin:"left",transform:"rotate("+~~(360/f.lines*k+f.rotate)+"deg) translate("+f.radius+"px,0)",borderRadius:(f.corners*f.width>>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k<f.lines;k++)i=e(a(),{position:"absolute",top:1+~(f.width/2)+"px",transform:f.hwaccel?"translate3d(0,0,0)":"",opacity:f.opacity,animation:j&&c(f.opacity,f.trail,l+k*f.direction,f.lines)+" "+1/f.speed+"s linear infinite"}),f.shadow&&b(i,e(h("#000","0 0 4px #000"),{top:"2px"})),b(d,b(i,h(g(f.color,k),"0 0 1px rgba(0,0,0,.1)")));return d},opacity:function(a,b,c){b<a.childNodes.length&&(a.childNodes[b].style.opacity=c)}});var o=e(a("group"),{behavior:"url(#default#VML)"});return!d(o,"transform")&&o.adj?i():j=d(o,"animation"),h});