One common request for Constellation Roamer is a history feature. Web application developers want to give their users the option to look over the trail of nodes they've viewed.
This can be done using the Javascript interface methods built into Roamer. Before we get into the howto, here's a demo of the feature in action.
The history list on the right shows the user where they've been and provides a way to back track. Click on the list items to return to that movie or actor.
For this little app, we'll be listening for change events from Roamer and adding the selected node to an HTML select list. We'll also be listening for events from the select list so we can allow the user to go back to visited nodes.
There's a small problem that makes this task a bit more complex, however. When the selected node changes in Roamer, it only gives us the node ID. Since we want to put the label in the select list, we need to load the graph data separately in Javascript so we can translate node IDs to labels.
You'll see below that this isn't a huge issue, but it does need to be considered.
First things first. We'll need a select list in our HTML page which will store the history. I named mine "history_list".
<select id="history_list" size="8" multiple="false" style="width:100%" onchange="historyChangeHandler(this)">
<option value="1" selected="true">The Godfather</option>
</select>
Note that I've included the initially selected node right in the HTML. Roamer doesn't fire an event for the first selected node, but since we passed the initial selected node ID as a flashvar, we already know which one it is.
You can also see that when the history changes, that is, when a user clicks an item, a call is made to historyChangeHandler(). This'll be important later.
As mentioned above, we need to load the XML graph data using Javascript. This is done using a method borrowed from Quirksmode.
function importXML(doc)
{
if (document.implementation && document.implementation.createDocument)
{
xmlDoc = document.implementation.createDocument("", "", null);
xmlDoc.onload = importXMLCompleteHandler;
}
else if (window.ActiveXObject)
{
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.onreadystatechange = function () {
if (xmlDoc.readyState == 4) importXMLCompleteHandler()
};
}
else
{
alert('Your browser can\'t handle this script');
return;
}
xmlDoc.load(doc);
}
The importXML() method takes care of browser discrepancies and will load the file for us. All we have to do is make the call:
importXML('sample_graph_data.xml');
Once the XML file is loaded, importXMLCompleteHandler() will be called and all we need to do is store the list of nodes.
function importXMLCompleteHandler()
{
nodeElems = xmlDoc.getElementsByTagName('node');
}
Now we can define another function that takes a node ID and uses the list of nodes to give us the label. The method below searches each node element in the XML document, checks whether the node ID matches, and returns the label attribute.
function getNodeLabel(nodeID)
{
var nodeElems = xmlDoc.getElementsByTagName('node');
for (var i = 0; i < nodeElems.length; i++)
{
var nodeElem = nodeElems[i];
if (nodeElem.nodeType != 1) continue;
var attrs = nodeElem.attributes;
if (attrs.getNamedItem('id').value == nodeID)
{
return attrs.getNamedItem('label').value;
}
}
// don't know the label so return the node ID
return '[' + nodeID + ']';
}
Now, in order to take advantage of the Javascript interface, you will need to set integration.use_javascript to true in your configuration file. It's a good idea to refresh the file in your browser or clear your browser cache to make sure the changes propagate.
With the Javascript interface enabled, Constellation Roamer provides an event handler and a Javascript method which are critical for our purposes.
// called whenever the user "roams" to a new node
constellationRoamer_onChange(nodeID)
// sets the Roamer's currently selected node
ConstellationRoamer.setSelectedNodeID(String id)
We'll be defining the constellationRoamer_onChange() method which is called each time the user re-centers the visualization by clicking a new node. The method below grabs the history list element, adds a new option, and selects it. When adding the new option, it takes advantage of the getNodeLabel() function we created earlier.
// called whenever the user "roams" to a new node
function constellationRoamer_onChange(nodeID)
{
var historyElem = getElem('history_list');
// add the option to the select list
historyElem.options[historyElem.options.length] =
new Option(getNodeLabel(nodeID), nodeID);
// select the option we just added
historyElem.selectedIndex =
historyElem.options.length - 1;
}
To revisit nodes in the history we need to listen for events from the select element. As mentioned before, when the user clicks an option in the select list, a call is made to historyChangeHandler().
function historyChangeHandler(selectElem)
{
var history = getElem('history_list');
// watch out for de-selections (when Ctrl+clicking a selected item)
if (history.selectedIndex < 0) return;
var selectedOption = history.options[history.selectedIndex];
var roamer = getConstellationRoamerElem();
roamer.setSelectedNodeID(selectedOption.value);
}
The method above figures out which option is selected, grabs the ID which is stored in "value," and tells Roamer to load up that node.
Now you've got a working history list!
Even though this implementation uses a select element to render the history, it's not the only option. The nodes could be linked items in a list or rows in a table. This implementation could even be modified to behave like a breadcrumb!
Finally, if you're looking for more resources related to this tutorial, see the links below.