
Utilities: Grid columns
Configuring columns in MiniShop3 admin tables.
Purpose
Main tool for configuring columns in MiniShop3 admin tables:
- Enable and disable columns
- Change column order
- Configure sorting and filtering
- Set column width
- Add custom columns
- Configure inline editing (for the
category-productsgrid)
From version 1.7.0
The system setting ms3_category_grid_fields was removed. Category product table columns are configured only here.
Available grids
| Grid | Description |
|---|---|
customers | Customer list |
orders | Order list |
category-products | Products in category |
order_products | Products in order |
vendors | Vendor list |
Interface
Grid selection
Select a grid from the dropdown at the top of the page.
Columns table
Shows current column configuration:
| Column | Description |
|---|---|
| Name | System field name |
| Label | Display header |
| Visible | Show column |
| Sortable | Allow sort on click |
| Filter | Show filter |
| Frozen | Fix on horizontal scroll |
| Width | Width in pixels |
Actions
- Drag — reorder columns
- Edit — click row to open dialog
- Add — button to create new column
Column parameters
Basic
| Parameter | Description |
|---|---|
| Field name | Model field name or alias |
| Label | Column header |
| Visible | Show column |
| Sortable | Allow sort on click |
| Filterable | Show filter field |
| Frozen | Fix on scroll |
Sizes
| Parameter | Description |
|---|---|
| Width | Width in px or % |
| Min width | Minimum width when resizing |
Column type
| Type | Description |
|---|---|
model | Field from data model |
template | Template column (HTML) |
relation | Data from related table |
computed | Computed value |
image | Image display |
boolean | Yes/No flag |
actions | Actions column |
Column types
Model (model field)
Standard column showing a field value.
Type: model
Field: email
Label: EmailTemplate (template)
Column with HTML template.
Type: template
Template: <a href="mailto:{email}">{email}</a>Variables — current row fields in curly braces.
Relation (relation)
Data from related table.
Relation parameters:
| Parameter | Description |
|---|---|
| Table | Related table name |
| Foreign key | Link field |
| Display field | Field to show |
| Aggregation | COUNT, SUM, AVG, MIN, MAX |
Example — customer order count:
Type: relation
Table: msOrder
Foreign key: customer_id
Aggregation: COUNTComputed (computed)
Value computed on server by a custom class.
Type: computed
Class: MyComponent\Columns\TotalSpentColumnImage (image)
Image thumbnail.
Type: image
Field: imageBoolean (boolean)
Yes/No flag with icon.
Type: boolean
Field: activeActions (actions)
Column with action buttons. Supports built-in and custom handlers.
Action config:
[
{
"name": "edit",
"handler": "edit",
"icon": "pi-pencil",
"label": "Edit",
"severity": null
},
{
"name": "delete",
"handler": "delete",
"icon": "pi-trash",
"label": "Delete",
"severity": "danger",
"confirm": true,
"confirmMessage": "Are you sure you want to delete this record?"
}
]Action parameters:
| Parameter | Type | Description |
|---|---|---|
name | string | Unique action name |
handler | string | Handler name from registry |
icon | string | PrimeIcons icon (without pi- prefix) |
label | string | Tooltip text / lexicon key |
severity | string | Button style: danger, success, secondary, info, warn |
confirm | boolean | Require confirmation |
confirmMessage | string | Confirmation text |
visible | boolean | Button visibility |
disabled | boolean/function | Disable button |
disabledField | string | Row field to check for disabled |
Built-in handlers:
| Handler | Description |
|---|---|
edit | Open record for edit |
delete | Delete record |
view | View record |
addresses | Manage addresses (customers) |
refresh | Refresh grid |
Configuration examples
Hide column
- Find the column in the list
- Uncheck "Visible"
- Click "Save"
Add "Total orders" column
- Click "Add column"
- Fill:
- Name:
total_spent - Label:
Total orders - Type:
relation - Table:
msOrder - Foreign key:
customer_id - Display field:
cost - Aggregation:
SUM
- Name:
- Save
Change column order
Drag columns into the desired order.
Add link in email column
- Find column
email - Change type to
template - Set template:
<a href="mailto:{email}">{email}</a> - Save
API Endpoints
Get grid config
GET /api/mgr/grid-config/{grid_name}Response:
{
"success": true,
"object": {
"columns": [
{
"name": "id",
"label": "ID",
"visible": true,
"sortable": true,
"filterable": false,
"frozen": true,
"width": 60,
"type": "model"
}
]
}
}Save config
PUT /api/mgr/grid-config/{grid_name}Request body:
{
"columns": [
{
"name": "id",
"label": "ID",
"visible": true,
"sortable": true,
"filterable": false,
"frozen": true,
"width": 60,
"sort_order": 0
}
]
}Reset to default
DELETE /api/mgr/grid-config/{grid_name}System columns
Some columns are marked as system columns and have restrictions:
- Cannot be deleted
- Field name cannot be changed
- Can only be hidden
System columns usually include id and action columns.
Custom actions
MiniShop3 provides the global action registry MS3ActionRegistry for adding custom buttons to the actions column.
Action registry
The registry is available globally as window.MS3ActionRegistry and lets you:
- Register new action handlers
- Add before/after hooks for existing actions
- Override built-in handlers
Registry API
register(name, handler, options)
Registers a new action handler.
Parameters:
| Parameter | Type | Description |
|---|---|---|
name | string | Action name |
handler | function | Handler (data, context) => void |
options.override | boolean | Allow overwriting existing handler |
Handler parameters:
data— grid row data objectcontext— execution context:gridId— grid identifieremit(event, data)— emit eventrefresh()— refresh gridtoast— PrimeVue toast serviceconfirm— PrimeVue confirm service_(key)— localization function
registerBeforeHook(actionName, hook)
Registers a hook that runs before the action.
MS3ActionRegistry.registerBeforeHook('delete', (data, context) => {
// Return false to cancel the action
if (data.is_system) {
context.toast.add({
severity: 'warn',
summary: 'Forbidden',
detail: 'Cannot delete system record'
})
return false
}
return true
})registerAfterHook(actionName, hook)
Registers a hook that runs after the action.
MS3ActionRegistry.registerAfterHook('delete', (data, context, result) => {
console.log('Record deleted:', data.id)
// Send analytics, log, etc.
})Other methods
| Method | Description |
|---|---|
has(name) | Check if handler exists |
get(name) | Get handler |
unregister(name) | Remove handler (except built-in) |
getRegisteredActions() | List all registered actions |
execute(name, data, context) | Execute action programmatically |
Custom action examples
Example 1: Block customer
Step 1. Register handler (in MODX plugin or custom JS):
// File: assets/components/mycomponent/js/customer-actions.js
document.addEventListener('DOMContentLoaded', () => {
// Wait for registry to load
if (!window.MS3ActionRegistry) {
console.error('MS3ActionRegistry not available')
return
}
// Register "Block" action
MS3ActionRegistry.register('blockCustomer', async (data, context) => {
try {
const response = await fetch('/assets/components/minishop3/connector.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'MyComponent\\Processors\\Customer\\Block',
id: data.id,
HTTP_MODAUTH: MODx.siteId
})
})
const result = await response.json()
if (result.success) {
context.toast.add({
severity: 'success',
summary: 'Success',
detail: `Customer ${data.email} blocked`,
life: 3000
})
context.refresh() // Refresh grid
} else {
throw new Error(result.message)
}
} catch (error) {
context.toast.add({
severity: 'error',
summary: 'Error',
detail: error.message,
life: 5000
})
}
})
// Register "Unblock" action
MS3ActionRegistry.register('unblockCustomer', async (data, context) => {
const response = await fetch('/assets/components/minishop3/connector.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'MyComponent\\Processors\\Customer\\Unblock',
id: data.id,
HTTP_MODAUTH: MODx.siteId
})
})
const result = await response.json()
if (result.success) {
context.toast.add({
severity: 'success',
summary: 'Customer unblocked',
life: 3000
})
context.refresh()
}
})
})Step 2. Load the script via MODX plugin:
<?php
// Plugin: MyCustomerActions
// Events: OnManagerPageBeforeRender
if ($modx->event->name !== 'OnManagerPageBeforeRender') return;
// Customers page only
$controller = $modx->controller ?? null;
if (!$controller || strpos(get_class($controller), 'Customers') === false) return;
$modx->regClientStartupScript(
MODX_ASSETS_URL . 'components/mycomponent/js/customer-actions.js'
);Step 3. Configure actions column in the UI:
- Open Utilities → Grid columns
- Select grid
customers - Find column
actionsand open editor - Add action:
- Name:
blockCustomer - Handler:
blockCustomer - Icon:
pi-ban - Severity:
danger - Confirm: Yes
- Message:
Block customer {email}?
- Name:
Example 2: Duplicate product
MS3ActionRegistry.register('duplicateProduct', async (data, context) => {
const response = await fetch('/assets/components/minishop3/connector.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'MiniShop3\\Processors\\Product\\Duplicate',
id: data.id,
HTTP_MODAUTH: MODx.siteId
})
})
const result = await response.json()
if (result.success) {
context.toast.add({
severity: 'success',
summary: 'Product duplicated',
detail: `Created product ID: ${result.object.id}`,
life: 3000
})
context.refresh()
}
})Action config:
{
"name": "duplicate",
"handler": "duplicateProduct",
"icon": "pi-copy",
"label": "Duplicate",
"severity": "secondary",
"confirm": false
}Example 3: Send notification
MS3ActionRegistry.register('sendNotification', async (data, context) => {
const template = prompt('Enter notification template name:')
if (!template) return
const response = await fetch('/assets/components/minishop3/connector.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'MiniShop3\\Processors\\Notification\\Send',
customer_id: data.id,
template: template,
HTTP_MODAUTH: MODx.siteId
})
})
const result = await response.json()
if (result.success) {
context.toast.add({
severity: 'success',
summary: 'Notification sent',
detail: `Email: ${data.email}`,
life: 3000
})
}
})Example 4: Conditional button visibility
Use a function for disabled:
// In column config
{
"name": "unblock",
"handler": "unblockCustomer",
"icon": "pi-unlock",
"label": "Unblock",
"severity": "success",
// Button disabled when customer is not blocked
"disabledField": "active" // disabled when active = true
}Or check via a data field:
{
"name": "block",
"handler": "blockCustomer",
"icon": "pi-ban",
"label": "Block",
"severity": "danger",
"disabledField": "blocked" // disabled when blocked = true
}Hooks for built-in actions
Logging deletes
MS3ActionRegistry.registerAfterHook('delete', async (data, context, result) => {
// Send to audit system
await fetch('/api/audit/log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'delete',
entity: context.gridId,
entityId: data.id,
user: MODx.user?.id,
timestamp: new Date().toISOString()
})
})
})Prevent delete
MS3ActionRegistry.registerBeforeHook('delete', (data, context) => {
// Prevent deleting records with status "paid"
if (context.gridId === 'orders' && data.status === 2) {
context.toast.add({
severity: 'error',
summary: 'Forbidden',
detail: 'Cannot delete paid order',
life: 5000
})
return false // Cancel action
}
return true // Continue
})Available icons
Uses icons from PrimeIcons. Popular choices:
| Icon | Class | Use |
|---|---|---|
| ✏️ | pi-pencil | Edit |
| 🗑️ | pi-trash | Delete |
| 👁️ | pi-eye | View |
| 📋 | pi-copy | Copy |
| ⬇️ | pi-download | Download |
| 📤 | pi-send | Send |
| 🔒 | pi-lock | Lock |
| 🔓 | pi-unlock | Unlock |
| 🚫 | pi-ban | Ban |
| ✅ | pi-check | Confirm |
| ❌ | pi-times | Cancel |
| 🔄 | pi-refresh | Refresh |
| ⚙️ | pi-cog | Settings |
| 🖨️ | pi-print | |
| 🔗 | pi-external-link | External link |
