mix stdlib viewstack - remixlabs/docs-public GitHub Wiki

Standard library - viewstack

module viewstack
  // manipulation with immediate display effect
  def load : appViewRequest -> null
  def push : appViewRequest -> data
  def pushWithTransition : string -> appViewRequest -> data                   // since PR 989
  def switchTop : appViewRequest -> data
  def switchTopWithTransition : string -> appViewRequest -> data              // since PR 989
  def switchCaller : number -> appViewRequest -> data
  def switchCallerWithTransition : number -> string -> appViewRequest -> data // since PR 989
  def pop : data -> null
  def popOrIgnore : data -> null
  def popCaller : number -> data -> null
  def toFront : number -> data -> data   // since PR 1160
  def reload : null -> null
  def reset : null -> null

  // manipulation of invisible views
  def drop : number -> null
  def swap : number -> number -> null

  // inspection
  def length : null -> number
  def get : number -> data
  def currentViewName : null -> string   // since PR1428
  def currentViewId : null -> string     // since PR1425

  // dynamic requests
  def dynamicAppView : string -> data -> appViewRequest
  def screenAppView : string -> data -> appViewRequest    // since PR1201
  def moduleAppView : string -> data -> appViewRequest    // since PR1201
  def agentAppView : string -> data -> appViewRequest     // since PR1201
  def currentAppView : null -> appViewRequest
  def entryRedirection : appViewRequest -> appViewRequest            // since PR1404
  def screenRedirection : string -> appViewRequest -> appViewRequest // since PR1404

  // schedule actions
  def schedule : data -> data -> null   // schedule a coroutine with same backgroundness as current routine
  def scheduleFG : data -> data -> null // schedule a foreground routine
  def scheduleBG : data -> data -> null // schedule a background routine

  // embedded views
  def embed : appViewRequest -> embeddedView
  def embedWithOptions : appViewRequest -> data -> embeddedView
  def embedRemote : appViewRequest -> data -> embeddedView
  def terminate : embeddedView -> null
  def cellMessages : embeddedView -> data*
  def invoke : appstate -> embeddedView -> data -> data -> data -> data
  def close : data -> null
  def popOrClose : data -> null

  // embedded views with compiler sessions
  def embedCompiler : data -> embeddedView                 // since PR 909
  def getSessionOfRemote : embeddedView -> track.sid       // since PR 909
  def sendRequestToRemote : embeddedView -> data -> null   // since PR 909
  def errorMessages : embeddedView -> data*                // since PR 909
  def logMessages : embeddedView -> data*                  // since PR 909

  // embedded viewstacks and agents
  def selfTerminate : appstate -> null   // since PR 1657

  // call spreadsheet propagation
  def propagate : appstate -> null   // since PR#799
  def flush : appstate -> null

  // helpers
  def isLocation : data -> bool
module end

The current viewstack

Most of the functions in viewstack target the current viewstack, i.e. from the perspective of the view calling the functions. This can be the viewstack that is mainly displayed on the screen of the device, or an embedded viewstack.

The viewstack consists of a list of views, ordered from front to back. The view in front has index 0, and the view at the bottom of the stack has index n-1.

Only the view in front is active and can react on user input.

REPL sessions start with an automatically created view called $REPL so that there is always a view. (Viewstacks are never empty.)

Manipulation with immediate display effect

Create appViewRequest:

  appView M(p1, p2, ...)

An appViewRequest is a value describing how to invoke a module as a view. You can create such a request via the special appView keyword. For example, when M is a module, appView M is the request to use M as view. It is also possible to pass parameters, like in appView M(p1,p2). See below for a way how to set the module name dynamically.

  def load : appViewRequest -> null
  def push : appViewRequest -> data
  def pushWithTransition : string -> appViewRequest -> data                   // since PR 989
  def switchTop : appViewRequest -> data
  def switchTopWithTransition : string -> appViewRequest -> data              // since PR 989
  def switchCaller : number -> appViewRequest -> data
  def switchCallerWithTransition : number -> string -> appViewRequest -> data // since PR 989
  def pop : data -> null
  def popOrIgnore : data -> null
  def popCaller : number -> data -> null
  def toFront : number -> data -> data   // since PR 1160
  def reload : null -> null
  def reset : null -> null

