В предыдущем уроке мы проделали огромную работу — собрали Главную страницу. Теперь мы пожнем плоды этого труда.
В этом уроке мы применим принцип переиспользования. Мы увидим, что 80% внутренних страниц (Услуги, Контакты) собираются из уже готовых блоков, как конструктор Lego. Нам останется лишь создать пару специфических секций.
Наша цель — собрать 3 шаблона:
- Услуги (Services List): общий список услуг.
- Детальная услуга (Service Detail): страница конкретной услуги.
- Контакты (Contact Us): страница с контактной информацией.
Этап 1: «Универсальный солдат» (Page Header)
Если вы откроете исходный код файлов services.tpl , services_detail.tpl или contacts.tpl , вы увидите абсолютно одинаковую верхнюю секцию: серый фон, декоративные круги, заголовок и хлебные крошки.

Чтобы не дублировать код, мы создадим один универсальный блок, который будем ставить на все внутренние страницы. Для этого:
- Создайте блок «Заголовок страницы» (
pb.page_header). - Поля:
title(Текст),bg_class(Текст), значение по умолчаниюcustom-bg-color-light-1(можно менять цвет) иshow_breadcrumbs(Да/Нет), чтобы можно было скрыть крошки, при этом не скрывая заголовок. - Код: скопируйте HTML верхней секции из любого файла (например,
services.tpl). - Модернизация: вместо статичной верстки
<ul>...</ul>вставьте вызов сниппетаpdoCrumbs, чтобы они строились автоматически.
В конечном итоге должен примерно такой получится чанк pb.page_header:
<section class="page-header page-header-lg {$bg_class ?: 'custom-bg-color-light-1'} border-0 m-0">
<div class="container position-relative pt-5 pb-4 mt-5">
{* --- Декоративные круги (оставляем как есть, это часть дизайна) --- *}
<div class="custom-circle custom-circle-wrapper custom-circle-big custom-circle-pos-1 custom-circle-pos-1-1 appear-animation" data-appear-animation="expandInWithBlur" data-appear-animation-delay="900" data-appear-animation-duration="2s">
<div class="bg-color-tertiary rounded-circle w-100 h-100" data-plugin-float-element data-plugin-options="{ 'startPos': 'bottom', 'speed': 0.5, 'transition': true, 'transitionDuration': 1000 }"></div>
</div>
{* ... остальные круги (копируем из исходника contacts.tpl строки 393-396) ... *}
<div class="custom-circle custom-circle-medium custom-circle-pos-2 custom-circle-pos-2-2 appear-animation" data-appear-animation="expandInWithBlur" data-appear-animation-delay="1450" data-appear-animation-duration="2s">
<div class="custom-bg-color-grey-2 rounded-circle w-100 h-100" data-plugin-float-element data-plugin-options="{ 'startPos': 'bottom', 'speed': 0.2, 'transition': true, 'transitionDuration': 2000 }"></div>
</div>
{* ... (сократил код кругов для читаемости) ... *}
<div class="row py-5 mb-5 mt-2 p-relative z-index-1">
<div class="col">
{if $show_breadcrumbs != 0}
<div class="overflow-hidden">
{* Вызов хлебных крошек MODX *}
{'pdoCrumbs' | snippet : [
'showHome' => 1,
'tplWrapper' => '@INLINE <ul class="breadcrumb d-block text-center appear-animation" data-appear-animation="maskUp" data-appear-animation-delay="800">{$output}</ul>',
'tpl' => '@INLINE <li><a href="{$link}">{$menutitle}</a></li>',
'tplCurrent' => '@INLINE <li class="active">{$menutitle}</li>'
]}
</div>
{/if}
<div class="overflow-hidden mb-4">
<h1 class="d-block text-color-quaternary font-weight-bold text-center mb-0 appear-animation" data-appear-animation="maskUp" data-appear-animation-delay="1000">
{$title ?: $_modx->resource.pagetitle}
</h1>
</div>
</div>
</div>
</div>
</section>
Этап 2: Раздел услуг
А. Страница «Все услуги» (services.tpl)

