1

I want to create a Google map custom control using the Javascript API.

The control is an 'add waypoint' button. The user is supposed to be able to drag from the control, causing a marker to appear at the mouse pointer, and drop this marker on to the map.

The intended behavior is nearly identical to the existing pegman feature.

The problem is that the Google map controls seem to interact with drag, even when the drag has been explicitly disabled, as seen below.

In the example code, The first time I click and drag, it works as intended. The second time I click and drag, I get a 'no drag' icon and the control spawns a ghost of itself. The third time I drag, it's working again. On the next drag, there's a 'no drag'. The sequence continues, with odd drags working, and even drags failing.

Here's what I believe to be the relevant section:

<div id="map"></div>
<script>
    var spawning = false;
    var inProgress = false;
    var waypointSpawner;
    var targetLat;
    var targetLng;

    function initMap()
    {
        map = new google.maps.Map(document.getElementById('map'), {
            center: {lat: 0.0000, lng: 0.0000},
            zoom: 2
        });

        // Create the DIV to hold the control and call the CenterControl()
        // constructor passing in this DIV.
        var centerControlDiv = document.createElement('div');
        var centerControl = new CenterControl(centerControlDiv, map);

        centerControlDiv.index = 1;
        map.controls[google.maps.ControlPosition.LEFT_TOP].push(centerControlDiv);
    }

    function CenterControl(controlDiv, map) {

        // Set CSS for the control border.
        var controlUI = document.createElement('div');
        controlUI.style.backgroundColor = '#fff';
        controlUI.style.border = '2px solid #fff';
        controlUI.style.borderRadius = '3px';
        controlUI.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)';
        controlUI.style.cursor = 'pointer';
        controlUI.style.marginBottom = '22px';
        controlUI.style.width = '40px';
        controlUI.style.height = '50px';
        controlUI.style.opacity = "0.7";
        controlUI.style.backgroundImage = "url('create_waypoint_icon.png')";
        controlUI.title = 'Add Waypoint';
        controlUI.draggable = "false";


    //controlDiv.draggable = "false";
    controlDiv.appendChild(controlUI);
    }

I was unable to get the above code to work in jsfiddle, but there are a couple of other examples that illustrate the problem.

http://jsfiddle.net/maunovaha/jptLfhc8/

Attempting to drag and drop the blue and green squares in the top left of the map results in some unintended behavior. Depending on your zoom level, and somewhat randomly, you will get a variety of different outcomes -

1 - Nothing happens. The pointer remains as a finger and moves normally.
2 - The finger pointer changes to a 'no drag' but otherwise moves normally.
3 - The finger pointer changes to a 'no drag' and a blue-and green semi-transparent 'to-paste' widget.
4 - The finger pointer changes to a 'no drag', and either the blue or green half of the widget (depending on your selection) goes with it.

This behavior is also somewhat evident in the Google control example code:
https://developers.google.com/maps/documentation/javascript/examples/control-custom

In this instance, when the text is highlighted, you get the 'no drag' icon. The parent control doesn't appear to be selectable, which is the desired goal.

I believe that the custom control is acquiring some kind of 'focus' status, and that clicking it again clears the focus. This behavior is evident in the green/blue example. Perhaps the control is being 'highlighted' somehow?

I attempted to implement the solutions given in How can I make a div unselectable? to no effect. I also tried a variety of ways to make the div undraggable in the code, but this did not appear to have any impact.

I also tried simulating mouse clicks on other parts of the program, and double clicking on the control, to change the focus/highlight status, but it appears the API doesn't actually carry out the clicks, only generates events from them. I couldn't find a way to cause a 'real' click. Manually clicking, then clicking and dragging worked.

I also tried making the control draggable but it still has the 'no drag' mouse pointer even when dragging it about.

Sky_Paladin
  • 103
  • 1
  • 9

1 Answers1

0

If I understand correctly what you are saying, you want to be able to drag a marker onto the map, from outside, and place a point on that position.

I have the following solution which does that.

First we need to build the map:

function buildMap() {
        var g = google.maps;
        var mapOptions = {        
        center: new g.LatLng(52.052491, 9.84375),
        zoom: 4,
        mapTypeId: g.MapTypeId.ROADMAP,
        streetViewControl: false,
        panControl: false
      };
      map = new g.Map(document.getElementById("map"), mapOptions);
      iw = new g.InfoWindow();
      g.event.addListener(map, "click", function() {
        if (iw) iw.close()
      });
      drag_area = document.getElementById("markers");
      var b = drag_area.getElementsByTagName("div");
      b[0].onmousedown = initDrag
      dummy = new DummyOView()
    }

We create a standard map and add features such as infoWindows to display the lat lng when the marker is added.

In addition we get where the marker is held and onMouseDown call a function (soon to come).

We use b[0] as we are getting the first set of <div> tags in the mark-up below, this is where the draggable icon is held:

<div id="markers">
  <div id="m1" class="drag" style="left:0; background-image: url('https://maps.gstatic.com/mapfiles/ms/icons/ltblue-dot.png')">
  </div>