With viewstack.load(req) the views on the viewstack are discarded, and replaced by a single new view obtained by executing the request req.

viewstack.push(req) creates a new view by executing req and puts it on top of the current view. The current view is suspended (set to inactive, and the coroutine is waiting). When this view is again at the front (i.e. all other views on top are popped away), the view is activated again and displays cell messages. The return value of push is the value coming from the immediately preceding pop call.

viewstack.switchTop(req) creates a new view by executing req and replaces the current view with the new view. (NB. for some time this function was called just viewstack.switch but "switch" will be a keyword soon.)

viewstack.switchCaller(k,req) first removes k views from the top of the stack, and then replaces the top-most view by a new one as switch would do. For example, viewstack.switchCaller(1,req) drops the current view, and replaces the preceding view with the new view.

There are variants XXXWithTransition taking an additional string parameter. These variants send a special message to the client announcing the push or switch (unless the parameter is "$unused"). The parameter is the transition type.

viewstack.pop(v) removes the current view from the top of the stack, and resumes the view below that view. The value v is passed back to the viewstack.push call. Note that it is considered as an error when there is no underlying screen to return to.

viewstack.popOrIgnore(v) works like pop but does not throw an error when the screen is the topmost one and there is no other screen to return to. The request is ignored in this case.

viewstack.popCaller(k, v) first removes k extra views from the top of the stack, and then removes another one as pop would do.

viewstack.toFront(k, v) brings the view with index k to the top of the stack so that it is visible and then pops to it, passing the value v to this view as if we ran popCaller(k, v). Additionally, the current view is suspended as if we ran push, and when the new front view pops back the passed value is returned as result of the toFront call. (Note that toFront(0, v) is a no-op and just returns v.) toFront is useful for implementing tab views where the tabs are represented by views on the stack, and when the user wants to see a certain tab it is brought to the front position by a toFront call.

A viewstack.reload() evaluates the current spreadsheet again while keeping any input values. In particular, this forces the db queries to be executed again (hence the name). A viewstack.reset() goes beyond that, and also resets the inputs to their initial values (like switching to the current screen, i.e. local edits are discarded).

Manipulation of invisible views

  def drop : number -> null
  def swap : number -> number -> null

These functions can only target views that are currently not at the top of the stack. With viewstack.drop(k) the view with index k is removed from the stack (k >= 1). With viewstack.swap(j,k) the views with indices j and k are swapped.

Inspection

  def length : null -> number
  def get : number -> data
  def currentViewName : null -> string   // since PR1428
  def currentViewId : null -> string     // since PR1425

The call viewstack.length() returns the number of views on the stack (always >= 1). The call viewstack.get(k) obtains a description of the view at index k.

The currentViewName() is the name of the current (topmost) screen or agent. Works also in the builder.

The currentViewId() is a string that uniquely identifies the view in the current session.

Dynamic requests

  def dynamicAppView : string -> data -> appViewRequest
  def screenAppView : string -> data -> appViewRequest    // since PR1201
  def moduleAppView : string -> data -> appViewRequest    // since PR1201
  def agentAppView : string -> data -> appViewRequest     // since PR1201
  def currentAppView : null -> appViewRequest             // since PR 478
  def entryRedirection : appViewRequest -> appViewRequest            // since PR1404
  def screenRedirection : string -> appViewRequest -> appViewRequest // since PR1404

With viewstack.dynamicAppView(name, [ p1, p2, ... ]) it is possible to create an appViewRequest dynamically from a module name that is given as string.

