Skip to content
  1. Extras
  2. MiniShop3
  3. Manager interface
  4. Utilities
  5. Grid columns

Utilities: Grid columns

Configuring columns in MiniShop3 admin tables.

Purpose

The tool configures columns in admin grids (tables):

  • Enable and disable columns
  • Change column order
  • Configure sorting and filtering
  • Set column width
  • Add custom columns

Available grids

GridDescription
customersCustomer list
ordersOrder list
productsProducts in category (category-products)
order_productsProducts in order
vendorsVendor list

Interface

Grid selection

Select a grid from the dropdown at the top of the page.

Columns table

Shows current column configuration:

ColumnDescription
NameSystem field name
LabelDisplay header
VisibleShow column
SortableAllow sort on click
FilterShow filter
FrozenFix on horizontal scroll
WidthWidth in pixels

Actions

  • Drag — reorder columns
  • Edit — click row to open dialog
  • Add — button to create new column

Column parameters

Basic

ParameterDescription
Field nameModel field name or alias
LabelColumn header
VisibleShow column
SortableAllow sort on click
FilterableShow filter field
FrozenFix on scroll

Sizes

ParameterDescription
WidthWidth in px or %
Min widthMinimum width when resizing

Column type

TypeDescription
modelField from data model
templateTemplate column (HTML)
relationData from related table
computedComputed value
imageImage display
booleanYes/No flag
actionsActions column

Column types

Model

Standard column showing a field value.

Type: model
Field: email
Label: Email

Template

Column with HTML template.

Type: template
Template: <a href="mailto:{email}">{email}</a>

Variables — current row fields in curly braces.

Relation

Data from related table.

Relation parameters:

ParameterDescription
TableRelated table name
Foreign keyLink field
Display fieldField to show
AggregationCOUNT, SUM, AVG, MIN, MAX

Example — customer order count:

Type: relation
Table: msOrder
Foreign key: customer_id
Aggregation: COUNT

Computed

Value computed on server by a custom class.

Type: computed
Class: MyComponent\Columns\TotalSpentColumn

Image

Image thumbnail.

Type: image
Field: image

Boolean

Yes/No flag with icon.

Type: boolean
Field: active

Actions

Column with action buttons. Supports built-in and custom handlers.

Action config:

json
[
  {
    "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:

ParameterTypeDescription
namestringUnique action name
handlerstringHandler name from registry
iconstringPrimeIcons icon (without pi- prefix)
labelstringTooltip text / lexicon key
severitystringButton style: danger, success, secondary, info, warn
confirmbooleanRequire confirmation
confirmMessagestringConfirmation text
visiblebooleanButton visibility
disabledboolean/functionDisable button
disabledFieldstringRow field to check for disabled

Built-in handlers:

HandlerDescription
editOpen record for edit
deleteDelete record
viewView record
addressesManage addresses (customers)
refreshRefresh grid

Examples

Hide column

  1. Find the column in the list
  2. Uncheck "Visible"
  3. Click "Save"

Add "Total orders" column

  1. Click "Add column"
  2. Fill:
    • Name: total_spent
    • Label: Total orders
    • Type: relation
    • Table: msOrder
    • Foreign key: customer_id
    • Display field: cost
    • Aggregation: SUM
  3. Save

Change column order

Drag columns into the desired order.

  1. Find column email
  2. Change type to template
  3. Set template: <a href="mailto:{email}">{email}</a>
  4. Save

API Endpoints

Get grid config

GET /api/mgr/grid-config/{grid_name}

Response:

json
{
  "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:

json
{
  "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:

ParameterTypeDescription
namestringAction name
handlerfunctionHandler (data, context) => void
options.overridebooleanAllow overwriting existing handler

Handler parameters:

  • data — grid row data object
  • context — execution context:
    • gridId — grid identifier
    • emit(event, data) — emit event
    • refresh() — refresh grid
    • toast — PrimeVue toast service
    • confirm — PrimeVue confirm service
    • _(key) — localization function

registerBeforeHook(actionName, hook)

Registers a hook that runs before the action.

javascript
MS3ActionRegistry.registerBeforeHook('delete', (data, context) => {
  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.

javascript
MS3ActionRegistry.registerAfterHook('delete', (data, context, result) => {
  console.log('Record deleted:', data.id)
})

Other methods

MethodDescription
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):

javascript
document.addEventListener('DOMContentLoaded', () => {
  if (!window.MS3ActionRegistry) {
    console.error('MS3ActionRegistry not available')
    return
  }

  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()
      } else {
        throw new Error(result.message)
      }
    } catch (error) {
      context.toast.add({
        severity: 'error',
        summary: 'Error',
        detail: error.message,
        life: 5000
      })
    }
  })

  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 script via MODX plugin:

php
<?php
// Plugin: MyCustomerActions
// Events: OnManagerPageBeforeRender

if ($modx->event->name !== 'OnManagerPageBeforeRender') return;

$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:

  1. Open Utilities → Grid columns
  2. Select grid customers
  3. Find column actions and open editor
  4. Add action:
    • Name: blockCustomer
    • Handler: blockCustomer
    • Icon: pi-ban
    • Severity: danger
    • Confirm: Yes
    • Message: Block customer {email}?

Example 2: Duplicate product

javascript
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:

json
{
  "name": "duplicate",
  "handler": "duplicateProduct",
  "icon": "pi-copy",
  "label": "Duplicate",
  "severity": "secondary",
  "confirm": false
}

Example 3: Send notification

javascript
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 disabledField in column config:

json
{
  "name": "unblock",
  "handler": "unblockCustomer",
  "icon": "pi-unlock",
  "label": "Unblock",
  "severity": "success",
  "disabledField": "active"
}

Or:

json
{
  "name": "block",
  "handler": "blockCustomer",
  "icon": "pi-ban",
  "label": "Block",
  "severity": "danger",
  "disabledField": "blocked"
}

Hooks for built-in actions

Logging deletes

javascript
MS3ActionRegistry.registerAfterHook('delete', async (data, context, result) => {
  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

javascript
MS3ActionRegistry.registerBeforeHook('delete', (data, context) => {
  if (context.gridId === 'orders' && data.status === 2) {
    context.toast.add({
      severity: 'error',
      summary: 'Forbidden',
      detail: 'Cannot delete paid order',
      life: 5000
    })
    return false
  }
  return true
})

Available icons

Icons from PrimeIcons. Common ones:

IconClassUse
✏️pi-pencilEdit
🗑️pi-trashDelete
👁️pi-eyeView
📋pi-copyCopy
⬇️pi-downloadDownload
📤pi-sendSend
🔒pi-lockLock
🔓pi-unlockUnlock
🚫pi-banBan
pi-checkConfirm
pi-timesCancel
🔄pi-refreshRefresh
⚙️pi-cogSettings
🖨️pi-printPrint
🔗pi-external-linkExternal link