</div>

The DummyOView is a map OverlayView which allows us to drag onto the map and grab the coords and position. More information on SO + credit to : More info

function DummyOView() {
  this.setMap(map);
  this.draw = function() {}
}

DummyOView.prototype = new google.maps.OverlayView();

The initDrag function is where most of the work is done, this is quite a lengthy function so I did comment the code, any questions on clarification just add a comment.

//function that allows us to drag the marker from the div to the map
function initDrag(e) {
  //allows us to drag the marker and keep record of the clientX client Y coordinates
  var j = function(e) {
    var a = {};
    if (!e) var e = window.event;
    a.x = e.clientX;
    a.y = e.clientY
    return a
  };
  //function called whenever the mouse moves. this will keep track of the marker as we move around
  var k = function(e) {
    //check to ensure that the object is of class drag - otherwise we could drag everything
    if (obj && obj.className == "drag") {
      var i = j(e),
        deltaX = i.x - l.x,
        deltaY = i.y - l.y;
      obj.style.left = (obj.x + deltaX) + "px";
      obj.style.top = (obj.y + deltaY) + "px";
      obj.onmouseup = function() {
        //get the information to check to see if the dragObj is on the map on mouse up
        var a = map.getDiv(),
          mLeft = a.offsetLeft,
          mTop = a.offsetTop,
          mWidth = a.offsetWidth,
          mHeight = a.offsetHeight;
        var b = drag_area.offsetLeft,
          areaTop = drag_area.offsetTop,
          oWidth = obj.offsetWidth,
          oHeight = obj.offsetHeight;
        //check to see if obj is in bounds of map div X and Y
        var x = obj.offsetLeft + b + oWidth / 2;
        var y = obj.offsetTop + areaTop + oHeight / 2;
        if (x > mLeft && x < (mLeft + mWidth) && y > mTop && y < (mTop + mHeight)) {
          var c = 1;
          var mapTemp = google.maps;
          var point = new mapTemp.Point(x - mLeft - c, y - mTop + (oHeight / 2));
          var proj = dummy.getProjection();
          var latlng = proj.fromContainerPixelToLatLng(point);
          var backImage = obj.style.backgroundImage.slice(4, -1).replace(/"/g, "");
          createDraggedMarker(latlng, backImage);
          fillMarker(backImage)
        }
      }
    }
    return false
  };

  //assign the event to a windows event
  obj = e.target ? e.target : e.srcElement;
  //if the object where the event took place is not called drag cancel the event
  if (obj.className != "drag") {
    if (e.cancelable) e.preventDefault();
    obj = null;
    return
  } else {
    z_index += 1;
    obj.style.zIndex = z_index.toString();
    obj.x = obj.offsetLeft;
    obj.y = obj.offsetTop;

    var l = j(e); //get the initial position of the marker relative to the client

    document.onmousemove = k;
    //if we lift the mouse up outside the map div set to null and leave where it is
    document.onmouseup = function() {
      document.onmousemove = null;
      document.onmouseup = null;
      if (obj) obj = null
    }
  }
  return false
}

Summarised this function is called whenever the mouse has been clicked and dragged. It registed the starting position and the positions of the map div and checks when the onmouseup to see if the current mouse position is over the map div. If so we create a marker on the map and re-add the marker icon to the div to allow redragging.

//when the marker is dragged onto the map
//we call this function to create a marker on the map
function createDraggedMarker(position, iconImage) {
  var mapLocal = google.maps;
  var icon = {
    url: iconImage,
    size: new mapLocal.Size(32, 32),
    anchor: new mapLocal.Point(15, 32)
  };
  var marker = new mapLocal.Marker({
    position: position,
    map: map,
    clickable: true,
    draggable: true,
    crossOnDrag: false,
    optimized: false,
    icon: icon,
    zIndex: highestOrder()
  });

  mapLocal.event.addListener(marker, "click", function() {
    actual = marker;
    var lat = actual.getPosition().lat();
    var lng = actual.getPosition().lng();
    var innerHtml = "<div class='infowindow'>" + lat.toFixed(6) + ", " + lng.toFixed(6) + "<\/div>";
    iw.setContent(innerHtml);
    iw.open(map, this)
  });
  mapLocal.event.addListener(marker, "dragstart", function() {
    if (actual == marker) iw.close();
    z_index += 1;
    marker.setZIndex(highestOrder())
  })
}

Re-adding the marker to the div

//Used to replace the marker
//once we have moved it from its div
function fillMarker(a) {
  var div = document.createElement("div");
  div.style.backgroundImage = "url(" + a + ")";
  var padding;
  if (obj.id == "m1") {
    padding = "0px"
  }
  div.style.left = padding;
  div.id = obj.id;
  div.className = "drag";
  div.onmousedown = initDrag;
  drag_area.replaceChild(div, obj);
  obj = null
}

JSfiddle you can use : https://jsfiddle.net/q3wjrpdw/7/

Any questions feel free to ask.