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.
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:
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.
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.
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
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!).
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:
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:
@DataView("Show volume", duration_guess=1)
def show_volume(self, params, **kwargs):
volume = params.length * params.width * params.height
data = DataGroup(
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.
- 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
GeometryVieweach 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
ImageVieweach time the user refreshes the corresponding view tab or modifies the input within the editor (the figure is never stored).
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.
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.
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.
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
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.
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.
Another common example is the implementation of a
DownloadButton (e.g. generating a result report) and a
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.
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!