How-to: Custom view controller API
A custom view written in JavaScript talks back to the form through a controller object. In a React custom view the controller is props.controller; the same controller is also passed to a JavaScript function bound by an INTERNAL CLIENT action. With it the view sets the current object of a group, changes property values, looks up suggestions, and calls actions or scripts on the server.
Properties and actions are addressed by their integration name — the name on the form (or the alias / NEW / DELETE integration name of a button), the same name the external JSON/REST API uses.
Changing the current object and property values
controller.changeObject(groupSID, object) sets the current object of the group groupSID. The object is a data row of that group, or a raw objects handle (see the identity rules below) — not a bare row.key.
controller.changeProperty(property, value) changes property for the group's current object. To target a specific row, pass it in between: controller.changeProperty(property, object, value), where object is a data row or a raw handle. When property is an action (or any property with no editable value), the value is omitted: controller.changeProperty('edit') execs it on the current object, controller.changeProperty('edit', object) on the given row.
function orderView(props) {
const controller = props.controller;
return (
<div>
<button onClick={() => controller.changeProperty('note', 'checked')}>Mark</button>
{props.data.o.list.map(row =>
<div key={row.key} onClick={() => controller.changeObject('o', row)}>
{row.number}
</div>)}
</div>
);
}
In the two-argument changeProperty(property, X) form the platform decides whether X is a value or a row: when the property accepts a value and X resolves to a row (a data row or a raw handle), it is read as the row and the call execs on it; otherwise X is the value and the call changes the current object.
controller.changeProperties(properties, objects, values) applies several changes at once from parallel arrays — properties[i] is changed to values[i] for objects[i] (an entry may be null for the current object). An optional fourth array groupSIDs scopes each property to a group when its integration name is not unique across the form.
controller.changeProperties(['note', 'qty'], [null, row], ['checked', 5]);
Looking up values
controller.getPropertyValues asks the server for a capped suggestion list for a property. The result is delivered to the ok callback as { data: [ { displayString, rawString, objects }, ... ], more }; more is true when the list was truncated, so it is a suggestion list, not a full SELECT DISTINCT. In a classic GRID custom view the same lookup is exposed as getValues(property, value, ok, fail), equivalent to getPropertyValues in the default 'objects' mode; the form-level / React controller uses getPropertyValues.
controller.getPropertyValues(property[, object], value[, mode], ok, fail[, count]);
-
value— the typed query to match against. -
object— an optional row (data row or raw handle) that scopes the lookup to that row; omit it for the current object. -
mode— one of:moderesult item.objects'objects'(default)the matching OBJECTSfor the property — an object pickera raw objectshandle for that object'values'the distinct values of the property null(usedisplayString/rawString)'change'the property's edit-time suggestions depends on the property 'change'reflects how the property is edited — a customINPUTlist, anotNullconstraint, or a custom change action — rather than the distinct values already present. For a property that cannot be changed in this context (for example, a read-only property, or a computed property without a change action) it returns an empty list, whereas'values'still returns its distinct values. -
ok(result)/fail()— success and failure callbacks. -
count— raises the number of items requested, for paging.
Pass item.objects from an 'objects' result straight back into changeObject or changeProperty to act on the picked object:
controller.getPropertyValues('customer', text, 'objects',
result => result.data.forEach(item => console.log(item.displayString)),
() => console.log('failed'));
// picking the first suggestion as the group's customer
controller.getPropertyValues('customer', text, 'objects', result => {
const item = result.data[0];
if (item) controller.changeObject('c', item.objects);
}, () => {});
Calling the server
exec, eval, evalAction and change each run on the server and return a Promise. They behave exactly as described in How-to: Custom Components (objects) — the same authorization gate and the same conversion of the result to a JS value.
controller.exec(action, ...params)— runs a named action; resolves to itsRETURNvalue.controller.eval(script, ...params)— runs an lsf script that defines its ownrunaction (typed parameters).controller.evalAction(script, ...params)— runs an action body wrapped into arunaction, with parameters referenced as$1,$2, ….controller.change(property, ...keyParams, value)— changes a global property; the last argument is the value, the preceding ones are the keys.
const total = await controller.exec('recalc', orderId);
const doubled = await controller.eval('run(INTEGER a) { RETURN a * 2; }', 21); // 42
await controller.change('note', orderId, 'checked');
A call made after the form has been closed rejects with a Form is closed error — it never hangs — so an await on a closed form lands in the catch branch.
Row identity rules
A method that targets a row accepts one of:
- a data row object the view received (from the React props / the
updatelist); - a spread or
Object.assignclone of such a row — the enumerableobjectshandle is copied with it, so the clone resolves to the same object; - a raw
objectshandle —row.objects, oritem.objectsfrom agetPropertyValues'objects'result.
A bare row.key is not accepted: key is a display / React-key / diff token, not a resolution input. The field names key, isCurrent and objects are reserved on a row — an application property or column with one of these integration names would be overwritten.
If an explicit object argument resolves to neither a row nor a raw handle, the platform does not silently fall back to the current row and does not throw: changeProperty logs a console error and skips that change, and changeObject is a no-op.
Classic CUSTOM compatibility
The classic render/update custom components keep their own controller with the render/update/isCurrent/getDiff helpers and the controller.change event method. Their rows now also carry the public key and the enumerable objects handle, so a row from such a view can be passed wherever a row is expected. A controller.changeProperty(property, ...) on a property that is not a column of that view's own grid is delegated to the form controller, which resolves the property form-wide — so a classic view can change a property it does not display.