Saltar al contenido principal
Version: próxima

awe-106 Actions and dependencies

In this tutorial we will see how to give some dynamism to the screen that we have developed. Thanks to actions and dependencies it is possible to make requests to the server, to modify the content of a component given the values of other components or to change the attributes of a criteria.

Actions

Actions are executions that can be done on client (browser) or server. This executions may be launched in an ordered way (synchronous executions) or in parallel (asynchronous executions).

Right now, actions may be launched from buttons (button-action) and dependencies (dependency-action). Both kinds of actions work in the same way and have the same attributes, the only difference between them is the time they are launched. The former ones are launched when you click the button and the latter ones are when the conditions of the launchers of the dependency are met.

There are several kinds of actions that may be executed on the client. The attribute type indicates the action we want to execute. The list of available actions can be found here.

<button-action type="reset"/>

This action will delete the content of all criteria and grids on the screen.

Reset criteria

When we want the action to be executed on a specific component, we can use its name in the target attribute of the action:

<button-action type="filter" target="[grid ID]"/>

This action will tell a grid to reload with the attributes it has defined.

Grid filter

Actions also allow us to navigate between application screens, in this case the target attribute says what option we want to access:

<button-action type="screen" target="name of screen we want to navigate to"/>

This action will indicate the system to change the screen for a new one.

Navigation action

Sometimes we want to show a dialog asking for confirmation with a message that we may define like this:

<message id="message identifier" title="message title" message="message description"/>

To open a dialog that uses this message, we will define an action of type confirm:

<button-action type="confirm"  target="message identifier"/>

This action will open a dialog with a confirmation message like the following:

Confirmation action

If the user clicks Accept, already defined actions will be launched. If clicks Cancel then the action stack to be launched will be emptied.

There are other actions like validate that also act on the action stack depending on the result. In case that validation is not successful, this action cancels the rest.

There are some actions that are launched against the server. These actions start with the word server and they require a series of extra attributes: the type of action that we want to execute in the server (server-action) and the identifier of that action (target-action):

<button-action type="server"  server-action="action in the server" target-action="action identifier"/>

If the action returns data (is of type data or value), we can say the action to what component we want to give the received data:

<button-action type="server"  server-action="action that returns data" target-action="action identifier" target="component that receives the data"/>
Server action = mark= actions returned by the server" src=/es/img/training/accion-servidor.gif />

Most of times, actions send to the server return more actions, that are stored in the stack before the pending actions (unless they are asynchronous). These actions returned by the server will be executed before the rest and one of them may even cancel the rest (type cancel).

Synchronous launching

Inside of a button or a dependency of type action you can define as many actions as you need:

<button label="BUTTON_CONFIRM" icon="save" id="ButCnf" help="HELP_CONFIRM_BUTTON">
<button-action type="validate" />
<button-action type="confirm" target="id mensaje" />
<button-action type="server" server-action="maintain" target-action="id maintain server" />
<button-action type="server" server-action="maintain-silent" target-action="id maintain server" />
<button-action type="filter" target="id componente" />
</button>

By default, all action defined are synchronous (they are executed sequentially given the order of execution), and no action is executed until the previous one ends. There are some actions, like cancel, that remove the rest of actions from the current action stack.

Action stack

While the current action stack contains actions in execution, all buttons in the application are disabled (to avoid the overload of actions).

Disabled buttons

If we open a modal dialog with dialog action, the current stack will freeze and a new one will be created that will manage dialog's actions and will become the current stack:

<button-action type="dialog"  target="dialog identifier"/>

When opening a new stack, the enabled/disabled state of buttons will depend on the new stack.

Modal dialog and new stack

Asynchronous launching

There is an attribute that modifies the way an action works: async="true"

<button-action type="server"  server-action="action that returns data" target-action="action identifier" target="component that receives data" async="true"/>

This action is stored in the stack of synchronous actions, as with the rest, but when it time to be executed arrives, it is removed from the synchronous stack (and the next one is executed), added to the asynchronous one and executed inmediatly.

Asynchronous actions

This allows several requests to the server or concurrent updates of several componentes to be launched. The fact that they are initially stored in the stack of synchronous actions is because this way we can cancel them if, for instance a validation fails or a confirmation is cancelled by the user.

