Skip to content

Tracing Storage Traversal

Ken Manheimer edited this page Mar 6, 2013 · 7 revisions

Table of Contents

Intro

The SpiderOak HTML5 mobile client is implemented using a Model View ViewModel architecture, which significantly helps tame code complexity, but can obscure control flow. In this document we describe the control transitions that happen when traversing an account's storage content hierarchy, in order to provide orientation useful for understanding how content traversal works and how to extend it.

Background

By using Backbone, Backstack, and other fairly lightweight Model View ViewModel facilities, we are able to interconnect model, view, server synchronization, and etc. code, without the inherent complexities that comes from tightly coupling functions across separate concerns.

While allowing us to write simpler, less conflated functions, that decoupling also presents a challenge to tracing control flow across the program. For orientation, particularly for people getting acquainted with Backbone/Backstack (including some of the authors), we offer this examination of user account storage navigation control flow.

We use the techniques described in the wiki Home page Running Testing and Debugging section to do the tracing.

(The subject code is under active development at time of writing, so the accuracy of this account is likely to drift. We hope it will remain useful, conveying the principles and techniques for understanding the control flow, even as the specifics change.)

Some documentation formatting details

  • a leading '.' dot on function and element names means they are attributes of the spiderOakApp object
  • leading '↓' single down-arrow indicates that the described function directly invokes a subsequently described function
    • a leading '⇓' double down-arrow indicates that the described function indirectly - asynchronously or via an event - triggers another function
  • a leading '→' single right-arrow indicates that the described function was directly invoked by an above described function. (Possibly but not always the most recent one with a '↓' single down-arrow.)
    • a leading '⇒' double right-arrow indicates that the described function was indirectly invoked - asynchronously or by event - by an above described function. (Possibly but not always the most recent one with a '⇓' double down-arrow.)
  • All of the views are associated with DOM $el elements, usually in their .initialize() methods, and they present their renderings there. (This is regular Backbone stuff, but I skip over most of the initialize actions, so worth mentioning once.)

Tracing Storage Traversal

We trace a single path through the code, focusing on successful actions to traverse downward a few levels into a user's storage content hierarchy.

1. User fills in the login form and taps the Log in button.

» The system responds with an "Please Wait / Authenticating" busy dialog, and eventually returns with a page entitled "Spider Oak" and the app's slide-right menu button.
  • Hitting the "Log in" button fires the loginButton_tapHandler event, associated with the button by the spiderOakApp.LoginView.events object ( src/views/LoginView.js)
  • That event triggers the .LoginView.loginButton_tapHandler(event) function:
    • Prevent the default tap handling
    • ↓ Invoke this.form_submitHandler(event)
  • → .LoginView.form_submitHandler(event):
    • use .dialogView.showWait(...) (from src/views/DialogView.js) to present a busy dialog
    • collect the form credentials and data, blur the document activeElement, etc
    • ⇓ using .AccountModel() ( src/models/AccountModel.js ), do account.login() with the credentials, plus success and error callbacks from this object.
  • ⇒ successful account.login() invokes the form_submitHandler() success(apiRoot) function, passed-in by and residing in .LoginView.form_submitHandler():
    • set some incidental login metadata (rememberme)
    • ↓ do this.dismiss()
      • → .LoginView.dismiss(): shuffles some css so the login form is dismissed
  • ⇒ ⇓ successful .AccountModel.login() fires the loginSuccess event
  • ⇒ the loginSuccess event triggers .onLoginSuccess (from src/app.js ):
    • hide class="modal" and class="wait-dialog" divs
    • ↓ create the .MenuSheetView() and .MenuSheetView.render() it.
  • → .MenuSheetView.render() ( src/views/MenuSheetView.js ):
    • create the .DevicesCollection() ( src/collections/DevicesCollection.js )
    • assign the .DevicesCollection.url from accountModel.getStorageURL()
    • ↓ create the devicesListView ( src/views/DevicesView.js ) with the devices collection
      • → .DevicesListView().initialize(), .render() ( src/views/DevicesView.js ):
        • fetch the account's devices and render them
    • equip the MenuSheetView with iScroll, for scroll features absent outside of Android context.
Notes:
  • » Dismissing the login view (.LoginView.dismiss(), above) reveals the generic (index.html) main page
  • » The main page has a menu button by which the user can expose the menu sheet.

2. User taps the menu button, in the upper left-hand corner of the main page.

» The system slides the main page to the right, revealing the menu sheet (or slides the main page to the left, hiding the menu sheet, if the page was already to the right).
  • ⇓ .MainView.events() ( src/views/MainView.js ) associated the index.html DOM element with class="menu-btn" with the menuButton_handler event, fired by tapping the button.
  • ↓ .MainView.menuButton_handler() calls .MainView.openMenu() (if it was closed; otherwise, it calls .Mainview.closeMenu()).
  • → .MainView.openMenu() adjusts CSS to slide the main page right and mark the menuButton state as "open".
  • ⇓ .MainView.openMenu() also triggers the "menuOpening" event.
  • ⇒ the "menuOpening" event is caught by the .MenuSheetView object, invoking .MenuSheetView.menuOpening(), which invokes the iScroll .menuScroller.refresh() method, to refresh the menu sheets scrolling provisions.

3. User taps one of the menu-sheet device entries.

&raquo: The main page is occupied by entries showing the contents of the tapped device, and made prominent by sliding the main page back to hide the menu sheet.
  • ⇓ Rendering the .MenuSheetCollection includes rending of the devices collection. That associates each .DevicesView.DeviceItemView's "a_tapHandler" event with its entry, which is raised on tap.
  • ⇒ the "a_tapHandler" event triggers the respective view's .DeviceItemView.a_tapHandler() method, which:
    • calls .mainView.closeMenu() to adjust CSS that slides the main page left to cover the menu-sheet.
    • !! » packages the specific device model, and some attributes, in an options object, to be passed to a folder view
    • removes the "current" class from the li element within the id "menusheet" div, and adds the "current" class to the device entry's li element.
    • uses the Backstack viewsStack to make and present a FolderView, passing the prepared options object to convey the specific device model to be presented by the folder view
      • (The view is pushed on the stack if it's empty, not added to the stack at all if the current view currently presents the device's model, and made to replace the contents of the stack if there are other, different contents.)
» The packaging of the specific device model in the options, to be passed to the folder view, is distinguished, above, because it was the key to resolving confusion the author had about how the device was conveyed to the FolderView.

4. User taps one of the Device's folder subentries on the main page.

» The main page switches to present the contents of the tapped device's contents, subfolders and constitutent files alike.
(Details about delving into the device contents FolderView remains to be done. The mechanisms are probably quite similar to those described for the above activities, so the reader should have enough orientation to figure it out. I have enough to resolve my immediate questions, so may leave exposition of this step as an exercise for the reader - though I would not mind someone actually filling it in, and may just do so myself, at some point, if/when time allows.)