Skip to main content

VIKTOR's call flow

As a developer it is important that you fully understand how the VIKTOR platform handles code-calls when a user interacts with an application within his or her browser, in order to write effective code and prevent unexpected bugs. VIKTOR's coding philosophy might be different from the approach you are familiar with, especially if you are used to writing standalone (Python) scripts.

Triggering actions

The app's code (within the controller of the current entity type) is triggered if (and only if) one of the following actions take place:

Modifying input

Probably the most occurring trigger of the app's code is by changing the value of an input field (e.g. updating the value in a NumberField). The entry point in the code is the currently active view-method.

note

If the current view is 'slow', the method is not triggered.

Refreshing a view

Similar to modifying the input, the currently active view-method is triggered if the user refreshes the view. This takes place at entering the editor, switching view tab, or, in case of a 'slow' view, by clicking the 'update' button.

Clicking a button

The third way of triggering the code is by clicking a button (e.g. DownloadButton, OptimizationButton (previously OptimiseButton), SetParamsButton). The entry point in the code is the method linked to the button that is clicked.

Pre-process upload file

A less typical case occurs when defining an entity type as file. During the uploading of the file, a method with the ParamsFromFile is triggered (a method with this decorator on the controller class is required).

In all cases, a method on the corresponding controller class is called, which ends with returning the required result to the platform (e.g. DataResult for a DataView, DownloadResult for a DownloadButton, etc.). The call (also called job) is completed after this return and code execution is terminated, waiting for a next trigger (which might already be present in the queue). The next trigger follows the same procedure (and if it is the same action it actually runs the same piece of code again!).

Stateless code

A consequence of above-mentioned repetitive call flow is that the app's Python code is stateless (though within a call-cycle the code can still be stateful). This means that data is not stored within the code (e.g. on a global variable) for using it in the next call cycle. Instead, the data is recalculated each time it is requested. The only data that is stored (and thus can be used for the next call cycle) are the parameters (params) on each of the entities within the app. This makes the coding philosophy for writing a Python app using VIKTOR essentially different than for writing a standalone Python script, of which the developer should be aware.

VIKTOR's stateless design is clarified in the following example:

Imagine a Cube (entity) defined by its length, width en height, that can be set by the user. The parametrization of this entity would look something like this:

class Parametrization(ViktorParametrization):
length = NumberField("Length")
width = NumberField("Width")
height = NumberField("Height")

If we want to show the volume of this cube to the user, we can do so by creating a DataView, in which the volume is calculated and returned:

class Controller(ViktorController):
...

@DataView("Show volume", duration_guess=1)
def show_volume(self, params, **kwargs):
volume = params.length * params.width * params.height

data = DataGroup(
DataItem('Volume', volume)
)

return DataResult(data)

Note that even though we calculate the volume of the cube, we never store this data. Each time the result is requested (e.g. when updating one of the dimensions in the editor), the above code is rerun and the volume is calculated anew.

Similarly:

  • A report is generated and returned as download each time the user clicks the DownloadButton (the report is never stored).
  • A 3D-model is created and visualized in a GeometryView each time the user refreshes the corresponding view tab or modifies the input within the editor (the 3D-model is never stored).
  • A plot of the results is drawn and visualized in an ImageView each time the user refreshes the corresponding view tab or modifies the input within the editor (the figure is never stored).

Single source

The examples of output above (report, 3D-model, plot) can be seen as representations of one and the same model (defined solely by its parametrization). Besides it is not possible to store these alternative representations, it is also not desired. What would happen if we would store the 3D-model of the cube and subsequently modify its length? Exactly, the visualization would be outdated, possibly without the user even noticing.

The concept of defining a 'model' by its minimal set of independent parameters is called single source modelling.

note

As a matter of fact, besides the parametrization also the summary is stored. The developer does however not have to care about keeping the summary up to date, as this is handled by VIKTOR internally.

Long-running results

Sometimes, recalculating the results each time the information is requested is too time-consuming (e.g. if doing some long-running analysis to calculate results) and therefore not an option. We call such type of output long-running results. Below are solutions to two of the most common cases.

Slow views

For example, a user would not expect (nor desire) a plot of the results of a long-running analysis (e.g. SCIA) to be cleared if he or she (accidentally) changes the value in a NumberField.

Here slow views come in handy. A slow view is a View that is refreshed by clicking its 'update' button. It is triggered by setting a high duration_guess. The user is notified by means of a warning if the result has become outdated, but the figure would remain visible.

note

It is not possible to link a SummaryItem to data within a slow view. As updating of the summary is done internally, doing so would be too time-consuming, rendering the application unresponsive.

Memoize

Another common example is the implementation of a DownloadButton (e.g. generating a result report) and a View (e.g. for showing the results in a plot) calling the same analysis. If a user wants to visualize the results within the view and download the report to its local hard disk, running the analysis twice would be undesirable.

Preferably, the result is stored internally, so that it will return instantly on the second (or third) call. The memoize function can be used to achieve exactly this.

Persistent storage

VIKTOR offers storage which can be used to store and retrieve files within an app workspace. The storage is persistent, meaning that the data will remain available with no time limit. This can be very helpful in cases where (intermediate) results need to be shared between jobs. For example, a long-running task is performed in job A, of which the results can be accessed in job B without the need to rerun the task. Read our guide for more detail!