DataTable Widget Events
The datatable widget is one of the most complex components and triggers multiple event types. Understanding these events is crucial for proper backend implementation.
Datatable Load Events
Triggered: When form renders or pagination changes
Event Type: onTableLoadData or onTableEditDataLoad
Async Behavior: Runs asynchronously when form loads
{
"widgetName": "customerstbl",
"widgetEvent": "onTableLoadData",
"formData": { /* current form data */ },
"DataTableMeta": {
"serverPaginationEnabled": true,
"rowsPerPage": 50,
"pageIndex": 0,
"nextToken": "",
"previousToken": "",
"paginationDirection": "first",
"rowCount": 0,
"qId": ""
}
}
- C# / .NET
- PHP
else if (eventType == "ONTABLELOADDATA")
{
// Parse pagination metadata
var tableMeta = requestBody.GetProperty("DataTableMeta");
int pageIndex = tableMeta.GetProperty("pageIndex").GetInt32();
int rowsPerPage = tableMeta.GetProperty("rowsPerPage").GetInt32();
// Load data from database with pagination
var tableData = await _dbContext.Customers
.Skip(pageIndex * rowsPerPage)
.Take(rowsPerPage)
.Select(c => new Dictionary<string, string>
{
{ "_id", c.Id.ToString() },
{ "_sk", c.Id.ToString() },
{ "_code", "CUSTOMER" },
{ "name", c.Name },
{ "email", c.Email },
{ "status", c.Status }
})
.ToListAsync();
response["widgetData"] = tableData;
// For server-side pagination
response["tableMeta"] = new
{
rowCount = await _dbContext.Customers.CountAsync(),
nextToken = "", // Your pagination token if needed
serverPaginationEnabled = true,
pageIndex = pageIndex,
rowsPerPage = rowsPerPage
};
}
elseif ($eventType === 'ONTABLELOADDATA') {
// Parse pagination metadata
$tableMeta = $requestData['DataTableMeta'] ?? [];
$pageIndex = $tableMeta['pageIndex'] ?? 0;
$rowsPerPage = $tableMeta['rowsPerPage'] ?? 50;
// Load data from database with pagination
$offset = $pageIndex * $rowsPerPage;
$customers = DB::table('customers')
->offset($offset)
->limit($rowsPerPage)
->get();
$tableData = [];
foreach ($customers as $customer) {
$tableData[] = [
'_id' => $customer->id,
'_sk' => $customer->id,
'_code' => 'CUSTOMER',
'name' => $customer->name,
'email' => $customer->email,
'status' => $customer->status
];
}
$response['widgetData'] = $tableData;
// For server-side pagination
$response['tableMeta'] = [
'rowCount' => DB::table('customers')->count(),
'nextToken' => '', // Your pagination token if needed
'serverPaginationEnabled' => true,
'pageIndex' => $pageIndex,
'rowsPerPage' => $rowsPerPage
];
}
Table Row Actions
Triggered: When user clicks action button in a table row
Event Type: onTableRunActionEvent
Actions: edit, delete, copy, confirm, sign, download, etc.
{
"widgetName": "customerstbl",
"widgetEvent": "onTableRunActionEvent",
"widgetValue": "{\"action\":\"edit\",\"rowData\":{\"_id\":\"123\",\"_sk\":\"123\",\"_code\":\"CUSTOMER\"}}",
"widgetContext": "{\"referer\":\"/forms/main\",\"hasParentForm\":false}"
}
- C# / .NET
- PHP
else if (widgetName == "CUSTOMERSTBL" && eventType == "ONTABLERUNACTIONEVENT")
{
// Parse the widget value JSON
var widgetValue = requestBody.GetProperty("widgetValue").GetString();
var actionData = JsonSerializer.Deserialize<JsonElement>(widgetValue);
string action = actionData.GetProperty("action").GetString();
var rowData = actionData.GetProperty("rowData");
string rowId = rowData.GetProperty("_id").GetString();
if (action == "edit")
{
// Open the customer form for editing
feCommands.Add(new
{
command = "OpenRecord",
args = new
{
id = Guid.NewGuid().ToString(),
pluginCode = "NONE",
formCode = "CUSTOMERFORM",
guid = rowId,
pGuid = (string)null,
isFormEditorMode = false,
originator = (object)null,
parent = (object)null,
folderData = (object)null,
prefillData = (object)null
}
});
}
else if (action == "delete")
{
// Delete the record from database
await _dbContext.Customers
.Where(c => c.Id.ToString() == rowId)
.ExecuteDeleteAsync();
// Show success message
feCommands.Add(new
{
command = "ShowMessage",
args = new
{
type = "success",
message = "Customer deleted successfully"
}
});
// Reload the table widget
feCommands.Add(new
{
command = "ReloadWidget",
args = new { widgetName = widgetName.ToLower() }
});
}
else if (action == "download")
{
// Trigger download of customer data
string filePath = await GenerateCustomerPDF(rowId);
feCommands.Add(new
{
command = "Download",
args = new
{
fileName = "customer.pdf",
filePath = filePath
}
});
}
}
elseif ($widgetName === 'CUSTOMERSTBL' && $eventType === 'ONTABLERUNACTIONEVENT') {
// Parse the widget value JSON
$widgetValue = json_decode($requestData['widgetValue'], true);
$action = $widgetValue['action'];
$rowData = $widgetValue['rowData'];
$rowId = $rowData['_id'];
if ($action === 'edit') {
// Open the customer form for editing
$response['feCommand'][] = [
'command' => 'OpenRecord',
'args' => [
'id' => uniqid('', true),
'pluginCode' => 'NONE',
'formCode' => 'CUSTOMERFORM',
'guid' => $rowId,
'pGuid' => null,
'isFormEditorMode' => false,
'originator' => null,
'parent' => null,
'folderData' => null,
'prefillData' => null
]
];
}
elseif ($action === 'delete') {
// Delete the record from database
DB::table('customers')->where('id', $rowId)->delete();
// Show success message
$response['feCommand'][] = [
'command' => 'ShowMessage',
'args' => [
'type' => 'success',
'message' => 'Customer deleted successfully'
]
];
// Reload the table widget
$response['feCommand'][] = [
'command' => 'ReloadWidget',
'args' => ['widgetName' => strtolower($widgetName)]
];
}
elseif ($action === 'download') {
// Trigger download of customer data
$filePath = $this->generateCustomerPDF($rowId);
$response['feCommand'][] = [
'command' => 'Download',
'args' => [
'fileName' => 'customer.pdf',
'filePath' => $filePath
]
];
}
}
Editable Table Row Actions
Triggered: When inline editing is enabled and user saves/edits a row
Event Type: onTableEditRunActionEvent
Actions: add, edit, delete (inline editing mode)
{
"widgetName": "customerstbl",
"widgetEvent": "onTableEditRunActionEvent",
"widgetValue": "{\"action\":\"add\",\"data\":\"{\\\"name\\\":\\\"New Customer\\\",\\\"email\\\":\\\"new@example.com\\\"}\"}",
"formData": { /* current form data */ }
}
- C# / .NET
- PHP
else if (widgetName == "CUSTOMERSTBL" && eventType == "ONTABLEEDITRUNACTIONEVENT")
{
// Parse the widget value JSON
var widgetValue = requestBody.GetProperty("widgetValue").GetString();
var actionData = JsonSerializer.Deserialize<JsonElement>(widgetValue);
string action = actionData.GetProperty("action").GetString();
string dataJson = actionData.GetProperty("data").GetString();
var rowData = JsonSerializer.Deserialize<Dictionary<string, string>>(dataJson);
if (action == "add")
{
// Add new row to database
var newCustomer = new Customer
{
Id = Guid.NewGuid(),
Name = rowData["name"],
Email = rowData["email"],
Status = rowData.GetValueOrDefault("status", "Active")
};
await _dbContext.Customers.AddAsync(newCustomer);
await _dbContext.SaveChangesAsync();
// Return updated table data
var tableData = await _dbContext.Customers
.Select(c => new Dictionary<string, string>
{
{ "_id", c.Id.ToString() },
{ "_sk", c.Id.ToString() },
{ "name", c.Name },
{ "email", c.Email },
{ "status", c.Status }
})
.ToListAsync();
response["widgetData"] = tableData;
// Update form data with the new table content
formData[widgetName.ToLower()] = JsonSerializer.Serialize(tableData);
response["formData"] = formData;
}
else if (action == "edit")
{
// Update existing row
var customerId = rowData["_id"];
var customer = await _dbContext.Customers.FindAsync(Guid.Parse(customerId));
if (customer != null)
{
customer.Name = rowData["name"];
customer.Email = rowData["email"];
customer.Status = rowData.GetValueOrDefault("status", customer.Status);
await _dbContext.SaveChangesAsync();
}
// Return updated table data
var tableData = await _dbContext.Customers
.Select(c => new Dictionary<string, string>
{
{ "_id", c.Id.ToString() },
{ "_sk", c.Id.ToString() },
{ "name", c.Name },
{ "email", c.Email },
{ "status", c.Status }
})
.ToListAsync();
response["widgetData"] = tableData;
formData[widgetName.ToLower()] = JsonSerializer.Serialize(tableData);
response["formData"] = formData;
}
}
elseif ($widgetName === 'CUSTOMERSTBL' && $eventType === 'ONTABLEEDITRUNACTIONEVENT') {
// Parse the widget value JSON
$widgetValue = json_decode($requestData['widgetValue'], true);
$action = $widgetValue['action'];
$dataJson = $widgetValue['data'];
$rowData = json_decode($dataJson, true);
if ($action === 'add') {
// Add new row to database
$newId = DB::table('customers')->insertGetId([
'name' => $rowData['name'],
'email' => $rowData['email'],
'status' => $rowData['status'] ?? 'Active',
'created_at' => now(),
'updated_at' => now()
]);
// Return updated table data
$customers = DB::table('customers')->get();
$tableData = [];
foreach ($customers as $customer) {
$tableData[] = [
'_id' => $customer->id,
'_sk' => $customer->id,
'name' => $customer->name,
'email' => $customer->email,
'status' => $customer->status
];
}
$response['widgetData'] = $tableData;
// Update form data with the new table content
$response['formData'][strtolower($widgetName)] = json_encode($tableData);
}
elseif ($action === 'edit') {
// Update existing row
$customerId = $rowData['_id'];
DB::table('customers')
->where('id', $customerId)
->update([
'name' => $rowData['name'],
'email' => $rowData['email'],
'status' => $rowData['status'] ?? 'Active',
'updated_at' => now()
]);
// Return updated table data
$customers = DB::table('customers')->get();
$tableData = [];
foreach ($customers as $customer) {
$tableData[] = [
'_id' => $customer->id,
'_sk' => $customer->id,
'name' => $customer->name,
'email' => $customer->email,
'status' => $customer->status
];
}
$response['widgetData'] = $tableData;
$response['formData'][strtolower($widgetName)] = json_encode($tableData);
}
}
Batch Actions
Triggered: When user selects multiple rows and performs batch action
Event Type: onTableRunBatchActionEvent
Actions: delete, download, createsigncontainer
{
"widgetName": "customerstbl",
"widgetEvent": "onTableRunBatchActionEvent",
"widgetValue": "{\"action\":\"delete\",\"data\":[{\"_sk\":\"123\",\"_code\":\"CUSTOMER\"},{\"_sk\":\"456\",\"_code\":\"CUSTOMER\"}]}"
}
- C# / .NET
- PHP
else if (widgetName == "CUSTOMERSTBL" && eventType == "ONTABLERUNBATCHACTIONEVENT")
{
// Parse the widget value JSON
var widgetValue = requestBody.GetProperty("widgetValue").GetString();
var actionData = JsonSerializer.Deserialize<JsonElement>(widgetValue);
string action = actionData.GetProperty("action").GetString();
var dataArray = actionData.GetProperty("data").EnumerateArray();
var idsToProcess = dataArray
.Select(d => d.GetProperty("_sk").GetString())
.ToList();
if (action == "delete")
{
// Delete multiple records
await _dbContext.Customers
.Where(c => idsToProcess.Contains(c.Id.ToString()))
.ExecuteDeleteAsync();
// Show success message
feCommands.Add(new
{
command = "ShowMessage",
args = new
{
type = "success",
message = $"{idsToProcess.Count} customers deleted successfully"
}
});
// Return updated table data
var tableData = await _dbContext.Customers
.Select(c => new Dictionary<string, string>
{
{ "_id", c.Id.ToString() },
{ "_sk", c.Id.ToString() },
{ "name", c.Name },
{ "email", c.Email },
{ "status", c.Status }
})
.ToListAsync();
response["widgetData"] = tableData;
}
else if (action == "download")
{
// Generate ZIP file with all selected customer PDFs
string zipPath = await GenerateCustomersBatchPDF(idsToProcess);
feCommands.Add(new
{
command = "Download",
args = new
{
fileName = "customers.zip",
filePath = zipPath
}
});
}
}
elseif ($widgetName === 'CUSTOMERSTBL' && $eventType === 'ONTABLERUNBATCHACTIONEVENT') {
// Parse the widget value JSON
$widgetValue = json_decode($requestData['widgetValue'], true);
$action = $widgetValue['action'];
$dataArray = $widgetValue['data'];
$idsToProcess = array_map(function($item) {
return $item['_sk'];
}, $dataArray);
if ($action === 'delete') {
// Delete multiple records
DB::table('customers')->whereIn('id', $idsToProcess)->delete();
// Show success message
$response['feCommand'][] = [
'command' => 'ShowMessage',
'args' => [
'type' => 'success',
'message' => count($idsToProcess) . ' customers deleted successfully'
]
];
// Return updated table data
$customers = DB::table('customers')->get();
$tableData = [];
foreach ($customers as $customer) {
$tableData[] = [
'_id' => $customer->id,
'_sk' => $customer->id,
'name' => $customer->name,
'email' => $customer->email,
'status' => $customer->status
];
}
$response['widgetData'] = $tableData;
}
elseif ($action === 'download') {
// Generate ZIP file with all selected customer PDFs
$zipPath = $this->generateCustomersBatchPDF($idsToProcess);
$response['feCommand'][] = [
'command' => 'Download',
'args' => [
'fileName' => 'customers.zip',
'filePath' => $zipPath
]
];
}
}
Create Record Event
Triggered: When user clicks "Add New" button in table toolbar
Event Type: onTableCreateRecordEvent or onFolderCreateRecordEvent
- C# / .NET
- PHP
else if (widgetName == "CUSTOMERSTBL" && eventType == "ONTABLECREATERECORDEVENT")
{
// Open a new customer form
feCommands.Add(new
{
command = "OpenRecord",
args = new
{
id = Guid.NewGuid().ToString(),
pluginCode = "NONE",
formCode = "CUSTOMERFORM",
guid = "new",
pGuid = (string)null,
isFormEditorMode = false,
originator = (object)null,
parent = (object)null,
folderData = (object)null,
prefillData = new Dictionary<string, string>
{
{ "status", "New" },
{ "createdDate", DateTime.Now.ToString("yyyy-MM-dd") }
}
}
});
}
elseif ($widgetName === 'CUSTOMERSTBL' && $eventType === 'ONTABLECREATERECORDEVENT') {
// Open a new customer form
$response['feCommand'][] = [
'command' => 'OpenRecord',
'args' => [
'id' => uniqid('', true),
'pluginCode' => 'NONE',
'formCode' => 'CUSTOMERFORM',
'guid' => 'new',
'pGuid' => null,
'isFormEditorMode' => false,
'originator' => null,
'parent' => null,
'folderData' => null,
'prefillData' => [
'status' => 'New',
'createdDate' => date('Y-m-d')
]
]
];
}
Checkbox Click Event
Triggered: When user clicks a checkbox in a table row
Event Type: onTableRunActionEvent with action onrowcheckboxclicked
{
"widgetName": "customerstbl",
"widgetEvent": "onTableRunActionEvent",
"widgetValue": "{\"action\":\"onrowcheckboxclicked\",\"rowData\":{\"_id\":\"123\",\"_sk\":\"123\"}}"
}
- C# / .NET
- PHP
else if (eventType == "ONTABLERUNACTIONEVENT")
{
// Parse the widget value JSON
var widgetValue = requestBody.GetProperty("widgetValue").GetString();
var actionData = JsonSerializer.Deserialize<JsonElement>(widgetValue);
string action = actionData.GetProperty("action").GetString();
if (action == "onrowcheckboxclicked")
{
// Track selected rows or trigger related actions
// The frontend automatically manages selection state
var rowData = actionData.GetProperty("rowData");
string rowSk = rowData.GetProperty("_sk").GetString();
// Optionally store selection in widgetRelatedData
response["widgetRelatedData"] = new
{
customerstbl = new[] { rowSk }
};
// Or trigger custom logic based on selection
// For example, load related data when a row is selected
}
}
elseif ($eventType === 'ONTABLERUNACTIONEVENT') {
// Parse the widget value JSON
$widgetValue = json_decode($requestData['widgetValue'], true);
$action = $widgetValue['action'];
if ($action === 'onrowcheckboxclicked') {
// Track selected rows or trigger related actions
// The frontend automatically manages selection state
$rowData = $widgetValue['rowData'];
$rowSk = $rowData['_sk'];
// Optionally store selection in widgetRelatedData
$response['widgetRelatedData'] = [
'customerstbl' => [$rowSk]
];
// Or trigger custom logic based on selection
// For example, load related data when a row is selected
}
}
File Upload Event
Triggered: When user uploads files through datatable upload button
Event Type: onFileUploadCreateDocumentEvent
Note: Uses FormData multipart upload, not JSON. Requires a separate upload endpoint.
- C# / .NET
- PHP
[HttpPost("uploadfiles")]
[AllowAnonymous]
public async Task<IActionResult> UploadFiles(
[FromForm] IFormFileCollection files,
[FromForm] string widgetName,
[FromForm] string widgetEvent,
[FromForm] string formData,
[FromForm] string widgetContext)
{
var uploadedFiles = new List<Dictionary<string, string>>();
foreach (var file in files)
{
// Save file to storage (S3, local disk, etc.)
var fileName = $"{Guid.NewGuid()}_{file.FileName}";
var filePath = Path.Combine("uploads", fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// Create document record in database
var document = new Document
{
Id = Guid.NewGuid(),
FileName = file.FileName,
FilePath = filePath,
FileSize = file.Length,
ContentType = file.ContentType,
UploadedAt = DateTime.UtcNow
};
await _dbContext.Documents.AddAsync(document);
uploadedFiles.Add(new Dictionary<string, string>
{
{ "_id", document.Id.ToString() },
{ "fileName", document.FileName },
{ "fileSize", document.FileSize.ToString() }
});
}
await _dbContext.SaveChangesAsync();
// Return response with commands to refresh UI
var response = new Dictionary<string, object>
{
{ "success", true },
{ "uploadedFiles", uploadedFiles },
{
"FECommand", new List<object>
{
new
{
command = "ReloadWidget",
args = new { widgetName }
},
new
{
command = "ShowMessage",
args = new
{
type = "success",
message = $"{files.Count} file(s) uploaded successfully"
}
}
}
}
};
return Ok(response);
}
public function uploadFiles(Request $request) {
$files = $request->file('files');
$widgetName = $request->input('widgetName');
$widgetEvent = $request->input('widgetEvent');
$formData = json_decode($request->input('formData'), true);
$widgetContext = json_decode($request->input('widgetContext'), true);
$uploadedFiles = [];
foreach ($files as $file) {
// Save file to storage
$fileName = uniqid() . '_' . $file->getClientOriginalName();
$filePath = $file->storeAs('uploads', $fileName, 'public');
// Create document record in database
$documentId = DB::table('documents')->insertGetId([
'file_name' => $file->getClientOriginalName(),
'file_path' => $filePath,
'file_size' => $file->getSize(),
'content_type' => $file->getMimeType(),
'uploaded_at' => now()
]);
$uploadedFiles[] = [
'_id' => $documentId,
'fileName' => $file->getClientOriginalName(),
'fileSize' => $file->getSize()
];
}
// Return response with commands to refresh UI
return response()->json([
'success' => true,
'uploadedFiles' => $uploadedFiles,
'FECommand' => [
[
'command' => 'ReloadWidget',
'args' => ['widgetName' => $widgetName]
],
[
'command' => 'ShowMessage',
'args' => [
'type' => 'success',
'message' => count($files) . ' file(s) uploaded successfully'
]
]
]
]);
}
Important Notes
- Async Loading: When a form with multiple datatables loads, each table triggers its
onTableLoadDataevent asynchronously and in parallel - Required Row Fields: Always include
_id,_sk, and_codein row data for proper action handling - Pagination: Use
DataTableMetafor server-side pagination implementation - widgetRelatedData: Use this to track row selections across requests
- Performance: For large tables, always implement server-side pagination (
backendpagination: true)