Conventions
JavaScript conventions
Block scripts, API calls, window management, and DOM rendering all follow a single set of conventions in Paradigm CMS.
The $block pattern
Every block script uses $block = {}; as its namespace object. Never use classes, modules, or
IIFE patterns.
<script>
$block = {};
$block.organizationId = <?php echo intval($organizationId); ?>;
$block.items = <?php echo json_encode($items); ?>;
$block.currentPage = 1;
$block.totalPages = 1;
$block.searchTimeout = null;
$block.init = function() {
$block.renderItems();
}
if (document.readyState === 'complete') {
$block.init();
} else {
document.addEventListener('DOMContentLoaded', $block.init);
}
</script>
Block file anatomy
Block files are PHP files with three sections: a PHP header that fetches data,
HTML markup with css="" styling, and a <script> block.
<?php
global $ParadigmPages, $site;
$blockData = PAPI_get_block_data();
$organizationId = isset($blockData['organization_ID'])
? intval($blockData['organization_ID']) : 0;
// Auth check for admin blocks
if (PAPI_is_user_logged_in() !== true || CURRENT_USER['is_admin'] !== true) {
echo '<p>You do not have permission to view this page.</p>';
return;
}
// Fetch initial data
$items = PAPI_get_objects('{ext}:{object}', array(
'conditions' => array(
array(array('organization_ID', '=', $organizationId)),
array(array('status', '=', 'active'))
),
'orderBy' => array('id' => 'DESC'),
'limit' => 20
), 'internal');
if (!is_array($items)) $items = array();
?>
<!-- HTML with css="" attributes -->
<div id="$block-items-container"></div>
<script>
$block = {};
// ... block logic
</script>
Event handlers
Event handlers are always inline in the HTML. Never use addEventListener for UI events inside blocks.
<button onclick="$block.submit(event)">Save</button>
<input oninput="$block.handleSearch(event)" />
<a onclick="$block.deleteItem(event, 42)">Delete</a>
API requests (extension API)
For handlers in API/requests/, use ParadigmAPI.REQUEST():
const response = await ParadigmAPI.REQUEST(SITEURL + '/API/index.php', {
action: 'extension:entity->action',
actionVars: {
organization_ID: $block.organizationId,
page: $block.currentPage
}
});
if (response.errors) {
ParadigmAPI.processErrors(response.errors);
return;
}
if (response.success) {
$block.items = response.items;
$block.renderItems();
}
API requests (REST endpoints)
For handlers in API/REST/, use the native fetch() API:
const response = await fetch(SITEURL + '/API/extension/resource', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
param1: value1,
param2: value2
})
});
const data = await response.json();
if (data.success) {
// Handle success
} else if (data.errors) {
ParadigmAPI.processErrors(data.errors);
}
Error handling
Always check response.errors first, call ParadigmAPI.processErrors(),
then check response.success:
if (response.errors) {
ParadigmAPI.processErrors(response.errors);
return;
}
if (response.success) {
// Handle success
}
Window management
All modals and windows use WindowManager.createWindow():
WindowManager.createWindow({
title: 'Create New Item',
width: 800,
focus: 'exclusive',
content: async function() {
return await ParadigmPages.fetchBlock(
'extension:admin->items->create',
{ organization_ID: $block.organizationId }
);
},
onClose: function() {
$block.loadItems();
}
});
Options:
title— Window title.width— Width in pixels.focus— Set to'exclusive'for modal behavior.content— Async function that returns HTML.onClose— Callback when the window closes.
Fetching blocks
Use ParadigmPages.fetchBlock() to load block content dynamically:
let html = await ParadigmPages.fetchBlock('extension:admin->entity->edit', {
entity_ID: entityId,
organization_ID: $block.organizationId
});
Class and ID naming
Always prepend $block- to all classes and IDs within a block to prevent naming conflicts:
<div id="$block-items-container" class="$block-items-list">
<div class="$block-item" id="$block-item-1">Item 1</div>
<div class="$block-item" id="$block-item-2">Item 2</div>
</div>
CSS rendering
After updating DOM content, call ParadigmCSS.render() to process
css="" attributes:
$block.renderItems = function() {
const container = document.getElementById('$block-items-container');
container.innerHTML = /* HTML content */;
ParadigmCSS.render();
}
For blocks using ParadigmUI bindings (bind="", loop="", pui-if):
await ParadigmUI.render({
selector: '#$block-items-list-container'
});
ParadigmCSS.render();
Init pattern
Always use the readyState check so the init function runs whether the DOM is already loaded or still loading:
if (document.readyState === 'complete') {
$block.init();
} else {
document.addEventListener('DOMContentLoaded', $block.init);
}
Debounced search
Use clearTimeout / setTimeout with a 300ms delay:
$block.searchTimeout = null;
$block.handleSearch = function(event) {
if ($block.searchTimeout) {
clearTimeout($block.searchTimeout);
}
$block.searchTimeout = setTimeout(async () => {
$block.currentPage = 1;
await $block.loadItems();
}, 300);
}
Critical rules summary
- Always use
$block = {}— never classes, modules, or IIFEs. - Event handlers are always inline:
onclick="$block.method(event)". - Use
ParadigmAPI.REQUEST()for extension API;fetch()for REST. - Prepend
$block-to all classes and IDs. - Use
WindowManager.createWindow()for modals. - Use
ParadigmPages.fetchBlock()to load blocks. - Call
ParadigmCSS.render()after DOM updates. - Check
response.errorsfirst, thenresponse.success. - Use the
document.readyStateinit pattern. - Debounce search inputs with 300ms delay.