Skip to content
  1. Extras
  2. MiniShop3
  3. Development
  4. Product tabs

Product tabs integration ​

MiniShop3 provides an API for adding custom tabs to the product edit page. You can integrate both Vue components and ExtJS panels.

Architecture ​

The product edit page uses a hybrid architecture:

ExtJS TabPanel (top level)
├── Document (MODX system tab)
├── Product (Vue ProductTabs)
│   ├── Properties (Vue)
│   ├── Gallery (Vue)
│   ├── Categories (Vue ProductCategoriesTab)
│   ├── Links (Vue ProductLinksTab)
│   ├── Product options (Vue)
│   └── [Your tabs]
├── Page Settings (MODX system tab)
└── Access Permissions (MODX system tab)

The "Product" tab is the Vue component ProductTabs (PrimeVue TabView). Categories and links are Vue as well, not ExtJS.

Plugin Registry API ​

The global object window.MS3ProductTabsRegistry is used to register tabs.

Methods ​

register(tabConfig) ​

Registers a new tab.

Parameters:

ParameterTypeRequiredDescription
keystringYesUnique tab identifier
titlestringYesTab title (shown in tab)
typestringNoTab type: 'vue' or 'extjs' (default 'vue')
componentstringFor VueVue component name
xtypestringFor ExtJSExtJS component xtype
extConfigobjectNoExtra config for ExtJS component
propsobjectNoProps for Vue component
positionnumberNoTab position (default 100)

Returns: boolean — whether registration succeeded

Vue component integration ​

Step 1: Create a Vue component ​

javascript
// my-component/src/VariantsTab.vue
<template>
  <div class="variants-tab">
    <h3>Product #{{ productId }} variants</h3>
    <!-- Your content -->
  </div>
</template>

<script setup>
defineProps({
  productId: {
    type: Number,
    required: true
  },
  record: {
    type: Object,
    required: true
  }
})
</script>

Step 2: Create entry point ​

javascript
// my-component/src/entries/variants-tab.js
import { createApp } from 'vue'
import PrimeVue from 'primevue/config'
import VariantsTab from '../components/VariantsTab.vue'

// Register component globally
window.MS3VariantsTab = VariantsTab

// Register tab
if (window.MS3ProductTabsRegistry) {
  window.MS3ProductTabsRegistry.register({
    key: 'variants',
    title: 'Variants',
    type: 'vue',
    component: 'MS3VariantsTab',
    position: 5 // After Options
  })
}

Step 3: Load script in MODX controller ​

php
// core/components/mycomponent/controllers/product.class.php
public function loadCustomCssJs()
{
    $assetsUrl = $this->modx->getOption('mycomponent.assets_url');

    // Load after product-tabs.min.js
    $this->addLastJavascript($assetsUrl . 'js/variants-tab.min.js');
}

Step 4: Handling in ProductTabs ​

For full Vue integration, register the component in ProductTabs. Add handling in the template:

vue
<!-- ProductTabs.vue already supports plugin components -->
<template v-else-if="tab.type === 'vue' && tab.component">
  <component
    :is="getComponent(tab.component)"
    v-bind="tab.props"
    :product-id="productId"
    :record="record"
  />
</template>
javascript
// In script setup
function getComponent(name) {
  // Look up component in global scope
  return window[name] || null
}

ExtJS component integration ​

Step 1: Create ExtJS panel ​

javascript
// assets/components/mycomponent/js/mgr/widgets/custom.panel.js
MyComponent.panel.CustomTab = function(config) {
    config = config || {};

    Ext.apply(config, {
        border: false,
        layout: 'anchor',
        items: [{
            xtype: 'modx-grid',
            // ... grid config
        }]
    });

    MyComponent.panel.CustomTab.superclass.constructor.call(this, config);
};
Ext.extend(MyComponent.panel.CustomTab, MODx.Panel);
Ext.reg('mycomponent-custom-tab', MyComponent.panel.CustomTab);

Step 2: Register the tab ​

javascript
// assets/components/mycomponent/js/mgr/product-integration.js
Ext.onReady(function() {
    if (window.MS3ProductTabsRegistry) {
        window.MS3ProductTabsRegistry.register({
            key: 'custom',
            title: 'Custom tab',
            type: 'extjs',
            xtype: 'mycomponent-custom-tab',
            extConfig: {
                // record is passed automatically!
                // Extra params:
                myParam: 'value'
            },
            position: 6
        });
    }
});

record is passed automatically

For ExtJS tabs, record with product data is passed automatically in extConfig. You do not need to set it explicitly.

Step 3: Load scripts ​

php
public function loadCustomCssJs()
{
    $assetsUrl = $this->modx->getOption('mycomponent.assets_url');

    // Panel first
    $this->addLastJavascript($assetsUrl . 'js/mgr/widgets/custom.panel.js');
    // Then integration
    $this->addLastJavascript($assetsUrl . 'js/mgr/product-integration.js');
}

Tab positions ​

Built-in tabs use these positions:

TabPosition
Properties0
Gallery1
Categories2
Links3
Options4

Recommended positions for custom tabs: 5–99.

Accessing product data ​

Your component receives:

Vue components ​

javascript
const props = defineProps({
  productId: Number,  // Product ID
  record: Object      // Full product data
})

// Usage
console.log(props.record.pagetitle)
console.log(props.record.price)

ExtJS components ​

javascript
MyComponent.panel.CustomTab = function(config) {
    // config.record contains product data
    console.log(config.record.id);
    console.log(config.record.pagetitle);

    // ...
};

Example: ms3Variants integration ​

javascript
// variants.wrapper.js
(function() {
    'use strict';

    // Wait for MS3ProductTabsRegistry
    function registerVariantsTab() {
        if (!window.MS3ProductTabsRegistry) {
            setTimeout(registerVariantsTab, 100);
            return;
        }

        window.MS3ProductTabsRegistry.register({
            key: 'variants',
            title: _('ms3variants_tab_title') || 'Variants',
            type: 'extjs',
            xtype: 'ms3variants-product-panel',
            extConfig: {
                // record is passed automatically
            },
            position: 5
        });

        console.log('[ms3Variants] Tab registered');
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', registerVariantsTab);
    } else {
        registerVariantsTab();
    }
})();

Important notes ​

  1. Load order: Your scripts must load AFTER product-tabs.min.js

  2. Unique keys: Each tab must have a unique key

  3. Lazy loading: ExtJS components are mounted only when the tab is opened

  4. Cleanup: ExtJS components are destroyed when the page is closed

  5. CSS scope: Vue components inside ProductTabs inherit styles from the .vueApp container

Debugging ​

Check tab registration in the console:

javascript
// List registered tabs
console.log(window.MS3ProductTabsRegistry.pendingTabs);

// ProductTabs instance
console.log(window.MS3ProductTabsRegistry._instance);