Посмотрите на макет страницы списка услуг. Он состоит из:
- Шапки.
- Сетки услуг (точно такой же, как на Главной).
- Формы SEO Аудита (почти точно такой же, как на Главной).
Так как форма содержит тот же набор полей, что и на главной, поступим следующим образом:
- Сделайте копию конфигурации блока Форма захвата (после нажмите Да).

- Далее заходим в копию конфигурации и переименовываем ее в «Форма захвата перед Footer» и пишем новое название чанка pb.footer_form и сохраняем.

- Создаем чанк
pb.footer_formсо следующим кодом:<section class="section section-height-3 bg-color-secondary position-relative border-0 m-0"> <div class="container position-relative z-index-1 pt-2 pb-5 mt-3 mb-5"> <div class="row justify-content-center mb-3"> <div class="col-md-8 col-lg-6 text-center"> <div class="overflow-hidden mb-2"> <h2 class="font-weight-bold text-color-light text-7 line-height-2 mb-0 appear-animation" data-appear-animation="maskUp" data-appear-animation-delay="250">{$title}</h2> </div> <div class="overflow-hidden mb-1"> <p class="lead custom-text-color-light-1 mb-0 appear-animation" data-appear-animation="maskUp" data-appear-animation-delay="400">{$subtitle_1}</p> </div> {if $subtitle_2}<div class="overflow-hidden mb-3"> <p class="custom-text-color-light-1 mb-0 appear-animation" data-appear-animation="maskUp" data-appear-animation-delay="550">{$subtitle_2}</p> </div>{/if} </div> </div> <div class="row mb-3"> <div class="col"> {* Здесь будет вызов FetchIt *} </div> </div> </div> </section>
Сборка:
- Создайте ресурс «Услуги».
- В PageBlocks добавьте:
- Блок «Заголовок страницы» (создан на Этапе 1).
- Блок «Сетка Услуг» (создан в уроке про Главную).
- Блок «Форма захвата перед Footer».
Готово. Страница собрана.
Б. Детальная страница услуги (services_detail.tpl)
Здесь уже есть полу-уникальный контент:

Подобный блок мы уже создавали на более широком блоке на главной (Блок 3: О нас + Фичи), по сути одно и тоже, единственное нужно убрать слайды и добавить поле контент и все). Самое простое это создать копию конфигурации блока О нас + Фичи, Назвать к примеру Контент услуги (чанк pb.service_content) и поле Логотипы переименовываем в Контент и меняем тип на Текстовый редактор.

