I'm looking for a way to add an extra button to L.control.zoom. Leaflet is being loaded from a CDN and I'm using vanilla Javascript (no preprocessors or anything).
I was hoping there'd be something like L.control.zoom.extend({}), but unfortunately that doesn't exist. Trying L.Control.extend({...L.control.zoom}) didn't work either.
For context, doing it the long way by copy-pasting the original code and adding the code for my custom button at line 42 would look like this:
let zoomControls = L.Control.extend({
// #section
// #aka Control.Zoom options
options: {
position: 'topleft',
// #option zoomInText: String = '+'
// The text set on the 'zoom in' button.
zoomInText: '+',
// #option zoomInTitle: String = 'Zoom in'
// The title set on the 'zoom in' button.
zoomInTitle: 'Zoom in',
// #option zoomOutText: String = '−'
// The text set on the 'zoom out' button.
zoomOutText: '−',
// #option zoomOutTitle: String = 'Zoom out'
// The title set on the 'zoom out' button.
zoomOutTitle: 'Zoom out'
},
onAdd: function (map) {
var zoomName = 'leaflet-control-zoom',
container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
options = this.options;
let locationLink = L.DomUtil.create('a', 'leaflet-bar-part leaflet-bar-part-single', container);
L.DomEvent.disableClickPropagation(locationLink);
locationLink.title = 'My location';
let locationIcon = L.DomUtil.create('span', 'fa-lg fas fa-map-marker-alt', locationLink);
L.DomEvent.on(locationLink, 'click', () => {
alert('BUTTON CLICKED');
});
this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
zoomName + '-in', container, this._zoomIn);
this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
zoomName + '-out', container, this._zoomOut);
this._updateDisabled();
map.on('zoomend zoomlevelschange', this._updateDisabled, this);
return container;
},
onRemove: function (map) {
map.off('zoomend zoomlevelschange', this._updateDisabled, this);
},
disable: function () {
this._disabled = true;
this._updateDisabled();
return this;
},
enable: function () {
this._disabled = false;
this._updateDisabled();
return this;
},
_zoomIn: function (e) {
if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
}
},
_zoomOut: function (e) {
if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
}
},
_createButton: function (html, title, className, container, fn) {
var link = L.DomUtil.create('a', className, container);
link.innerHTML = html;
link.href = '#';
link.title = title;
/*
* Will force screen readers like VoiceOver to read this as "Zoom in - button"
*/
link.setAttribute('role', 'button');
link.setAttribute('aria-label', title);
L.DomEvent.disableClickPropagation(link);
L.DomEvent.on(link, 'click', L.DomEvent.stop);
L.DomEvent.on(link, 'click', fn, this);
L.DomEvent.on(link, 'click', this._refocusOnMap, this);
return link;
},
_updateDisabled: function () {
var map = this._map,
className = 'leaflet-disabled';
L.DomUtil.removeClass(this._zoomInButton, className);
L.DomUtil.removeClass(this._zoomOutButton, className);
if (this._disabled || map._zoom === map.getMinZoom()) {
L.DomUtil.addClass(this._zoomOutButton, className);
}
if (this._disabled || map._zoom === map.getMaxZoom()) {
L.DomUtil.addClass(this._zoomInButton, className);
}
}
});
While not being explicit in the Leaflet class customization tutorial, there is a subtle distinction between factories, which are lowerCased and that you cannot extend, and Classes, which are PascalCased and on which you can use Leaflet extend mechanism:
var MyNewZoomControl = L.Control.Zoom.extend({
onAdd: function (map) {
// your new method content
}
}
That being said, if your new button does not really share functionality with the zoom buttons or is not "merged" with them, you could simply make a separate Control and insert it in the same corner position. There is also Leaflet EasyButton plugin which can help in this regard.
Related
When i delete the following "label" as a block using the backspace key, i would like to detect this event and do an ajax call, for example.
This part is deleted when i use backspace
Rendered
The problem is: there can be lots of labels (a tags) in textarea with different id's, i want to detect when any block is going to be deleted, save the id and remove.
Configuration
I'm using a custom configuration with a custom entity called annotation which represents the HTML above.
// Create CodeMirror (with lineWrapping on).
var codeMirror = CodeMirror(document.getElementById('firepad-container'), {lineWrapping: true});
// Create Firepad (with rich text toolbar and shortcuts enabled).
var firepad = Firepad.fromCodeMirror(firepadRef, codeMirror, {
richTextShortcuts: true,
richTextToolbar: true,
userId: userId,
userColor: userImage,
});
//// Create FirepadUserlist (with our userId, user name and avatar to display).
var firepadUserList = FirepadUserlist.fromDiv(firepadRef.child('users'),
document.getElementById('userlist'), userId, displayName, userImage);
firepad.registerEntity('annotation', {
render: function (info, entityHandler) {
var inputElement = document.createElement('a');
if(info.id) {
inputElement.setAttribute('id', info.id);
}
if(info.classList) {
inputElement.setAttribute('class', info.classList);
}
if(info.color || info.cursor) {
inputElement.setAttribute('style', 'color:' + info.color + '; cursor:' + info.cursor + ';');
}
inputElement.innerText = info.innerText;
inputElement.addEventListener('click', function(e){
editAnnotation(info.id, e);
entityHandler.remove();
});
return inputElement;
}.bind(this),
fromElement: function (element) {
var info = {};
if(element.hasAttribute('id')) {
info.id = element.id;
}
if(element.hasAttribute('class')) {
info.classList = element.classList;
}
info.color = element.style["color"];
info.cursor = element.style["cursor"];
info.innerText = element.innerText;
return info;
},
update: function (info, element, entityHandler) {
if(info.id) {
element.setAttribute('id', info.id);
}
if(info.classList) {
element.setAttribute('class', info.classList);
}
if(info.color || info.cursor) {
element.setAttribute('style', 'color:' + info.color + '; cursor:' + info.cursor + ';');
}
element.innerText = info.innerText;
},
export: function () {
var inputElement = document.createElement('a');
return inputElement;
}
});
If you want to add this new entity just use this:
firepad.insertEntity('annotation', { 'id': annotationId, 'color': color, 'cursor': 'pointer', 'innerText': 'whatever you want'});
I'm trying to build a basic color picker plugin (mainly as an exercise to learn about plugin development). I have a callback called "onSelected" that fires when you pick a color, but it breaks another feature of the plugin (the ability to toggle the visibility of the swatch list).
I am new to plugin development so I'm sure it's a simple mistake I'm making...
jsfiddle
Plugin:
(function ($) {
$.colorPicker2 = function (el, options) {
// the wrapper around the colors
var $pickerContainer = $("<div>");
// To avoid scope issues, use 'base' instead of 'this'
// to reference this class from internal events and functions.
var base = this;
// Access to jQuery and DOM versions of element
base.$el = $(el);
base.el = el;
// Add a reverse reference to the DOM object
base.$el.data("colorPicker2", base);
base.init = function () {
console.log("base.init");
base.options = $.extend({}, $.colorPicker2.defaultOptions, options);
// Put your initialization code here
// code goes here
$.each(base.options.colors, function (index, value) {
var $item = $('<div class="colorPicker-colorOption">').css({
"background-color": "#" + value
})
$item.click(function () {
console.log("item.click");
base.selectColor(value);
})
$pickerContainer.append($item);
});
//$pickerContainer.hide();
base.$el.append($pickerContainer);
if (base.options.toggleElement != null) {
base.options.toggleElement.click(function (e) {
base.togglePicker();
e.preventDefault();
});
}
};
base.togglePicker = function()
{
$pickerContainer.toggle();
}
base.selectColor = function (color) {
base.togglePicker();
if (typeof base.options.onSelected == 'function') {
base.options.onSelected.call(this, color);
}
}
// Sample Function, Uncomment to use
// base.functionName = function(paramaters){
//
// };
// Run initializer
base.init();
};
$.colorPicker2.defaultOptions = {
colors: [
'000000', '993300', '333300', '000080', '333399', '333333', '800000', 'FF6600',
'808000', '008000', '008080', '0000FF', '666699', '808080', 'FF0000', 'FF9900',
'99CC00', '339966', '33CCCC', '3366FF', '800080', '999999', 'FF00FF', 'FFCC00',
'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0', 'FF99CC', 'FFCC99',
'FFFF99', 'CCFFFF', '99CCFF', 'FFFFFF'
],
toggleElement: null,
onSelected: function (color) { }
};
$.fn.colorPicker2 = function (options) {
return this.each(function () {
(new $.colorPicker2(this, options));
});
};
})(jQuery);
How I hook into the onSelected event:
$(function () {
$('#primaryColorPicker').colorPicker2({
toggleElement: $('#selectPrimaryColor'),
onSelected: function (color) {
$('#selectedPrimaryColor').html("(#" + color + ")");
}
});
});
The HTML:
<a id="selectPrimaryColor">Toggle Color Picker</a>
<span id="selectedPrimaryColor" />
<div id="primaryColorPicker"></div>
You just have to learn how to write valid HTML
replace
<span id="selectedPrimaryColor" />
with
<span id="selectedPrimaryColor"></span>
FIDDLE
Using javascript, how can I alter the leaflet.draw "Trash" button to delete all polygons that have been drawn and automatically save. Below is the code I've implemented but it is a complete hack. It removes the active polygon, but after I delete an object once I begin to get errors in the console when I click the "Trash" icon like NotFoundError: Node was not found and TypeError: this._deletedLayers is null
map.on('draw:editstart', function (e) {
if(e.handler == 'remove' && typeof drawnItem != 'undefined' && drawnItem !== null){
if(window.console) window.console.log('Drawing deleted...');
if(typeof drawnItem != 'undefined' && drawnItem !== null){
drawnItems.removeLayer(drawnItem);
}
$('.leaflet-draw.leaflet-control .leaflet-draw-actions').hide();
$('.leaflet-popup-pane .leaflet-draw-tooltip').remove();
}
});
Solved my own problem with a custom control (thanks to stackexchange - https://gis.stackexchange.com/questions/60576/custom-leaflet-controls):
UPDATED! added #SpiderWan suggestions (thanks!) so no need for custom CSS. Also, the event was previously attached to the entire control bar. Instead attached to just the controlUI button itself.
Javascript:
L.Control.RemoveAll = L.Control.extend({
options: {
position: 'topleft',
},
onAdd: function (map) {
var controlDiv = L.DomUtil.create('div', 'leaflet-control leaflet-bar');
var controlUI = L.DomUtil.create('a', 'leaflet-draw-edit-remove', controlDiv);
controlUI.title = 'Remove all drawn items';
controlUI.setAttribute('href', '#');
L.DomEvent
.addListener(controlUI, 'click', L.DomEvent.stopPropagation)
.addListener(controlUI, 'click', L.DomEvent.preventDefault)
.addListener(controlUI, 'click', function () {
drawnItems.clearLayers();
if(window.console) window.console.log('Drawings deleted...');
});
return controlDiv;
}
});
removeAllControl = new L.Control.RemoveAll();
map.addControl(removeAllControl);
Like jduhls's answer but using Leaflet.draw CSS classes :
L.Control.RemoveAll = L.Control.extend(
{
options:
{
position: 'topleft',
},
onAdd: function (map) {
var controlDiv = L.DomUtil.create('div', 'leaflet-draw-toolbar leaflet-bar');
L.DomEvent
.addListener(controlDiv, 'click', L.DomEvent.stopPropagation)
.addListener(controlDiv, 'click', L.DomEvent.preventDefault)
.addListener(controlDiv, 'click', function () {
drawnItems.clearLayers();
});
var controlUI = L.DomUtil.create('a', 'leaflet-draw-edit-remove', controlDiv);
controlUI.title = 'Remove All Polygons';
controlUI.href = '#';
return controlDiv;
}
});
var removeAllControl = new L.Control.RemoveAll();
map.addControl(removeAllControl);
You can also overwrite the delete tool's enable method to simply delete all layers instead of opening the delete menu:
L.EditToolbar.Delete.include({
enable: function () {
this.options.featureGroup.clearLayers()
}
})
I have this notification system that works with the following jQuery / javascript and displays a notification when called.
What I am having some trouble doing and what I am trying to do is once a new notification is create to hide and remove / destroy any existing notifications.
I've tried something like this: $('.notification').not(this).hide().remove();, but that didn't work.
Here is the jQuery behind the notifications:
;(function($) {
$.notificationOptions = {
className: '',
click: function() {},
content: '',
duration: 5000,
fadeIn: 400,
fadeOut: 600,
limit: false,
queue: false,
slideUp: 200,
horizontal: 'right',
vertical: 'top',
afterShow: function(){},
afterClose: function(){}
};
var Notification = function(board, options) {
var that = this;
// build notification template
var htmlElement = $([
'<div class="notification ' + options.className + '" style="display:none">',
'<div class="close"></div>',
options.content,
'</div>'
].join(''));
// getter for template
this.getHtmlElement = function() {
return htmlElement;
};
// custom hide
this.hide = function() {
htmlElement.addClass('hiding');
htmlElement.animate({ opacity: .01 }, options.fadeOut, function() {
var queued = queue.shift();
if (queued) {
$.createNotification(queued);
}
});
htmlElement.slideUp(options.slideUp, function() {
$(this).remove();
options.afterClose();
});
};
// show in board
this.show = function() {
// append to board and show
htmlElement[options.vertical == 'top' ? 'appendTo' : 'prependTo'](board);
htmlElement.fadeIn(options.fadeIn, options.afterShow());
//$('.notification').css('marginLeft', -$('.notification').outerWidth()/2);
$('.notification-board.center').css('marginLeft', -($('.notification-board.center').width()/2));
$(window).on('resize', function(){
$('.notification-board.center').css('marginLeft', -($('.notification-board.center').width()/2));
});
};
// set custom click callback
htmlElement.on('click', function() {
options.click.apply(that);
});
// helper classes to avoid hide when hover
htmlElement.on('mouseenter', function() {
htmlElement.addClass('hover');
if (htmlElement.hasClass('hiding')) {
// recover
htmlElement.stop(true);
// reset slideUp, could not find a better way to achieve this
htmlElement.attr('style', 'opacity: ' + htmlElement.css('opacity'));
htmlElement.animate({ opacity: 1 }, options.fadeIn);
htmlElement.removeClass('hiding');
htmlElement.addClass('pending');
}
});
htmlElement.on('mouseleave', function() {
if (htmlElement.hasClass('pending')) {
// hide was pending
that.hide();
}
htmlElement.removeClass('hover');
});
// close button bind
htmlElement.children('.close').on('click', function() {
that.hide();
});
if (options.duration) {
// hide timer
setTimeout(function() {
if (htmlElement.hasClass('hover')) {
// hovering, do not hide now
htmlElement.addClass('pending');
} else {
that.hide();
}
}, options.duration);
}
return this;
};
var queue = [];
$.createNotification = function(options) {
options = $.extend({}, $.notificationOptions, options || {});
// get notification container (aka board)
var board = $('.notification-board.' + options.horizontal + '.' + options.vertical);
if (!board.length) {
board = $('<div class="notification-board ' + options.horizontal + ' ' + options.vertical + '" />');
board.appendTo('body');
}
if (options.limit && board.children('.notification:not(.hiding)').length >= options.limit) {
// limit reached
if (options.queue) {
queue.push(options);
}
return;
}
// create new notification and show
var notification = new Notification(board, options)
notification.show(board);
return notification;
};
})(jQuery);
and here is how the notifications are called / created:
$.createNotification({
horizontal:'center',
vertical:'top',
content:'No more cards at this time.',
duration:6000,
click:function(){
this.hide();
}
});
The code:
$('.notification').not(this).hide().remove();
will work just fine to remove all .notification DOM elements currently in the DOM except the current one IF this is the current notification DOM element. If that code isn't working, then it's likely because this isn't the desired notification DOM element that you want to keep. If this is an instance of your Notification class, then that's the wrong type of object. For that above code to work, this has to be the notification DOM object.
If you want to just remove all old notification DOM elements BEFORE you insert your new one, then you can just do this before your new one is in the DOM:
$('.notification').remove();
That will clear out the old ones before you insert your new one.
Since you don't have this line of code in your currently posted code, I can't tell where you were trying to use it so can't advise further on what might be wrong. Please describe further where in your code you were trying to use this.
Primefaces 3.5, Mojara 2.1.21, Omnifaces 1.5
I want to use Primefaces component color picker to select a color and update a color in a text box.
<h:outputText value="#{bean.color}" id="colorId"/>
<p:colorPicker value="#{bean.color}" />
So the question how can I update the value (I need it only client side) in h:outputText.
The JQuery color picker component has a nice inteface to do this. But how can I use it ? How can I registry onChange event in color picker of generated component ?
$('#colorSelector').ColorPicker({
color: '#0000ff',
onChange: function (hsb, hex, rgb) {
$('#colorSelector div').css('backgroundColor', '#' + hex);
}
});
I was looking on web for your problem, but also couldn't find any useful solution. So I dicided to use same approach as in this my answer.
Here is my suggestion:
Take JS code from primefaces and rewrite it:
<h:form prependId="false">
<h:outputText value="t#{testas.color}" id="colorId3"/>
<p:colorPicker id="cid" value="#{testas.color}" widgetVar="co" />
</h:form>
<script type="text/javascript">
PrimeFaces.widget.ColorPicker = PrimeFaces.widget.BaseWidget.extend({
init: function(cfg) {
this._super(cfg);
this.input = $(this.jqId + '_input');
this.cfg.popup = this.cfg.mode == 'popup';
this.jqEl = this.cfg.popup ? $(this.jqId + '_button') : $(this.jqId + '_inline');
this.cfg.flat = !this.cfg.popup;
this.cfg.livePreview = false;
this.cfg.nestedInDialog = this.jqEl.parents('.ui-dialog:first').length == 1;
this.bindCallbacks();
//ajax update check
if(this.cfg.popup) {
this.clearOrphanOverlay();
}
//create colorpicker
this.jqEl.ColorPicker(this.cfg);
//popup ui
if(this.cfg.popup) {
PrimeFaces.skinButton(this.jqEl);
this.overlay = $(PrimeFaces.escapeClientId(this.jqEl.data('colorpickerId')));
this.livePreview = $(this.jqId + '_livePreview');
}
},
bindCallbacks: function() {
var _self = this;
this.cfg.onChange = function(hsb, hex, rgb) {
_self.input.val(hex);
if(_self.cfg.popup) {
_self.livePreview.css('backgroundColor', '#' + hex);
}
};
this.cfg.onShow = function() {
if(_self.cfg.popup) {
_self.overlay.css('z-index', ++PrimeFaces.zindex);
}
var win = $(window),
positionOffset = _self.cfg.nestedInDialog ? '-' + win.scrollLeft() + ' -' + win.scrollTop() : null;
if(_self.cfg.nestedInDialog) {
_self.overlay.css('position', 'fixed');
}
//position the overlay relative to the button
_self.overlay.css({
left:'',
top:''
})
.position({
my: 'left top'
,at: 'left bottom'
,of: _self.jqEl,
offset : positionOffset
});
}
this.cfg.onHide = function(cp) {
_self.overlay.css('z-index', ++PrimeFaces.zindex);
$('#colorId3').html(_self.input.val()); // -> ADDED BY ME
$(cp).fadeOut('fast');
return false;
}
},
/**
* When a popup colorpicker is updated with ajax, a new overlay is appended to body and old overlay
* would be orphan. We need to remove the old overlay to prevent memory leaks.
*/
clearOrphanOverlay: function() {
var _self = this;
$(document.body).children('.ui-colorpicker-container').each(function(i, element) {
var overlay = $(element),
options = overlay.data('colorpicker');
if(options.id == _self.id) {
overlay.remove();
return false; //break;
}
});
}
});
</script>
I added this part: $('#colorId3').html(_self.input.val());
I hope someone, who knows JQuery (I am not), will can write compact script to this function. But this worked for me.
Give me opinions on this please ;) I am new here too.