Saturday, January 24, 2009
« GTAC and AFTER!! | Main | Microsoft Velocity CTP3 and Windows 7 In... »

Boy it has been over a year since I have written on this site. I probably wouldn't even be writing this if I found someone else writing about this problem and a good solution but since I didn't find anyone I figured I would put this out for your benefit and for mine in case I run into it again.

Tonight I stumbled across the ImageMap control. I had written a custom control that inherited from IPostBackEventHandler so that I could have all of my code for an image map generated from the server side. But for some reason I was not getting asynchronous postbacks working correctly and it would only do full postbacks. (If anyone knows why I would be interested to hear.) So I started looking around and that is when I found the ImageMap control.

For those of you not familiar with the ImageMap control it is just as it sounds. It gives you a server side control for image maps. Which was perfect and it handled the ansynchronous postbacks just as I was hoping. But then when I went to attach my mouseover and mouseout events that is where I ran into problems. It seems that the control does not allow you to add attributes to the HotSpots that you define. Also if you try to add onmouseover or onmouseout then the page throws an error informing you that those attributes are not supported for the server control. So I was faced with a dilemma. The control was working perfectly for me for my postbacks but not for my rollovers. So what would I do? Sacrifice the rollovers for asynchronous postbacks or keep my rollovers and live with full page postbacks. Well we didn't get into this field for the easy road so of course I started on a path to figure out a way that I could have my cake and eat it too. Enter javascript...

Now I do not believe that this is by any means the best solution. Probably the best route would be to use something lilke reflector and grab the source for the control and then modify it to include support for HotSpot attributes but I just didn't feel like taking the time to do that. So being that I am pretty familiar with javascript I thought I would see what I could do with that. After about an hour of playing around I had exactly what I was looking for.

Here is the step by step implementation that I took to get this working for me. As with most blog posts YMMV and I give no guarantees that this will work in all browsers. But I do know that it works in IE 7 and FF 3. But once you see the code you will see that there isn't anything happening here that really would prevent it from working in other browsers. I just didn't test them.

First I added an onload event to my image. Since I knew that they fire off an onload event I figured it would be a clean way to work only on the image that I was particularly dealing with. This helps because if you are creating a user control that would allow for more than one instance of this code block you could get into problems if you tried to use a hard coded id for your image. Using this event will allow it to be dynamic. So from your control in the page or server code add the following attribute to your image:

_rating_image.Attributes.Add("onload", "AttachMouseEventsToAreaTags(this);");

Here is what my ImageMap declaration looks like:

<asp:ImageMap ID="image_rating_enabled" runat="server" Width="78" Height="20" HotSpotMode="PostBack" OnClick="image_rating_enabled_Click">
   
<asp:RectangleHotSpot Left="0" Top="0" Right="16" Bottom="20" PostBackValue="1" />
   
<asp:RectangleHotSpot Left="17" Top="0" Right="31" Bottom="20" PostBackValue="2" />
   
<asp:RectangleHotSpot Left="32" Top="0" Right="46" Bottom="20" PostBackValue="3" />
   
<asp:RectangleHotSpot Left="47" Top="0" Right="61" Bottom="20" PostBackValue="4" />
   
<asp:RectangleHotSpot Left="62" Top="0" Right="76" Bottom="20" PostBackValue="5" />
</asp:ImageMap>

Next is the method that we call when the page loads. There are several things going on here. So let me start from the top down. First the declaration of the array variable. This is used to check whether or not you have already attached the events to the area tags. The reason for this is that every time you change the source of the image the onload event will fire and it will want to run this method again and reattach the events. The other option is to add a call at the bottom of this load event that removes the event from the image once it has been called once. It's 6 one way half a dozen the other so you choose. Next you see we get the image map contol reference by getting the usemap attribute of the image that was output to the page. Then we loop throug every defined area and attach the mouseover and mouseout events. AddEvent is just a custom method I either found or wrote (I can't remember) that will handle the checking of which browser so I can make the appropriate call. I am attaching generic methods that will do some checking on the event that is passed to determine which area tag I am working with.

var _controls_loaded = new Array();
function
AttachMouseEventsToAreaTags(img)
{
   
if (_controls_loaded[img.id] == null)
   {
      
var image_map = document.getElementById(img.getAttribute("usemap").substring(1));

      for (var i = 0; i < image_map.areas.length; i++)
      {
         AddEvent(image_map.areas[i],
"mouseover", MouseOverStar);
         AddEvent(image_map.areas[i],
"mouseout", MouseOutStar);
      
}

      _controls_loaded[img.id] =
true;
   
}
}

Here is the code for the generic events in case you are curious. First I get the area tag that raised the event. Then I pass the image id and then postbackvalue that I specified in my control declaration. Here is what my postback code looked like so the methods might make more sense:

                                 //this is the control id,                                                      this is the postback value from HotSpot declaration
javascript:__doPostBack('ctl00$ctl00$content_placeholder_main$image_rating_enabled','0')

function MouseOverStar(e)
{
   
var area_tag = (e.srcElement || e.target);
   ShowUserRatingImage(area_tag.parentNode.id.substring(8), parseInt(area_tag.href.substring(area_tag.href.length - 3), 10) + 1);
}

function MouseOutStar(e)
{
   
var area_tag = (e.srcElement || e.target);
   RestoreRatingImageToOriginal(area_tag.parentNode.id.substring(8));
}

That's it! Like I said there must be a more elegant way to implement a solution but I couldn't find it and I didn't feel like looking around for hours to find it when I knew I could do something with javascript. That's what I have always loved about javascript is the simplicity of the language and yet the power that it can have. I mean prior to ASP.NET and AJAX, DHTML was king. Grid controls, scrolling windows, async postbacks all of that was done wih JS and maybe some hidden iframes. But now with server controls getting to where they are today we really don't have to do as much of this low level manipulation. But if we need to we still can. This is why javascript is still one of my favoite languages to work with.

Hope this helps someone. If you have any questions drop me a note in the comments.

Time to go to bed! 

Saturday, January 24, 2009 9:46:29 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  |  Related posts:
Ken Dao Photography is live!!
Real Time Updates to Web Page Using Macromedia Flash