Category Archives: Rich Internet Applications

Creating a Firefox Sidebar for Clearspace: Part II

It looks like it was almost 2 months ago that I wrote a blog post about the Clearspace plugin for Firefox (called Clearfox), promising that I would follow up with the details on the JavaScript side of the project. I guess time flies when you’re having fun.

Getting started on the JavaScript sidebar was easy. The Mozilla folks have a nice document here that shows you how to get a really simple sidebar created, but like a lot of things in software, the last 20% of the features take 80% of the time. It’s also worth noting that Firefox extensions are deployed as an XPI file, themselves nothing more than glorified zip files, so if you’re curious about how an extension (say Firebug, LiveHTTPHeaders or Del.icio.us Bookmarks), you can download the extension, unzip it’s contents and then poke around to your hearts content. It’s just like viewing source on HTML, which is something that other browser extensions don’t offer out of the box. Here are couple things that either weren’t documented in the above document or caused me to repeatedly bang my head against the wall.

Adding Your Icon

When I originally wrote about the plug-in, a number of people asked “why?” I think one of most important things about browser plug-ins is that they bring your web application (in this case Clearspace) front and center in your customers everyday browsing experience. Front and center in Firefox means that your plug-in sits right next to the Back | Forward | Reload | Stop | Home button in the navigation bar. If you want to put a 24×24 picture of yourself in that spot, be my guest. Since the purpose of Clearfox was to get embed Clearspace in the browser, I chose (wisely I think) to use the Clearspace logo. This was one of the simpler things to accomplish. I added the following element to the overlay.xul:

<toolbox id="navigator-toolbox">
    <toolbarpalette id="BrowserToolbarPalette">
      <toolbarbutton id="clearfox-button" class="clearfoxbutton-1 chromeclass-toolbar-additional"
                     observes="viewClearfoxSidebar" />
    </toolbarpalette>
  </toolbox>

and then in a CSS file (that you also reference in overlay.xul), I added this:

#clearfox-button {
  list-style-image: url("chrome://clearfox/skin/clearfox.png");
  -moz-image-region: rect(0px 24px 24px 0px);
}

Finally, you’ll need to have execute some JavaScript when Firefox loads:

var toolbox = document.getElementById("navigator-toolbox");
var toolboxDocument = toolbox.ownerDocument;
var hasClearfoxButton = false;
for (var i = 0; i < toolbox.childNodes.length; ++i) {
  var toolbar = toolbox.childNodes[i];
  if (toolbar.localName == "toolbar" && toolbar.getAttribute("customizable") == "true" ) {
    if (toolbar.currentSet.indexOf("clearfox-button") > -1)
      hasClearfoxButton = true;
  }
}
if (!hasClearfoxButton) {
  for (var i = 0; i < toolbox.childNodes.length; ++i) {
    toolbar = toolbox.childNodes[i];
    if (toolbar.localName == "toolbar" &&  toolbar.getAttribute("customizable") == "true"
      && toolbar.id == "nav-bar") {
      var newSet = "";
      var child = toolbar.firstChild;
      while (child) {
        if (!hasClearfoxButton && (child.id=="clearfox-button" || child.id=="urlbar-container")) {
          newSet += "clearfox-button,";
          hasClearfoxButton = true;
        }
        newSet += child.id + ",";
        child = child.nextSibling;
      }
      newSet = newSet.substring(0, newSet.length-1);
      toolbar.currentSet = newSet;
      toolbar.setAttribute("currentset", newSet);
      toolboxDocument.persist(toolbar.id, "currentset");
      BrowserToolboxCustomizeDone(true)
      break;
    }
  }
}

The key takeaways: the ID of the toolbarbutton element is used in the CSS declaration, the list-style-image property is used to specify the image you want for the button and the observes attribute points to the ID of the broadcaster element, which is used for showing / hiding the sidebar. I'm not all that good with Fireworks / Photoshop so I didn't go the extra mile to create a separate image to show when a user mouses over the button (check out the the excellent del.icio.us bookmarks extension for an example), but adding a mouseover / hover image is as simple as adding another CSS property:

