# Custom WordPress Blocks (Vanilla JS + PHP)

This guide explains the "No-Compiler" architecture for building native WordPress blocks. This method bypasses the need for Node.js/NPM by using the global `window.wp` object and a manual PHP autoloader.

---

## 1. Folder Structure
Every block lives in its own directory within the theme's `/blocks` folder:

```text
wp-content/themes/wp-scratch/
└── blocks/
    └── call-to-action/
        ├── block.json    # Metadata and PHP link
        ├── index.js      # Editor UI (Vanilla JS)
        └── render.php    # Front-end Template (PHP)

```

## 2. The Metadata (block.json)

The JSON must be kept minimal. Do not include the editorScript key here, as we load the script manually in PHP to handle dependencies.

```json
{
  "apiVersion": 3,
  "name": "wp-scratch/call-to-action",
  "title": "Call to Action",
  "category": "text",
  "icon": "megaphone",
  "render": "file:./call-to-action.php"
}
```

## 3. The Editor Logic (index.js)

Since there is no compiler to handle import statements or JSX, we use the global wp object. We wrap the code in a recursive init function to ensure the script doesn't fire until window.wp is fully defined by the browser.

```js
(function () {
    const initBlock = () => {
        if (typeof wp !== 'undefined' && wp.blocks && wp.blockEditor) {
            const el = wp.element.createElement;
            const useBlockProps = wp.blockEditor.useBlockProps;
            const InnerBlocks = wp.blockEditor.InnerBlocks;

            wp.blocks.registerBlockType('wp-scratch/call-to-action', {
                edit: function () {
                    const blockProps = useBlockProps({ className: 'custom-admin-wrapper' });
                    // Return the Editor UI using React.createElement (el)
                    return el('div', blockProps, el(InnerBlocks));
                },
                save: function () {
                    // For dynamic blocks, save returns the InnerBlocks content
                    return el(InnerBlocks.Content);
                },
            });
        } else {
            setTimeout(initBlock, 50); // Retry if WP isn't ready
        }
    };
    initBlock();
})();
```

## 4. The Front-end Template (render.php)

This file controls the HTML on the website. The variable $content automatically contains all blocks placed inside the InnerBlocks area in the editor.

```php
<?php
$wrapper_attributes = get_block_wrapper_attributes(['class' => 'my-cta-section']);
?>
<section <?php echo $wrapper_attributes; ?>>
    <div class="container mx-auto">
        <?php echo $content; ?>
    </div>
</section>
```

## 5. The PHP Autoloader (functions.php)

This loop scans the /blocks folder. It performs two critical tasks:

1. It registers the block via block.json.
2. It forces the index.js to load only in the editor with the necessary dependencies (wp-blocks, wp-element, etc.).

```php
add_action( 'init', function() {
    $blocks_dir = get_template_directory() . '/blocks';
    $folders = array_diff( scandir( $blocks_dir ), array( '..', '.' ) );

    foreach ( $folders as $folder ) {
        $path = $blocks_dir . '/' . $folder;
        if ( file_exists( $path . '/block.json' ) ) {
            register_block_type( $path );

            add_action( 'enqueue_block_editor_assets', function() use ( $folder, $path ) {
                wp_enqueue_script(
                    $folder . '-js',
                    get_template_directory_uri() . '/blocks/' . $folder . '/index.js',
                    array( 'wp-blocks', 'wp-element', 'wp-block-editor' ),
                    filemtime( $path . '/index.js' ),
                    true
                );
            });
        }
    }
});
```

## 6. Adding Editable Attributes

To make a block truly "custom" (e.g., a background color picker), follow this pattern:

**Update `block.json`**
Add an attributes object to define what data WordPress should save.
```json
"attributes": {
    "backgroundColor": {
        "type": "string",
        "default": "#ffffff"
    }
}
```
**Update `index.js`**
Extract attributes and setAttributes from the edit function
```js
edit: function (props) {
    const { attributes, setAttributes } = props;
    const el = wp.element.createElement;
    const InspectorControls = wp.blockEditor.InspectorControls;
    const PanelBody = wp.components.PanelBody;
    const ColorPicker = wp.components.ColorPicker;

    return [
        // Sidebar UI
        el(InspectorControls, {}, 
            el(PanelBody, { title: 'Settings' },
                el(ColorPicker, {
                    color: attributes.backgroundColor,
                    onChangeComplete: (val) => setAttributes({ backgroundColor: val.hex })
                })
            )
        ),
        // Editor UI
        el('div', { style: { background: attributes.backgroundColor } }, 'Content here...')
    ];
}
```
**Update `render.php`**

###  Key Takeaways

    No JSX: Use wp.element.createElement (aliased as el) instead of HTML tags in JS.

    Dependencies: The script must have the dependency array in wp_enqueue_script or window.wp will be undefined.

    Dynamic Names: Ensure the namespace (e.g., wp-scratch/) is consistent across the JSON and the JS registration.

### Tips

- Use `/scripts/newblock/ps.1` to add new blocks in a starter tempalte. 
    - `.\newblock.ps1 -Name name-of-block` 
    - .\newblock.ps1 [-Name] [name-of-block] 
        - [-Name] use this to call the command to name the newblock folder/files
        - [name-of-block] add any name here to to name the block folder/files

- Call blocks within blocks TEMPLATE
```js
 const TEMPLATE = [                
    ['wp-scratch/icon-title', {}], //calls blocks/icon-title
```
- When editing block template files, sometimes have to delete them from the editor and re-add to fetch the latest changes
