Jump To …

pointselector.js

wax = wax || {};
wax.mm = wax.mm || {};

Point Selector

This takes an object of options:

  • callback: a function called with an array of com.modestmaps.Location objects when the map is edited

It also exposes a public API function: addLocation, which adds a point to the map as if added by the user.

wax.mm.pointselector = function(map, tilejson, opts) {
    var mouseDownPoint = null,
        mouseUpPoint = null,
        tolerance = 5,
        overlayDiv,
        MM = com.modestmaps,
        pointselector = {},
        locations = [];

    var callback = (typeof opts === 'function') ?
        opts :
        opts.callback;

Create a com.modestmaps.Point from a screen event, like a click.

    function makePoint(e) {
        var coords = wax.util.eventoffset(e);
        var point = new MM.Point(coords.x, coords.y);

correct for scrolled document

and for the document

        var body = {
            x: parseFloat(MM.getStyle(document.documentElement, 'margin-left')),
            y: parseFloat(MM.getStyle(document.documentElement, 'margin-top'))
        };

        if (!isNaN(body.x)) point.x -= body.x;
        if (!isNaN(body.y)) point.y -= body.y;

TODO: use wax.util.offset correct for nested offsets in DOM

        for (var node = map.parent; node; node = node.offsetParent) {
            point.x -= node.offsetLeft;
            point.y -= node.offsetTop;
        }
        return point;
    }

Currently locations in this control contain circular references to elements. These can't be JSON encoded, so here's a utility to clean the data that's spit back.

    function cleanLocations(locations) {
        var o = [];
        for (var i = 0; i < locations.length; i++) {
            o.push(new MM.Location(locations[i].lat, locations[i].lon));
        }
        return o;
    }

Attach this control to a map by registering callbacks and adding the overlay

Redraw the points when the map is moved, so that they stay in the correct geographic locations.

    function drawPoints() {
        var offset = new MM.Point(0, 0);
        for (var i = 0; i < locations.length; i++) {
            var point = map.locationPoint(locations[i]);
            if (!locations[i].pointDiv) {
                locations[i].pointDiv = document.createElement('div');
                locations[i].pointDiv.className = 'wax-point-div';
                locations[i].pointDiv.style.position = 'absolute';
                locations[i].pointDiv.style.display = 'block';

TODO: avoid circular reference

                locations[i].pointDiv.location = locations[i];

Create this closure once per point

                MM.addEvent(locations[i].pointDiv, 'mouseup',
                    (function selectPointWrap(e) {
                    var l = locations[i];
                    return function(e) {
                        MM.removeEvent(map.parent, 'mouseup', mouseUp);
                        pointselector.deletePoint(l, e);
                    };
                })());
                map.parent.appendChild(locations[i].pointDiv);
            }
            locations[i].pointDiv.style.left = point.x + 'px';
            locations[i].pointDiv.style.top = point.y + 'px';
        }
    }

    function mouseDown(e) {
        mouseDownPoint = makePoint(e);
        MM.addEvent(map.parent, 'mouseup', mouseUp);
    }

Remove the awful circular reference from locations. TODO: This function should be made unnecessary by not having it.

    function mouseUp(e) {
        if (!mouseDownPoint) return;
        mouseUpPoint = makePoint(e);
        if (MM.Point.distance(mouseDownPoint, mouseUpPoint) < tolerance) {
            pointselector.addLocation(map.pointLocation(mouseDownPoint));
            callback(cleanLocations(locations));
        }
        mouseDownPoint = null;
    }

API for programmatically adding points to the map - this calls the callback for ever point added, so it can be symmetrical. Useful for initializing the map when it's a part of a form.

    pointselector.addLocation = function(location) {
        locations.push(location);
        drawPoints();
        callback(cleanLocations(locations));
    };

    pointselector.add = function(map) {
        MM.addEvent(map.parent, 'mousedown', mouseDown);
        map.addCallback('drawn', drawPoints());
        return this;
    };

    pointselector.deletePoint = function(location, e) {
        if (confirm('Delete this point?')) {
            location.pointDiv.parentNode.removeChild(location.pointDiv);
            locations.splice(wax.util.indexOf(locations, location), 1);
            callback(cleanLocations(locations));
        }
    };

    return pointselector.add(map);
};