从 table 抓取地址并使用 Google 地图 API 在地图上显示标记,但标记的悬停标题或信息窗口 (onclick) 不起作用

Grabs addresses from table and displays markers on map using Google Maps API but marker's hover title or infoWindow (onclick) doesn't work

此脚本可以用作独立的 javascript 或 greasemonkey 脚本。我要修复的是悬停标题和 on-click 的 info-window(它应该显示地址)。 here is a jsFiddle

// ==UserScript==
// @name        mapMarkers
// @namespace   mapMarkers
// @include     https://www.example.com/*
// @description map markers of addresses in table
// @version     1
// @grant       none
// ==/UserScript==
// find the table and loop through each rows to get the 11th, 12th, 13th cell's content (street address, city and zip respectively
// convert to lat/lon and show markers on map


if (document.getElementById('main_report') !== null) {

    API_js_callback = 'https://maps.googleapis.com/maps/api/js?v=3.exp&callback=initialize';
    var script = document.createElement('script');
    script.src = API_js_callback;
    var head = document.getElementsByTagName("head")[0];
    (head || document.body).appendChild(script);

    var Table_1 = document.getElementById('main_report');


    var DIVmap = document.createElement('div');
        DIVmap.id = 'DIVmap';
        DIVmap.style.border = '2px coral solid';
        DIVmap.style.cursor = 'pointer';
        DIVmap.style.display = '';
        DIVmap.style.height = '35%';
        DIVmap.style.margin = '1';
        DIVmap.style.position = 'fixed';
        DIVmap.style.padding = '1';
        DIVmap.style.right = '1%';
        DIVmap.style.bottom = '1%';
        DIVmap.style.width = '35%';
        DIVmap.style.zIndex = '99';

    var DIVinternal = document.createElement('div');
        DIVinternal.id = 'DIVinternal';
        DIVinternal.style.height = '100%';
        DIVinternal.style.width = '100%';
        DIVinternal.style.zIndex = '999';

        document.body.appendChild(DIVmap);
        DIVmap.appendChild(DIVinternal);

    //Adds a button which allows the user to re-run calcRoute
    var reloadMapButton = document.createElement("button");
    reloadMapButton.setAttribute("type", "button");
    reloadMapButton.setAttribute("href", "#");
    reloadMapButton.textContent="Reload map";
    reloadMapButton.id="calcRoute";
    reloadMapButton.style.zIndex = '1000';
    document.getElementById('Content_Title').appendChild(reloadMapButton);

    window.initialize = setTimeout(function () {
        var myWindow;
        try{
            myWindow = unsafeWindow;
        }catch(e){
            myWindow = window;
        }
        google = myWindow.google;
        directionsService = new google.maps.DirectionsService();
        directionsDisplay = new google.maps.DirectionsRenderer();

        var myLoc = new google.maps.LatLng(28.882193,-81.317936);
        var myOptions = {
            zoom: 13,
            mapTypeId: google.maps.MapTypeId.ROADMAP,
            center: myLoc
        }

        map = new google.maps.Map(document.getElementById("DIVinternal"), myOptions);
        var infoWindow1 = new google.maps.InfoWindow();
        //var bounds = new google.maps.LatLngBounds();
        var geocoder = new google.maps.Geocoder();

        function codeAddress(address,i) {
            setTimeout( function () {  // timer to avoid google geocode limits
                geocoder.geocode( { 'address': address}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    //map.setCenter(results[0].geometry.location);
                    var marker = new google.maps.Marker({
                        map: map,
                        position: results[0].geometry.location
                    });
                } else {
                    alert("Geocode was not successful for the following reason: " + status);
                }
                });
            }, i * 350);
        }

        function calcRoute() {
            if (Table_1.rows.length > 1) { // table has 1 empty row if no search results are returned and first row is always empty
                var newPoint;
                for (var i = 1, row; row = Table_1.rows[i]; i++) {
                    newPoint = codeAddress(row.cells[10].title +  ', ' + row.cells[11].innerHTML + ', ' + row.cells[12].innerHTML, i);
                // bounds.extend(newPoint);
                    marker = new google.maps.Marker({
                        position: newPoint,
                        map: map,
                        title: row.cells[10].title +  ', ' + row.cells[11].innerHTML + ', ' + row.cells[12].innerHTML
                    });
                google.maps.event.addListener(marker, 'click', function () {
                    infoWindow1.setContent(row.cells[10].title +  ', ' + row.cells[11].innerHTML + ', ' + row.cells[12].innerHTML);
                    infoWindow1.open(map, this);
                });
                    // Automatically center the map fitting all markers on the screen
                // map.fitBounds(bounds);
                }
            }
        }
        //reloadMapButton.addEventListener('click', calcRoute);
        document.getElementById("calcRoute").onclick = calcRoute;
        calcRoute();
    }, 1000);
} // if (document.getElementById('main_report') !== null)

