multiple custom map webparts single page

kyle shapiro asked on April 22, 2015 21:40

I apologize in advance; This question is very long and probably has an equally long answer. I created a wonderful custom bing map web part from scratch and hooked it into Kentico. It works beautifully, I am very proud of it. My problems begin when I tried to place two instances of the map on a single page. I was not thinking about multiple map instances on a single page while developing this control, but it is a requirement for my page. The way it currently works, is that the .ascx contains <div ID="myMap"></div> and a part map = new Microsoft.Maps.Map(document.getElementById("myMap"), mapOptions); So I know that "myMap" div not being unique is a problem. The current behavior is that each subsequent map added will be placed in the first maps' creation location, "myMap"div, and all of the maps' pin click events will work, but the mouse enter and mouse out event handlers only work on the most recently created map. How can I have each instance of this custom control create a unique element with unique ID? Should I be using placeholders instead of Divs? I can't really tell how the built in Kentico webparts handle such a situation. I am currently using the onPageLoad method, and I have been told that's probably not right, and I should be using OnContentLoaded, but I don't really know how to do that properly either. Any examples, suggestions, advice, or resources would be greatly appreciated. If it would help, I can also post all of my code here.

Correct Answer

kyle shapiro answered on May 18, 2015 17:38

I found some time to revisit this item, and was able to find a solution. Some key components are as follows:

  1. Unique divisions (or whatever container you use to place the maps into) are required. There are multiple ways to do this, but Charles' method works well. "...in the map web part properties, I created a property for MapID. Then I grab the property in the code behind and assign it to the div during the control setup and any reference code..." - Charles Matvchuk<div ID="myMap<%=MapID%>"></div>map = new Microsoft.Maps.Map(document.getElementById("myMap"+<%=MapID%>), mapOptions);
  2. In my solution, infoboxes are created upon mouse-over of a pin and destroyed on mouse-out. Because the new infoboxes added by mouse-over are the last entity for that map, I use this code for the mouse-out event: map.entities.removeAt(map.entities.getLength()-1);
  3. This is the part that tripped me up most. If you have a script element with the ecn.dev.virtualearth.net src occur AFTER a map has already been created, then it will break the hover event listener of those previous maps. This means that there should be one and only one of these script elements. I achieve this with the following code:

    //opening script tag
    if (typeof testForScript == 'undefined') {
        document.write('\x3Cscript type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0">\x3C/script>');
        var testForScript = 0;
    }
    //closing script tag
    

    \x3C I am told is the hexadecimal representation of < and is the most reliable method of writing a script element to the page using document.write. I first attempted to create a document node, but it wasn't working. This works because the script looks for a variable that doesn't exist, when it is not found, creates the script element we need, and then sets that variable to something besides undefined so that no future script elements will be created.

The GetMap functions and the map variable name don't need to be unique. It should also be noted that upon creation of the pushpins, I the value of what I want the infobox to display on the pushpin object. pushpin.infoBoxValue = whatever; And then when I create the infobox, I use that to be the title property. title: e.target.infoBoxValue. InfoBoxValue of course is a property I made up, and could be anything. Title is a infobox option property.

I hope this helps somebody down the line. Thank you again Charles for your continued help. This is a great community, and I hope to eventually contribute as much or more than I have been helped myself.

0 votesVote for this answer Unmark Correct answer

Recent Answers


Charles Matvchuk answered on April 23, 2015 00:14 (last edited on April 23, 2015 00:18)

What I did, and I ran into the same problem, is in the map web part properties, I created a property for MapID. Then I grab the property in the code behind and assign it to the div during the control setup and any reference code, dynamic JavaScript as well. Think about dynamically creating your markup from code behind.

I then give every map that I drop on the page a different ID in the web part configuration.

Feel free to post your code if you like.

1 votesVote for this answer Mark as a Correct answer

kyle shapiro answered on April 23, 2015 21:46

Thank you Charles, I have added that MapID web part property. All my pins have unique ID's, but I don't know how to modify the event handler to use this new property. My mouseover event handler is pushpinMouseOver = Microsoft.Maps.Events.addHandler(pushpin, 'mouseover', openPopUp); It is inside of a function named PlacePin() that executes once per longitude value I pass in. The function openPopUp looks like this

function openPopUp(e)
{
    var infoboxOptions = { visible: true, title: e.target.ID, offset: new Microsoft.Maps.Point(13, 45), showPointer: false, width:200, height:30 };
    infobox = new Microsoft.Maps.Infobox(new Microsoft.Maps.Location(e.target.getLocation()), infoboxOptions);
    map.entities.push(infobox);
}

each pin has a unique ID that can be viewed with alerts on the click event pushpin.ID = i + "00" + mapID; I am still getting the behavior of all the click events work correctly, but the mouseover events are only working for the last map created on the page. I don't understand why the click event works for all maps, but the mouseover only works for the last created map.

