I am attempting to generate a custom tooltip using Microsoft Chart Controls. Microsoft Chart Controls supports the ability to use keywords which helps automate the data you wish to display.
For example,
string toolTip = string.Format("<div> {0}: {1} {3} ({2}) </div>", seriesName, "#VALY", "#VALX", "<br>");
series.MapAreaAttributes = "onfocus=\"if(this.blur)this.blur();\" onmouseover=\"DisplayTooltip('" + JavaScriptStringLiteral(toolTip) + "');\" onmouseout=\"DisplayTooltip('');\"";
In the above code, "#VALY" and "#VALX" are keywords. At run time, these keywords are replaced with actual values. In my scenario, #VALY is a double and #VALX is a date-time.
Observe:
Now, this works perfectly fine if I use the data series' tooltip property. Unfortunately, Firefox and Opera do not (easily) support multi-line tooltips. I am attempting to illicit this feature out of them by using Custom Tooltips.
As such, I have the onmouseover and onmouseout code -- this is JavaScript code which is responsible for the tooltip.
The problem is that when #VALX is evaluated it contains illegal JavaScript characters. This causes the error message "Uncaught SyntaxError: Unexpected token ILLEGAL"
Note that I have wrapped the tooltip with the method JavaScriptStringLiteral. Here is the utility function:
private static readonly Regex scriptTagRegex = new Regex("script", RegexOptions.IgnoreCase | RegexOptions.Multiline);
/// <summary>
/// Processes the provided string, creating a quoted JavaScript string literal.
/// </summary>
/// <param name="str">The string to process</param>
/// <returns>A string containing a quoted JavaScript string literal</returns>
public static string JavaScriptStringLiteral(string str)
{
var sb = new StringBuilder();
sb.Append("\"");
foreach (char c in str)
{
switch (c)
{
case '\"':
sb.Append("\\\"");
break;
case '\\':
sb.Append("\\\\");
break;
case '\b':
sb.Append("\\b");
break;
case '\f':
sb.Append("\\f");
break;
case '\n':
sb.Append("\\n");
break;
case '\r':
sb.Append("\\r");
break;
case '\t':
sb.Append("\\t");
break;
default:
int i = (int)c;
if (i < 32 || i > 127)
{
sb.AppendFormat("\\u{0:X04}", i);
}
else
{
sb.Append(c);
}
break;
}
}
sb.Append("\"");
// If a JavaScript tag contains "</script>", then it terminates a
// script block. Start by replacing each 's'/'S' with an escape
// sequence so it doesn't trigger this.
return scriptTagRegex.Replace(
sb.ToString(),
m => (m.Value[0] == 's' ? "\\u0073" : "\\u0053") + m.Value.Substring(1));
}
If #VALX was not run-time evaluated, I believe this utility method would solve my issue. But, as such, the utility function evalutes "#VALX" as a string literal. Then, after filtering no illegal characters, #VALX is evaluated and places illegal characters into my JavaScript.
Is there a way of preventing this issue? Something equivalent to the "@" character for paths in C#?
I figured out the solution and it is dumb.
foreach (HistoricalDataValue value in data)
{
series.Points.AddXY(string.Format("{0:d}{1}{0:T}", value.TimeStamp, "\\n"), value.AttributeValue);
}
Note that this says "\\n". This used to say Environment.NewLine, but this does not work. In addition, if you use Environment.NewLine -or- "\n" you can't edit "#VALX" later because of run time evaluation. Thus, you have to use an escaped out newline when adding the X value, so that when #VALX gets it it is already formatted properly.
When you call the
JavaScriptStringLiteral
your code already opened a single-quote string, while that function returns a string already wrapped in double quotes.You should strip first and last character from what
JavaScriptStringLiteral
returns.