Tutorials: Intro, command, pref, menu, munger

Creating a Munger

We're going to take a break from /slap, since this doesn't give us a good setup for adding a text munger. So what is a munger, anyway?

A text munger is an item that processes text being displayed in ChatZilla's display. Some of the many existing mungers include the ones to make "*bold*" appear in bold, and even turning things like "http://www.mozilla.org/" into a link you can click on.

Warning: This tutorial jumps into some rather fun, but harder, JavaScript than the previous ones. While the basics are easy enough, the actual munger we write is not so simple. Don't be put off, though - have a read through, and see what you can learn.

How to Define A Munger

The munger system is quite a bit simpler than the Menu Manager and other managers, but mungers are very powerful. To define one, we just call:

  client.munger.addRule(name, regex, className, enable);

The parameters are as follows:

Parameter Description
name The name of the munger.
regex A Regular Expression that determins what to munge, or a function to call to find each match.
className Either a HTML class to use for the matched text, or a function to call for each match.
enable (optional) Should the munger be enabled to start with.

Generally, mungers either use a RegExp and a HTML class, or a RegExp and a className function. There are no mungers in ChatZilla that use a function for regex.

Example Mungers

  client.munger.addRule("bold", /(?:\s|^)(\*[^*()]*\*)(?:[\s.,]|$)/, "chatzilla-bold");
  client.munger.addRule("bugzilla-link", /(?:\s|\W|^)(bug\s+#?\d{3,6})/i, insertBugzillaLink);

The first munger rule simply matches some text enclosed in asterisks and tells ChatZilla to use the HTML class "chatzilla-bold". This means the text ends up enclosed in <span class="chatzilla-bold">...</span>. The second munger rule calls the function insertBugzillaLink for each match of the RegExp. We will discuss the function format in a bit.

Our Munger

For this tutorial, we're going to make the automatic bug links fetch the bug summary for use as a tooltip. This isn't nessessarilly very useful outside of Mozilla developement, but does show what can be done.

  client.munger.addRule("bugzilla-link", /(?:\s|\W|^)(bug\s+#?\d{3,6})/i, mungerBugNumber);

This will actually replace the built-in "bugzilla-link" munger rule with our own. This goes in the initPlugin routine like usual.

Munger Replace Functions

All munger functions need to follow a particular format, like the command handling functions. The format is as follows:

  function handler(matchText, containerTag, data, mungerRule);

Parameter Description
matchText The full text that the RegExp matched.
containerTag The HTML tag we must add our items too.
data Misc. data, very like the "context" for menus.
mungerRule Ourself (the munger rule we're handling).

A lot of munger rules won't need the last two parameters, and they can be skipped if they aren't needed for your munger. In our case, we will only need the first two.

Our Replace Function

  function mungerBugNumber(matchText, containerTag) {

The first thing we'll need to do is extract the bug number we matched in the text. This is where matchText comes in.

      var number = matchText.match(/(\d+)/i)[1];

This matches the numbers in the text match (which will be something like "bug 10000") and returns the number. String.match returns an array of items, the first ([0]) is the entire match, then the rest are the captured bits (the bits in parentheses), thus [1] will return the (\d+) match.

You may have been wondering why we have a DOM node passed in to the replace function (containerNode). This is because the munger needs to add it's "output" to this node, using normal DOM manipulation. Since we're turning the text into a link, we need to create a <a> node from HTML:

      var anchor = document.createElementNS("http://www.w3.org/1999/xhtml", "html:a");

Next, we set some attributes on it:

      anchor.setAttribute("href", client.prefs["bugURL"].replace("%s", number));
      anchor.setAttribute("class", "chatzilla-link");
      anchor.setAttribute("target", "_content");
      anchor.setAttribute("title", "Loading...");

You'll notice we're using a preference for the href - bugURL defaults to "http://bugzilla.mozilla.org/show_bug.cgi?id=%s" and is used by the built-in bug munger. We replace %s with the bug number to make the URL valid. Now to put some text in the link:

      anchor.appendChild(document.createTextNode(matchText + "*"));

Just normal DOM code again, though we're putting a "*" on the end of the matched text to indicate it's not loaded the summary yet. Next, add the <a> to the container tag, as we're required to.

      containerTag.appendChild(anchor);

This is the fun bit, now. We need to load a remote page from the munger, and know when it's loaded. This can be done using the XMLHttpRequest object, found in Mozilla (copied from IE's object by the same name). First, make up the URL and get a new request object:

      var url = client.prefs["bugURL"].replace("show_bug.cgi?", "buglist.cgi?format=rdf&bug_").replace("%s", number);
      var req = new XMLHttpRequest();

The first replace turns a URL like "http://bugzilla.mozilla.org/show_bug.cgi?id=%s" into "http://bugzilla.mozilla.org/buglist.cgi?format=rdf&bug_id=%s". This new URL is how we load the data about a single bug, so we then replace %s like before.

Now we need to refine the function that gets called when it's loaded. Note that because of JavaScript's scoping, anchor and req will work fine inside this nested function, even though it's not called until later!

This function is just a big hack to extract the useful information out of the XML/RDF without actually using any XML functions. The key bits are that it changes the "title" attribute, and replaces the text node with matchText but without the "*".

      var loaded = function _bugSummaryLoaded() {
          if (req.responseXML) {
              var text = req.responseText;
             
              var status = text.match(/<bz:bug_status>(.*?)<\/bz:bug_status>/)[1];
              var resolution = text.match(/<bz:resolution>(.*?)<\/bz:resolution>/)[1];
              var component = text.match(/<bz:component>(.*?)<\/bz:component>/)[1];
              var op_sys = text.match(/<bz:op_sys>(.*?)<\/bz:op_sys>/)[1];
              var short_desc = text.match(/<bz:short_short_desc>(.*?)<\/bz:short_short_desc>/)[1];
             
              text = status.substr(0, 4) + (resolution ? ":"+resolution.substr(0, 4) : "");
              text += ", " + component + ", " + op_sys + ", " + short_desc;
             
              anchor.setAttribute("title", text);
          } else {
              anchor.setAttribute("title", "Error loading bug summary.");
          }
          anchor.replaceChild(document.createTextNode(matchText), anchor.firstChild);
      }

And finally, tell the XMLHttpRequest object what to call when it's loaded, then make the "GET" request with no data (send(null)).

      req.onload = loaded;
      req.open("GET", url);
      req.send(null);
  }

Powered by the Content Parser System, copyright 2002 - 2009 James G. Ross.