0 votesVote for this answer Mark as a Correct answer

kyle shapiro answered on April 23, 2015 22:09

I think what I need is code to say "find the infobox with infobox.id equal to XYZ and change it's visible property to true." I set it to where with every pin created, an infobox with the same ID is also created. Am I on the right route?

0 votesVote for this answer Mark as a Correct answer

kyle shapiro answered on April 23, 2015 22:17

Oh wait, but if the infoboxes are being created correctly, they should be visible. right? They are not visible. I don't think they exist. I feel like the source of the problem must be within infobox creation. The pin mouseover function must be working because alert(pushpin.ID); works. Sorry for the multiple posts. Keep thinking of new things.

0 votesVote for this answer Mark as a Correct answer

kyle shapiro answered on April 23, 2015 23:03

About to head out for the day, I've of some new information to add. Once again, sorry for the excessive posts. I was able to make all the infoboxes appear, now I only need a function for my event mouseover to detect that specific infobox and set it's infobox option visible to false.

function setInfoboxVisible(e)
    {
        var infoboxID = e.target.ID;
        $("div:contains('" + infoboxID + "')").setOptions(visible:true);
    }  

I was trying something like this, but the jquery syntax is not right. It seems like a bad idea in general to search based on the content of a div, but the infobox.ID or Pin.ID are not part of the html. (I think).

0 votesVote for this answer Mark as a Correct answer

Charles Matvchuk answered on April 24, 2015 03:43

Searching based on the content of a div is fine I use it all the time in unique scenario's. What is the output of the page. What are you seeing for the infoboxID ?

Maybe .css('visibility', 'visible'); or .css('display', 'block');

I am just throwing stuff out there, real hard without all code.

0 votesVote for this answer Mark as a Correct answer

kyle shapiro answered on April 24, 2015 18:09

Yes, sorry, I should have just posted my code from the beginning.

%@ Control Language="C#" AutoEventWireup="true" CodeFile="----------.ascx.cs" Inherits="CMSWebParts_-------------" %>
<div ID="myMap<%=MapID%>" style="position:relative; display:inline-block" ></div>
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
<script type="text/javascript">
    function GetMap<%=MapID%>() {
        //global variables for the map and the infobox
        var map = null, infobox = null, myDirections = null, mapID = null;
        myDirections = '<%=UseDirections%>';
        mapID = "<%=MapID%>";

        // Set the map options
        var mapOptions = { credentials: "<%=BingMapKey%>",
            width: <%=MapWidth%>,
            height: <%=MapHeight%>,
            center: new Microsoft.Maps.Location(<%=DefaultLatitude%>,<%=DefaultLongitude%>),
            zoom: <%=ZoomLevel%>,
            enableSearchLogo:false,
            showMapTypeSelector:false,
            enableClickableLogo:false,
            showScalebar:false };  

        //create the map
        map = new Microsoft.Maps.Map(document.getElementById("myMap"+mapID), mapOptions);

        // Disable mouse wheel
        var noMouseWheel = function (e) { e.handled = true; return true;}
        Microsoft.Maps.Events.addHandler(map, 'mousewheel', noMouseWheel);

        // Retrieve the location of the map center 
        var center = map.getCenter();

        function pinClick(e)
        {
            alert(e.target.hoverText);
        }

        //create js arrays of the long / lat list strings coming from code behind file
        var longeArray = "<%= Longitude %>";
        longeArray = longeArray.split(",");
        var latArray = "<%= Latitude %>";
        latArray = latArray.split(",");
        var officeNameArray = "<%= OfficeName %>";
        officeNameArray = officeNameArray.split(",");
        var officeURLArray = "<%= OfficeURL %>";
        officeURLArray = officeURLArray.split(",");
        var officeDirectionsArray = "<%= OfficeDirections %>";
        officeDirectionsArray = officeDirectionsArray.split(",");

        //loop through the arrays and create pushpin / infobox for each
        var pushpin = [];
        var infobox = [];
        for(i=0;i<longeArray.length;i++)
        {
            var latitude = latArray[i], longitude = longeArray[i], docURL = officeURLArray[i], hoverText = officeNameArray[i], directionsURL = officeDirectionsArray[i];
            var pushPinOptions = { icon: "-------------------.png" }
            pushpin[i] = new Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(latitude, longitude), pushPinOptions);
            pushpin[i].docURL = docURL;
            pushpin[i].ID = i + "00" + mapID;
            pushpin[i].hoverText = hoverText;
            pushpin[i].directions = directionsURL;

            var infoboxOptions = { visible: true, title: pushpin[i].ID, offset: new Microsoft.Maps.Point(13, 45), showPointer: false, width:200, height:30 };
            infobox[i] = new Microsoft.Maps.Infobox(new Microsoft.Maps.Location(latitude,longitude), infoboxOptions);

            //attach pins and infoboxes to map
            map.entities.push(pushpin[i]);
            map.entities.push(infobox[i]);

            //event handlers
            pushpinClick = Microsoft.Maps.Events.addHandler(pushpin[i], 'click', pinClick);
            pushpinMouseOver = Microsoft.Maps.Events.addHandler(pushpin[i], 'mouseover', closeInfobox);
        }

        function closeInfobox(e)
        {
            //I can alert stuff about the pins, but that's about it.
            //alert(e.target.ID);
        }
    }
    GetMap<%=MapID%>();
