The controller orchestrates app startup, coordinates state variables, the Model, and the View, and manages persistent storage. The app communicates with the Controller using the ctl object, which contains the Controller's methods and properties.
When the app starts up, the initial app file (usually app.js, see App Startup) imports all the pages the app requires and calls ctl.startApp() with information like the app's title and version number, as well as a template for the initial View. This is usually a Component reference to the Nav page. For example:
tml`<Nav ...${navProps} />`
where navProps contains attributes for page layout and the initial page.
ctl.startApp() simply records the passed information and sets up the View's root template. Once the app is fully loaded, the Controller does the following:
init() method to do any remaining setup after the app is fully loaded and the saved state is restored.view.init() to initialize the View and instantiate the DOM elements for the root template.ctl.doSync() to immediately synchronize the View with the current values of state variables.ctl.isInitialized to true.server.startSession() to let the server know a new session has started.When the Controller is initialized, it evaluates the computeInfo for every page. For each object in a computeInfo array, it does the following:
inputs, outputs, and referenced arrays.computeInfo that supply inputs to any object in this computeInfo. The list includes the precedents of the precedent pages. The list is roughly ordered so that precedents of precedent occur first, though circular dependencies are allowed.io state variable namespace to be passed to the computation function consisting of those specified by the inputs, outputs, and referenced arrays.When the Model is executed, the Controller goes through all pages, checking to see if any page's or page precedent's computations are out of date with respect to their inputs, using the previously computed input SvMonitors. If a page or its precedent pages have out-of-date computations, they are executed in order: the precedent page first, then the computInfo computations. This means that it's more efficient to arrange the computeInfo array so that computations that have outputs used as inputs to other computations appear earlier in the array.
When a computation in a computeInfo object is out of date, the Controller sets the state variables in the output array to their default state before calling the computation function. If the computation function terminates early due to an error, it does not have to worry about resetting outputs computed later in the function. This also prevents outputs from one invocation from accidentally remaining through the next invocation. If a state variable is referenced by a function but should neither trigger the computation when changed nor be reset to its default value, then it should be included in the referenced array so that it will appear the the io namespace.
The Controller repeatedly executes the Model until there is no computation that remains out of date. If the Controller is forced to do too many passes, it will display an assertion failure.
Model-type state variables (e.g. all state variables associated with visible inputs and outputs) call ctl.sync() to schedule a synchronization whenever the state variable is assigned a changed value (according to the state variable's change criteria). The actual synchronization happens later when the current thread of execution terminates. This allows many state variables to change in the thread while scheduling only one synchronization event. In addition, the Controller interposes on click and change events to ensure that synchronization is scheduled when a user interaction occurs.
When the synchronization event fires, it calls ctl.doSync(), which executes the Model as described above. When Model execution stabilizes, the Controller calls view.sync() to have the View synchronize the DOM state with the updated state variable values caused by Model execution.
The Controller maintains persistent storage for saved application state on the device and on the server. The storage system uses the DOM localStorage for saved data. The server-based storage is accessed using HTTP requests. See Server API and Reference.
When the controller completes a synchronization, it checks to see whether any saved page state has changed since the last save by calling each page's hasChanged() method. If any savable state has changed, it builds a saved state object by calling each page's getSavedState() method and stores it locally and, if online, to the server.
In general, only state variables with a save property of 'local' or 'global' will be saved in persistent storage. Both types are saved to local device storage and to the server. The difference between the two comes when values are restored. When state variables marked 'local' are restored, the data for that state variable in local storage always overrides the server data.
This is useful, for example, for recording that the user considers this device to be only used by themselves, and there is no need to display and confirm the terms of use every time the application starts.
The server requires some way of identifying users to associate with saved data. By default, the Controller creates a GUID (globally unique ID) using a timestamp the first time it starts up on a particular device. This becomes the user ID within the saved data and for the id argument of the server request. The server will keep backups using this ID as well.
If the user enters an email address and security word, the user ID used to save the data and record other server requests becomes a combination of those two items. The only purpose of the security word is to ensure that third parties that know a user's email address can't access the data without also knowing the security word. The security word functions like a password, but it does not require prior registration with the server, nor is it stored or transmitted securely.
The email address and security word combination can be used to reinstate the last saved data when opening the app on a new device. When a new combination is entered, the Controller checks whether the combination exists on the server. If the combination does not exist, it askes the user to confirm whether they would like to create new saved values for this combination in case they typed something wrong. If the combination already exists on the server, it asks the user whether they'd like to use the device or cloud data. If the former, the current device data will overwrite the cloud data. The user can request that the server email a list of all the security words associated with that email address in case they've forgotten it.
If the Controller detects that the data has been updated by another device, it asks the user whether they'd like to use the device or cloud data. If the latter, the server data will overwrite the device.
The server maintains up to four daily backup copies of the data. When a new set of data is saved to the server and more than 24 hours have passed since the last save, the server will save the old version as a backup, deleting the oldest backup if there are more than four. The user can request restoring the local data from one of the previous backups.
The Controller tracks the following events:
isActive property and the onActivityChange() methodisOnline property and the onConnectivityChange() methodview.resize() and the onResize() method