pbStudio: Создаём сайт с PageBlocks – настройка и главная страница
        В этом уроке мы установим MODX, настроим необходимые компоненты и системные параметры для работы с PageBlocks. Затем создадим главную страницу и добавим на неё два блока. Это часть серии уроков, в которых мы пошагово создадим полноценный сайт для студии с использованием PageBlocks.

Демо сайта.
Навигация по урокам:
1. Устанавливаем MODX — следуйте официальной инструкции.
2. Устанавливаем компоненты:
На этом базовая настройка завершена.
1. Создаем наш первый маршрут в core/App/routes/web.php:
3. Создаем базовый шаблон core/App/elements/templates/base.tpl:
Примеры готовых чанков:
4. Добавляем блоки в метод getBlocks в core/App/Models/Resource.php.
В результате получаем блок с таблицей:

и форму для слайда:

Теперь для отображения блока нам нужно создать чанк core/App/elements/chunks/slider.tpl:
Верстку возьмем отсюда
Вот и все, наш первый блок готов. Результат можно увидеть на сайте или на первой картинке в статье.

Настройка:
Описание полей:

Давайте улучшим конфигурацию колонок таблицы для блока Services:
Результат:

Создаем чанк coreApp/elements/chunks/services.tpl
Вот и всё. Главная страница готова.
Все, кто дочитал до конца, ставьте лайк! Встретимся в следующем уроке, где продолжим создавать наш сайт.     
    
    
                                                        
Навигация по урокам:
- pbStudio: Создаём сайт с PageBlocks – настройка и главная страница
 - pbStudio: Меню и страница «О нас»
 - pbStudio: Чистый контроллер или FetchIt — два способа обработки форм
 - pbStudio: Портфолио, Услуги и Контакты
 - pbStudio: Подключаем мультиязычность
 
Настройка
1. Устанавливаем MODX — следуйте официальной инструкции.
2. Устанавливаем компоненты:
- PageBlocks – основной компонент (без него никуда)
 - ColorPicker – инструмент для выбора цветов
 - pThumb – оптимизация изображений
 - TinyMCE Rich Text Editor –мощный текстовый редактор
 
- pageblocks_development_mode – Developer (режим разработчика)
 - pageblocks_routing – Full API (полный контроль маршрутизации)
 - pageblocks_parser – pbFenom (шаблонизатор Fenom)
 
- Для Apache: переименуйте ht.access в .htaccess
 - Для Nginx: настройте соответствующие правила
 
На этом базовая настройка завершена.
Главная страница
1. Создаем наш первый маршрут в core/App/routes/web.php:
use PageBlocks\App\Http\Controllers\ResourceController;
Route::get('/{alias?}', [ResourceController::class, 'index']);Этот маршрут обрабатывает:- / — главная страница
 - some_alias — другие страницы.
 
<?php
namespace PageBlocks\App\Http\Controllers;
class ResourceController extends Controller
{
    public $classKey = \modResource::class;
    public function index(string $alias = '')
    {
        // Условия для поиска ресурса
        $where = [
            'published' => 1, // ресурс должен быть опубликованный
            'deleted' => 0, // не удален
        ];
        
        // Если $alias пустой, значит это главная страница, иначе ищем ресурс по алиасу.
        if (empty($alias)) {
            $where['id'] = $this->modx->getOption('site_start', null, 1, true);
        } else {
            $where['alias'] = $alias;
        }
        
        // Если ресурс не найден то отдаем 404 ошибку
        if (!$resource = $this->modx->getObject($this->classKey, $where)) {
            abort();
        }
        
        // Можем сделать просто так:
        // $this->modx->resource = $resource; // для того чтобы в шаблоне и у сниппетов был доступ к ресурсу
        
        // но мы сделаем так, для того, чтобы поля созданные через PageBlocks тоже учитывались
        $this->modx->resource = updateResource($resource);
        
        // Возвращаем базовый шаблон
        return response()->view('templates/base');
    }
}3. Создаем базовый шаблон core/App/elements/templates/base.tpl:
<!doctype html>
<html lang="en">
<head>
    {block 'head'}
        {insert 'chunks/head.tpl'}
    {/block}
</head>
<body>
    {block 'header'}
        {insert 'chunks/header.tpl'}
    {/block}
    
    {*Выводим блоки*}
    {block 'content'}
	<div class="container">
            {'!pbBlocks'|snippet: [
                'fileElements' => 1,
            ]}
	</div>
    {/block}
    {block 'footer'}
        {insert 'chunks/footer.tpl'}
    {/block}
    
    {block 'modal'}
        {insert 'chunks/modal.tpl'}
    {/block}