Это поле перенесите мышкой в самый низ.
Мы создаем отдельную конфигурацию, чтобы в админке этот блок имел понятное название «Форма захвата перед Footer» и менеджер не путался, хотя технически это почти копия блока с главной.
Ну и создаем чанк pb.service_content:
<section class="section bg-color-light position-relative border-0 pt-0 m-0">
<svg class="custom-page-header-curved-top-1" width="100%" height="700" xmlns="http://www.w3.org/2000/svg">
<path transform="rotate(-3.1329219341278076 1459.172607421877,783.5322875976566) " d="m-12.54488,445.11701c0,0 2.16796,-1.48437 6.92379,-3.91356c4.75584,-2.42918 12.09956,-5.80319 22.45107,-9.58247c20.70303,-7.55856 53.43725,-16.7382 101.56202,-23.22255c48.12477,-6.48434 111.6401,-10.27339 193.90533,-7.05074c41.13262,1.61132 88.20271,5.91306 140.3802,12.50726c230.96006,32.89734 314.60609,102.57281 635.26547,59.88645c320.65938,-42.68635 452.47762,-118.72154 843.58759,3.72964c391.10997,122.45118 553.23416,-82.15958 698.49814,-47.66481c-76.25064,69.23438 407.49874,281.32592 331.2481,350.5603c-168.91731,29.52009 85.02254,247.61162 -83.89478,277.13171c84.07062,348.27313 -2948.95065,-242.40222 -2928.39024,-287.84045" stroke-width="0" stroke="#000" fill="#FFF" id="svg_2"/>
</svg>
<div class="container pb-2 mb-4">
<div class="row justify-content-center align-items-center">
<div class="col-md-10 col-lg-6 mb-5 mb-lg-0">
<h2 class="text-color-dark font-weight-semibold text-6 line-height-3 mb-0 pe-5 me-5 appear-animation" data-appear-animation="fadeInUpShorter" data-appear-animation-delay="1600">{$title}</h2>
<span class="d-block mb-3 appear-animation" data-appear-animation="fadeInUpShorter" data-appear-animation-delay="1800">{$subtitle}</span>
<p class="lead pe-5 mb-4 pb-2 appear-animation" data-appear-animation="fadeInUpShorter" data-appear-animation-delay="2000">{$lead}</p>
{* Список фич *}
{foreach $features as $item}
<div class="feature-box appear-animation" data-appear-animation="fadeInUpShorter" data-appear-animation-delay="2200">
<div class="feature-box-icon custom-feature-box-icon-size-1 bg-color-{$item.color} top-0">
<i class="{$item.icon} position-relative left-1"></i>
</div>
<div class="feature-box-info mb-4 pb-3">
<h4 class="font-weight-bold line-height-3 custom-font-size-1 mb-1">{$item.title}</h4>
<p class="mb-0">{$item.text}</p>
</div>
</div>
{/foreach}
</div>
{* ПРАВАЯ КОЛОНКА (Изображение) *}
<div class="col-md-6 col-lg-5 offset-lg-1 ps-5 pb-lg-3 mb-md-4 mb-lg-5 appear-animation" data-appear-animation="fadeInRightShorter" data-appear-animation-delay="2200">
{if $image}
<img src="{$image.url}" class="img-fluid mb-4 pb-2" width="100%" alt="" />
{/if}
</div>
{* НИЖНИЙ ТЕКСТ *}
<div class="col-md-10 col-lg-12 mt-4">
<div class="appear-animation" data-appear-animation="fadeInUpShorter" data-appear-animation-delay="250">
{$content}
</div>
</div>
</div>
</div>
</section>
И еще у нас есть полу уникальный блок: Простой слайдер отзывов, который точно такой же как кусок блока на главной (Блок 6: Статистика и отзывы). Думаю самостоятельно справитесь с конфигурацией полей этого блока (таблица уже создана, можно использовать ее), я назвал его чанк pb.testimonials_simple и вот такой код у меня получился:
<section class="section bg-color-light position-relative border-0 pb-4 m-0">
<svg class="custom-section-curved-top-6" width="100%" height="600px" xmlns="http://www.w3.org/2000/svg">
<path id="svg_1" d="m-12.66406,442.40068c352.72654,-76.36348 565.45337,5.45453 696.36219,19.99996c130.90882,14.54542 270.90852,-23.63632 367.27196,-47.27263c96.36344,-23.63631 379.99921,-154.54513 527.27163,-209.09047c147.27242,-54.54535 381.813,-92.55755 406.36076,-99.00598c12.27388,-3.22421 917.96684,-113.93032 715.00991,10.61478c-202.95693,124.5451 -210.46055,521.28714 -198.64021,540.29354c11.82034,19.0064 -2500.90899,-15.53962 -2500.0019,-16.36399c-0.90709,0.82437 -9.99798,-180.99343 -9.09089,-181.8178" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#f7f8f9"></path>
<path id="svg_2" d="m-116.90461,507.88064c314.5448,-112.72704 523.63527,-21.81814 878.17999,12.72724c354.54471,34.54538 632.72595,-225.45407 978.17978,-294.54484c172.72691,-34.54538 291.36195,-62.7275 368.52007,-78.40952c77.15812,-15.68202 352.84215,-22.50036 359.66142,-7.04537c13.63854,30.90997 97.72734,614.54347 50.90961,639.99858c-46.81772,25.4551 -855.68236,4.54593 -1433.63569,1.81866c-577.95334,-2.72727 -1155.90718,-5.45466 -1155.45364,-5.45491" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#fbfcfc"></path>
<path id="svg_3" d="m-115.93584,623.27542c234.54496,-132.72699 429.09001,-112.72703 678.1804,-83.63619c249.09039,29.09085 389.09011,30.90903 656.36228,-107.2725c267.27217,-138.18153 816.36193,-207.2723 1121.81584,-170.90873c305.45391,36.36356 -292.72666,-19.99996 -293.63778,-18.18228c71.36548,8.18218 627.05432,68.63506 626.48637,265.22584c-0.56794,196.59079 -20.11364,456.59134 -31.02284,531.13767c-10.90919,74.54633 -1561.82313,-36.3646 -1565.45948,-34.54642c-3.63636,1.81818 -1249.08831,-1.81818 -1248.18122,-1.81869c-0.90709,0.00051 39.09282,-234.54445 39.99992,-234.54496c-0.9071,0.00051 -4.54345,-76.36297 -3.63636,-76.36348" stroke-opacity="null" stroke-width="0" stroke="#000" fill="#ffffff"></path>
</svg>
<div class="container position-relative z-index-1">
<div class="row mt-5 mb-5">
<div class="col-lg-4 pe-lg-0">
<h2 class="text-color-dark font-weight-semibold text-6 line-height-3 mb-3">{$reviews_title}</h2>
<p>{$reviews_text}</p>
</div>
<div class="col-lg-8 ps-lg-4">
<div class="owl-carousel custom-carousel-style-1 custom-carousel-dots-style-1" data-plugin-options="{ 'responsive': { '0': { 'items': 1 }, '479': { 'items': 1 }, '979': { 'items': 2 }, '1199': { 'items': 2 } }, 'margin': 0, 'loop': true, 'dots': true, 'nav': false, 'autoplay': true }">
{foreach $reviews as $review}
<div>
<div class="testimonial testimonial-style-3 custom-testimonial-style-1">
<blockquote>
<p class="mb-0">{$review.text}</p>
</blockquote>
<div class="testimonial-author">
<div class="testimonial-author-thumbnail">
<img src="{$review.photo.url}" class="img-fluid rounded-circle" alt="{$review.author}">
</div>
<p><strong class="font-weight-semibold text-4 mb-1">{$review.author}</strong><span class="text-2">{$review.role}</span></p>
</div>
</div>
</div>
{/foreach}
</div>
</div>
</div>
</div>
</section>
Сборка. В ресурсе услуги собираем конструктор: Заголовок страницы + Контент услуги + Тарифы (с главной) + Простой слайдер отзывов + Форма захвата перед Footer.
Этап 3: Страница контактов (contacts.tpl)
На этой странице у нас только 1 уникальный блок с контактной информацией (остальные уже есть):