Accesing actions workflow

AWE offers a tool for developer that allows to visualize the execution of the defined actions. You only need to type

alt + shift + `[number]`

This adds a delay in the execution of [number] seconds (you may change the amount) and also shows the execution of actions in each of the stacks. To the left you can find the asynchronous stack and to the right the synchronous. The current stack is the one found to the left of the synchronous stack.

Dependencies

Dependencies, along with actions, offer dynanism to the generated screen almost effortlessly. They can act on the component where they are defined or launch one or several actions on any screen component.

Dependencies are defined inside of the interactable components of the application:

On a button:

<button ...>
...
<dependency ...>
</dependency>
</button>

On a criteria:

<criteria ...>
<dependency ...>
</dependency>
</criteria>

On a grid:

<grid ...>
...
<dependency ...>
</dependency>
</grid>

For a dependency to be launched, all the conditions defined in launchers must be met. In the dependency a series of modifiers can be defined to treat the conditions of the launchers.

By default, the launching of the dependency is done when all conditions are met (and).

Launchers

Launchers are the conditions to be met for a dependency to be launched. The syntax goes as follows:

<dependency ...>
<dependency-element id="component identifier" condition="launching condition"/>
</dependency>

By default, launching condition is is not empty, that say, that the component has a value. Everytime the value of the component changes, all launchers will be checked and if all of them are valid, dependency will be launched.

Value

Value launchers are those that check the value of a component. If the value meets the defined condition, launcher is valid. For instance:

<dependency ...>
<dependency-element id="criterioA"/>
</dependency>

This dependency will be launched when criterioA has any value or when it changes.

<dependency ...>
<dependency-element id="criterioA" condition="eq" value="value1"/>
</dependency>

This dependency will be launched when criterioA has value value1

<dependency ...>
<dependency-element id="criterioA" condition="gt" value="3"/>
<dependency-element id="criterioA" condition="ne" id2="criterioB"/>
</dependency>

This dependency will be launched when the value of criterioA is greater than 3 and the value of criterioA is different from criterioB's.

Atributo

In some components we need the value of an attribute to launch the dependencies. Each type of component has a series of attributes that can be checked:

<dependency ...>
<dependency-element id="criterioA" attribute="unit" condition="eq" value="EUR"/>
</dependency>

This dependency will be launched when the unit of criterioA is EUR.

When working with grids and treegrids is necessary to define the column we want to access to to recover the value:

<dependency ...>
<dependency-element id="gridA" column="columnaA" attribute="selectedRowValue" condition="eq" value="R"/>
</dependency>

This dependency will be launched when the cell on column columnaA and selected row from gridA has the value R. In editable grids, the selected row is the one being edited in that moment.

selectedRowValue

Attributes are also useful to retrieve values from cells in a row and perform calculations with them. With currentRowValue the value of the current row is retrieved (these dependencies will be over columns, because otherwise is imposible to know the value of the current row).

<column name="columnaC" component="text">
<dependency ...>
<dependency-element id="gridA" column="columnaA" attribute="currentRowValue" alias="valorColumnaA"/>
<dependency-element id="gridA" column="columnaB" attribute="currentRowValue" alias="valorColumnaB"/>
</dependency>
</column>

This dependency retrieves the values of cells columnaA and columnaB from the same row where columnaC can be found. On their own, these launchers would not execute the dependency, simply it would be taken into account that those cells had any value. It would be necessary to include a event launcher that would cause a recalculation of the whole grid.

Event

Along with attributes, some components launch events at a given time. These events can be used to launch dependencies.

For instance, if we use the previous example and add anevent launcher, the dependency will be launched and the values of those columns will be retrieved every time that event is fired:

<column name="columnaC" component="text">
<dependency ...>
<dependency-element id="gridA" column="columnaA" attribute="currentRowValue" alias="valorColumnaA"/>
<dependency-element id="gridA" column="columnaB" attribute="currentRowValue" alias="valorColumnaB"/>
<dependency-element id="botonA" event="click"/>
</dependency>
</column>

Every time a user clicks on button botonA, whole column columnaC from grid gridA will be recalculated.

Recalculation with button

We can do it even more dynamic and, every time someone edits and saves a row, we will launch the recalculation:

