     #
     # @param path the path to show links for
     #
-    # @return this
+    # @return {Promise} that is resolved when all the indices are visible
     showIndices = ( path ) =>
       log "showIndices( #{path} )"
 
+      dfd = new $.Deferred().resolve()
       trails = getIndexTrail path
 
-      # mark all link elements as stale before we iterate through the
-      # path
-      $( '.' + @options.index ).addClass @options.stale
-
       for level in [ 0...trails.length ]
 
         trail = trails[ level ]
-
-        # If an element with the index level's class is not on the page,
-        # create a div with the class
-        levelClass = getIndexClassForLevel level
-        $index = $( '.' + levelClass )
-        if $index.length is 0
-          $index = $( '<div />' )
-            .addClass( @options.index + ' ' + levelClass )
-            .data( 'pseudositer', { } )
-          @$el.append $index
-
-        # If the previous path is the same, leave the element alone
-        # otherwise, change the path, empty, and append new links
-        prevPath = $index.data( 'pseudositer' ).path
-        unless trail is prevPath
-          $index
-            .empty()
-            .data( 'pseudositer', path : trail )
-            .append @pathIndices[ trail ]
-
-        # ensure that this index element is not removed.
-        $index.removeClass @options.stale
+        dfd = dfd.pipe -> trigger 'create.index' dfd, level, @pathIndices[ trail ]
+
+
 
       # remove any index elements that are deeper than the new path
-      $( '.' + @options.index + '.' + @options.stale ).remove()
-
-      this
-
-    # Clear the displayed content
-    #
-    # @return this
-    clearContent = ( ) =>
-      log "clearContent( )"
-
-      $( @$content ).empty()
-
-      this
+      trigger 'destroy.index', new $.Deferred(), trails.length
+
+      dfd.promise()
 
     # call init, and return the output
     @init()
 
   # default options
   $.pseudositer.defaultOptions =
-    content   : 'pseudositer-content'
-    stale     : 'pseudositer-stale'
-    index     : 'pseudositer-index'
-    linkSelector     : 'a:not([href^="?"],[href^="/"])' # Find links from an index page that go deeper
-    loadingEvent     : 'loading.pseudositer'
-    loadedEvent      : 'loaded.pseudositer'
-    finishedEvent    : 'finished.pseudositer'
-
-    hideContentEvent : 'hide.content.pseudositer'
-    showContentEvent : 'show.content.pseudositer'
+
+    linkSelector   : 'a:not([href^="?"],[href^="/"])' # Find links from an index page that go deeper
+
+    # default handlers
+    loading.pseudositer      : [ showLoadingNotice ]
+    destroy.index.pseudositer: [ destroyIndex ]
+    create.index.pseudositer : [ createIndex ]
+    load.image.pseudositer   : [ loadImage ]
+    load.text.pseudositer    : [ loadText ]
+    hide.content.pseudositer : [ hideContent ]
+    show.content.pseudositer : [ showContent ]
+    show.error.pseudositer   : [ showError ]
+    done.pseudositer         : []
+    always.pseudositer       : [ hideLoadingNotice ]
+
     timeout          : 2000
     recursion        : true
 
   # Default handler map.  The handlers will be called with a deferred and
   # a path to the content with the named extension.
   $.pseudositer.defaultMap  =
-    png : imageLoadEvent
-    gif : imageLoadEvent
-    jpg : imageLoadEvent
-    jpeg: imageLoadEvent
-    txt : textLoadEvent
-    html: textLoadEvent
+    png : 'load.image'
+    gif : 'load.image'
+    jpg : 'load.image'
+    jpeg: 'load.image'
+    txt : 'load.text'
+    html: 'load.text'
 
   $.fn.pseudositer = (hiddenPath, options, map = {}) ->
 

    # @return this
    @destroy = =>

      # Unbind events
      # Empty out the original element and unbind events
      $( window ).unbind 'hashchange', update
      @$el.empty()
        .unbind imageLoadEvent
        .unbind textLoadEvent

      this

    ###
    # Private methods.
    ###

    # Get the class name for an index of a specified level.
    #
    # @param level the {int} level, starting with 0, of the index.
    #
    # @return {String} the name of the class
    getIndexClassForLevel = ( level ) =>

      @options.index + '-' + level

    # Construct the absolute path for an ajax request from an absolute
    # path that would have been found in a fragment.

          else
            @realPath = @visiblePath + '/' + hiddenPath

      # Bind hash change to callback
      $( window ).bind 'hashchange', update

      # Set up handlers
      for event in events
        for handler in @options[ event ]
          @$el.bind event, ( args... ) => handler.apply( @$el, args )

      # Immediately update view
      update()
