I would like to change some leaflet marker properties by clicking a link outside the map, but it doesn't work.
Check out this fiddle:
http://jsfiddle.net/Ls59qLLa/2/
js:
var app = angular.module('demoapp',['leaflet-directive']);
app.controller('DemoController', [ '$scope', 'leafletData', function($scope, leafletData) {
var local_icons = {
defaultIcon: {},
gmapicon: {
iconUrl: 'http://maps.google.com/mapfiles/kml/shapes/capital_big.png',
iconSize: [25, 25],
iconAnchor: [12, 12],
popupAnchor: [0, 0]
}
}
angular.extend($scope, {
markers: {
m1: {
lat: 41.85,
lng: -87.65,
clickable: false,
message: "I'm a static marker",
icon: local_icons.gmapicon
}
}
});
$scope.makeIconClickable = function(){
alert('function called');
var whichmarker = 'm1';
$scope.markers[whichmarker].clickable = true;
}
}]);
HTML:
<body ng-controller="DemoController">
<leaflet markers="markers"></leaflet>
<a href="#" ng-click=makeIconClickable()>Make Icon Clickable</a>
</body>
Leaflet directive has separate $watch for every marker (and path) on the map. When you change one of the marker property, this $watch is fired and it checks if some of properties has changed. Apparently it does not look for the clickable property, but it looks for message property. So in your function you could set message of the marker and everything binds properly:
$scope.makeIconClickable = function(){
var whichmarker = 'm1';
$scope.markers[whichmarker].clickable = true;
$scope.markers[whichmarker].message = "I'm a static marker";
}
If you need to set message on marker initialization phase, you can use some temporary property:
angular.extend($scope, {
markers: {
m1: {
lat: 41.85,
lng: -87.65,
icon: local_icons.gmapicon,
tmpmessage: 'I am a static marker'
}
}
});
And then:
$scope.makeIconClickable = function(){
var whichmarker = 'm1';
$scope.markers[whichmarker].clickable = true;
$scope.markers[whichmarker].message = $scope.markers[whichmarker].tmpmessage;
}
Working example: http://jsfiddle.net/3zgL8m4u/
I had the same problem. I sometimes need the map to fire an onClick Event even if the user clicked the Marker. The answer provided by "Agnieszka Ćwiecznik" is a quick and easy workaround if all you need is no Popup to be shown. It does not solve my problem, since it only removes the Popup, but no onClick Event on the map is fired.
To solve this i found this official Thread which seems to have been opened by the OP: https://github.com/tombatossals/angular-leaflet-directive/issues/676
Here a link to a modified JSFiddle is Provided: http://jsfiddle.net/nmccready/gbd1aydL/
The user who provided this fix included the following codeblock in the "angular-leaflet-directive.js" which can be found in the jsfiddle under "external resources":
var _destroy = function(markerModels, oldMarkerModels, lMarkers, map, layers){
// Delete markers from the array
var hasLogged = false,
modelIsDiff = false;
var doCheckOldModel = isDefined(oldMarkerModels);
for (var name in lMarkers) {
if(!hasLogged) {
$log.debug(errorHeader + "[markers] destroy: ");
hasLogged = true;
}
if(doCheckOldModel){
//might want to make the option (in watch options) to disable deep checking
//ie the options to only check !== (reference check) instead of angular.equals (slow)
modelIsDiff = !angular.equals(markerModels[name],oldMarkerModels[name]);
}
if (!isDefined(markerModels) ||
!Object.keys(markerModels).length ||
!isDefined(markerModels[name]) ||
!Object.keys(markerModels[name]).length ||
modelIsDiff) {
deleteMarker(lMarkers[name], map, layers);
delete lMarkers[name];
}
}
};
Note that this seems to be an older Version, as noted in the file itself: "angular-leaflet-directive 0.7.11 2015-04-08". When included in my AngularJS Project this allowed me to change the clickable property of the marker by just modifiyng the corresponding boolean in my "$scope.markers"-equivalent.
Related
I am having a small issue sending data from service to a controller with google API. can anyone have a look at code below and give me some advice?
injection is good and I don't see any errors. I tried few things.
1. normal binding using a service method(e.g. getCurrentPos()). it will return an object that stores the pos info
2. $rootScope.$broadcast
3. angular.copy()
//in the service--------
var position = new google.maps.LatLng(markers[i][1], markers[i][2]);
this.marker = new google.maps.Marker({
position: position,
map: map,
title: markers[i][0],
draggable: true,
icon: '../img/png/shopper1.png'
});
var personMarker = this.marker;
this.marker.addListener('drag', function() {
console.log('lat:'+personMarker.getPosition().lat()+' lng:'+personMarker.getPosition().lng());
currentPos.lat = personMarker.getPosition().lat();
currentPos.lng = personMarker.getPosition().lng();
// myPos = personMarker.getPosition();
myPos = [personMarker.getPosition().lat(), personMarker.getPosition().lng()];
angular.copy(myPos, scope.currentPos); //not working
console.log('scope.currentPos',scope.currentPos);
// $rootScope.$broadcast('evtUpdateMyPos', { //tried but not working.
// 'lat': personMarker.getPosition().lat(),
// 'lng': personMarker.getPosition().lng()
// });
console.log("mypos:", myPos);
});
//in the controller-------
$scope.currentPos = [];
//Listen on a broadcast event
// $scope.$on('evtUpdateMyPos', function (event, myPos){
// console.log('evtUpdateMyPos is fired.', myPos); //this logs here.
// // $scope.currentPos = angular.copy(myPos); //this dos not help
// // $scope.currentPos = myPos; //this does not hlep.
// })
In some cases when you're trying to update your model from an external library like in your case using google maps you should wrap the setter in something like this:
$timeout(function(){
myModel = 'Google.map.data'
});
or
$rootScope.$applyAsync(function(){ // this could also be $scope
myModel = 'Google.map.data'
});
myModel could be a property of your service or a $scope variable ($scope.myModel)
I'm using Angular Google Maps to get a map with a searchbox in it.
I'm trying to restrict the search to 'address' and to a country (France), so I'd like to use the 'Autocomplete' option of the search-box directive, as specified in the documentation.
The problem is that the autocomplete:true bit in my code does effectively restrict the search, but it prevents the events from firing (the map and the marker don't get updated). But there is no error in the console.
However, if I comment out or remove autocomplete:true, then the events fire (the map and the marker are refreshed), but then the search isn't restricted to France and addresses...
Here is my code :
var events = {
places_changed:function (searchBox) {
var place = searchBox.getPlaces();
if (!place || place == 'undefined' || place.length == 0) {
console.log('no place data :(');
return;
}
// refresh the map
$scope.map = {
center:{
latitude:place[0].geometry.location.lat(),
longitude:place[0].geometry.location.lng()
},
zoom:10
};
// refresh the marker
$scope.marker = {
id:0,
options:{ draggable:false },
coords:{
latitude:place[0].geometry.location.lat(),
longitude:place[0].geometry.location.lng()
}
};
}
};
$scope.searchbox = {
template:'searchbox.tpl.html',
events:events,
options:{
autocomplete:true,
types:['address'],
componentRestrictions:{
country:'fr'
}
}
};
And in the html file :
<div style="height: 500px;">
<script type="text/ng-template" id="searchbox.tpl.html">
<input type="text"
placeholder="{{'searchbox.google.maps' | text}}"
style="width:270px; height:30px;">
</script>
<ui-gmap-google-map center='map.center' zoom='map.zoom'>
<!--SEARCHBOX-->
<ui-gmap-search-box template="searchbox.template"
events="searchbox.events"
position="BOTTOM_RIGHT"
options="searchbox.options"></ui-gmap-search-box>
<!--MARKER-->
<ui-gmap-marker coords="marker.coords"
options="marker.options"
events="marker.events"
idkey="marker.id">
</ui-gmap-marker>
</ui-gmap-google-map>
</div>
What am I doing wrong ?
Is there a way to get the restrictions on the search AND the events fired ?
Thank you in advance !
OK, I figured it out, so I answer my own question, in case someone else gets stuck !
If autocomplete:true , then it's not places_changed and searchBox.getPlaces() , but it's place_changed and searchBox.getPlace() . And we don't get an array of places, but just one place.
Here is a working code :
var events = {
place_changed:function (searchBox) {
var place = searchBox.getPlace();
if (!place || place == 'undefined') {
console.log('no place data :(');
return;
}
// refresh the map
$scope.map = {
center:{
latitude:place.geometry.location.lat(),
longitude:place.geometry.location.lng()
},
zoom:10
};
// refresh the marker
$scope.marker = {
id:0,
options:{ draggable:false },
coords:{
latitude:place.geometry.location.lat(),
longitude:place.geometry.location.lng()
}
};
}
};
I have a directive which renders a HTML table where each td element has an id
What I want to accomplish is to use the mousedown.dragselect/mouseup.dragselect to determine which elements have been selected, and then highlight those selected elements. What I have so far is something like this:
var $ele = $(this);
scope.bindMultipleSelection = function() {
element.bind('mousedown.dragselect', function() {
$document.bind('mousemove.dragselect', scope.mousemove);
$document.bind('mouseup.dragselect', scope.mouseup);
});
};
scope.bindMultipleSelection();
scope.mousemove = function(e) {
scope.selectElement($(this));
};
scope.mouseup = function(e) {
};
scope.selectElement = function($ele) {
if (!$ele.hasClass('eng-selected-item'))
$ele.addClass('eng-selected-item'); //apply selection or de-selection to current element
};
How can I get every td element selected by mousedown.dragselect, and be able to get their ids and then highlight them?
I suspect using anything relating to dragging won't give you what you want. Dragging is actually used when moving elements about (e.g. dragging files in My Computer / Finder), when what you're after is multiple selection.
So there a number of things the directive needs:
Listen to mousedown, mouseenter and mouseup, events.
mousedown should listen on the cells of the table, and set a "dragging" mode.
mouseenter should listen on the cells as well, and if the directive is in dragging mode, select the "appropriate cells"
mouseup should disable dragging mode, and actually be on the whole body, in case the mouse is lifted up while the cursor is not over the table.
jQuery delegation is useful here, as it can nicely delegate the above events to the table, so the code is much more friendly to cells that are added after this directive is initialised. (I wouldn't include or use jQuery in an Angular project unless you have a clear reason like this).
Although you've not mentioned it, the "appropriate cells" I suspect all the cells "between" where the mouse was clicked, and the current cell, chosen in a rectangle, and not just the cells that have been entered while the mouse was held down. To find these, cellIndex and rowIndex can be used, together with filtering all the cells from the table.
All the listeners should be wrapped $scope.$apply to make sure Angular runs a digest cycle after they fire.
For the directive to communicate the ids of the selected elements to the surrounding scope, the directive can use bi-directional binding using the scope property, and the = symbol, as explained in the Angular docs
Putting all this together gives:
app.directive('dragSelect', function($window, $document) {
return {
scope: {
dragSelectIds: '='
},
controller: function($scope, $element) {
var cls = 'eng-selected-item';
var startCell = null;
var dragging = false;
function mouseUp(el) {
dragging = false;
}
function mouseDown(el) {
dragging = true;
setStartCell(el);
setEndCell(el);
}
function mouseEnter(el) {
if (!dragging) return;
setEndCell(el);
}
function setStartCell(el) {
startCell = el;
}
function setEndCell(el) {
$scope.dragSelectIds = [];
$element.find('td').removeClass(cls);
cellsBetween(startCell, el).each(function() {
var el = angular.element(this);
el.addClass(cls);
$scope.dragSelectIds.push(el.attr('id'));
});
}
function cellsBetween(start, end) {
var coordsStart = getCoords(start);
var coordsEnd = getCoords(end);
var topLeft = {
column: $window.Math.min(coordsStart.column, coordsEnd.column),
row: $window.Math.min(coordsStart.row, coordsEnd.row),
};
var bottomRight = {
column: $window.Math.max(coordsStart.column, coordsEnd.column),
row: $window.Math.max(coordsStart.row, coordsEnd.row),
};
return $element.find('td').filter(function() {
var el = angular.element(this);
var coords = getCoords(el);
return coords.column >= topLeft.column
&& coords.column <= bottomRight.column
&& coords.row >= topLeft.row
&& coords.row <= bottomRight.row;
});
}
function getCoords(cell) {
var row = cell.parents('row');
return {
column: cell[0].cellIndex,
row: cell.parent()[0].rowIndex
};
}
function wrap(fn) {
return function() {
var el = angular.element(this);
$scope.$apply(function() {
fn(el);
});
}
}
$element.delegate('td', 'mousedown', wrap(mouseDown));
$element.delegate('td', 'mouseenter', wrap(mouseEnter));
$document.delegate('body', 'mouseup', wrap(mouseUp));
}
}
});
Another thing that will make the experience a bit nicer, is to set the cursor to a pointer, and disable text selection
[drag-select] {
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
You can also see this in action in this working demo
I'm trying to implement Google maps in Angularjs using ui.Map (http://angular-ui.github.io/ui-map/)
I've followed the example pretty closely and the map loads, I can create a marker in the map center and the 'map-tilesloaded' event works fine.
My problem is adding a marker where the user clicks. The click function is receiving an empty $params parameter. In my controller:
$scope.newMapOptions = {
center : new google.maps.LatLng($scope.position.lat, $scope.position.lng),
zoom : 18,
mapTypeId : google.maps.MapTypeId.ROADMAP
};
$scope.getLocation = function() {
if (navigator.geolocation) {
return navigator.geolocation.getCurrentPosition(setPosition);
}
};
$scope.addMarker = function($event, $params) {
$scope.newTingMarker = new google.maps.Marker({
map : $scope.myNewTingMap,
position : $params[0].latLng
});
};
$scope.initMap = function() {
if (!$scope.mapLoaded)
$scope.getLocation();
$scope.mapLoaded = true;
};
function setPosition(pos) {
$scope.position = {
lat : pos.coords.latitude,
lng : pos.coords.longitude
};
$scope.meMarker = new google.maps.Marker({
map : $scope.myNewTingMap,
position : new google.maps.LatLng($scope.position.lat, $scope.position.lng)
});
$scope.myNewTingMap.setCenter(new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude));
$scope.$apply();
}
The html:
<div ui-map-info-window="myInfoWindow">
<b>Current location</b>
</div>
<div ui-map-marker="meMarker" ></div>
<div ui-map-marker="newTingMarker" ui-event="{'map-click': 'openMarkerInfo(newTingMarker)'}"></div>
<section id="newTingMap" >
<div ui-map="myNewTingMap" ui-options="newMapOptions" class="map-canvas"
ui-event="{'map-tilesloaded': 'initMap()', 'map-click': 'addMarker($event, $params)' }"></div>
</section>
$scope.addMarker should receive $event and $params where $params[0] has the latlng object. At the moment is it an empty array: []
I'm using angular 1.1.5, but I've tried using the same as the ui.Map example with no effect.
I should also note that this is in a view but putting it outside the view in the main controller makes no difference.
If I try to follow the code running from the ui-map directive I can see that the latlng object does start off in the event:
ui-map.js:
angular.forEach(eventsStr.split(' '), function (eventName) {
//Prefix all googlemap events with 'map-', so eg 'click'
//for the googlemap doesn't interfere with a normal 'click' event
google.maps.event.addListener(googleObject, eventName, function (event) {
element.triggerHandler('map-' + eventName, event);
//We create an $apply if it isn't happening. we need better support for this
//We don't want to use timeout because tons of these events fire at once,
//and we only need one $apply
if (!scope.$$phase){ scope.$apply();}
});
});
element.triggerHandler('map-' + eventName, event); ... has the latlng object in 'event' but is seems to get lost after that
Not sure what your issue is, I took your code and created a fiddle that works fine(something you should have done).
I did a console log when you click that logs the $params.
The most important thing to note is your code crashes at first because you reference $scope.position.lat before setting it. I updated it to default to RVA.
,
You do need to handle the case a little more gracefully.
function MapCtrl($scope, watchArray) {
var center;
if ($scope.position) {
center = new google.maps.LatLng($scope.position.lat, $scope.position.lng);
}
else {
center = new google.maps.LatLng(37.5410, 77.4329); //if null use rva
}
$scope.newMapOptions = {
center: center,
zoom: 18,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
...
}
Console.log:
[Ps]
v 0: Ps
> la: Q
> latLng: O
> pixel: Q
> __proto__: Ps
length: 1
> __proto__: Array[0]
I am working with Twitter Bootstrap and ran into something I could not fix when testing on iPad and iPhone. On mobile (at least those devices) you need to click to engage the tip or popover (as expected). The issue is that you can never close it once you do. I added a listener to close it if you click it again, but I find it hard to believe that the default behavior would not be to click to remove it. Is this a bug in Bootstrap popover and tooltip?? My code is below - it seems to work, but ONLY if you click the same item that created the tip or popover - not anywhere on the page (could not get that to work).
Code to fire:
$(function () {
//Remove the title bar (adjust the template)
$(".Example").popover({
offset: 10,
animate: false,
html: true,
placement: 'top',
template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><div class="popover-content"><p></p></div></div></div>'
//<h3 class="popover-title"></h3>
//Need to have this click check since the tooltip will not close on mobile
}).click(function(e) {
jQuery(document).one("click", function() {
$('.Example').popover('hide')
});
});
});
HTML:
<a href="javascript:void(0);" class="Example" rel="popover" data-content="This is the Data Content" data-original-title="This is the title (hidden in this example)">
Thanks in advance!
Dennis
I tried dozens of solutions posted to stackoverflow and other various corners of the web, and the following is the only one that worked for me!
Explanation
As noted here, you can a CSS-directive the element in order to make it touch-device-clickable. I can't tell you why that works or what's going on there, but that seems to be the case. So, I want to make the entire document aka body clickable on mobile devices, which will allow me to touch anywhere to dismiss the popover.
Popover JS
$(function () {
$('[data-toggle="popover"]').popover({ trigger: "hover"}})
});
Directions
1. Install Modernizr
I'm using rails, so I used the gem.
gem 'modernizr-rails'
2. Create a touch class with a css-directive
Add the following to your CSS:
.touch {
cursor: pointer
}
3. On touch devices only, add the touch class to the body
If you want other elements to be clickable, instead of the entire body, add the touch class to them.
if (Modernizr.touch) {
$( "body" ).addClass( "touch" );
}
That's it! Now, you can use your popover normally on desktop (even with hover-trigger) and it will be touch-dismissible on mobile.
I had the same problem with my IPad. But in browser it works fine. Solution for me was adding listeners for all possible element that i can hide tooltip:
$('*').bind('touchend', function(e){
if ($(e.target).attr('rel') !== 'tooltip' && ($('div.tooltip.in').length > 0)){
$('[rel=tooltip]').mouseleave();
e.stopPropagation();
} else {
$(e.target).mouseenter();
}
});
Yes, it's small overhead to send event for all tooltips, but you can't define which element tooltip is showing.
Main concept is that make popover manually on mobile device
$(document).ready(function() {
if ('ontouchstart' in window) {
$('[data-toggle="popover"]').popover({
'trigger': 'manual'
});
}
});
Refer following code snippet to get it works:
$('[data-toggle="popover"]').popover();
$('body').on('click', function (e) {
$('[data-toggle="popover"]').each(function () {
//the 'is' for buttons that trigger popups
//the 'has' for icons within a button that triggers a popup
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
$(this).popover('hide');
}
});
});
This is the easiest way of detecting clicks on the body and close all the tooltips on the page.
You can check the live example here
Solution on this jsfiddle,
test on iOS (iPad and iPhone), Android and Windows.
$(document).ready(function(){
var toolOptions;
var toolOptions2;
var isOS = /iPad|iPhone|iPod/.test(navigator.platform);
var isAndroid = /(android)/i.test(navigator.userAgent);
///////////////////////////////////////// if OS
if (isOS){
toolOptions = {
animation: false,
placement:"bottom",
container:"body"
};
$('.customtooltip').tooltip(toolOptions);
$('.customtooltip').css( 'cursor', 'pointer' );
$('body').on("touchstart", function(e){
$(".customtooltip").each(function () {
// hide any open tooltips when the anywhere else in the body is clicked
if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.tooltip').has(e.target).length === 0) {
$(this).tooltip('hide');
}////end if
});
});
///////////////////////////////////////// if Android
} else if(isAndroid){
toolOptions = {
animation: false,
placement:"bottom",
container:"body"
};
toolOptions2 = {
animation: false,
placement:"left",
container:"body"
};
$('.c_tool1').tooltip(toolOptions);
$('.c_tool2').tooltip(toolOptions);
$('.c_tool3').tooltip(toolOptions2);
///////////////////////////////////////// if another system
} else {
toolOptions = {
animation: true,
placement:"bottom",
container:"body"
};
$('.customtooltip').tooltip(toolOptions);
}//end if system
document.getElementById("demo").innerHTML = "Sys: "+navigator.platform+" - isOS: "+isOS+" - isAndroid: "+isAndroid;
});
<h6>
first tooltip
Second tooltip
third tooltip
</h6>
<p id="demo"></p>
Bootstap-tooltip v3.3.7
Actual: tooltip on hover doesn't work with touch devices in our project
Solution: Subscribe to tooltip's show event and call mouseenter
$body = $('body');
$body.tooltip({selector: '.js-tooltip'});
// fix for touch device.
if (Modernizr.touch) { // to detect you can use https://modernizr.com
var hideTooltip = function(e) {
tooltipClicked = !!$(e.target).closest('.tooltip').length;
if (tooltipClicked) { return; }
$('.js-tooltip').tooltip('hide');
}
var emulateClickOnTooltip = function(e) {
tooltipsVisible = !!$('.tooltip.in').length;
if (tooltipsVisible) { return; }
$(e.target).mouseenter();
}
var onTooltipShow = function(e) {
tooltipClicked = !!$(e.target).closest('.tooltip').length;
if (tooltipClicked) { return; }
$body.on('touchend', hideTooltip);
}
var onTooltipHide = function() {
$body.off('touchend', hideTooltip);
}
$body
.on('touchend', '.js-tooltip', emulateClickOnTooltip)
.on('show.bs.tooltip', onTooltipShow)
.on('hide.bs.tooltip', onTooltipHide);
}