sample data

更改 calcRoute() 函数以在每次循环迭代时调用另一个函数。这将在闭包中捕获该函数的参数和局部变量,因此它们将在嵌套在其中的异步事件处理程序中保持有效。

您有一个冗长的字符串表达式重复了三次。将其提取到一个公共变量中,这样您只需执行一次。

对局部变量使用 var。您在 marker.

上遗漏了一个

注意你的缩进。它在您的代码中不一致:标记 click 侦听器的缩进与其嵌套在其中的函数的其余部分不同。这使得很难看到嵌套在 what 中的内容。

不要使用这种循环样式:

for( var i = 1, row;  row = Table_1.rows[i];  i++ ) { ... }

我曾经提倡这种循环,但事实证明这不是一个好主意。在优化 JavaScript 环境中,很可能会强制数组取消优化。这可能会或可能不会发生在您的特定代码中,并且数组可能足够短以至于无关紧要,但现在这不是一个好习惯。你最好使用带有 i < xxx.length 测试的传统循环。

所以将这些技巧放在一起,我们为您准备了这个 calcRoute():

function calcRoute() {
    // table has 1 empty row if no search results are returned,
    // and first row is always empty
    if( Table_1.rows.length < 2 )
        return;

    for( var i = 1;  i < Table_1.rows.length;  i++ ) {
        addMarker( i );
    }
}

function addMarker( i ) {
    var row = Table_1.rows[i];
    var title = 
        row.cells[10].title +  ', ' +
        row.cells[11].innerHTML + ', ' +
        row.cells[12].innerHTML;
    var newPoint = codeAddress( title, i );
    var marker = new google.maps.Marker({
        position: newPoint,
        map: map,
        title: title
    });
    google.maps.event.addListener( marker, 'click', function () {
        infoWindow1.setContent( title );
        infoWindow1.open( map, this );
    });
}

这里还有其他问题。您正在调用此函数:

var newPoint = codeAddress( ... );

但是 codeAddress() 没有 return 值!它也不可能 return 任何有用的值,因为该函数发出异步地理编码器请求。如果您想使用地理编码器提供的位置,您需要从地理编码器回调中调用一个函数。我不明白你在做什么,无法在这里提出更具体的建议。

Thanks mostly to IanSan5653 on Reddit, I used mostly his code but had to change it a little. Here is working version on jsFiddle 和下面的代码:

// ==UserScript==
// @name        mapMarkers
// @namespace   mapMarkers
// @include     https://www.volusia.realforeclose.com/*
// @description map markers of addresses in table
// @version     1
// @grant       none
// ==/UserScript==

// find the table and loop through each rows to get the 11th, 12th, 13th cell's content (street address, city and zip respectively
// convert to lat/lon and show markers on map

