Vue Components

Vue Components

Build reactive user interfaces with Vue 3 components.

October CMS provides a powerful framework for building Vue 3 components that integrate seamlessly with the backend panel and CMS. Vue components consist of three parts: a PHP class, a JavaScript ES module, and a template partial. This architecture enables reactive user interfaces while maintaining clean separation of concerns.

# Directory Structure

Vue components reside in the vuecomponents directory within a plugin or module. Each component has its own subdirectory containing the PHP class, JavaScript files, optional CSS, and template partials.

├── vuecomponents | ├── mycomponent | | ├── assets | | | ├── js | | | | └── mycomponent.js ← JavaScript Module | | | └── css | | | └── mycomponent.css ← StyleSheet File (optional) | | └── partials | | └── _mycomponent.php ← Template Partial | └── MyComponent.php ← Component Class

The naming convention uses lowercase for directories and file names, while the PHP class uses PascalCase. The JavaScript and partial files derive their names from the component class.

# Component Class

The PHP class defines the server-side configuration for your Vue component. It must extend Backend\Classes\VueComponentBase and define a component name.

namespace Acme\Blog\VueComponents;

use Backend\Classes\VueComponentBase;

class PostEditor extends VueComponentBase
{
    /**
     * @var string componentName is the Vue component tag name (kebab-case)
     */
    protected $componentName = 'acme-blog-post-editor';
}

The componentName property defines the HTML tag used in templates. Use a unique, namespaced name in kebab-case to avoid conflicts with other components.

# Requiring Dependencies

Components can declare dependencies on other Vue components using the $require property. Dependencies are automatically registered before the component.

class PostEditor extends VueComponentBase
{
    protected $componentName = 'acme-blog-post-editor';

    /**
     * @var array require lists dependent Vue component classes
     */
    protected $require = [
        \Backend\VueComponents\RichEditor::class,
        \Backend\VueComponents\Modal::class
    ];
}

# Loading Additional Assets

Override the loadAssets method to include additional JavaScript or CSS files beyond the automatic defaults.

protected function loadAssets()
{
    $this->addJs('js/helpers.js', ['type' => 'module']);
    $this->addCss('css/custom-styles.css');
}

To load assets from dependencies (like vendor libraries), use loadDependencyAssets. These are loaded before the component's own assets.

protected function loadDependencyAssets()
{
    $this->addCss('vendor/library/styles.css');
    $this->addJs('vendor/library/script.js');
}

# Preparing Template Variables

Use the prepareVars method to pass data from PHP to the template partial.

protected function prepareVars()
{
    $this->vars['maxFileSize'] = Config::get('cms.max_upload_size');
    $this->vars['allowedTypes'] = ['image/png', 'image/jpeg'];
}

# JavaScript Module

The JavaScript file defines the Vue component using the Options API and ES module syntax. The module exports a default object containing the component definition.

export default {
    props: {
        initialContent: {
            type: String,
            default: ''
        },
        readonly: {
            type: Boolean,
            default: false
        }
    },
    data() {
        return {
            content: this.initialContent,
            isDirty: false
        };
    },
    computed: {
        characterCount() {
            return this.content.length;
        }
    },
    methods: {
        save() {
            this.$emit('save', this.content);
            this.isDirty = false;
        },
        reset() {
            this.content = this.initialContent;
            this.isDirty = false;
        }
    },
    watch: {
        content(newValue, oldValue) {
            this.isDirty = newValue !== this.initialContent;
        }
    },
    mounted() {
        // Component is ready
    },
    beforeUnmount() {
        // Cleanup before removal
    }
};

# Importing Utilities

JavaScript modules can import utilities and helper classes from other files.

import { formatDate, parseDate } from './classes/date-utils.js';
import { validateInput } from './classes/validators.js';

export default {
    methods: {
        formatTimestamp(timestamp) {
            return formatDate(timestamp);
        }
    }
};

# Extending Other Components

Components can extend other component definitions using the extends property.

import BaseEditor from './base-editor.js';

export default {
    extends: BaseEditor,
    methods: {
        // Override or add methods
        customMethod() {
            // Call parent method if needed
        }
    }
};

# Template Partial

The template partial contains the Vue template markup. It's a PHP file that can include server-side logic if needed.

<div class="post-editor-component">
    <div class="editor-toolbar">
        <button
            type="button"
            class="btn btn-primary"
            :disabled="!isDirty"
            @click="save"
        >
            <?= e(trans('acme.blog::lang.editor.save')) ?>
        </button>
        <button
            type="button"
            class="btn btn-secondary"
            @click="reset"
        >
            <?= e(trans('acme.blog::lang.editor.reset')) ?>
        </button>
    </div>

    <textarea
        v-model="content"
        :readonly="readonly"
        class="form-control"
    ></textarea>

    <div class="editor-footer">
        <span v-text="characterCount"></span> characters
    </div>
</div>

Templates support standard Vue 3 syntax including directives, event bindings, and slots. PHP can be used for translations, asset URLs, and server-side configuration.

# Using Slots

Define slots to allow parent components to inject content.

<div class="modal-component">
    <div class="modal-header">
        <slot name="header">
            <h5>Default Title</h5>
        </slot>
    </div>
    <div class="modal-body">
        <slot></slot>
    </div>
    <div class="modal-footer">
        <slot name="footer">
            <button @click="close">Close</button>
        </slot>
    </div>
</div>

# Subcomponents