<column name="columnaC" component="text">
<dependency ...>
<dependency-element id="gridA" column="columnaA" attribute="currentRowValue" alias="valorColumnaA"/>
<dependency-element id="gridA" column="columnaB" attribute="currentRowValue" alias="valorColumnaB"/>
<dependency-element id="gridA" event="save-row"/>
</dependency>
</column>

Every time someone saves a modification on a row, column columnaC from grid gridA will be recalculated.

Recalculation of a column

Modifiers

There are a series of modifiers that change the way the expressions of launchers are evaluated by default: the initial evaluation, the kind of evaluation to be done and the reverse evaluation.

Initial evaluation

Sometimes is necessary to perform an initial launch of the dependency to check if it has to be launched (by default, this is not done because it would make the application to perform quite badly). To indicate to a dependency that it should make an initial check of values when loading the screen, it is necessary to set the initial attribute to "true":

<dependency initial="true" ...>
<dependency-element id="criterioA" condition="gt" value="3"/>
<dependency-element id="criterioA" condition="ne" id2="criterioB"/>
</dependency>

This dependency will check when the screen is loaded the values from criterias criterioA and criterioB and will launch the dependency if conditions are met.

Kind of evaluation

By default, a dependency is launched when all launchers meet the conditions defined. This can be changed when the attribute type="or" (by default its value is "and"), that will launch the dependency when any of the launchers meet the condition defined.

<dependency type="or" ...>
<dependency-element id="criterioA" condition="gt" value="3"/>
<dependency-element id="criterioA" condition="ne" id2="criterioB"/>
</dependency>

This dependency will be launched when the condition of the first launcher is met (criterioA > 3) or when the conditions of the second is met (criterioA != criterioB).

Reverse evaluation

Some dependencies (not all of them, only the ones having reversible actions) require another dependency to be included to restore the modified value in case that the conditions are not met. To facilitate this kind of dependencies, there is an attribute (invert="true") that evaluates in the oposite way the conditions defined in the launchers, so if we define the following dependencies:

<dependency ...>
<dependency-element id="criterioA" condition="gt" value="3"/>
<dependency-element id="criterioA" condition="ne" id2="criterioB"/>
</dependency>
<dependency invert="true" ...>
<dependency-element id="criterioA" condition="gt" value="3"/>
<dependency-element id="criterioA" condition="ne" id2="criterioB"/>
</dependency>

if first dependency is not launched, second will be, and viceversa.

Saving data from launcher

Launchers may be used also to recover data from other components that are needed for the execution of the dependency (for instance, to calculate a formula). To do this, we need to give an alias in the launcher where we will indicate the identifier that will be used to obtain the value retrieved:

<criteria type="numeric" id="suma">
<dependency initial="true" source-type="formule" target-type="input" formule="[a] + [b]">
<dependency-element id="numeroA" alias="a"/>
<dependency-element id="numeroB" alias="b"/>
</dependency>
</criteria>

This dependency will be launched when components numeroA and numeroB have value (and also everytime those values change), and also everytime it does it it will update the value of the criteria suma with the sum of the values of the components. Also, it will check the value of components numeroA and numeroB when loading the screen and will launch the dependency if both have values defined initially (because initial attribute is set to true).

On the component

Once all conditions of components are met, the dependency is executed. Usually a dependency retrieves one or several data and with that it updates the component where is defined.

Origin of data

The source-type attribute defines the origin of data that will be used to modify the component where the dependency is defined.

By default, the source-type is none, meaning that if left undefined, it does not try to recover any data.

The origin of data may be one of the launchers (source-type="launcher" would retrieve the value of the alias defined in the target-action attribute)

<criteria type="numeric" id="copia">
<dependency source-type="launcher" target-type="input" target-action="a">
<dependency-element id="criterioA" alias="a"/>
</dependency>
</criteria>

a static value (source-type="value", that would retrieve the value from value attribute in the dependency)

<criteria type="text" id="estatico">
<dependency source-type="value" target-type="input" value="estatico">
<dependency-element id="criterioA" alias="a"/>
</dependency>
</criteria>

the execution of a formula (source-type="formule" defined in formule attribute)

