Accessing the Value of an EntityField
This chapter explains the relationship between an EntityField value and the variables associated with it.
In this chapter, the term "value of an EntityField" refers to the internal value processed by the ADITO core. For practical purposes, you can think of it as the value shown in the client.
If you create an EntityField named MYFIELD, ADITO automatically provides the system variable $field.MYFIELD. You can read its value with vars.get("$field.MYFIELD").
The EntityField value and the corresponding $field variable are linked in the ADITO core. Both are calculated at different times and are synchronized at defined points.
Synchronization
Synchronization takes place in two directions:
- EntityField value ->
$fieldvariable
If the EntityField value changes, the variable is recalculated. $fieldvariable -> EntityField value
If the variable changes, its value is written back into the EntityField.
How does an EntityField value get set?
The value of an EntityField is set when a user enters data in a View. This triggers the recalculation of the variable according to the process sequence described in Execution Order of Entity Processes.
How does a "$field" variable get its value?
There are three ways in which the value of a $field variable can be determined:
- Record
If the EntityField is linked to the RecordContainer and novalueProcessis defined, the value is taken from the record. - Process
If the EntityField is not linked to the RecordContainer, thevalueProcessdetermines the value. - Record process
If the EntityField is linked to the RecordContainer and also has avalueProcess, the system first uses the record value. Only if the record does not return a value does thevalueProcessrun.
The following behavior is important:
- When the system enters state
NEWorEDIT, for example when an EditView is opened, all fields of the Entity are loaded or calculated, regardless of whether they are currently visible. This ensures that all required data is up to date during editing. - More generally, whenever a View is opened, ADITO determines which fields might be required. This includes not only fields referenced directly in the View configuration, but also fields used in processes such as
titleProcessoronActionProcess. These fields are then loaded immediately by including the corresponding column orexpressionin theSELECTclause. This avoids additional loading while the user works with the View, but it can also cause performance issues if many fields are calculated through complexexpressionlogic. Fields that are calculated only throughvalueProcessordisplayValueProcessare evaluated on demand instead. If both approaches are configured,expressiontakes precedence as long as it returns a value.
Do not assume that logic is executed only because the corresponding field, action, or button is visible in the current View.
When an Entity is opened, ADITO may also execute related UI processes such as stateProcess, tooltipProcess, titleProcess, or iconProcess during initialization. This also applies when opening an EditView for a single record.
This becomes relevant when such processes call helper functions that inspect view-dependent system variables such as $sys.filter or $sys.selectionsRecordsRecipe, or when they trigger expensive follow-up loading via entities.createConfigForLoadingRows() or similar APIs.
In EDIT or NEW, such logic can accidentally load far more data than intended, because there is no active FilterView context. Guard these code paths explicitly with $sys.recordstate, $sys.operatingstate, or $sys.presentationmode, depending on what exactly the logic requires.
The determination of the $field value also depends on the record state:
-
VIEWmode
InVIEWmode, the user can only read data. The value is determined by the variants described above. -
EDITmode
InEDITmode, ADITO first checks whether avalueProcessexists. If it does, the process is executed and can be used for presetting. If thevalueProcessdoes not return a value, the user's input is used.
$this.value
$this.value is a special system variable available inside an EntityField valueProcess. It is accessed via vars.get("$this.value") and contains the current field value, whether entered by the user or preset by logic. It is therefore useful for checking whether the field already contains a value.
If the field has no current value, $this.value is null. If the user deletes an existing value, $this.value becomes an empty string rather than null.
If you want to preset a value, check vars.get("$this.value") == null so that the preset is only applied when no value exists yet. In onValidation and onValueChange, the same variable can be used to process the user's current input.
Example: Presetting currency to "EUR":
if (vars.get("$sys.recordstate") == neon.OPERATINGSTATE_NEW && vars.get("$this.value") == null)
{
result.string($KeywordRegistry.currency$eur());
}
Figure: Example of a valueProcess that presets the currency to EUR.
$this.value vs. $field.MYFIELD
While $this.value contains the current field value, $field.<FIELDNAME> contains the last calculated value synchronized to that field. In most situations both values are identical, but they can differ temporarily, for example
- while the initial field value is being determined, or
- when a record is reloaded.
$this.value and $field.MYFIELD in valueProcess
Generally, in a valueProcess you have to distinguish between the stored field value ($field.MYFIELD) and the new value to set ($this.value).
Here is an overview about when and how a valueProcess is executed and what reactions are possible:
- Initial loading of the field values
If the value of the field is initially loaded from the Entity, then$this.valueis null. This case is mostly used for presets when entering new data oder editing it, because this case occurs only once. If you miss to check for$this.value == nullthen the value of the field will be overwritten with every refresh. - Field is explicitely set empty
In case the field is explicitely set empty (e.g., by the user), a check for!this.valuewould fail, because in this case$this.value == "" - Changes of the field itself
If the value of the field itself changes directly (e.g., by user input, WriteEntity,neon.setFieldValue, etc.) then$this.valueis filled with a value (or an empty string in case 2, see above) and the field itself ($field.MYFIELD) ist empty (""). Knowing this, you can, e.g., prevent that the field is automatically filled by dependencies from other fields, because the given value was entered explicitely. - Trigger of the
valueProcessby other fields
This case only happens when entering new data, not when editing it. It is the constellation that the value of the field itself has not changed, but it has been updated because of triggers/calls etc. and thus thevalueProcessis executed. In this case,$this.valueand$field.MYFIELDhave the same value. This constellation can be used, e.g., to set the field depending on other fields. This is the recommended approach, instead of usingneon.setFieldValuein theonValueChangeprocess of another field.
neon.setFieldValue should only be used in onValueChange, if there is absolutely no other possibility to realize the task. The performance of neon.setFieldValue is very low, because many dependencies need to be updated and it can happen that the same code needs to be written in multiple fields.
If the value of a field is set and you do not return anything in the valueProcess, then the set value will be used. (This means, in case 3 only the return must be prevented.)
Example of the implementation of the above cases 1, 3, and 4: The example task is that a field A should initially be filled with value 1, when entering a new dataset. It should be possible to overwrite the field's value when entering a new dataset or when editing it. If field B is set to a specific value, then field A should automatically filled with the value 2 (works for "NEW", not for "EDIT").
var fieldA = vars.get("$field.A");
var thisValue = vars.get("$this.value");
// The value of the field is not required in this case,
// but it can work as trigger, if B changes.
var fieldB = vars.get("$field.B");
var recordState = vars.get("$sys.recordstate");
if([neon.OPERATINGSTATE_NEW, neon.OPERATINGSTATE_EDIT].includes(recordState))
{
if(recordState == neon.OPERATINGSTATE_NEW && thisValue == null)
//case 1: initial presetting the field with value 1
{
result.string(1);
}
else if(fieldA == "" && thisValue)
//case 3: value was changed -> should be set now
{
result.string(thisValue);
// In this case, you can alternatively simply return nothing.
// Then $this.value will be set as field value.
}
else if(fieldA == thisValue)
// case 4: thisValue and field have the same value
// -> The field was not changed, but was triggered somewhere,
// e.g., because a change of field B -> fieldA should be set to 2
{
result.string(2);
}
}
Example valueProcess for cases 1, 2, and 4
$local.value
This is a local system variable that is accessible in the onValidation and the onValueChange processes and contains the entered value before it is written to the variable value, so you can validate it before the data enters the system.
Example: Checking if the user has input a wrong entry date:
var entryDate = vars.get("$local.value");
if (!DateUtils.validateNotInFuture(entryDate)) {
result.string(translate.text("Entrydate must not be in the future"));
}
Activity_entity.ENTRYDATE.onValidation.js (module activity)
$local.rowdata and $local.initialRowdata
If you want to access the values of EntityFields in specific processes of a RecordContainer, you must exclusively use $local.rowdata or $local.initialRowdata, because $field variables might contain outdated values at that time. In particular, these are the following processes:
- dbRecordContainer:
onDBInsert,onDBUpdate, andonDBDelete - jDitoRecordContainer:
onInsert,onUpdate, andonDelete(see also Practical implications in chapter JDitoRecordContainer)
Find more information on $local.rowdata and $local.initialRowdata in the chapter about $local variables.