Complex components can define subcomponents that share the parent's context. Register subcomponents in the PHP class.

class Modal extends VueComponentBase
{
    protected $componentName = 'backend-modal';

    protected function registerSubcomponents()
    {
        $this->registerSubcomponent('alert');
        $this->registerSubcomponent('confirm');
    }
}

Each subcomponent requires its own JavaScript file and template partial.

├── vuecomponents | ├── modal | | ├── assets | | | └── js | | | ├── modal.js ← Main component | | | ├── alert.js ← Subcomponent | | | └── confirm.js ← Subcomponent | | └── partials | | ├── _modal.php | | ├── _alert.php | | └── _confirm.php | └── Modal.php

Subcomponents are named by appending the subcomponent name to the parent name. For example, backend-modal with subcomponent alert becomes backend-modal-alert.

Subcomponents can extend the parent component or other subcomponents.

// alert.js
import modal from './modal.js';

export default {
    extends: modal,
    data() {
        return {
            alertType: 'info'
        };
    },
    methods: {
        showAlert(message) {
            this.content = message;
            this.show();
        }
    }
};

# Registering Components

# Backend Controllers

In backend controllers, register Vue components using the registerVueComponent method. This is typically done in the controller's constructor or action methods.

class Posts extends Controller
{
    public function edit($id)
    {
        $this->registerVueComponent(\Acme\Blog\VueComponents\PostEditor::class);
        $this->registerVueComponent(\Backend\VueComponents\Modal::class);

        // ... rest of the action
    }
}

The controller's layout automatically outputs the component templates and JavaScript registration code.

# CMS Components

Vue component support in CMS Components will be available in a future update.

CMS components can also register Vue components for use in the frontend. The VueMaker trait provides the same registration methods available to backend controllers.

class MyComponent extends ComponentBase
{
    use \Backend\Traits\VueMaker;

    public function onRun()
    {
        $this->registerVueComponent(\Acme\Blog\VueComponents\PostViewer::class);
    }
}

# Using Components in Templates

Once registered, use Vue components in your templates with their tag name.

<div id="app">
    <acme-blog-post-editor
        ref="editor"
        :initial-content="content"
        :readonly="isLocked"
        @save="onSave"
    ></acme-blog-post-editor>

    <backend-modal ref="confirmModal">
        <template #header>
            <h5>Confirm Action</h5>
        </template>
        <p>Are you sure you want to proceed?</p>
        <template #footer>
            <button @click="confirm">Yes</button>
            <button @click="$refs.confirmModal.hide()">No</button>
        </template>
    </backend-modal>
</div>

# Creating Vue Applications

To mount a Vue application, use the oc.createVueApp factory function. This automatically registers all Vue components that have been output to the page.

<script type="module">
    const app = oc.createVueApp({
        data() {
            return {
                content: '',
                isLocked: false
            };
        },
        methods: {
            onSave(content) {
                // Handle save
            },
            confirm() {
                this.$refs.confirmModal.hide();
                // Proceed with action
            }
        }
    });

    app.mount('#app');
</script>

For convenience, use oc.mountVueApp to create and mount in one step.

<script type="module">
    const { app, vm } = oc.mountVueApp('#app', {
        data() {
            return {
                message: 'Hello World'
            };
        }
    });
</script>

# Inline Templates

If the mount element contains a <template> child, it will be used as the app's template automatically.

<div id="app">
    <template>
        <div>
            <h1 v-text="title"></h1>
            <acme-blog-post-editor></acme-blog-post-editor>
        </div>
    </template>
</div>

<script type="module">
    oc.mountVueApp('#app', {
        data() {
            return {
                title: 'Edit Post'
            };
        }
    });
</script>

# Component Communication

# Props and Events

Components communicate through props (parent to child) and events (child to parent).

<!-- Parent template -->
<child-component
    :value="parentValue"
    @update="handleUpdate"
></child-component>
// Child component
export default {
    props: ['value'],
    methods: {
        save() {
            this.$emit('update', this.localValue);
        }
    }
};

# Template Refs

Access child component instances using template refs.

<child-component ref="child"></child-component>
<button @click="$refs.child.doSomething()">Trigger</button>

# Provide and Inject

Share data across deeply nested components without prop drilling.

// Ancestor component
export default {
    provide() {
        return {
            theme: this.theme,
            updateTheme: this.updateTheme
        };
    }
};

// Descendant component
export default {
    inject: ['theme', 'updateTheme'],
    computed: {
        themeClass() {
            return `theme-${this.theme}`;
        }
    }
};

# Global Objects

October CMS exposes several global objects for Vue component development.

Object Description
oc.createVueApp() Create a Vue app with registered components
oc.mountVueApp() Create and mount a Vue app
oc.vueComponents Registry of all Vue component definitions
oc.vueDirectives Registry of custom Vue directives

# Best Practices

# Naming Conventions

  • Use a unique namespace prefix for component names (e.g., acme-blog-)
  • Use kebab-case for component tag names
  • Use PascalCase for PHP class names
  • Use lowercase for directory and file names

# Component Design

  • Keep components focused on a single responsibility
  • Use props for configuration, events for communication
  • Avoid direct DOM manipulation; use Vue's reactivity
  • Clean up resources in beforeUnmount

# Performance

  • Lazy load heavy components when possible
  • Use v-show instead of v-if for frequently toggled content
  • Avoid expensive computations in templates

# See Also