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.

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.
warning

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.

In principle, the contentProcess looks like this:

var entityField1value1 = (...);
var entityField2value1 = (...);
var entityField3value1 = (...);

var myDataArray = [];
myDataArray.push([entityField1value1, entityField2value1, entityField3value1]);
myDataArray.push([entityField1value2, entityField2value2, entityField3value2]);
myDataArray.push([entityField1value3, entityField2value3, entityField3value3]);

result.object(myDataArray);

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.


Practical implications

The main benefit of JDitoRecordContainer is flexibility. The trade-off is that sorting, paging, and filtering have to be implemented explicitly.

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.


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.page 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 to avoid double execution of contentProcess. If 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.

The following contentProcess example returns a nested array:

var 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);

The following onInsert example handles new records using $local.rowdata:

var rowdata = vars.get("$local.rowdata");
var columns = ["UID", "C1", "C2", "C3"];
var values = [rowdata["UID.value"], rowdata["C1.value"], rowdata["C2.value"], rowdata["C3.value"]];

new SqlBuilder().insertData("YOURTABLE", columns, null, values);
warning

Use $local.rowdata, not $field, in onInsert.

The following onUpdate example updates changed data fields:

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

var columns = [];
var data = [];

for (let field in changedFields) {
columns.push(changedFields[field].split(".")[0]);
data.push(rowData[changedFields[field]]);
}

newWhereIfSet("YOURTABLEID = '" + vars.get("$local.uid") + "'")
.updateData(true, "YOURTABLE", columns, null, data);
warning

Use $local.rowdata, not $field, in onUpdate.

The following onDelete example deletes a record:

newWhereIfSet("YOURTABLEID = '" + vars.get("$local.uid") + "'")
.deleteData(true, "YOURTABLE");
warning

Use $local.uid, not $field, in onDelete. For web services, adapt onInsert, onUpdate, and onDelete accordingly.

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, especially FilterSqlTranslator, 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.