Monday, March 24, 2008

Customizing Timeline Event Bubbles

Tonight is a two for one special on blog posts. I'm heading out to Boston tomorrow for the rest of the week, so I don't know how much time I'm going to have to blog while out there. If you happen to be reading and are going to be in the Boston area (either for the NSTA meeting like me or whatever), shoot me an email and let's meet up for a beer.

In this post, I'm going to show you how to customize the rendering of event bubbles in Simile Timeline. The default bubbles look pretty nice:
but they lack a few features that I needed, namely clicking on the thumbnail (if present) should take the user to the event link and the start and end depth should be rendered on a single line:

The markup for the event bubble is built programmatically in the fillInfoBubble() function on Timeline.DefaultEventSource.Event class. You can find the code at the end of the sources.js file in the Timeline distribution.

To customize the event bubble rendering, we just need to modify the fillInfoBubble() function and make it generate the markup we want. One option would be to go in and edit the sources.js file directly. This would accomplish our goal but it would mean we'd be modifying the Timeline source code and we would have to patch the file every time it was changed in the Timeline trunk.

The second option is to replace the function "on the fly" by modifying the Timeline.DefaultEventSource.Event prototype in our own code. The end is the same but we can do it without touching the Timeline sources. To accomplish this, we just need to overwrite the function after sources.js loads:

(function() {
Timeline.DefaultEventSource.Event.prototype.fillInfoBubble = function (elmt, theme, labeller) {
// build our custom markup
};
})();


What this code does is create an anonymous function that runs immediately. Inside that function, we replace the current fillInfoBubble() implementation on the Event class with our own.

This code is an improvement over editing sources.js directly, but we can do one better. The problem with this approach is that we're replacing fillInfoBubble() for everyone. We're breaking any clients that depend on the old implementation, which is just bad form. What we'd really like to do is selectively apply our new implementation. Fortunately that's easy enough to do:

(function() {
var default_fillInfo = Timeline.DefaultEventSource.Event.prototype.fillInfoBubble;
Timeline.DefaultEventSource.Event.prototype.fillInfoBubble = function (elmt, theme, labeller) {
// if not a depth labeller, use the original
if (!labeller.isDepthLabeller) {
default_fillInfo.apply(this, arguments);
return;
}

// build our custom markup
};
})();


In this updated snippet we save the old function implementation then we check to see if the labeller is a DepthLabeller. If it isn't, we just pass it on to the original implementation, otherwise we use our customized implementation.

For completeness sake, here's the snippet I used to make the thumbnail image clickable and to render the depths on the same line:

if (image != null) {
var img = doc.createElement("img");
img.src = image;
theme.event.bubble.imageStyler(img);

if (link != null) {
var a = doc.createElement("a");
a.href = link;
a.target = "_blank"
a.appendChild(img);
elmt.appendChild(a);
} else {
elmt.appendChild(img);
}
}

var divTime = doc.createElement("div");
if (this.isInstant()) {
divTime.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this.getStart())));
} else {
divTime.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this.getStart())));
divTime.appendChild(elmt.ownerDocument.createTextNode(" - "));
divTime.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this.getEnd())));
}


Hope that was clear as mud!

6 comments:

Anonymous said...

Many thanks, I hope you don't mind me posting a link to your page at,
http://www.nabble.com/RichUI-plugin%3A-Timeline-widget-query-to19543994.html#a19635811

Josh Reed said...

No worries. Glad it was useful.

Cheers,
Josh

Anonymous said...

I want to modify Sources.jar, but do not know the location of this file. If you can please tell me where I can find this file.

Anonymous said...

Thank you for the proper syntax to override the Timeline function. I worked for days trying to override Timeline.OriginalEventPainter.prototype._showBubble so that I could insert an eventID in the Event XML and then retrieve it when the event was clicked and still show the native bubble. After discovering your post, I had it working in 10 minutes.

Many, many thanx!

Anonymous said...

how to disable event bubbles

Anonymous said...

hiiii

my name is palak and i am trying to change the interval unit of timeline to 4 weeks on upper band and 2 months on lower band . Is it possible to do this, if there is any solution to this problem kindly mail me at prismatic.palak@gmail.com