clearfox-button:hover {
  list-style-image: url("chrome://clearfox/skin/clearfox-hover.png");
}

Loading Sidebar Content

I think I mentioned this when I wrote the original post, but the thing that really got me started with the Firefox extension was looking at the source for the twitbin Firefox extension. When I checked out the source for that extension, I was stunned to learn that they were simply loading up an HTML page from their server and then refreshing the page every couple minutes using an AJAX request. I thought it must have been way more complex than that, but HTML + JavaScript + CSS with a little bit of XUL sprinkled in and you're golden. In the broadcaster element (mentioned above), you add an attribute 'oncommand' that tells Firefox what JavaScript method(s) you want invoked when the user clicks on your button; in Clearfox I use that hook to load the HTML content from Clearspace. The JavaScript looks like this:

var sidebar = top.document.getElementById("sidebar");
sidebar.contentWindow.addEventListener("DOMContentLoaded",
  Clearfox.clearfoxContentLoaded, false);
sidebar.loadURI('http://example.com/yourpage.html');

DOMContentLoaded

Once the content has loaded the DOMContentLoaded event is fired and then Clearfox runs the clearfoxContentLoaded method. I initially tried loading the page and then re-loading that same page every couple minutes: I was cheating and trying not have to do the AJAX part. I added a meta refresh tag to the page to have it reload every n minutes, but the DOMContentLoaded event was only fired the first time the page was loaded. Key takeaway: if you rely on DOMContentLoaded, you're only going to get the event once, even if your page reloads.

Opening New Tabs

You can run your own little application over in the sidebar if you want to, never opening new tabs or doing anything in the main window, but the main point of the Clearfox extension was to give users the ability to see new content in Clearspace and then able to view the full thread, document or blog post in their main browser window as a new tab. There's no way you can create new tabs in JavaScript outside of XUL, you have to be inside XUL to create a tab so the DOMContentLoaded event is important because this is where the links are all rewritten to open new tabs rather than work as normal links. There are two parts to the clearfoxContentLoaded method: the first rewrites all the links so that clicking on a link in the sidebar opens a new tab, the second adds listeners to the document so that when new content is added via AJAX, the same first part is repeated. The link conversion looks like this:

var sidebar = top.document.getElementById("sidebar");
var doc = sidebar.contentDocument;
var all_links = new Array();
var links = doc.evaluate("//a", doc, null, XPathResult.ANY_TYPE, null);
var link = links.iterateNext();
while (link) {
  all_links.push(link);
  link = links.iterateNext();
}
for (var i = 0; i < all_links.length; i++) {
  link = all_links[i];
  if (!link.hasAttribute('onclick') &&
    link.hasAttribute('href') &&
    (link.hasAttribute('class') && link.getAttribute('class').indexOf('tabbable') > -1)
  ) {
    var target = link.getAttribute('href');
    link.setAttribute('onclick', 'return false;');
    link.removeAttribute('target');
    link.addEventListener('click', Clearfox.clearfoxCreateOpenFunction(target), true);
  }
}

In English, get all the links that don't have an onclick attribute and add an onclick event, whose callback creates a new tab:

clearfoxCreateOpenFunction: function(url) {
  return function() {
    gBrowser.selectedTab = gBrowser.addTab(url);
  };
}

The second part of the clearfoxContentLoaded method listens for any modifications (anytime something is added to the document via an AJAX call, I remove the last n rows, so in this case I listen for the removal of nodes) to the document and adds handlers for the modification event:

var sidebar = top.document.getElementById("sidebar");
var table = sidebar.contentDocument.getElementById("clearfox-hidden-content");
table.addEventListener("DOMNodeRemoved", Clearfox.clearfoxConvertLinks, false);

Debugging

Two things about debugging your extension: a) none of the tools you've got installed for debugging JavaScript / CSS (cough! Firebug cough!) will work in the sidebar window and b) your only other option is to resort to the Firefox equivalent of Java's System.out.println:

function logClearfoxMsg(message) {
  var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
  consoleService.logStringMessage("Clearfox: " + message);
}

and then to log a message:

logClearfoxMsg('your advertisement here');

To see the log message, go to Tools --> Error Console.

Showing The Sidebar When Firefox Opens

I spent way more time than I should have working on this last part: when you first install the extension, you're asked to restart Firefox, which you do and then you see the nice Clearspace button, you click on it and the sidebar opens and you're happy. Then a couple days later when you need to restart Firefox again (for whatever reason), you'll notice that the sidebar is open but that no content shows and you're sad. You probably didn't take it personally, but I did and I wanted to figure out why Firefox wouldn't load up the content if the sidebar was already open. There actually is onload attribute that I tried using in the sidebar.xul and that didn't work for reasons I won't get into here. What ended up doing the trick for me (and I really think it's a trick but I couldn't get anything else to work and this was just about the last option I had) was to check to see if the sidebar was open when Firefox was loading:

var broadcaster = top.document.getElementById('viewClearfoxSidebar');
  if (broadcaster.hasAttribute('checked')) {
    ...

and then, if it is open, *forcing* the sidebar to open again:

toggleSidebar('viewClearfoxSidebar', true);

For whatever reason, this was the only way I could get the rest of my code to work, but work it did. And now I'm happy.

And I hope you are too. If you have any questions about creating a Firefox sidebar, shoot me an email, I'll be glad to help. If you want to see all the code, you can download the Clearfox source over on the Jive Software Community site. All the JavaScript / XUL code I discussed in this post is in the 'xpi' directory off the root.

Not drinking the Flex / Silverlight / Apollo Kool-Aid

From billhiggins.us:

If you’re considering or actively building Ajax/RIA applications, you should consider the Uncanny Valley of user interface design and recognize that when you build a “desktop in the web browser”-style application, you’re violating users’ unwritten expectations of how a web application should look and behave. This choice may have significant negative impact on learnability, pleasantness of use, and adoption. The fact that you can create web applications that resemble desktop applications does not imply that you should; it only means that you have one more option and subsequent set of trade-offs to consider when making design decisions.

See also: The Other Road Ahead by Paul Graham. Scroll down to the section entitled ‘Just Good Enough’.

Ajax.Autocompleter is not a constructor

Using script.aculous to create some cool effects on your website? Ever get this error message?

Ajax.Autocompleter is not a constructor

All of the results I found on Google suggested that the solution to the problem was to make sure that you were including controls.js (which is where the autocompletion stuff lives in script.aculous). If you checked and double checked that you have controls.js (or that you’re including scriptaculous.js which itself includes controls.js), then your problem could be that you included prototype.js twice. Example:

<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="scriptaculous.js"></script>
<script>
  function createAC() {
    new Ajax.Autocompleter('mytextbox', 'myautocomplete', 
      'autocomplete/url', {});
  }
</script>
<input type="text" id="mytextbox" name="username" value="" />
<div id="myautocomplete" class="autocomplete"></div>
<a href="#" onclick="createAC(); return false;">start autocomplete</a>
<script type="text/javascript" src="prototype.js"></script>

Why? Prototype defines a class ‘Ajax’:

var Ajax = {
  getTransport: function() {
...

and then Script.aculous extends that class:

Ajax.Autocompleter = Class.create();
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
...

So including prototype.js a second time blows away the class defined by Script.aculous. Really easy to fix, but if you’re working on a large team, make sure that prototype.js is only included once on every page.

Shallow comparison of ASP.NET to Flex

Flex seems pretty interesting when you realize how similar it is to something like ASP.NET. Look how similar this snippet of Flex:

<?xml version="1.0" encoding="iso-8859-1"?>
<mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml">
   <mx:script>
   function copy() {
      destination.text=source.text
      }
   </mx:script>
   <mx:TextInput id="source" width="100"/>
   <mx:Button label="Copy" click="copy()"/>
   <mx:TextInput id="destination" width="100"/>
</mx:Application>

to this snippet of ASP.NET code:

<script language="C#" runat="server">
   public void Copy(Object src, EventArgs e) {
      destination.Text = source.Text;
   }
</script>
<form runat="server">
<asp:textbox id="source" size="30" runat="server" />
<asp:button id="copy" OnClick="Copy" text="Copy" runat="server" />
<asp:textbox id="destination" size="30" runat="server" />
</form>

I can’t wait to see the IDE rolled out for Eclipse and the .NET version. Cool stuff Macromedia!

Open Source Flash Remoting: OpenAMF

I found this link on the flash emerging issues document (could they have come up with a more trendy name for the list of latest bugs? I think not) via JD. Anyway, the interesting parts of that review of Flash 2004 were the links at the end, specifically the link to OpenAMF. OpenAMF is an open-source J2EE implementation of Flash Remoting and although I’ve not used it yet, according the gentleman who is writing the Flash Remoting book for Oreilly, it has even more features than the version that Macromedia sells. I’ll try to check it out soon.

Rich Internet Application Realized

Last Thursday we (Mindseye) launched a pretty cool rich internet application: FootJoy MyJoys. MyJoys is the new program from FootJoy that lets you customize the #1 waterproof brand in golf, DryJoys.

There are really two parts to the application: a front end built for consumers and a back end for FootJoy customer service reprepsenatives to enter trade orders and lookup order status, both using Flash and Flash Remoting. The site uses a smorgasbord of technologies: Flash 6 and Flash Remoting .NET, C# and ASP on the server side, SQL Server 2000, wget, rsync, aspexec, Java, FedEx & PGP.

Co-worker Tai and Dave did the all the Flash work, I did all (99.95%) of the server side work.

Flash Remoting Docs bug

Got an email from Scott who was trying to work with C# and Flash Remoting. He couldn’t get the “Creating an assembly that returns an ActionScript object” example to work from the FlashRemoting LiveDocs. Looks to me like the documentation is a bit off. From the documentation:

 public ASObject returnObject()
    {
      ASObject aso = new ASObject();
      aso.ASType = “Calculator”;
      aso.Add(“x”, 100);
      aso.Add(“y”, 300);
      Flash.result = aso;
    }

should probably be:

 public ASObject returnObject()
    {
      ASObject aso = new ASObject();
      aso.ASType = “Calculator”;
      aso.Add(“x”, 100);
      aso.Add(“y”, 300);
      return aso;
    }

Personal rant: I tried creating a Livedocs account, but for whatever reason, the account I created didn’t work… why does everyone make you create a username? Why not just use your email address?

Server-Side Flash Detection Using BrowserHawk @ DEVNET

I wrote an article for Macromedia Devnet which was just published today! Check it out here: Server-Side Flash Detection Using BrowserHawk. If you’ve never used BrowserHawk before, it’ll be a good introduction. However, if you’ve used it with ColdFusion, you should definitely read it, I was able to use BrowserHawk 4J (the Java version) with CFMX rather than using the ASP bridge (which was and is required if you want to access the extended properties of BrowserHawk in versions of ColdFusion prior to MX).

I should thank Shena at my work for bringing it up as an option (she contacted Macromedia originally), thanks Shena!

Mozilla 1.3.1 and Flash 6 Detection

I’m doing some testing with Mozilla 1.3.1 and Flash 6, which I didn’t have installed for Mozilla. I went to the Macromedia site as prompted by Mozilla and saw this:

Download Time Estimate: 1 minute @ 56K modem
Version: 3,0,10,0
Platform: Windows 3.1
Browser: Internet Explorer, Netscape or Netscape-compatible
File size: 190 K
Date Posted: 5/10/1998
Language: English

Basically, they sniffed my platform incorrectly as Windows 3.1. Ouch. I was prompted to download the Windows 3.1 version of Flash 6. Not good. Any Macromedia people wanna comment on why that might be?