Первым делом создайте таблицу: info_boxes — Контактные данные (Адрес, Телефон, Email), со следующим набором полей:
icon(Текст, класс иконки, напримерicon-globe icons),title(Текст),content(RichText — чтобы можно было вставить ссылку<a>).color(Select:secondary,quaternary— для цвета иконки).
Теперь создаем блок «Контакты + Форма». Чанк: pb.seo_contacts. со следующим набором полей:
title(Текст) — Заголовок (Free Website Audit…).subtitle(Текст) — Надзаголовок (CONTACT US TODAY).description(Textarea) — Описание.icon(Таблица) — Контакты.form_title(Текст) — Заголовок над формой.form_text(Textarea) — Текст над формой.
Чанк pb.contacts:
<section class="section bg-color-light position-relative border-0 pt-3 m-0">
{* SVG Background *}
<svg class="custom-page-header-curved-top-1" width="100%" height="700" xmlns="http://www.w3.org/2000/svg">
<path d="..." fill="#FFF" id="svg_2"/>
{* (Полный путь SVG берем из contacts.tpl строки 399) *}
</svg>
<div class="container pb-2 mb-4">
<div class="row justify-content-center">
{* ЛЕВАЯ КОЛОНКА: ИНФОРМАЦИЯ *}
<div class="col-md-10 col-lg-6 mb-5 mb-lg-0">
<div class="overflow-hidden mb-1">
<h2 class="text-color-dark font-weight-semibold text-6 line-height-3 mb-0 pe-5 me-5 appear-animation" data-appear-animation="maskUp" data-appear-animation-delay="1200">{$title}</h2>
</div>
<div class="overflow-hidden mb-3">
<span class="d-block mb-0 appear-animation" data-appear-animation="maskUp" data-appear-animation-delay="1400">{$subtitle}</span>
</div>
<p class="lead pe-5 mb-4 pb-2 appear-animation" data-appear-animation="fadeInUpShorter" data-appear-animation-delay="1600">{$description}</p>
{* Цикл по инфо-боксам *}
{foreach $info_boxes as $item}
<div class="feature-box align-items-center mb-4 pb-1 appear-animation" data-appear-animation="fadeInUpShorter" data-appear-animation-delay="1800">
<div class="feature-box-icon custom-feature-box-icon-size-1 bg-color-{$item.color} top-0">
<i class="{$item.icon} position-relative"></i>
</div>
<div class="feature-box-info">
<h4 class="font-weight-bold line-height-1 custom-font-size-1 mb-1">{$item.title}</h4>
<p class="mb-0">{$item.content}</p>
</div>
</div>
{/foreach}
</div>
{* ПРАВАЯ КОЛОНКА: ФОРМА *}
<div class="col-md-10 col-lg-6 appear-animation" data-appear-animation="fadeInRightShorter" data-appear-animation-delay="2000">
<div class="card border-0 custom-border-radius-1 box-shadow-1 p-2">
<div class="card-body p-4 z-index-1">
<h4 class="text-color-dark font-weight-semibold text-5 line-height-3 ls-0 mb-1 mt-2 pe-5 me-5">{$form_title}</h4>
<p class="pb-2 mb-4">{$form_text}</p>
{* Здесь место для вызова FetchIt. Пока оставляем верстку *}
<form class="contact-form custom-form-style-1" action="{$_modx->resource.id | url}" method="POST">
<div class="contact-form-success alert alert-success d-none mt-4">
<strong>Success!</strong> Your message has been sent to us.
</div>
<div class="contact-form-error alert alert-danger d-none mt-4">
<strong>Error!</strong> There was an error sending your message.
</div>
<div class="row">
<div class="form-group col mb-3">
<input type="text" maxlength="100" class="form-control custom-bg-color-light-1 border-0" name="name" placeholder="Your Name" required>
</div>
</div>
<div class="row">
<div class="form-group col mb-3">
<input type="email" maxlength="100" class="form-control custom-bg-color-light-1 border-0" name="email" placeholder="E-mail Address" required>
</div>
</div>
<div class="row">
<div class="form-group col mb-3">
<textarea maxlength="5000" rows="6" class="form-control custom-bg-color-light-1 border-0" name="message" placeholder="Your Message" required></textarea>
</div>
</div>
<div class="row">
<div class="form-group col mb-3">
<input type="submit" value="SEND NOW" class="btn btn-gradient btn-rounded font-weight-bold px-5 py-3 text-3">
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
Форму мы оставили HTML-кодом, но подготовим её для работы с FetchIt (добавим необходимые классы и ID, если нужно).
Сборка:
- Создайте ресурс «Контакты».
- В PageBlocks добавьте:
- Блок «Заголовок страницы» (создан на Этапе 1).
- Блок «Контакты + Форма» (создан только что).
- Блок «Форма захвата перед Footer» (создана в этом уроке).
Готово. Страница собрана.
Итоги урока
Поздравляю! Только что вы на практике ощутили главную мощь компонентного подхода в MODX.
Мы собрали три абсолютно разные по структуре страницы (Список услуг, Детальная услуга, Контакты), потратив минимум времени на написание нового кода. Мы создали всего пару уникальных блоков, а остальные 80% контента собрали из уже готовых «кубиков», которые сделали для Главной страницы.
Именно так выглядит современная и быстрая разработка сайтов.
Что дальше? Сейчас наши страницы выглядят отлично, но формы на них пока «мертвые» — это просто HTML-каркас. В следующем уроке мы вдохнем в них жизнь: интегрируем компонент FetchIt, настроим отправку заявок на почту и сделаем валидацию полей, не ломая при этом красивую верстку.