if (document.getElementById('main_report') != null) {

    var script = document.createElement('script');
    script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&callback=initialize';

    (document.head || document.body).appendChild(script);

    var Table_1 = document.getElementById('main_report');

    var DIVmap = document.createElement('div');
        DIVmap.id = 'DIVmap';
        DIVmap.style.border = '2px coral solid';
        DIVmap.style.height = DIVmap.style.width = '35%';
        DIVmap.style.margin = DIVmap.style.padding = '1';
        DIVmap.style.position = 'fixed';
        DIVmap.style.right = DIVmap.style.bottom = '1%';
        DIVmap.style.zIndex = '999';

    var DIVinternal = document.createElement('div');
        DIVinternal.id = 'DIVinternal';
        DIVinternal.style.height = DIVinternal.style.width = '100%';

        document.body.appendChild(DIVmap);
        DIVmap.appendChild(DIVinternal);

    //Adds a button which allows the user to re-run calcRoute
    var reloadMapButton = document.createElement("button");
        reloadMapButton.setAttribute("type", "button");
        reloadMapButton.textContent="Reload map";
        reloadMapButton.id="calcRoute";
        reloadMapButton.style.zIndex = '1000';
        document.getElementById('Content_Title').appendChild(reloadMapButton);
        // reloadMapButton.onclick = calcRoute;

    window.initialize = function () {

        var google = window.google,
            directionsService = new google.maps.DirectionsService(),
            directionsDisplay = new google.maps.DirectionsRenderer(),
            myLoc = new google.maps.LatLng(28.882193,-81.317936),
            myOptions = {
                zoom: 13,
                mapTypeId: google.maps.MapTypeId.ROADMAP,
                center: myLoc
            },
            infoWindow1 = new google.maps.InfoWindow(),
            map = new google.maps.Map(document.getElementById("DIVinternal"), myOptions),
            geocoder = new google.maps.Geocoder();

        function calcRoute() {
            for (var i = 1, row; row = Table_1.rows[i]; i++) {
                console.log("processing row " + i);
                address = row.cells[10].title +  ', ' + row.cells[11].innerHTML + ', ' + row.cells[12].innerHTML,

                setTimeout( function(addr) {  // timer to avoid google geocode limits
                        geocoder.geocode( { 'address': address}, function(results, status) {
                            if (status == google.maps.GeocoderStatus.OK) {
                                var marker = new google.maps.Marker({
                                        map: map,
                                        position: results[0].geometry.location,
                                        title: addr
                                    });
                                google.maps.event.addListener(marker, 'click', function() {
                                    infoWindow1.setContent(addr);
                                    infoWindow1.open(map,this);
                                });
                            } else {
                                console.error("Geocode was not successful for the following reason: " + status);
                            }
                        });
                }(address), i * 400);
            }
        }
        document.getElementById("calcRoute").onclick = calcRoute;
        calcRoute();
    }
}

从 Reddit 复制的答案 post:

如果您一步一步仔细思考代码,您就会明白为什么 infowindow 没有将自己分配给标记。从calcRoute函数开始:

  1. 如果 table 不止一行
  2. 创建newPoint变量
  3. 对于 table 中的每一行,从第二行开始:
  4. 调用codeAddress函数并将其赋值给newPoint

让我插进来。这就是您的困惑开始的地方。 codeAddress 没有 return 语句(即使它有,它也是异步的并且无关紧要 [AJAX]),所以它实际上没有对 [=12 做任何事情=].整个标记是在 内部 函数 codeAddress 中创建的,使下面的行变得无用 - newPoint 中没有存储位置,因此没有创建标记,因此没有创建 infoWindow

相反,您必须在创建 marker 之后立即在地理编码回调中创建 infoWindow。您还必须使用点击事件将 infoWindow 分配给 marker。此外,如果您想要悬停标题,只需将其添加为名为 title.

的标记的 属性

最终代码在这里:http://pastebin.com/3rpWEnrp

您会注意到我对它进行了一些清理,以使其更具可读性和清晰度:

  • 可以使用document.head访问文档的头部——不需要通过标签名
  • 获取它
  • 有一个shorthand用于给两个变量赋相同的值:x = y = 3
  • 不需要内部 div 上的 z-index;外部 div 已经有了。 (把这想象成拿一块硬纸板盖住什么东西——硬纸板上的任何东西都会被抬起)
  • 不需要unsafeWindow;这实际上是不安全的
  • Shorthand一次定义多个变量是var x = 3, y = 4;
  • 你不需要 codeAddress 函数,因为你只调用它一次,所以它的全部内容都被放入 calcRoute 函数。
  • 使用控制台而不是 alert 会减少干扰,除非您真的想让用户知道,在这种情况下您需要更简单的消息
  • 为什么要检查 table 是否超过一行,而 if 语句不会触发,因为它从第 2 行开始?