<criteria label="a" id="a" component="numeric" style="col-xs-6 col-sm-3 col-lg-2" value="3"/>
<criteria label="b" id="b" component="numeric" style="col-xs-6 col-sm-3 col-lg-2" value="6"/>
<criteria label="a + b" id="aplusb" component="numeric" style="col-xs-6 col-sm-3 col-lg-2">
<dependency source-type="formule" target-type="input" formule="[a] + [b]">
<dependency-element id="a"/>
<dependency-element id="b"/>
</dependency>
</criteria>
<criteria label="a * b" id="amultipliedbyb" component="numeric" style="col-xs-6 col-sm-3 col-lg-2">
<dependency initial="true" source-type="formule" target-type="input" formule="[a] * [b]">
<dependency-element id="a"/>
<dependency-element id="b"/>
</dependency>
</criteria>
Formula dependency

As can be seen, when loading the page the first calculation is not done, because it is not defined as initial="true"

or by the data recovered by a query done on the server (source-type="query" that requires also the attributes server-action and target-action).

<grid id="grid">
<column name="columna1"/>
<column name="columna2"/>
<dependency source-type="query" target-type="input" server-action="data" target-action="consulta">
<dependency-element id="criterioA" alias="a"/>
<dependency-element id="criterioB" condition="ne" value="raro"/>
</dependency>
</grid>
Query dependency

Acting on the component

The attribute target-type indicates the part of the component to be updated with the data calculated or recovered from source-type. Those parts may be the actual value of the component (input), the label of a criteria, the unit, etc.

In some cases, is not necessary to recover a data to act on a component, but knowing if the conditions of the dependency have been met or not. This happens with actions like show, set-required, etc.

<button id="boton">
<dependency target-type="enable">
<dependency-element id="criterioA" condition="eq" value="lala"/>
</dependency>
</button>

This dependency would activate the button only when the criterioA has the value 'lala'

Some actuations are reversible, meaning that, if dependency's conditions are not met, the oposite action is executed, so it is not necessary to include two dependencies (one that acts as we want and another to do the opposite when the inverse conditons are met). This happens with actions like show, that will launch a hide action in case conditions are not met, an enable would launch a diable, etc.

<criteria id="criterioB" type="text">
<dependency target-type="show">
<dependency-element id="criterioA" condition="eq" value="mostrar"/>
</dependency>
</criteria>

This dependency would show the criterioB only when criterioA has the value 'mostrar'. Otherwise, the criteria is kept hidden.

Reversible actuation

Launching actions

Until now, all dependencies that we have seen act solely on the component (criteria, grid, column, chart) where it was defined. There are other kind of dependencies, action dependencies, that can be defined in any component but they only launch the actions (dependency-action) that they have defined inside, they do not act with the component.

These dependencies are defined after one or more launchers, and they are added (as dependency-action):

<dependency>
<dependency-element id="gridA" attribute="selectedRows" condition="eq" value="1" />
<dependency-element id="gridA" event="select-row" />
<dependency-action type="server" server-action="data" target-action="accion de servidor" target="gridB"/>
</dependency>

This dependency will launch a data load on gridB when we select one and only one row of gridA.

Selection of a row reloads another grid

Launching order

Dependencies are evaluated in the order they are defined. If there is more than one dependency defined on the same component that matches the conditions to be executed in that moment, they will be executed in a synchronous way (in the order they are defined).

<criteria id="criterioA" component="hidden">
<dependency source-type="launcher" target-type="input" target-action="valor">
<dependency-element id="criterioB" alias="valor"/>
<dependency-element id="botonA" event="click"/>
</dependency>
<dependency source-type="launcher" target-type="input" target-action="valor">
<dependency-element id="criterioC" alias="valor"/>
<dependency-element id="botonA" event="click"/>
</dependency>
</criteria>

In the previous dependency, the criteria criterioA will take the value from criterioC because they are executed in order, the value from the first dependency will be overwritten (value from criterioB) given that criteria criterioC has any value.

Performance

Dependencies affect to the performance of a screen (a lot of calculation is done on the browser). Adding too many dependencies to a window may cause a bad user experience, so it is necessary to design correctly the functionality we want to offer and look for possible alternatives (like updating criteria and grids massively, or initializing the data when the screen loads).