If you've ever tried using the "mouseover" tipStyle in <cfchart /> with an image, you may have noticed there are several issues with the JavaScript that the WebCharts3D engine generates:
- The position of the tooltips isn't always accurate--if you're got parent elements that are absolutely position from a relative element, the position is drastically mis-calculated
- If your chart happens to be on the right edge of the screen, the tooltips will push off to the right of the screen (both clipping content and creating horizontal scrollbars.)
Here we can see an example of the tooltip running off the screen:
You can see the scrollbar and how the tip goes off the edge of the screen. Obviously, this isn't very usable. To fix the problem, we're going to have to override the native mouse handler functions. Unfortunately, this isn't particularly easy to fix because when the <cfchart /> is executed, it embeds an external call to a JavaScript file which looks something like:
<script language="javascript" src="/CFIDE/GraphData.cfm?graphCache=wc50&graphID=script.js"></script>
This makes overriding the functions a bit tricky. You can override the functions during the window.onload event, but what happens is you're loading your chart via AJAX? Plus, why load JS you have no intention on using?
The solution is to strip out the <script /> call. You can do this by wrapping your <cfchart /> call inside the <cfsavecontent /> tag. This will give you a string containing the HTML you'll need to embed in the page. You can then use a regular expression to strip the <script /> from the output before writing it to the output stream. What you end up with is some source code that looks like:
<cfsavecontent variable="sChartOutput">
<!---// put your cfchart code here //--->
<cfchart width="1" height="1" />
</cfsavecontent>
<!---// we need to output the chart, but remove the call for the external JS library //--->
<cfoutput>#reReplace(trim(sChartOutput), "<script[^>]+></script>", "", "all")#</cfoutput>
By using a regular expression we're able to completely remove the reference to external script that contains the mouse handlers. Now we've got to replace the functionality with code that actually works.
The WebCharts3D script file contains a number of functions, but there are only two functions that are actually used by the HTML generated: xx_set_visible() and xx_move_tag().
The xx_set_visible() function handles hiding/showing the tooltip and the xx_move_tag() function handles positioning the tooltip on the screen.
Since my site is already using jQuery, I'm going to leverage jQuery to handle the positioning of the tooltip. In the new code I'm going to:
- Move the tooltip element (a <table /> tag) as a direct descendant to the <body /> tag. I do this to simplify positioning of the element—since we don't have to worry about positioning of any of the parent elements.
- Set the tooltip element's visibility to "visible"—since it's hidden by default (once again we only need to do this the first time we view a tooltip.)
- Calculate the edges of the screen (with some padding) to make sure my tooltip always stays in the viewport. We don't want our tooltip to ever be cropped or overlap off the screen. If the tooltip would run off the bottom of the page, we'll put the tooltip at the top of the page. If the tooltip would run off the right edge, we'll make sure it can't move any further than the very right edge of the screen (with some padding.)
So what does our code look like? Here it is:
<!---// we need to replace the WebCharts3D JS scripts with ones that actually work //--->
<script type="text/javascript">
// on first show, we need to move to the body
var __xx_set_visible = {};
function xx_set_visible(imgId, tipId, e, show){
// get the table we're going to show
var $tip = $("#" + tipId);
if( !__xx_set_visible[tipId] ){
// move to the body and make visible
$tip.appendTo("body").css("visibility", "visible");
__xx_set_visible[tipId] = true;
}
$tip[show ? "show" : "hide"]();
// make sure we place the tip in the correct location
xx_move_tag(imgId, tipId, e);
}
function xx_move_tag(imgId, tipId, e){
// get the table we're going to show
var $tip = $("#" + tipId);
// get the scroll offsets
var scroll = {top: $(window).scrollTop(), left: $(window).scrollLeft()};
// if we're IE we need to create the e.pageX/pageY events
if( !e.pageY ){
e.pageY = e.clientY + scroll.top;
e.pageX = e.clientX + scroll.left;
}
var pos = {top: e.pageY + 20, left: e.pageX + 10}; // add padding for cursor
var tip = {width: $tip.outerWidth() + 10, height: $tip.outerHeight() + 10}; // add padding for edge
var screen = {right: scroll.left + $("body").width(), bottom: scroll.top + $(window).height()};
// if we're going to be off the screen, adjust the position
if( pos.left + tip.width > screen.right ){
// don't move past most right of screen
pos.left = screen.right - tip.width; // pos.left - tip.width || screen.right - tip.width - 10;
}
if( pos.top + tip.height > screen.bottom ){
// don't move past most right of screen
pos.top = pos.top - tip.height - 15; // since we're moving tip above we need adjust for the original padding we add
}
// position the
$tip.css(pos);
}
</script>
That's all there is to it! Now our tooltips will never run off the edge of the page! Here's the result of our new custom mouse handlers:
22 Comments
Comments for this entry have been disabled.