2005.07.20 03:00 PM

FlexWiki Notes - JavaScript Issues, Part 2

In JavaScript Issues, Part 1, I mentioned that, among other problems, the JavaScript that FlexWiki uses to present a menu of multiple matching topic names doesn't work in FireFox. I then went on to describe how, before tackling this problem, we should separate the JavaScript from the FlexWiki engine so that subsequent JavaScript changes won't require recompilations of the entire engine. With that done, we can now move on to actually fixing the FireFox-busting JavaScript. Let's go.

At some point during topic rendering, the FlexWiki engine will find itself in the private LinkWikiNames method of the FlexWiki.Formatting.Formatter class. This method takes a string representing a semi-formatted line of text from the content of the topic being rendered (or some portion of a line representing a wiki link if called from ProcessWikiLinks), identifies the topic names within the string (i.e., PascalCased or bracketed values, as per the RegEx extractWikiLinks), and replaces them with one of the following three types of HTML anchors.

(By the way, remember how in my earlier post I chuckled at the suggestion that Formatter was 90% clean of HTML-specific logic? Well, chuckle, again.)

Topic Anchor #1
If the the topic doesn't already exist, the anchor is a simple HTML anchor element with an href attribute pointing at the non-existent topic. The only thing special about the anchor is its class, "create", which allows it to be styled in a way that makes it apparent the topic doesn't already exist. No JavaScript, no FireFox problems.

Topic Anchor #2
If the topic name has only one matching topic, the anchor is again just a simple HTML anchor element with an href attribute pointing at the (now) existent topic. In addition, the anchor's onmouseover and onmouseout event handlers are pointed at the JavaScript functions TopicTipOn and TopicTipOff, which brings us to the first FireFox problem.

The JavaScript function TopicTipOn takes a unique id, which is generated in LinkWikiNames using the private Formatter method NewUniqueIdentifier, and then uses the id to locate a div, which was also given this id by LinkWikiNames and was filled with topic tip info consisting of 1) a span with the topic's (optional) Summary property, retrieved using Formatter's private TipForTopic method, or the default "Click to read this topic" text, and 2) a div containing the topic's last modification date, time, and user, retrieved using the GetTopicModificationTime/By methods of the FlexWiki.Federation class. When TopicTipOn locates this uniquely id'd div containing the topic's tip info, it takes the div's innerHTML and plops it into another div having the id "TopicTip", sets its location (relative to where the click occured), and then shows it.

So here's the problem. The JavaScript functions TopicTipOn and TopicTipOff both address the "TopicTip" div, which is written to every topic page by the FlexWiki.Web.Default2.DoPage method, using IE-only syntax that assumes all elements having unique ids are expando'd onto the window object making them globally accessible by their ids. In other words, they do things like this:

TopicTip.style.left = targetX;

That's a no-no in FireFox. What these functions should do, and interestingly TopicTipOn does do to find the anchor's uniquely id'd topic tip info div, is use the document object's getElementById function to reference the "TopicTip" div.

For us, then, the easiest solution is to just declare and initialize a new global variable named TopicTip right above the TopicTipOn function that points to the "TopicTip" div using the proper syntax:

var TopicTip = document.getElementById('TopicTip');

With that simple addition, the TopicTipOn and TopicTipOff functions can continue to reference TopicTip in both IE and FireFox without any problem.

(By the way, does anyone know why TopicTipOn takes a reference to the source anchor? It's not used.)

Topic Anchor #3
After identifying actual topic names using the AllAbsoluteTopicNamesThatExist method of the FlexWiki.ContentBase class, the LinkWikiNames method checks to see if there are other existing non-plural versions of the same topic name using the AlternateForms property (why is this a property?) of the topic's instance of FlexWiki.RelativeName, which inherits this property from the abstract base class FlexWiki.TopicName. Here's the property's code:

ArrayList answer = new ArrayList();
string each = ToString();
if (each.EndsWith("s"))
  answer.Add(NewOfSameType(each.Substring(0, each.Length - 1)));
if (each.EndsWith("ies"))
  answer.Add(NewOfSameType(each.Substring(0, each.Length - 3) + "y"));
if (each.EndsWith("sses"))
  answer.Add(NewOfSameType(each.Substring(0, each.Length - 2)));
if (each.EndsWith("xes"))
  answer.Add(NewOfSameType(each.Substring(0, each.Length - 2)));
return answer;

Personally, I dislike this logic. In fact, I've commented it out in my implementations. But, I understand why some folks might want it.

Anyway, if LinkWikiNames finds more than one possible topic for a given topic name, it no longer returns a simple href-addressed HTML anchor. Instead, it returns an anchor with an onclick event handler pointing to the JavaScript function LinkMenu. The purpose of this function is to assemble a new div having the class "Menu" and fill it with span elements that act like links to the various matching topics. And it's here that we find the next batch of FireFox problems.

The first problem stems from a difference in the way IE and FireFox provide access to the event object, which is used a couple of times in LinkMenu. In IE, the event object is essentially global, accessible via a property of the window object. However, to get to the event object In FireFox you must either 1) assign your function to an element's event handler without parameters and then receive the the event object, which is passed implicitly by FireFox, in an explicitly declared parameter (the only one) in your function, or 2) pass the event object yourself to the function as a parameter in the element's event handler. You may have noticed that the TopicTipOn function uses the latter approach, which was necessitated by the fact that it needed additional parameters. Well, LinkMenu needs to do the same thing.

This requires two changes. First, LinkMenu needs another parameter:

function LinkMenu(anArray, event)

Next, the logic in LinkWikiNames responsible for assembling the onclick event handlers for these anchors must be changed. Locate this code:

// There's more than one; we need to generate a dynamic menu
string clickEvent;
clickEvent = "onclick='javascript:LinkMenu(new Array(";
bool first = true;
foreach (AbsoluteTopicName eachAbs in absoluteNames)
{	
  if (!first)
    clickEvent += ", ";
  first = false;
  clickEvent += "new Array(\"" + eachAbs + "\", \"" + LinkMaker().LinkToTopic(eachAbs) + "\")";
}
clickEvent += "));'";

And replace it with this code:

// There's more than one; we need to generate a dynamic menu
string clickEvent;
clickEvent = "onclick='LinkMenu(new Array(";
bool first = true;
foreach (AbsoluteTopicName eachAbs in absoluteNames)
{	
  if (!first)
    clickEvent += ", ";
  first = false;
  clickEvent += "new Array(\"" + eachAbs + "\", \"" + LinkMaker().LinkToTopic(eachAbs) + "\")";
}
clickEvent += "), event); return false;'";

The only important change is on the last line, where the event object was added as a parameter to LinkMenu. I also pulled the unnecessary "javascript:" from in front of the LinkMenu call, and I added a "return false;" statement to the end. I'll explain the latter change below. Back to LinkMenu.

Now that we have access to the event object, LinkMenu must be changed to use it. Replace this line of code:

window.event.cancelBubble = 'true';

With this one:

event.cancelBubble = 'true';

The original programmer(s) weren't terribly consistent, so the other two event object references in LinkMenu don't have to be changed because they don't use the fully qualified window syntax. Next.

As previously mentioned, LinkMenu dynamically adds a div element and populates it with span elements. While doing this, it increments the local variables h and w to reflect the div's growing height and width. It does this using the offsetHeight and offsetWidth properties of each span right after they're added to the div. This works great in IE. However, in FireFox these properties have no value until the spans become visible, which they don't do until their parent element, the dynamically added div, has its display style set to "block". Unfortunately, in the original code, this isn't done until the div is completely filled and positioned.

Fortunately, the solution is simple. We just have to move the div's positioning and display setting logic above the span-adding for loop. Pull these lines out from under the for loop:

menu.style.left = event.clientX;
menu.style.top = event.clientY;
...
menu.style.display = 'block';

And move them right above the for loop, just below this line of code:

menu.innerHTML = "";

While were at it, let's solve another problem. Apparently, unlike in the TopicTipOn function, no one thought to consider the document body scrollLeft and scrollTop properties when setting the dynamically added div's position. As a result, the div can wind up off-screen depending on where the clicked topic link appears. Again, the solution is simple. Just make the div positioning and display logic we just moved above the for loop look like this:

menu.style.left = document.body.scrollLeft + event.clientX;
menu.style.top = document.body.scrollTop + event.clientY;
menu.style.display = 'block';

The next problem is in the JavaScript function MenuClick, which is given as the onclick event handler for the spans added to the dynamically added div in LinkMenu. The problem is that MenuClick uses the window object's IE-only navigate method:

window.navigate(url);

Change this to set the window's location property instead:

window.location = url;

Now, let's return to the "return false;" statement I added to the topic anchor's onclick event handler earlier. All indications are that at some point in FlexWiki's evolution this anchor was handled differently. The existence of the unnecessary "javascript:" prefix in the onclick event handler and the missing href attribute seem to suggest that at some point this anchor used a function-based href and was only later changed to use onclick. That's cool. However, when they made the switch they didn't include an href attribute at all. This doesn't create a functional problem, it works fine without an href attribute, but it does create a styling problem. Without the href attribute, IE won't properly apply the anchor's pseudo-class :hover style. This makes these multi-topic topic anchors seem weird (in IE, anyway, FireFox doesn't have a problem with it). The solution is simple. Just add an empty href attribute by replacing this line of code in LinkWikiNames:

str = ReplaceMatch(answer, str, m, before + 
    "<a title='Different versions of this topic exist.  Click to pick one.' " + 
    clickEvent + ">" + displayname + "</a>" + after) ;

With this one:

str = ReplaceMatch(answer, str, m, before + 
    "<a href='' title='Different versions of this topic exist.  Click to pick one.' " + 
    clickEvent + ">" + displayname + "</a>" + after) ;

Of course, now that there's an href, the browser will follow it when the anchor is clicked, unless the onclick event returns false. Hence the additional statement in the onclick event handler.

Well, that's it, I think. I should probably explain that I actually made the various changes described here a few months ago and, to be honest, didn't stop to document them all at the time. So, for the purposes of this post, I actually had to track them down again by looking for my initials in the code and by doing diffs with an original copy of FlexWiki. Needless to say, it's possible I missed something. If so, just let me know. Also, I should clarify that I'm not claiming that these things represent the totality of issues FlexWiki has with FireFox. There are probably more (in fact I know there are styling issues in the admin tools). These are just the ones I've run across and taken the time to investigate and resolve. Again, if you discover something else, please let me know.

Next up, multiple fixes and enhancements to the regular expressions used to parse topic contents.

FlexWiki Notes - JavaScript Issues, Part 1
FlexWiki Notes - Indented Paragraphs
FlexWiki Notes - Starting Ordered Lists
FlexWiki Notes - Multiple Namespaces
FlexWiki Notes - Getting Started
FlexWiki Notes


Comments

very nice post!!!

Thanks

dancadosiri22 | 2009.04.20 03:45 AM


TrackBack

TrackBack URL:  http://www.typepad.com/services/trackback/6a00d8341c7bd453ef00d83551ff2369e2

Listed below are links to weblogs that reference FlexWiki Notes - JavaScript Issues, Part 2: