Эта статья продолжение статьи MastremindCMS2 - Как начать?. В ней я рассказывал как установить и настроить community-версию "безголовой" MastermindCMS2.

В этой статье речь пойдет о специальных тегах, которые вы можете использовать для сборки динамических шаблонов в MastermindCMS2.

За свою карьеру программиста я видел множество разных технологий и фреймворков от гигантов индустрии, таких как Oracle, Microsoft, IBM и т. п. Но в каждом из них было какое-то неудобство. А конкретнее у них у всех было одно общее, это необходимость реализовывать серверную логику чтобы можно было использовать ее в шаблонах. И это мне сильно не нравилось, приходилось делать одну и ту же работу из проекта в проект.

Один из таких подходов разнесения логики был паттерн программирования MVVM(Model-View-ViewModel). Его активно продвигали во фреймворках для C#. Структурно с точки зрения разнесения логики, я считаю этот паттерн самым удобным.

Когда я поставил задачу себе разработать удобный фреймворк для работы, то у меня в голове был только один концепт реализации. Который содержал в основе именно MVVM, но я не считал, что это то, что я хочу получить в итоге. Так как проблема написания серверной логики оставалась открытой. И я поставил себе задачу спроектировать фреймворк так чтобы не нужно было каждый раз писать бэк для очередного проекта. Ведь грубо говоря, мы с вами коллеги, решаем одни и те же задачи для разных проектов. Авторизация, управление каталогом продуктов, отправка писем по электронной почте, чаты и прочая энтерпрайз муть. Соответственно опираясь на эти задачи, я начал думать, что нужно сделать чтобы напрямую оперировать с этим на бэке. В результате я решил писать логику парсинга шаблонов на беке, а фронтенд часть делать гибкой и независимой.

Первым шагом я продумал маршрутизацию для обработки HTTP-запросов. Основной задачей было сделать обработку GET-запроса и при этом достать шаблон, где уже будет логика, состоящая из кастомных тегов, которые динамически соберут валидный HTML и отдадут его назад.

Основой для проектирования тегов я выбрал самые частые задачи, которые приходиться решать программисту разрабатывая пользовательский интерфейс. К частым задачам в проектировании пользовательского интерфейса я отношу следующие задачи:

  • Разбиение визуальных блоков на компоненты, и подключение их на страницы

  • Получение данных со сторонних микро-сервисов и отображение их на странице

  • Итеративное отображение данных

  • Отображения одиночных объектов из базы данных

  • Логические операции и отрисовка данных на основе выполнения условия отображения

  • Отображение данных в виде дерева

  • Расширение возможностей отображения выпадающего списка на основе HTML-элемента select

  • Отображение текстовых данных напрямую из базы данных

И в итоге я получил 8 специальных тегов которые решают вышеописанные задачи. А теперь по порядку я расскажу на примере этих задач как можно проектировать страницы с использованием тегов фреймворка MastermindCMS2.

Подключение фрагмента

Разработка фронтенд-приложения на MastermindCMS2 не отличается ничем от принципов разработки на других фреймворках. Фронтенд часть полностью автономна, и вам не нужно собирать приложение для того чтобы бекенд-логика заработала у вас в приложении. Вот несколько шагов демонстрирующих принципы разработки на нашей headless-cms.

<msm:fragment>- тег предназначен для использования внешних фрагментов HTML-кода для подключения их на страницы.

  1. Для начала создадим новый файл.

    Создание нового HTML-файла
    Создание нового HTML-файла
  2. Скопируем все содержимое страницы в новый файл

    Копирование HTML
    Копирование HTML
  3. Далее мы удалим некоторые части HTML, для того чтобы их перенести отдельно во внешние фрагменты.

    Убираем навигационное меню и основную часть страницы
    Убираем навигационное меню и основную часть страницы
  4. Обратимся к документации на сайте MastermindCMS2. Для примера мы используем <msm:fragment>

    Описание MSM Fragment Tag
    Описание MSM Fragment Tag
  5. <msm:fragment id="navbar" path="admin/components/navbar.html"/> - этот код вам нужно добавить на страницу

  6. Создаем новый файл для фрагмента.

    Перенос HTML для навигационной панели в отдельный файл
    Перенос HTML для навигационной панели в отдельный файл
  7. Подключаем созданный нами фрагмент на страницу.

    Подключение фрагмента HTML на страницу
    Подключение фрагмента HTML на страницу

Запросы на внешний REST API

<msm:rest>- тег предназначен для выполнения запроса на внешний ресурс, который доступен по REST API и возвращает данные в виде JSON.

Пример запроса который получает ответ в виде объекта
Пример запроса который получает ответ в виде объекта
Пример запроса который получает ответ в виде массива
Пример запроса который получает ответ в виде массива

Исходный код:

    <div class="container">
        <h1>Star Wars Ships</h1>
        <ul id="starWarsRestApiWrapper1" class="container">
            <msm:rest id="starWarsRestApi"
                      endpoint="https://swapi.dev/api/starships/9/?format=json"
                      request="{}" method="GET" item-name="ship">
                <msm:template><li>${ship|name}</li></msm:template>
            </msm:rest>
        </ul>
        <h1>Locations</h1>
        <ul id="locationsApiWrapper2" class="container">
            <msm:rest id="locationsApi"
                      endpoint="https://ghibliapi.herokuapp.com/locations"
                      request="{}" method="GET" item-name="loc">
                <msm:template><li>${loc|value.name}</li></msm:template>
            </msm:rest>
        </ul>
    </div>

Итерирование - цикл foreach

msm:foreach - тег позволяет сделать итерацию по элементам и отобразить их как повторяющийся HTML-элемент, который определен во вложенном теге msm:template.

Пример использования тега Foreach
Пример использования тега Foreach
Вид объектов в базе данных
Вид объектов в базе данных

Исходный код:

<form>
    <div class="form-group">
        <label for="inputFirstName">First Name</label>
        <input type="text" class="form-control" name="firstName" id="inputFirstName" autocomplete="off" placeholder="First Name">
    </div>
    <div class="form-group">
        <label for="inputLastName">Last Name</label>
        <input type="text" class="form-control" name="lastName" id="inputLastName" autocomplete="off" placeholder="Last Name">
    </div>
    <div class="form-group form-check">
        <input type="checkbox" class="form-check-input" name="status" value="false" id="checkStatus">
        <label class="form-check-label" for="checkStatus">Check me out</label>
    </div>
    <button type="button" class="btn btn-primary" onclick="app.addDocumentAndRender('foo','custom-users',this)">Submit</button>
</form>

<div id="userListWrapper" class="row gx-lg-5">
    <msm:foreach id="userList"
                 database="foo"
                 collection="custom-users"
                 filter="{}"
                 mode="DATABASE"
                 item-name="user">
        <msm:empty>
            <p>No users</p>
        </msm:empty>
        <msm:template>
            <div class="col-lg-6 col-xxl-4 mb-5">
                <div class="card bg-light border-0 h-100">
                    <div class="card-body text-center p-4 p-lg-5 pt-0 pt-lg-0">
                        <div class="feature bg-primary bg-gradient text-white rounded-3 mb-4 mt-n4"><i class="bi bi-collection"></i></div>
                        <h2 class="fs-4 fw-bold">${user|firstName} ${user|lastName}</h2>
                        <p class="mb-0">${user|_id}</p>
                    </div>
                </div>
            </div>
        </msm:template>
    </msm:foreach>
</div>

Отображение объектов - элемент block

msm:block - тег позволяет выводить объект на основе определенного HTML-элемента внутри вложенного тега msm:template.

В этом примере видно как входящий параметр от внешнего тега может быть использован на внутренних тегах:

<div id="userBlockWrapper">
    <msm:block id="userBlock"
               value="{  fullName: '${user|firstName} ${user|lastName}', movie : 'Last Hope'}"
               item-name="usr">
        <msm:template><h6 style="text-align:center">${usr|fullName} - ${usr|movie}</h6></msm:template>
    </msm:block>
</div>

Исходный код:

<div id="userListWrapper" class="row gx-lg-5">
    <msm:foreach id="userList"
                 database="foo"
                 collection="custom-users"
                 filter="{}"
                 mode="DATABASE"
                 item-name="user">
        <msm:empty>
            <p>No users</p>
        </msm:empty>
        <msm:template>
            <div class="col-lg-6 col-xxl-4 mb-5">
                <div class="card bg-light border-0 h-100">
                    <div class="card-body text-center p-4 p-lg-5 pt-0 pt-lg-0">
                        <div class="feature bg-primary bg-gradient text-white rounded-3 mb-4 mt-n4"><i class="bi bi-collection"></i></div>
                        <h2 class="fs-4 fw-bold">${user|firstName} ${user|lastName}</h2>
                        <p class="mb-0">${user|_id}</p>
                    </div>
                    <div id="userBlockWrapper">
                        <msm:block id="userBlock"
                                   value="{  fullName: '${user|firstName} ${user|lastName}', movie : 'Last Hope'}"
                                   item-name="usr">
                            <msm:template><h6 style="text-align:center">${usr|fullName} - ${usr|movie}</h6></msm:template>
                        </msm:block>
                    </div>
                </div>
            </div>
        </msm:template>
    </msm:foreach>
</div>

Логический оператор "если"

msm:if - тег позволяет выводить объект на основе условия, которое будет проверяться в атрибуте test.

Для примера изменим свойство в объектах следующим образом:

Пример данных для анализа условия
Пример данных для анализа условия
Пример использования тега msm:if
Пример использования тега msm:if

Исходный код:

<div id="userListWrapper" class="row gx-lg-5">
    <msm:foreach id="userList"
                 database="foo"
                 collection="custom-users"
                 filter="{}"
                 mode="DATABASE"
                 item-name="user">
        <msm:empty>
            <p>No users</p>
        </msm:empty>
        <msm:template>
            <div class="col-lg-6 col-xxl-4 mb-5">
                <div class="card bg-light border-0 h-100">
                    <div class="card-body text-center p-4 p-lg-5 pt-0 pt-lg-0">
                        <div class="feature bg-primary bg-gradient text-white rounded-3 mb-4 mt-n4"><i class="bi bi-collection"></i></div>
                        <h2 class="fs-4 fw-bold">${user|firstName} ${user|lastName}</h2>
                        <p class="mb-0">${user|_id}</p>
                    </div>
                    <div id="showUserBlockWrapper">
                        <msm:if id="showUserBlock" test="${user|status}">
                            <div id="userBlockWrapper">
                                <msm:block id="userBlock"
                                           value="{  fullName: '${user|firstName} ${user|lastName}', movie : 'Last Hope'}"
                                           item-name="usr">
                                    <msm:template><h6 style="text-align:center">${usr|fullName} - ${usr|movie}</h6></msm:template>
                                </msm:block>
                            </div>
                        </msm:if>
                    </div>
                </div>
            </div>
        </msm:template>
    </msm:foreach>
</div>

Отображение элемента в виде дерева

msm:tree - тег позволяет выполнить итерацию по элементам и отобразить их в виде древовидной HTML-структуры. Каждый вложенный элемент, имеющий подэлементы, должен содержать свойство children для отображения в виде элемента вложенного поддерева. Элемент для элемента итерации определяется во вложенном теге msm:template.

Исходный код:

<div class="dd" id="product-groups-tree">
    <msm:tree id="productGroups"
              database="msm2-application"
              collection="categories"
              filter="{}"
              mode="DATABASE" item-name="cat">
        <msm:template>
            <ol class="dd-list">
                <li class="dd-item dd3-item" data-id="${cat|id}" data-name="${cat|name}">
                    <div class="dd-handle dd3-handle"></div>
                    <div class="dd3-content">
                        <a href="#product-group/view/${cat|id}" class="truncate">${cat|name}</a>
                    </div>
                    <a class="dd3-right-handle modal-trigger" href="#remove-pgroup-dialog" data-item-id="${cat|id}"></a>
                </li>
            </ol>
        </msm:template>
    </msm:tree>
</div>

Выпадающий список

msm:select - тег выполняет рендеринг для на основе определения HTML-шаблона во вложенном теге msm:template в качестве дочернего элемента.

Исходный код:

<div id="selectTagWrapper" class="root">
    <msm:select id="selectTag"
                database="foo"
                collection="custom-users"
                filter="{}"
                selected="Darth"
                mode="DATABASE" item-name="option" class="select-file-types">
        <msm:template><option class="item" value="${option|firstName}" ${option|selected}>${option|firstName} ${option|lastName}</option></msm:template>
    </msm:select>
</div>

Получение текстовых значений из базы данных

msm:text - тег позволяет отображать свойства объекта в виде текста, который определяется согласно шаблону объявленному внутри.

<div id="textTagWrapper">
    <msm:text id="textTag"
              bean="dummyBean"
              function="getDummyUser"
              scope="PROTOTYPE"
              mode="BEAN" item-name="emp">
        ${emp|name} - ${emp|age}
    </msm:text>
</div>

Заключение

Все 8 специальных тегов были рассмотрены в статье с примерами. Я пострарался максимально подробно рассказать как все-таки это работает. В следующей статье я расскажу как использовать эти теги в связке с JavaScript-частью от данной технологии. Дата-байдинг, роутинг уже реализованы на уровне фреймворка, поэтому вам не придется писать тонны JS чтобы сделать интеграции с базой данных, и вам не нужно писать бэкенд часть для REST API. Основные все инструменты уже реализованы в фреймворке.

Как обычно напишу планы на развитие данной технолгии. Я думаю основной задачей для меня сейчас, стоит задача это создание маркетплейса для всех программистов, которые смогут там продавать свой модули и шаблоны написанные на этом фреймворке.

Я делюсь этой информацией тут, потому-что я хочу чтобы рутинная разработка веб-приложений превратилась в искусство шаблонизации. Где вы можете собрать в короткие сроки сложные платформы с поиском и интеграциями как простой конструктор LEGO.

Я делюсь с вами прежде своим инструментом, который мне сейчас позволяет работать быстрее и эффективнее. Но без вас я не смогу сделать это. И это хоть на грамм, на капельку сделает нашу жизнь программиста проще.

Спасибо что дочитали мою статью до конца, продолжение следует...

Комментарии (0)