Since PR1201, there is a more fine-grained distinction between types of requests:

  • a screenAppView requests a screen, either backed by a Mix module or by a mesh registered as screen
  • an agentAppView requests an agent, either backed by a Mix module or by a mesh registered as agent (note that it is currently not yet possible to use such requests)
  • a moduleAppView requests a Mix module (i.e. meshes are ignored), whatever it implements

Since PR1201, the interpretation of dynamicAppView is changed so that it requests a screen if called from a screen, and an agent if called from an agent.

appView M(p1,p2,...) is the same as viewstack.moduleAppView("M",[p1,p2,...]).

The function viewstack.currentAppView returns the request that was responded by the current view. Note that this so far only works in the runtime - in the builder nonsense is returned.

Note that requests created with this viewstack module by default do not redirect to _rmx_entry like it is done for external requests. The request is carried out directly. You can, however, enable the redirection to _rmx_entry for a particular request by calling viewstack.entryRedirection, e.g.

let req =
  viewstack.dynamicAppView(name, [p1,p2])
  |> viewstack.entryRedirection;
viewstack.push(req)

This means that the screen _rmx_entry is pushed instead of name, and that _rmx_entry receives the original request as parameter (see the page about _rmx_entry).

In addition to this, there is also viewstack.screenRedirection where the name of the screen to redirect to can be freely chosen. screenRedirection("_rmx_entry", req) is the same as entryRedirection(req).

Schedule actions

  def schedule : data -> data -> null
  def scheduleFG : data -> data -> null
  def scheduleBG : data -> data -> null

The first param is an action, and the second param is the argument of the action. The viewstack schedules that this action is executed in the near future. schedule creates a coroutine inherits foreground/background status from the parent routine, the FG/BG forms let you specify which you want.

An example is:

viewstack.schedule( action (p) {
  debug("p = " + p)
}, "123")

This will output "p = 123" in the near future to the debug console.

viewstack.schedule can be used in any situation, even while the spreadsheet propagation is ongoing.

Because the scheduled action is considered as a new user interaction, viewstack.schedule can be used to program loops via on triggers, e.g.

var cell x = 0
on (x) do {
  debug({x});
  if (x < 10) {
    // The next line doesn't work because "on" fires only once per user interaction:
    // x = x + 1
    // Workaround: schedule the assignment
    viewstack.schedule(action { x = x + 1 }, null)
  } 
}

Embedded views

  def embed : appViewRequest -> embeddedView
  def embedWithOptions : appViewRequest -> data -> embeddedView
  def embedRemote : appViewRequest -> data -> embeddedView
  def terminate : embeddedView -> null
  def cellMessages : embeddedView -> data*
  def invoke : appstate -> embeddedView -> data -> data -> data -> data
  def close : data -> null
  def popOrClose : data -> null

There is now enhanced documentation: Embedding viewstacks and applications

An embeddedView is actually a whole viewstack that lives side by side with the normal one that drives the display. An embeddedView can internally push, pop, switch, etc., i.e. it has its own navigation. The view currently at the front of the embedded viewstack may emit cell messages, and it is possible for the caller of viewstack.embed to observe these messages.

Action closures defined by an embedded view can be invoked just like the closures from the standard viewstack.

By calling viewstack.embed(req) the new viewstack is created, and the initial view is obtained by executing req.

The function viewstack.embedWithOptions(req,opts) additionally also takes an object with options, opts. So far this can only be:

let opts =
  { trace: <bool> }

Options:

  • trace: whether to enable tracing for the embedded spreadsheet

