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.
- Dojo's event system (dojo.event)
- Dojo's string handling (dojo.string)
- Dojo's HTML, CSS, and DOM libraries (dojo.html, dojo.style, dojo.dom)
- Dojo's production system optimizations (Dojo's build files, profiles, compressor, etc.)
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.
|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:
- 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.
- 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.
- The UI (hs.ui) calls a particular command, such as hs.commands.jumpItem with the hs.address.Address
- We call hs.address.Address.resolve()
- 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:
- 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.
- It fetches the XML document in the background if we need it and haven't loaded it before
- 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
- It simplifies our viewspecs and apply's them
- It executes content filters
- 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:
then we simply apply the pipeline above over and over for each address entered by the user.
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.