Events and the POST

An event binds a user action on a block to a 4D function. It is stored in the record's events field:

"events": {
  "onClick": [
    { "vt_ClassName": "CONTACT", "vt_FunctionName": "togglePublish", "vo_Param": { } }
  ]
}

One event type holds a collection: several actions can fire in order.

What vt_ClassName may be

The dispatcher accepts either a dataclass or a project class:

If (ds[$vo_POST.vt_ClassName]#Null)
    $vo_Controller:=ds[$vo_POST.vt_ClassName]          // dataclass: used directly
Else
    $vo_Controller:=cs[$vo_POST.vt_ClassName].new()    // project class: INSTANTIATED
    $vo_Controller.vo_Session:=$vo_Param.vo_Session    // and handed the session
End if

So a controller class works, and it even receives vo_Session, which a dataclass does not get this way. If a name exists in both, ds wins.

Worth knowing: documentation written for V20 states as a critical rule that vt_ClassName must be a dataclass and cannot be a project class. That is not true of V21. If a project class fails silently, the cause is visibility (below), not dispatch.

Being callable and being offered in the dev panel are two different things. BSPK_REFRESH_STORAGE builds the picker's list by scanning classes that declare Class extends WebFormController, DataClass, Entity or EntitySelection, keeping functions whose signature matches @(@)->$vo_WebResponse@. A plain project class with no extends can be dispatched but will never appear in the panel, which is how it ends up looking broken.

The WF suffix: only for value lists

There are two lists, built by BSPK_REFRESH_STORAGE with two different patterns:

ListPattern scannedUsed byNaming rule
AvailableController@(@)->$vo_WebResponse@events (onClick, onChange…)none, any name
AvailableControllerForSelect@WF($vo_POST : Object)->$vo_Return : Object@vt_ValuesFunction of fieldSelect / fieldCheckboxmust end in WF

For an event handler, what matters is the return type, not the name. For a function feeding a select or a checkbox group, the name must end in WF and the signature must match to the character. Get it wrong and the field renders empty, with no error anywhere.

After adding such a function, reload the code, then run BSPK_REFRESH_STORAGE. Until you do, the panel does not know it exists.

The function name is camel-cased before the call

The dispatcher calls $vo_Controller[BSPK_String_To_CamelCase($vo_POST.vt_FunctionName)]. A function declared Function TogglePublish is therefore looked up as togglePublish and not found: the request returns with nothing done. Declare your functions in camelCase.

What the function receives

Function togglePublish($vo_POST : Object)->$vo_WebResponse : cs.bspkComponent.WebFormController
    $vo_WebResponse:=cs.bspkComponent.WebFormController.new()
    …

The dispatcher actually passes four arguments: $vo_POST, Entity, the session and an info object. You may declare more than one parameter if you need them.

$vo_POST is never Null inside BSPK_WEB_ON_CONNECTION; declaring it and testing it for Null at the top of a method tests the wrong thing.

Getting form values into $vo_POST

Nothing is sent automatically. You declare what you need in a /* parameters */ comment block, right under the function declaration:

Function createPayment($vo_POST : Object)->$vo_WebResponse : cs.bspkComponent.WebFormController
/* parameters
vt_BlocForm:select:getBlocsNameCollection:["wrapper","accordion","modal"]:mandatory:tomSelect:label:Form
vt_SendAllObjectsOfBlocName:hidden:vt_BlocForm
*/

Two common needs:

GoalDeclarationArrives as
The rows selected in a listboxvt_GetBlocInfo:select:getBlocsNameCollection:mandatory:tomSelect$vo_POST.{blockName}_selectedRows
Every field of a containera block selector + vt_SendAllObjectsOfBlocName:hidden:<its name>$vo_POST.vt_FieldValue.{blockName}

The indirection everyone gets wrong

vt_SendAllObjectsOfBlocName holds the name of the other parameter, as a string. Never a uuid, never a block name:

"vo_Param": {
  "vt_BlocForm": "1DAC5696B4EB4A6A8C3ACB34E25A37C5",   // the block's uuidKey
  "vt_SendAllObjectsOfBlocName": "vt_BlocForm"          // the PARAMETER NAME
}

Get it wrong and vt_FieldValue arrives empty ({}) while the function runs normally and reports success. The diagnosis is always the same: look at the request body of /bweb/call-action in the browser's network tab.

Two related traps, both silent:

  • A field with no vt_FieldName is stripped by the security check, so it never reaches vt_FieldValue.
  • Values arrive as text, including for a single field's onChange. Convert explicitly, and use BSPK_STRING_TO_REAL rather than Num() for decimals.

Answering: the WebFormController

The returned object is what acts on the page: sendAlert, reloadBlock (one block per call, no collection), redirectToRoute, opening or closing a modal. It is serialised into the response and replayed client-side.

sendAlert followed by a redirect destroys the toast: the page reloads before it can be read. When the message carries information, do not redirect, or use sendAlertAfterRedirect.

When nothing happens at all

In order of likelihood:

  1. The security check refused the POST. Look for the JSON error on /bweb/call-action, not for a bug in your function: it was never called.
  2. The function is not loaded (new function, no BSPK_REFRESH_STORAGE). A call to a function that is not loaded hangs rather than failing.
  3. The name is not camelCase.
  4. The class has no extends, so the panel never offered it and the event is half-configured.

See also

  • Security: what the check validates, and why it strips fields
  • Button: the properties of the block that usually carries the event