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 ​

Main tool for configuring columns in MiniShop3 admin tables:

Cookbook

Badge column in orders and inline edit in category: Grid columns cookbook.

  • Enable and disable columns
  • Change column order
  • Configure sorting and filtering
  • Set column width
  • Add custom columns
  • Configure inline editing (for the category-products grid)

From version 1.7.0

The system setting ms3_category_grid_fields was removed. Category product table columns are configured only here.

Available grids ​

GridDescription
customersCustomer list
ordersOrder list
category-productsProducts in category
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
badgeColored label (source_field, color_field)
optionProduct option value (category-products grid)
computedComputed value
imageImage display
booleanYes/No flag
priceMoney value (displayConfig: currency, decimals)
weightWeight (displayConfig: unit, decimals)
datetimeDate and time (displayConfig: format)
actionsActions column

Column types ​

Model (model field) ​

Standard column showing a field value.

Type: model
Field: email
Label: Email

Template (template) ​

Column with HTML template.

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

Variables — current row fields in curly braces.

Badge ​

Colored label from text and HEX in other row fields. In orders, column order_status uses status_name and status_color.

Type: badge
source_field: status_name
color_field: status_color

Option (product option) ​

Option column in category-products. Set option.key (key from msOption).

Type: option
option.key: color

Relation (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 (computed) ​

Value is computed on the server. JSON config must include computed.className (class implements ComputedFieldInterface):

json
{
  "type": "computed",
  "computed": {
    "className": "MyComponent\\Columns\\TotalSpentColumn"
  }
}

Image (image) ​

Image thumbnail.

Type: image
Field: image

Boolean (boolean) ​

Yes/No flag with icon.

Type: boolean
Field: active

Price (price) ​

Formats a numeric field as money. Parameters in displayConfig JSON:

KeyDescription
decimalsDecimal places
currencyCurrency symbol
currency_positionbefore or after
thousands_separatorThousands separator
Type: price
Field: price
displayConfig: {"decimals":2,"currency":"₽","currency_position":"after","thousands_separator":" "}

Weight (weight) ​

Type: weight
Field: weight
displayConfig: {"decimals":2,"unit":"kg","unit_position":"after"}

Datetime (datetime) ​

Type: datetime
Field: createdon
displayConfig: {"format":"dd.MM.yyyy HH:mm"}

Format follows PrimeVue date formatter tokens (dd, MM, yyyy, HH, mm).

Actions (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

Configuration 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"
      }
    ],
    "direct_filter_keys": ["query", "status_id"],
    "editor_references": []
  }
}

editor_references is populated only for grid_key=category-products.

Save config ​

PUT /api/mgr/grid-config/{grid_key}

Request body:

json
{
  "fields": [
    {
      "name": "id",
      "label": "ID",
      "visible": true,
      "sortable": true,
      "filterable": false,
      "frozen": true,
      "width": 60,
      "type": "model"
    }
  ]
}

Permissions

GET /api/mgr/grid-config/{grid_key} requires view_document. Writes (PUT, POST, column DELETE) require mssetting_save.

Delete column ​

DELETE /api/mgr/grid-config/{grid_key}/{field_name}

System columns (is_system) cannot be deleted.

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) => {
  // 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.

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

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
// 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
<?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:

  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 a function for disabled:

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

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

Hooks for built-in actions ​

Logging deletes ​

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

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

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