i have a leaflet slider with range, i want to have two elements holding the start and end date of each of the handles, like in here, any idea how to do this. this is how i tried to modify the slider javascript file
L.Control.SliderControl = L.Control.extend({
options: {
position: 'topright',
layers: null,
timeAttribute: 'time',
isEpoch: false, // whether the time attribute is seconds elapsed from epoch
startTimeIdx: 0, // where to start looking for a timestring
timeStrLength: 19, // the size of yyyy-mm-dd hh:mm:ss - if millis are present this will be larger
maxValue: -1,
minValue: 0,
showAllOnStart: false,
markers: null,
range: false,
follow: false,
alwaysShowDate : false,
rezoom: null
},
initialize: function (options) {
L.Util.setOptions(this, options);
this._layer = this.options.layer;
},
extractTimestamp: function(time, options) {
if (options.isEpoch) {
time = (new Date(parseInt(time))).toString(); // this is local time
}
return time.substr(options.startTimeIdx, options.startTimeIdx + options.timeStrLength);
},
setPosition: function (position) {
var map = this._map;
if (map) {
map.removeControl(this);
}
this.options.position = position;
if (map) {
map.addControl(this);
}
this.startSlider();
return this;
},
onAdd: function (map) {
this.options.map = map;
// Create a control sliderContainer with a jquery ui slider
var sliderContainer = L.DomUtil.create('div', 'slider', this._container);
$(sliderContainer).append('<center><div id="leaflet-slider" style="width:280px; box-shadow: rgba(0,0,10,1) 1px 2px 50px;background-color:2E2E1F" ><div class="ui-slider-handle"></div><div id="slider-timestamp" style="width:280px; margin-top:13px; background-color:#FFFFFF; text-align:center; border-radius:5px;"></div></div></center>');
//Prevent map panning/zooming while using the slider
$(sliderContainer).mousedown(function () {
map.dragging.disable();
});
$(document).mouseup(function () {
map.dragging.enable();
//Hide the slider timestamp if not range and option alwaysShowDate is set on false
if (options.range || !options.alwaysShowDate) {
$('#slider-timestamp').html('');
}
});
var options = this.options;
this.options.markers = [];
//If a layer has been provided: calculate the min and max values for the slider
if (this._layer) {
var index_temp = 0;
this._layer.eachLayer(function (layer) {
options.markers[index_temp] = layer;
++index_temp;
});
options.maxValue = index_temp - 1;
this.options = options;
} else {
console.log("Error: You have to specify a layer via new SliderControl({layer: your_layer});");
}
return sliderContainer;
},
onRemove: function (map) {
//Delete all markers which where added via the slider and remove the slider div
for (i = this.options.minValue; i < this.options.maxValue; i++) {
map.removeLayer(this.options.markers[i]);
}
$('#leaflet-slider').remove();
},
startSlider: function () {
_options = this.options;
_extractTimestamp = this.extractTimestamp
var index_start = _options.minValue;
if(_options.showAllOnStart){
index_start = _options.maxValue;
if(_options.range) _options.values = [_options.minValue,_options.maxValue];
else _options.value = _options.maxValue;
}
$("#leaflet-slider").slider({
range: _options.range,
value: _options.value,
values: _options.values,
min: _options.minValue,
max: _options.maxValue,
step: 1,
slide: function (e, ui) {
var map = _options.map;
var fg = L.featureGroup();
if(!!_options.markers[ui.value]) {
// If there is no time property, this line has to be removed (or exchanged with a different property)
if(_options.markers[ui.value].feature !== undefined) {
if(_options.markers[ui.value].feature.properties[_options.f ]){
if(_options.markers[ui.value]) $('#slider-timestamp').html(
_extractTimestamp(_options.markers[ui.value].feature.properties[_options.timeAttribute], _options));
}else {
console.error("Time property "+ _options.timeAttribute +" not found in data");
}
}else {
// set by leaflet Vector Layers
if(_options.markers [ui.value].options[_options.timeAttribute]){
if(_options.markers[ui.value]){ $('#slider-timestamp').html(
_extractTimestamp(_options.markers[ui.value].options[_options.timeAttribute], _options));
$('#start').html($("#slider-timestamp").html());
$('#end').html(
_extractTimestamp(_options.markers[ui.value].options[_options.timeAttribute], _options));}
}else {
console.error("Time property "+ _options.timeAttribute +" not found in data");
}
}
var i;
// clear markers
for (i = _options.minValue; i <= _options.maxValue; i++) {
if(_options.markers[i]) map.removeLayer(_options.markers[i]);
}
if(_options.range){
// jquery ui using range
for (i = ui.values[0]; i <= ui.values[1]; i++){
if(_options.markers[i]) {
map.addLayer(_options.markers[i]);
fg.addLayer(_options.markers[i]);
}
}
}else if(_options.follow){
for (i = ui.value - _options.follow + 1; i <= ui.value ; i++) {
if(_options.markers[i]) {
map.addLayer(_options.markers[i]);
fg.addLayer(_options.markers[i]);
}
}
}else{
for (i = _options.minValue; i <= ui.value ; i++) {
if(_options.markers[i]) {
map.addLayer(_options.markers[i]);
fg.addLayer(_options.markers[i]);
}
}
}
};
if(_options.rezoom) {
map.fitBounds(fg.getBounds(), {
maxZoom: _options.rezoom
});
}
}
});
if (!_options.range && _options.alwaysShowDate) {
$('#slider-timestamp').html(_extractTimeStamp(_options.markers[index_start].feature.properties[_options.timeAttribute], _options));
}
for (i = _options.minValue; i <= index_start; i++) {
_options.map.addLayer(_options.markers[i]);
}
}
});
L.control.sliderControl = function (options) {
$('span').show();
return new L.Control.SliderControl(options);
};
Digging through the code for that example page gives some idea of how they are achieving the effect you're looking for, so most of this answer is a rough copy of what they've done there.
The main thing you need to do is create some divs within the sliderContainer where you can put the labels. If you do it at the same time you create the slider div, it would look like this:
$(sliderContainer).append('<div id="slider-min"></div><div id="leaflet-slider" style="width:280px; inline-block; margin: 0 5px 0 5px;"></div><div id="slider-max"></div><div id="slider-current"><span class="start-time"></span>-<span class="end-time"></span></div>');
where slider-min
and slider-max
are the divs for labels at the beginning and the end of the slider, respectively, and slider-current
is the div for the range labels (start-time
and end-time
) that change as you move the slider.
Populating slider-min
and slider-max
can be done within the onAdd function, which would look like this:
$('#slider-min', sliderContainer).html(this.options.markers[this.options.minValue].feature.properties[this.options.timeAttribute]);
$('#slider-max', sliderContainer).html(this.options.markers[this.options.maxValue].feature.properties[this.options.timeAttribute]);
Initializing the start-time
and end-time
values can also be done within onAdd, but because they will need to be updated, it's useful to assign them some names first and populate them with a function:
this.$currentStartDiv = $('#slider-current .start-time', sliderContainer);
this.$currentEndDiv = $('#slider-current .end-time', sliderContainer);
this._updateCurrentDiv(0,0);
where _updateCurrentDiv
is defined at the main level of the LeafletSlider function (i.e., outside of onAdd):
_updateCurrentDiv: function (startIdx, endIdx) {
this.$currentStartDiv.html(this.options.markers[startIdx].feature.properties[this.options.timeAttribute]);
this.$currentEndDiv.html(this.options.markers[endIdx].feature.properties[this.options.timeAttribute]);
},
For changing labels dynamically, this function can be referred to within the slide function, though for scoping reasons that I don't fully understand, this
needs to be re-assigned to a different variable (i.e. self = this
) first. Within the slider function, _updateCurrentDiv
would be called like this:
if (_options.range) {
// jquery ui using range
for (i = ui.values[0]; i <= ui.values[1]; i++) {
if (_options.markers[i]) {
map.addLayer(_options.markers[i]);
fg.addLayer(_options.markers[i]);
}
}
self._updateCurrentDiv(ui.values[0], ui.values[1]);
}
That pretty much covers all the functionality. The aesthetics can be taken care of with css, the two main things being that the slider and the min and max divs (if you want all of those lined up neatly) should be styled as display: inline-block
, so that they don't get bumped to separate lines, and the slider-current
div (if you want the dynamic labels outside the white control box boundary) should be styled as position: absolute
and left: 420px
(or whatever displacement works for your purposes).
A working example of all this is cobbled together in this fiddle: