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:
- Open the respective Entity in the Navigator window.
- Right-click it and choose New RecordContainer from the context menu.
- In the dialog, choose JDitoRecordContainer as the type and enter a name. The convention is to name it
jdito. - Select the new RecordContainer under RecordContainers and edit its properties.
- In
recordFieldMappings, add the EntityFields that are filled by theJDitoRecordContainer. - In
contentProcess, implement the core logic of theJDitoRecordContainer. The process result must always be a nested array that acts as the data source.
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.
You must handle sorting, paging, and filtering manually.
Important properties
| Property | Description |
|---|---|
jDitoRecordAlias | Defines the default alias used in database access methods. It is often set to Data_alias. |
recordFieldMappings | Maps EntityFields to the array values of contentProcess. The order must match exactly. |
isPageable | Enables paging. Access $local.page and $local.pagesize in contentProcess. |
isFilterable | Enables filtering. Access $local.filter, which contains a map of field, operator, and value. |
isRequireContainerFiltering | Enables server-side filtering for performance. |
isSortable | Enables sorting. Access $local.order, which contains a map of field and direction. |
contentProcess | Provides the data and returns a nested array. |
rowCountProcess | Returns 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. |
hasDependentRecords | Supports hierarchical structures, for example trees. contentProcess is re-executed after deletion. |
onInsert | Handles new records by using $local.rowdata. |
onUpdate | Updates changed data fields by using $local.changed and $local.rowdata. |
onDelete | Deletes 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);
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);
Use $local.rowdata, not $field, in onUpdate.
The following onDelete example deletes a record:
newWhereIfSet("YOURTABLEID = '" + vars.get("$local.uid") + "'")
.deleteData(true, "YOURTABLE");
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.
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.