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

  1. Always use $block = {} — never classes, modules, or IIFEs.
  2. Event handlers are always inline: onclick="$block.method(event)".
  3. Use ParadigmAPI.REQUEST() for extension API; fetch() for REST.
  4. Prepend $block- to all classes and IDs.
  5. Use WindowManager.createWindow() for modals.
  6. Use ParadigmPages.fetchBlock() to load blocks.
  7. Call ParadigmCSS.render() after DOM updates.
  8. Check response.errors first, then response.success.
  9. Use the document.readyState init pattern.
  10. Debounce search inputs with 300ms delay.