JS Hooks (UI & Editor)
Use the global window.CMS object to register tools and extensions in the Admin Panel.
Note
Always enqueue your JS files via the PHP hook
Hook::ADMIN_ENQUEUE_JS first.1. Visual Builder (Editor.js)
Register custom blocks for the drag-and-drop editor.
Creating a Custom Editor.js Tool
Example: A Google Maps block (components/editorjs/editor-map.js).
JAVASCRIPT
(function() {
class MapTool {
static get toolbox() {
return {
title: 'Google Map',
icon: '<svg width="20" height="20" viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>'
};
}
constructor({data, config, api}) {
this.data = {
lat: data.lat || '',
lng: data.lng || '',
zoom: data.zoom || 12
};
this.apiKey = config.apiKey || '';
this.wrapper = undefined;
}
render() {
this.wrapper = document.createElement('div');
this.wrapper.classList.add('custom-map-tool');
this.wrapper.style.padding = '15px';
this.wrapper.style.border = '1px solid #eee';
this.wrapper.style.backgroundColor = '#f9f9f9';
this.wrapper.innerHTML = `
<div style="font-weight:bold; margin-bottom:10px; color:#555;">📍 Map Settings</div>
<div style="display:flex; gap:10px;">
<input type="text" class="cdx-input map-lat" placeholder="Latitude (e.g. 48.8566)" value="${this.data.lat}">
<input type="text" class="cdx-input map-lng" placeholder="Longitude (e.g. 2.3522)" value="${this.data.lng}">
</div>
<div style="margin-top:10px; font-size:0.8em; color:#888;">
API Key: ${this.apiKey ? 'Yes (Hidden)' : 'Not defined'}
</div>
`;
return this.wrapper;
}
save(blockContent) {
return {
lat: blockContent.querySelector('.map-lat').value,
lng: blockContent.querySelector('.map-lng').value,
zoom: this.data.zoom
};
}
}
// Register tool in CMS
window.CMS = window.CMS || {};
window.CMS.editorTools = window.CMS.editorTools || {};
window.CMS.editorTools.map = {
class: MapTool,
tunes: ['containerTune'],
inlineToolbar: true,
config: {
apiKey: 'AIzaSyD...'
}
};
})();
2. Admin UI Form Field Renderers
The JS constructor exposes a global registry to let plugins render their own configuration UI for custom fields inside the Content Type / Form builders.
Entry point: window.CMS.fieldRenderers
JAVASCRIPT
// Register a renderer for a custom field type 'color_picker'
window.CMS.fieldRenderers['color_picker'] = function(fieldId, data) {
// Return HTML string or DOM element
return `<input type="color" id="${fieldId}" value="${data.value}">`;
};
JAVASCRIPT
document.addEventListener('DOMContentLoaded', () => {
window.CMS.fieldRenderers['rating'] = function(fieldId, data) {
// Return HTML string or DOM element
return `
<div class="form-container">
<label>Maximum number of stars</label>
<input type="number" class="form-control val-max-input" value="${data.validation_rules?.match(/max:(\d+)/)?.[1] || 5}">
<input type="hidden" name="fields[${fieldId}][validation_rules]" class="validation-rules-final">
</div>
`;
};
});
3. Media & Icons API
Interact dynamically with the built-in CMS managers.
| Method | Description |
|---|---|
window.CMS.media.open(options) | Open the Media Library modal. options: { multiple: bool, onSelect: func, trigger: HTMLElement }. |
window.CMS.media.load(page) | Force reload the media grid via AJAX. |
window.CMS.icons.open(options) | Open the Icon Picker modal. options: { onSelect: func }. |
window.CMS.icons.registerProvider(type, name, provider) | Register a new icon set (e.g. FontAwesome). provider must implement load() and render(). |
Registering an Icon Provider
Here is the implementation of fontawesome-provider.js to complement the PHP logic shown in Page 1:
JAVASCRIPT
window.CMS.icons.registerProvider('fontawesome', 'Font Awesome 7', {
// Path to the JSON catalog of icons
path: window.CMS.baseUrl + "assets/themes/mon-theme/assets/vendors/fontawesome/icons.json",
// Load the icon list
async load() {
const response = await fetch(this.path);
const jsonData = await response.json();
return Object.keys(jsonData).map(iconName => ({
name: iconName,
styles: jsonData[iconName].styles
}));
},
// Render the icon in the selection grid
render(iconData, callback) {
const item = document.createElement('div');
item.className = 'grid-icon-item';
const style = iconData.styles.includes('solid') ? 'solid' : iconData.styles[0];
const libraryKey = `fa-${style}`;
const iconClassName = `fa-${style} fa-${iconData.name}`;
item.innerHTML = `
<i class="${iconClassName}"></i>
<div class="icon-name">${iconData.name}</div>
`;
// Click action: Send data to the CMS
item.addEventListener('click', () => {
const iconDataForForm = {
html: `<i class="${iconClassName}"></i>`,
library: libraryKey,
name: iconData.name
};
callback(iconDataForForm);
});
return item;
}
});
4. Global Event Listeners
The CMS broadcasts specific events across the document when actions occur within the managers. You can listen to these from any JS extension.
JAVASCRIPT
// Triggered when a media is selected (Legacy Mode / no specific callback provided)
document.addEventListener('cms:media:selected', (e) => {
const mediaData = e.detail;
console.log('User selected an image:', mediaData.url);
});
// Triggered to force the media tool to reload its grid
document.addEventListener('cms:media:reload', (e) => {
if (window.CMS.media) { window.CMS.media.load(1); }
});
// Triggered when an icon is selected (Legacy Mode)
document.addEventListener('cms:icon:selected', (e) => {
const iconData = e.detail; // { library, name, html }
console.log('User selected an icon:', iconData.name);
});
// Triggered specifically by the Editor.js Gallery Tool when media array is confirmed
document.addEventListener('cms:gallery:confirmed', (e) => {
const mediaDataArray = e.detail;
console.log('User confirmed a gallery selection containing:', mediaDataArray.length, 'items');
});