</script>

This has changed a lot from the other day. I was just trying a bunch of different things. The original single map code only had a single infobox. The current code feels "spaghetti". I think i'm going to create a plain html outside of visual studio and try to recreate the 2 map scenario in it's most basic form without any dynamic content coming from other places. Again, any suggestions beyond simply finding a solution would be happily received. I am trying to get the task at hand done, but also want to learn from it.

0 votesVote for this answer Mark as a Correct answer

kyle shapiro answered on April 24, 2015 19:41

here is an example I have been working with that's easier to read and simplified. It alerts the map number and pin number upon pin mouseover. Drop it in an empty html file and enter a valid bingMap key for the key variable.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
   <head>
      <title></title>
      <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
      <script type="text/javascript">
      function randomIntFromInterval(min,max)
        {
            return Math.floor(Math.random()*(max-min+1)+min);
        }
      var key = "";
         function GetMap()
         {
            var map = null;
            var mapID = "1";
            map = new Microsoft.Maps.Map(document.getElementById("myMap"), {credentials:key}); 
            var noMouseWheel = function(e){e.handled = true;return true;}
            Microsoft.Maps.Events.addHandler(map, 'mousewheel', noMouseWheel); 
            var center = map.getCenter();
            function createPins(a){
                for(i=0;i<a;i++){
                    var num1 = randomIntFromInterval(0,60);
                    var num2 = randomIntFromInterval(0,60);
                    var pushpin = new Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(num1, num2), null);
                    map.entities.push(pushpin);
                    var infoboxOptions = { width:30, height:30, visible:false };
                    var infobox = new Microsoft.Maps.Infobox(new Microsoft.Maps.Location(num1, num2), infoboxOptions);
                    map.entities.push(infobox);
                    pushpin.ID = "The Map is " + mapID + ". The Pin is " + i;
                    pushpinMouseOver = Microsoft.Maps.Events.addHandler(pushpin, 'mouseover', pinMouseOver);
                }
            }
            createPins(5);

            function pinMouseOver(e)
            {
                alert(e.target.ID);
            }
         }

         function GetMap2()
         {
            var map = null;
            var mapID = "2";
            map = new Microsoft.Maps.Map(document.getElementById("myMap2"), {credentials:key}); 
            var noMouseWheel = function(e){e.handled = true;return true;}
            Microsoft.Maps.Events.addHandler(map, 'mousewheel', noMouseWheel); 
            var center = map.getCenter();
            function createPins(a){
                for(i=0;i<a;i++){
                    var num1 = randomIntFromInterval(0,60);
                    var num2 = randomIntFromInterval(0,60);
                    var pushpin = new Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(num1, num2), null);
                    map.entities.push(pushpin);
                    var infoboxOptions = { width:30, height:30, visible:false };
                    var infobox = new Microsoft.Maps.Infobox(new Microsoft.Maps.Location(num1, num2), infoboxOptions);
                    map.entities.push(infobox);
                    pushpin.ID = "The Map is " + mapID + ". The Pin is " + i;
                    pushpinMouseOver = Microsoft.Maps.Events.addHandler(pushpin, 'mouseover', pinMouseOver);
                }
            }
            createPins(5);

            function pinMouseOver(e)
            {
                alert(e.target.ID);
            }
         }


      </script>
   </head>
   <body onload="GetMap();GetMap2();">
      <div id='myMap' style="position:relative; width:500px; height:500px;"></div>
      <div id="myMap2" style="position:relative; width:500px; height:500px;"></div>
</html>
0 votesVote for this answer Mark as a Correct answer

kyle shapiro answered on April 24, 2015 21:23

I THINK I have found a solution. I am creating a new infobox every time the pin is moused over. Then, on the mouseout event I use map.entities.removeAt(map.entities.getLength()-1); which removes the last entity (the most recently created entity) for that map. This solution works in the little example. Now I will apply it where it will actually be used. If it all works, I will mark this answer as correct.

0 votesVote for this answer Mark as a Correct answer

Charles Matvchuk answered on April 27, 2015 02:33

Sounds good, sorry I was out of town. Share your answer if wish if it works.

0 votesVote for this answer Mark as a Correct answer

   Please, sign in to be able to submit a new answer.