It is also possible to embed a whole remotely running application with viewstack.embedRemote(req,opts) (since PR#490). The opts are:

let opts =
  { baseURL: <string>, app: <string>, token: <string or OAuthToken> }

Options:

  • baseURL is the amp URL of the remote application
  • app is the application ID of the remote application
  • token is the security token for getting access

The function viewstack.cellMessages(ev) returns a stream with cell messages. These are records { cellname : string, cellvalue : data }. There are some special cell names starting with a dollar sign. In particular, the name $location is used when the value describes the new location the embedded stack navigates to.

The function viewstack.terminate(ev) terminates the whole viewstack and causes that the stream with cell messages ends.

With viewstack.invoke(appstate,emb,ac,arg,opts) an action closure ac of an embedded view emb can be invoked with argument arg. This calls also takes an object with options, opts:

  • nextActions: the list with next actions to pass to the action closure

The function invoke returns a status object with:

  • ok: whether the closure was found
  • nextActions: the list with next actions that need to be run next

If the embedded view was opened with the help of the embed module, viewstack.close(v) closes the embedded view and passes v to the embedding view (by sending a $close message). The function viewstack.popOrClose(v) checks whether there is an underlying view and pops back to it if possible, and otherwiese it behaves like close.

Example

When the viewstack is created only once and never replaced by a different one:

cell emb = viewstack.embed(appView M)
live cell msg = viewstack.cellMessages(emb) | co.consume

Now, msg is set to always the latest cell message. Often, it is a good idea to filter out any messages except a particular one with the DOM:

cell flt = viewstack.cellMessages(emb)
         | stream.filter(msg -> msg.cellname == "myView")
live cell msg = flt | co.consume

Note that the filter should not be placed into the live cell as messages can otherwise be lost.

The function co.consume expects at least one element to return, or the page will hang. Be careful with the stream.filter and do not filter everything out.

When the viewstack may be replaced by a different one, in particular because a parameter p changes:

cell emb =
  let _ = if (emb'?) viewstack.terminate(emb') else ();
  viewstack.embed(appView M(p))

The trick here is to terminate the previous viewstack when the cell is evaluated again.

Embedded views with compiler sessions (since PR 909)

  def embedCompiler : data -> embeddedView                 // since PR 909
  def getSessionOfRemote : embeddedView -> track.sid       // since PR 909
  def sendRequestToRemote : embeddedView -> data -> null   // since PR 909
  def errorMessages : embeddedView -> data*                // since PR 909
  def logMessages : embeddedView -> data*                  // since PR 909

The embedCompiler function creates a new remote session (like embedRemote) but as compiler session instead of a pure runtime session. The function supports the following options:

  • baseURL is the amp URL of the remote application
  • app is the application ID of the remote application
  • token is the security token for getting access
  • options is a sub object with compiler-specific options

Compiler sessions are initially empty and run no code. You first have to send code to it. With viewstack.cellMessages you can retrieve the cell messages like for other embedded views. viewstack.terminate also works for compiler sessions.

In order to use compiler sessions there are a couple of helper functions:

  • getSessionOfRemote returns the session ID as string
  • sendRequestToRemote sends a message to the compiler session (example below)
  • errorMessages returns a stream with error objects, emitted when the compiler or the runtime run into an error
  • logMessages returns a stream with log objects coming from the compiler session

Example:

let emb = viewstack.embedCompiler( { baseURL: env.baseURL, app: env.app, token: env.token() });
let sid = viewstack.getSessionOfRemote(emb);
let msg = track.msgCompilerRunPhrases(sid, [ "def pi = 3.14" ]);
sendRequestToRemote(emb, msg)

Embedded viewstacks and agents

  def selfTerminate : appstate -> null   // since PR 1657

selfTerminate(appstate) terminates the current viewstack, which is only allowed for embedded viewstacks that were created by the user, and for local agents running postponed actions.

Call spreadsheet propagation

  def propagate : appstate -> null   // since PR#799
  def flush : appstate -> null

The propagate(appstate) function can be called in actions when an immediate propagation is needed. Example:

var cell x = 10
cell y = x+1
cell a = action {
  x = 11;
  viewstack.propagate(appstate);
  debug("y=" + y)
}

Without calling propagate the change of x would first be propagated at the end of the action.

flush(appstate) goes one step further, and also flushes all buffers so that the output is immeidately sent to the client.

⚠️ **GitHub.com Fallback** ⚠️