</ body>
</html>Это единственный шаблон сайта, который будет выводить уникальные блоки для каждой страницы.Примеры готовых чанков:
4. Добавляем блоки в метод getBlocks в core/App/Models/Resource.php.
Блок Slider
Block::make('Slider') // название блока
    // chunk можем не указывать, ведь make задает не только название блока, 
    // но и название чанка в нижнем регистре.
    // ->chunk('slider') // явно указываем имя чанка
    ->fields([
        Field::make('Slider')
        ->type('table')
        ->fields([
            Field::make('Background')
                ->type('image')
                ->sourcePath('/assets/images/')
                ->required(),
            
            Field::make('Title')
                ->required(),
            
            Field::make('Description')
                ->type('textarea'),
        ])
        ->columns([
            Column::make('Background')
                ->width(100)
                ->render('image'),
            
            Column::make('Title')
        ])
    ])В результате получаем блок с таблицей:

и форму для слайда:

Теперь для отображения блока нам нужно создать чанк core/App/elements/chunks/slider.tpl:
Верстку возьмем отсюда
<div id="pbSlider" class="carousel slide">
    <div class="carousel-indicators">
        {foreach $slider as $idx => $slide}
            <button type="button"
                    data-bs-target="#pbSlider"
                    data-bs-slide-to="{$idx}"
                    class="{!$idx ? 'active' : ''}"
                    aria-current="{!$idx ? 'true' : 'false'}"
                    aria-label="{$slide.title}"></button>
        {/foreach}
    </div>
    <div class="carousel-inner">
        {foreach $slider as $idx => $slide}
            <div class="carousel-item{!$idx ? ' active' : ''}">
                <img src="{$slide.background.url|pthumb:'w=1296&h=500&zc=1&f=webp'}"
                     class="d-block w-100 img-fluid object-fit-cover"
                     width="1296"
                     height="500"
                     style="min-height: 240px;"
                     alt="{$slide.title}">
                <div class="carousel-caption mx-auto top-50 start-50 translate-middle w-100" style="max-width:650px;">
                    <h1>{$slide.title}</h1>
                    {if $slide.description}
                        <p class="d-none d-md-block">{$slide.description}</p>
                    {/if}
                </div>
            </div>
        {/foreach}
    </div>
    <button class="carousel-control-prev" type="button" data-bs-target="#pbSlider" data-bs-slide="prev">
        <span class="carousel-control-prev-icon" aria-hidden="true"></span>
        <span class="visually-hidden">Previous</span>
    </button>
    <button class="carousel-control-next" type="button" data-bs-target="#pbSlider" data-bs-slide="next">
        <span class="carousel-control-next-icon" aria-hidden="true"></span>
        <span class="visually-hidden">Next</span>
    </button>
</div>Вот и все, наш первый блок готов. Результат можно увидеть на сайте или на первой картинке в статье.
Блок Services
Этот блок немного сложнее, так как нам нужно для каждого элемента задать:- Фон блока
 - Цвет текста
 - Расположение текста
 - Ссылку
 - Картинку
 

Настройка:
Block::make('Services')
    ->fields([
        // Создаем поле с типом таблица
        Field::make('Services')
            ->type('table')
            ->fields([
                // Фон блока
                Field::make('Background')
                    ->type('colorpicker')
                    ->width(50)
                    ->required(),
                
                // Цвет текста
                Field::make('Color')
                    ->type('colorpicker')
                    ->width(50)
                    ->default('#ffffff')
                    ->required(),
                
                // Тип блока: Image или Text. Определяет набор отображаемых полей.
                Field::make('Type')
                    ->type('select')
                    ->options([
                        'image' => 'Image',
                        'text' => 'Text',
                    ])
                    ->required(),
                
                // Картинка (только для типа Image)
                Field::make('Image')
                    ->type('image')
                    ->sourcePath('/assets/images/')
                    ->required()
                    ->hidden('type', '!=', 'image'),
                
                // Заголовок (только для типа Text)
                Field::make('Title')
                    ->required()
                    ->hidden('type', '!=', 'text'),
                
                // Позиция текста (только для типа Text)
                Field::make('Position')
                    ->type('select')
                    ->values([
                        '' => 'Center',
                        'mt-auto' => 'Bottom'
                    ])
                    ->required()
                    ->hidden('type', '!=', 'text'),
                
                // Описание (только для типа Text)
                Field::make('Description')
                    ->type('richtext')
                    ->hidden('type', '!=', 'text'),
                
                // ID ресурса для ссылки, прячем поле, если тип не выбран
                Field::make('resource_id')
                    ->label('Resource')
                    ->type('resourcelist')
                    ->hidden('type', '=', ''),
            ])
            // Настройка колонок для таблицы
            ->columns([
                Column::make('Background')
                    ->render('color'),
                
                Column::make('Color')
                    ->width(100)
                    ->render('color'),
                
                Column::make('Title')
                    ->default('---')
            ])
    ]),Описание полей:
- Background: Выбор цвета фона блока с помощью colorpicker. Обязательное поле.
 - Color: Выбор цвета текста с помощью colorpicker. По умолчанию #ffffff (белый). Обязательное поле.
 - Type: Выбор типа блока:
 - Image: Отображает только изображение.
 - Text: Отображает заголовок, описание и позволяет выбрать позицию текста. Обязательное поле. В зависимости от выбранного типа, отображаются соответствующие поля.
 - Image: Загрузка изображения. Отображается только если Type равен Image. Указывает путь к папке с изображениями (sourcePath).
 - Title: Заголовок текста. Отображается только если Type равен Text. Обязательное поле.
 - Position: Выбор позиции текста (центр или низ). Используется для вертикального выравнивания текста. Отображается только если Type равен Text. Обязательное поле.
 - Description: Описание блока. Отображается только если Type равен Text. Использует редактор richtext для форматирования текста.
 - Resource (resource_id): Выбор ресурса (страницы) для создания ссылки. Отображается если выбран какой-то Type.
 

Давайте улучшим конфигурацию колонок таблицы для блока Services:
->columns([
    Column::make('Colors')
        // Группируем фон и цвет текста в одну колонку
        ->group([
            Column::make('Background')
                ->render('color')
                ->withLabel(), // Добавляем заголовок, который равен имени колонки (Background)
            
            Column::make('Color')
                ->render('color')
                ->withLabel('Color text'), // Добавляем кастомный заголовок
        ]),
    Column::make('Title / Image')
        // Добавляем условие, показываем заголовок или картинку
        ->renderIf([
            Column::make('Title'),
            Column::make('Image')->render('image')
        ])
    ])Результат:

Создаем чанк coreApp/elements/chunks/services.tpl
<section class="services d-flex flex-wrap">
    {foreach $services as $item}
        <div class="service-item col-12 col-xl-6"
             style="background-color: {$item.background};color:{$item.color?:'#fff'}">
            {if $item.type === 'text'}
                <h2 class="{$item.position}">{$item.title}</h2>
                {if $item.description}
                    <div class="mb-0 {$item.position}">{$item.description}</div>
                {/if}
            {else}
                <img loading="lazy"
                     src="{$item.image.url}"
                     class="img-fluid mx-auto"
                     width="236"
                     height="236"
                     alt="{$item.image.title}">
            {/if}
            {if $item.resource_id}
                <a href="{$item.resource_id|url}"
                   class="service-link text-decoration-none d-flex align-items-center gap-3 position-absolute bottom-0 end-0">
                    {if $item.type === 'image'}
                        <span>{$item.resource_id|resource:'pagetitle'}</span>
                    {/if}
                    <svg fill="{$item.color}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                        <!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
                        <path d="M502.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L402.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l370.7 0-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l128-128z"/>
                    </svg>
                </a>
            {/if}
        </div>
    {/foreach}
</section>Вот и всё. Главная страница готова.
Все, кто дочитал до конца, ставьте лайк! Встретимся в следующем уроке, где продолжим создавать наш сайт.
            
                Поблагодарить автора            
            
                 Отправить деньги            
        
        
            Комментарии: 6
                Инструкция! Вот чего не хватало, спасибо!!!
Зачем нужен пункт 1 и 2? Ведь это решается стандартными полями ресурса в MODX?
                    Зачем нужен пункт 1 и 2? Ведь это решается стандартными полями ресурса в MODX?
Зачем нужен пункт 1 и 2? Ведь это решается стандартными полями ресурса в MODX?Для полного контроля над страницами и логикой отображения.
Block::make('Services')
    ->fields([
        // Создаем поле с типом таблица
        Field::make('Services')
            ->type('table')
            ->fields([Учить еще один язык програмирования, который типа облегчает контроль над html и css. Как говориться плох тот разработчик который не написал свой фрейворк.PageBlocks может и не плох но как же уже достал это зоопарк фреймворков :-)
- Режим менеджера — можно делать тоже самое, только клацать в админке
 - Режим разработчика — управлять всем через файлы, но нужно немного поучиться, зато получаем чистый и управляемый код, который можно хранить на гитхабе
 
PageBlocks может и не плох но как же уже достал это зоопарк фреймворков :-)PageBlocks имеет 2 режима разработки:
Как говорится, плох тот разработчик, который не учится новому!Новое так быстро выходит что не успеваешь написать программу как подоспел новый фреймворк и надо переписывать прогу уже с новым фреймворком.
П.С. Хочестя в тикеты при сохранении комментария добавить кнопку «проверить на ошибки» и при нажатии на нее проверить комментарий на ошибки в нейросети сохранить проверенный вариант. Но пока еще не настолько хочется чтоб взять и сделать :-)
Хочестя в тикеты при сохранении комментария добавить кнопку «проверить на ошибки» и при нажатии на нее проверить комментарий на ошибки в нейросети сохранить проверенный вариант.Хорошая идея. Запишу себе и реализую, когда буду делать уроки по созданию блога на PageBlocks.
                            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.