Skip to main content

JDitoRecordContainer

Unlike a dbRecordContainer, a JDitoRecordContainer uses JDito code as its data source. That code may still load data from a database, but the effective source is the result returned by contentProcess.

The result of contentProcess must be a nested array whose order matches the configured recordFieldMappings.

When to use it

Use a JDitoRecordContainer when the standard database mapping is not sufficient, for example when:

  • the data must be assembled dynamically,
  • multiple sources must be combined manually,
  • the result does not map cleanly to a standard table-based structure, or
  • custom paging, filtering, or sorting logic is required.

The main benefit of JDitoRecordContainer is flexibility. The data can be loaded from a database, a web service, or any other source that is accessible through JDito.

warning

You must handle sorting, paging, and filtering manually.


Creating a JDitoRecordContainer

To establish a JDitoRecordContainer, proceed as follows:

  1. Open the respective Entity in the Navigator window.
  2. Right-click it and choose New RecordContainer from the context menu.
  3. In the dialog, choose JDitoRecordContainer as the type and enter a name. The convention is to name it jdito.
  4. Select the new RecordContainer under RecordContainers and edit its properties.
  5. In recordFieldMappings, add the EntityFields that are filled by the JDitoRecordContainer.
  6. In contentProcess, implement the core logic of the JDitoRecordContainer. The process result must always be a nested array that acts as the data source.
info

The order of the fields in recordFieldMappings must match the order of the data in the array built in contentProcess. An EntityField named UID, spelled exactly like this and with content type TEXT, must always be present and included in the field mapping.

Example from xRM

In Turnover_entity in module revenue, contentProcess fills a variable chartData using:

// EntityFields: UID, PARENT, CATEGORY, X, Y
chartData.push([key, countDataSet.parent, countDataSet.category, countDataSet.x, countDataSet.count]);

Finally, return the array:

result.object(chartData);

The structured data is displayed in charts within a GroupLayout, for example in Sales > Opportunity > MainView > tab Forecast.


Important properties

PropertyDescription
jDitoRecordAliasDefines the default alias used in database access methods. It is often set to Data_alias.
recordFieldMappingsMaps EntityFields to the array values of contentProcess. The order must match exactly.
isPageableEnables paging. Access $local.startrow and $local.pagesize in contentProcess.
isFilterableEnables filtering. Access $local.filter, which contains a map of field, operator, and value.
isRequireContainerFilteringEnables server-side filtering for performance.
isSortableEnables sorting. Access $local.order, which contains a map of field and direction.
contentProcessProvides the data and returns a nested array.
rowCountProcessReturns the row count. If paging is enabled or permission-based filtering is used, this process is mandatory. It must return a numeric count and use the same effective filter logic as contentProcess.
hasDependentRecordsSupports hierarchical structures, for example trees. contentProcess is re-executed after deletion.
onInsertHandles new records by using $local.rowdata.
onUpdateUpdates changed data fields by using $local.changed and $local.rowdata.
onDeleteDeletes a record by using $local.uid.

contentProcess

The contentProcess is responsible for providing the data for the RecordContainer. The record data should be returned as a nested array, for example:

let data = [
["UID1", "VALUE1.1", "VALUE2.1", "VALUE3.1"],
["UID2", "VALUE1.2", "VALUE2.2", "VALUE3.2"],
["UID3", "VALUE1.3", "VALUE2.3", "VALUE3.3"]
];

result.object(data);

Available JDito variables

VariableDescription
$local.idvaluesUIDs of the requested records as an array. Is null when all records should be loaded.
$local.idvaluesExcludedUIDs of the records that should be excluded from the result. Can be null.
$local.orderThe requested sorting order as an object, with the field names as keys and the sorting direction as values. Can be null.
$local.filterFilter object that contains multiple sub-filters defining what records should be loaded.
$local.userfilterThe externally defined filter set by the requestor.
$local.filtersCombined search condition object that contains all relevant filter conditions.
$local.groupedThe name of the grouping field for tree data-structures. Only available when a grouping is defined.
$local.yAxisThe field which defines the Y axis of a chart.
$local.fieldhintsContains the names of the fields that should be loaded. Can be used for performance optimizations.
info

The following variables are exclusive to the contentProcess and not available in the rowCountProcess: $local.order, $local.userfilter, $local.fieldhints

Saving and deleting records

If the Entity should support the creation, modification or deletion of records, you must implement these operations yourself when using a JDitoRecordContainer. These actions can be implemented as JDito processes.

warning

Avoid referencing EntityFields in RecordContainer processes directly by using $field variables. You should use the RecordFields defined by the recordFieldMapping instead, which can be accessed through the variables $local.rowdata and $local.uid.

onInsert

The onInsert process handles the creation of new records. This example does that by inserting a row into the database:

let rowdata = vars.get("$local.rowdata");
let fieldValues = {
"UID": rowdata["UID.value"],
"SUBJECT": rowdata["SUBJECT.value"],
"DESCRIPTION": rowdata["DESCRIPTION.value"],
"ENTRYDATE": rowdata["ENTRYDATE.value"]
};

new SqlBuilder().insertFields(fieldValues, "YOURTABLE");

onUpdate

The onUpdate process handles the modification of existing records. The following example updates values in the database:

let changedFields = vars.get("$local.changed");
let rowData = vars.get("$local.rowdata");

let fieldValues = {};

changedFields.forEach(fieldName =>
{
// removes the ".value"/".displayValue" postfix
let dbFieldName = fieldName.split(".")[0];
fieldValues[dbFieldName] = rowData[fieldName];
});

newWhere("YOURTABLE.YOURTABLEID", vars.get("$local.uid"))
.updateFields(fieldValues);

onDelete

The onDelete process handles the deletion of a record, for example by removing a row in the database:

newWhere("YOURTABLE.YOURTABLEID", vars.get("$local.uid"))
.deleteData();

A step-by-step example is available in Examples.


Filtering a JDitoRecordContainer

The previous example shows basic filter integration. For more complex cases, use helper functions from RecordFilter_lib, RecordFilterUtils_lib, and FilterSqlTranslator_lib, to build SQL conditions or to implement manual filtering.

You can find practical implementations of advanced filtering in JDitoRecordContainers from contexts such as Attribute, Manager, or Workflow.

warning

If permission-based filtering is used, rowCountProcess is mandatory and must apply the same effective filtering as contentProcess.


Permissions in a JDitoRecordContainer

Permission-based filtering in a JDitoRecordContainer is evaluated through $local.filter.permissions. If this mechanism is used in contentProcess, rowCountProcess must also be implemented. This is not optional. Both processes must evaluate the same effective filter logic so that the visible rows and the reported row count stay consistent.

In practice, this means that contentProcess and rowCountProcess must both consider the complete filter state, including $local.filter.filter and $local.filter.permissions. In addition, rowCountProcess must always return a numeric value and must never return null.

If these requirements are not met, the container may still display rows correctly at first, but follow-up behavior can become inconsistent. Typical symptoms include missing or incorrect row counts, failing permission checks, or errors during editing even though contentProcess itself returns valid data.