HyperScope is a completely client-side system implemented with Ajax and DHTML. The system represents structured documents in OPML (which is XML-based). These documents are pulled by the client. Once the client has the XML, it applies XPath and XSLT in order to implement HyperScope's advanced addressing, rendering, and content filtering capabilities. Once these are applied, the manipulated XML is rendered as HTML, which is then displayed to the end-user.

Dojo and Sarissa

We use the following features of Dojo to aid in building the core addressing and rendering system as well as the user interface.

Sarissa provides easy, cross-browser APIs for instantiating XSLT stylesheets on the client-side, running them, executing XPath against an XML document, etc. This keeps our application code cleaner because it does not have to contain the cross-browser branching for XSLT and XPath. The branching is hidden behind a simpler, unified API.

Application Packages

The system is partitioned across a number of JavaScript packages:

hs.address Represents HyperScope's sophisticated addressing schemes; provides an external API to easily resolve a given address into an XML document or fragment that can be worked with
hs.model Represents our domain objects, such as an hs.model.Document that is a document that can be jumped through, rendered, have viewspecs applied to it, etc., and hs.model.Node's, which are node's in a document that can be jumped between, etc.
hs.exception Custom application exceptions, such as hs.exception.InvalidAddress and hs.exception.Filter
hs.filter A unified concept called filters, which are akin to a Java interface that have a single method called apply(). Much of the system is implemented as hs.filter.Filters, which take an hs.model.Document, work on it, then return it. For example, the process of transclusion is a filter.
hs.util Collection of classes that provide utility to the rest of the system, such as hs.util.XMLFetcher, which will fetch a remote XML document or return it locally from it's cache if already loaded
hs.ui Package with UI chrome, seperated from the rest of the system so we can attach other UIs in the future
hs.commands Command facade that allows the UI to easily execute commands against the core, such as hs.commands.jumpItem().

How Classes Work Together

From a high-level, the process of rendering a document always follows the following process, using these classes:

  1. We obtain an hs.address.Address, either from the browser's current location (on page load), from the user typing it in, from a hyperlink clicked on in the document, etc.
  2. This address internally uses helper classes to tokenize a string address, such as the address #025.n2u!2A, into an object form that can be worked with and interpreted.
  3. The UI (hs.ui) calls a particular command, such as hs.commands.jumpItem with the hs.address.Address
  4. We call hs.address.Address.resolve()
  5. Resolve() works it's magic and is the heart of the system. Externally, we call resolve() and magically get back an hs.model.Document that is ready to go and display, so externally we are sheltered from the following which internally occurs:
    1. It internally expands relative addresses against where we are currently located and our current state. For example, it might expand the relative filename ../../someDir to it's full URL so we can work with it.
    2. It fetches the XML document in the background if we need it and haven't loaded it before
    3. It executes each "piece" of the address, such as 025 then .n then .2u etc. in the example above, applying them to our XML document
    4. It simplifies our viewspecs and apply's them
    5. It executes content filters
  6. We can now render this document by calling hs.model.Document.render(); internally, this applies our rendering stylesheet to render the final document

Every address always follows this process. For example, if the user does a Jump to Item with the following relative address:


and they are viewing the following HyperScope document:!2A:m

then we simply apply the pipeline above over and over for each address entered by the user.

Unit Testing

The HyperScope addressing, rendering, and jumping schemes can be very sophisticated and can interact in a combinatorial way to produce many complicated edge cases. There is a huge footprint for these things.

To make sure we have high quality, and can handle all of the different ways the different features can play together, we have adopted an aggresive unit testing suite. We use JSUnit, a full JavaScript clone of JUnit complete with a DHTML test-runner, to write our unit tests. At this point there are probably thousands of discrete tests. There are unit tests across specific classes as well as integration tests at larger and larger levels to make sure things are working well together. These are all chained together into a single regression test that is run regularly across our target browsers, which are Firefox and Internet Explorer currently. All of the unit tests are written in JavaScript.


original screencast