[PHP] Как я писал кодогенератор на PHP и что из этого получилось
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Причины и проблемы, которые нужно было решить
В этой статье я вам расскажу о том как я писал кодогенератор на php. Расскажу о пути, который он прошел от генерации простых таблиц, до довольно полноценного генератора html и css кода. Приведу примеры его использования и покажу уже сделанные проекты.
В этом семестре на одном из предметов можно было использовать только PHP.
После бесконеного ренейма проекта Проект получил имя MelonPHP. Чтобы люди думали о еде когда произносили его имя? Но у нас тут статья не о генерации бреда, по этому давайте я вам расскажу о причине его создания.
Написать надо было много, но это не проблема. Основная проблема заключалась в выводе HTML кода через PHP. Я постараюсь объяснить проблему ниже.
Например вот вывод текста через всем знакомое echo:
$text = "out text";
echo "<p>$text</p>";
Выглядит просто и понятно. Давайте возьмем отрезок кода моего друга, где он генерирует таблицу:
...
$sql = "SELECT * FROM table";$result = $conn->query($sql);
if($result->num_rows > 0) {
echo "<b>Table table</b><br><br>";
echo "<table border=2>";
echo "<tr><td> name </td>"."<td> name </td>"."<td> name </td></tr>";
while($row = $result->fetch_assoc()) {
echo "<tr><td>".$row["name"]."</td><td>".$row["name"]."</td><td>".$row["name"]."</td></tr>";
}
echo "</table>";
} else {
echo "0 results";}
...
Это страшный код демонстрирует проблемы, которые я хотел решить:
- Присутствие html в php коде, что делает его по моему мнению менее читаемым. Все-таки файл для одного яп должен содержать код только одного яп(а), по моему мнению
- Нет разделения логики, все в каше. Хотелось более приятный "фронтенд" на PHP
Стоит отметить, что я относительно давно пишу на Flutter и мне очень нравится идея заложенная в его основе, связанная с написанием интерфейса с помощью постройки дерева из виджетов. Я решил позаимствовать оттуда идею с нодами (виджетами).
Я был уверен, что быстрее будет написать небольшой кодогенератор, чем терпеть это.
Изначально генератор занимался генерацией таблиц через функции. Но потом перерос в ней-то более масштабное.
Основными идеями были следующими:
- UI пишется из элементов/компонентов (привет React)
- Удобные макеты (Избавиться от div, div, div, div...)
- Чтобы весь UI писался на PHP (без JS, без HTML, без CSS).
- Rebuild через callback события, через AJAX + JQuery не суждено
- Удобная система роутов не суждено
- Поддержка CSS (и не просто строку писать, на уровне "width: 100px", а полноценная поддержка прямо в PHP коде)
- ООП
Особенности MelonPHP
- Почти все элементы (кроме текста, кнопки и еще нескольких) по умолчанию имеют ширину и высоту в 100%, в том числе и документ.
- Кроме того, если элементы будут выходить за пределы страницы, то скролла по умолчанию не будет. Для этого нужно использовать ScrollView.
- Так же по умолчанию нельзя выделать никакие элементы.
Архитектура
Очень печально, что в PHP отсутствует передачу параметров по имени, и по этому я решил использовать вместо них функции. Я уверен что это не менее удобный аналог (Какого было мое удивление, когда Microsoft показали MAUI, который использует ту же идею с функциями).
Все классы в MelonPHP наследуется от Node. Это простой класс, который имеет только 2 функции: Generate(), static Create().
- Generate() возвращает string — сгенерированный код.
- Create() — это статическая функция. Она нужна чтобы было проще создавать ноды в дереве.
abstract class Node
{
abstract function Generate() : string;
static function Create() ...
}
Element
Element — это более высокоуровневый класс, который нужен для более комфортного написания своих элементов.
Элемент в основном занимается генерацией чистого html кода.
Элементами в Фреймворк являются такие сущности как контейнер, кнопка, таблица и тд.
Component
Основная идея компонента в том, что этот класс управляет, и состоит из дерева элементов в нем. Компонент наследуется от элемента (бредовая идея).
Компонентами могут быть например дисплеи (аналог страниц в MelonPHP), списки, карточки, навигационные меню и тд.
abstract class Component extends Element
{
function Initialize() ...
abstract function Build() : Element;
function Detach() ...
}
Компонент удобен тем что вам не надо генерировать строки, чем занимается Element, так же в нем есть удобные функции для использования логики.
Попробуем написать простой компонент. Создадим класс ListItem наследуемый от Component.
Перезапишем функции Initialize() и Build().
Initialize() вызывается при создание компонента. В ней например можно инициализировать переменные или обработать логику.
Build() вызывается при генерации элемента. В ней обязательно должен возвращаться элемент. Обязательная для перезаписи.
Detach() вызывается при удалении компонента.
В Build() возвратим контейнер, а в качестве его ребенка, элемент текста и присвоим ему текст из переменной класса $Text.
В Initialize() пропишем значение $Text по умолчанию.
Добавим функцию Text(string) в которой будет записываться значение пользователя в переменную $Text.
Обязательно надо возвращать $this в функциях, которые будут вызываться в дереве.
class ListItem extends Component
{
private $Text;
function Initialize() {
$this->Text = "Name";
}
function Build() : Element {
return Container::Create()
->Child(
Text::Create()
->Text($this->Text)
);
}
function Text(string $string) {
$this->Text = $string;
return $this;
}
}
DisplayComponent
DisplayComponent — это компонент, который может выводит сгенерированный код на страницу. Для генерации нужно вызвать функцию Display.
Попробуем написать пример простого дисплея.
В функции Build() возвратим Document и присваиваем ему Title(string).
В DisplayComponent, в функции Build() всегда должен возвращаться Document. Document — это класс, который генерирует стандартную разметку HTML5.
Создадим функцию BuildList(), в которой через цикл заполним колонку созданными выше ListItem.
В качестве ребенка документа вызовем BuildList() функцию. Разделение дерева из нод на функции не дает ему превратиться в макаронного монстра.
Если будет ситуация что надо выполнить какую-то логику прямо в дереве, то для того есть класс Builder. Но так лучше не делать...
После тела класса вызовем функцию Diplay(), которая при переходе на данный файл, на сайте cгенерирует его и выведит.
class ListDisplay extends DisplayComponent
{
function Build() : Document {
return Document::Create()
// название страницы
->Title("test page")
->Child($this->BuildList());
}
function BuildList() {
$column = new Column;
for($i = 0; $i < 10; $i++)
$column->Children(
ListItem::Create()
->Text("number: $i")
);
return $column;
}
} ListDisplay::Display();
Макеты
Одна из целей преследуемых при создании фреймворка — удобство при создание макетов сайта.
Container
Container — это макет, который используется для декоративных целей.
Может содержать только одного ребенка.
Column и Row
У большинства элементов есть дети. Если у элемента доступен метод Child то он может иметь только одного ребенка, а если Children то у него может быть больше одного ребенка.
Так же Child перезаписывает переменную ребенка в то время как Children добавляет аргументы в верх стека.
В Children если один элемент в аргументе то не обязательно его заносить в массив.
Тоесть вместо Children([Text::Create()]) можно написать Children(Text::Create())
Column — это макет который выравнивает его детей вертикально.
Обращаясь к функциям CrossAlign и MainAlign можно выравнивать детей внутри колонки.
Row идентичен Column, но выравнивает детей горизонтально.
Stack
Stack — это макет дети которого не позиционированы. Это полезно если вы хотите чтобы ноды пересекались, для создания более красивого дизайна.
ScrollView, HorizontalScrollView, VerticalScrollView
Эти контейнеры который является областью для скороллинга.
HorizontalScrollView — в этом контейнере можно скроллить только по горизонтальной оси.
VerticalScrollView — в этом контейнере можно скроллить только по вертикальной оси.
ScrollView — в этом контейнере можно скроллить по всем осям.
Стилизация
Долга думая как лучше встроить css в фреймворк я пришел к идее с константами.
Например у нас в css есть background-color. Я строку записываю в константу и в php коде можно будет использовать без "". Это намного удобнее.
...
const BackgroundBlendMode = "background-blend-mode";
const BackgroundAttachment = "background-attachment";
const Border = "border";
const BorderSpacing = "border-spacing";
const BorderRadius = "border-radius";
const BorderImage = "border-image";
...
Что качается например такой конструкции "34".Px. Тут идея с константами выглядит не читабельно. По этому я решил в таких ситуациях использовать функции для css — например Px(34). Выглядит понятно и вписывается в пхп код.
Простая стилизация
Для простой стилизации в элементе функция ThemeParameter(...). Первый аргумент — это название параметра, а второй аргумент — это или массив из значений/значение.
Рассмотрим пример.
В первом параметре мы изменим цвет фона на #f0f0f0.
В втором параметре мы добавим отступы. Сверху и снизу 20px, справа и слева 15px.
Значения в массиве генерируются через пробел. Если вы хотите чтобы генерация происходила через запятую, то для етого есть функция CommaLine().
...
Container::Create()
->ThemeParameter(BackgroundColor, Hex("f0f0f0"))
->ThemeParameter(Padding, [Px(20), Px(15)]);
...
Как видно все очень просто и удобно, но если нам понадобятся модификаторы (hover например)? Для этого сделаны темы.
Темы
Темы в этом Фреймворк — это более продвинутый css, с media, keyframes, и модификаторами.
Напишем тему для контейнера с модификатором hover и active.
Для того надо создать класс темы и добавить в него ThemeBlock через метод ThemeBlocks.
Блоку темы нужно присвоить ключ / ключи. Я назову ключ my_container.
Дальше в блок темы можно добавить модификаторы. Я добавил: StandartModifier, HoverModifier, ActiveModifier. И задал для них параметры тебя через метод Parameter(...). Parameter работает так же как ThemeParameter.
function GetMyTheme() : Theme {
return Theme::Create()
->ThemeBlocks([
ThemeBlock::Create()
->Keys("my_container")
->Modifiers([
StandartModifier::Create()
->Parameter(BackgroundColor, Red)
->Parameter(Padding, [Px(10), Px(12)]),
HoverModifier::Create()
->Parameter(BackgroundColor, Green),
ActiveModifier::Create()
->Parameter(BackgroundColor, Blue)
])
]);
}
Дальше контейнеру я присвоил ключ (имя класса в css) темы через метод ThemeKeys. Но для того чтобы тему можно было использовать ее надо добавить в документ через метод Themes.
class TestThemeDisplay extends DisplayComponent
{
function Build() : Document {
return Document::Create()
->Themes(GetMyTheme())
->Child(
Container::Create()
->ThemeKeys("my_container")
);
}
} TestThemeDisplay::Display();
После можно запустить дисплей и увидеть что тема по ключу применилась.
Продвинутые анимации
Для продвинутой анимации есть keyframes.
Для того чтобы добавить в тему keyframe, используйте метод FrameBlocks.
Добавим уже в существующую тему FrameBlock.
В FrameBlock есть метод Frames. Вызовем его и добавим несколько фреймов, так же для каждого фрейма надо указывать Value. Оно может быть в процентах (используйте функцию Pr(value)) или может быть константа From, To.
Каждому кадру можно добавлять параметры которые будут уникальны и плавно изменятся между другими кадрами.
Так же добавим блок темы в котором будет применяться эта анимация и назовем его shake_text.
Создадим текст в предыдущем дисплее, в контейнере и применим ему ключ темы.
Так же на примере видно что в параметре можно использовать и обычные строки.
function GetMyTheme() : Theme {
return Theme::Create()
->ThemeBlocks([
ThemeBlock::Create()
->Keys("my_container")
->Modifiers([
StandartModifier::Create()
->Parameter(Padding, [Px(10), Px(12)]),
HoverModifier::Create()
->Parameter(BackgroundColor, Green),
ActiveModifier::Create()
->Parameter(BackgroundColor, Blue)
]),
ThemeBlock::Create()
->Keys("shake_text")
->Modifiers([
StandartModifier::Create()
->Parameter(Color, Red)
->Parameter(Animation, ["shake_text_anim", ".2s", "ease-in-out", "5", "alternate-reverse"])
])
])
->FrameBlocks(
FrameBlock::Create()
->Key("shake_text_anim")
->Frames([
Frame::Create()
->Value(Pr(0))
->Parameter(Transform, Translate(0, 0)),
Frame::Create()
->Value(Pr(25))
->Parameter(Color, Hex("ff4040"))
->Parameter(Filter, Blur(Px(0.5))),
Frame::Create()
->Value(Pr(50))
->Parameter(Filter, Blur(Px(1.2))),
Frame::Create()
->Value(Pr(75))
->Parameter(Color, Hex("ff4040"))
->Parameter(Filter, Blur(Px(0.5))),
Frame::Create()
->Value(Pr(100))
->Parameter(Transform, Translate(Px(10), 0)),
])
);
}
class TestThemeDisplay extends DisplayComponent
{
function Build() : Document {
return Document::Create()
->Themes(GetMyTheme())
->Child(
Container::Create()
->ThemeKeys("my_container")
->Child(
Text::Create()
->ThemeKeys("shake_text")
->Text("Error text")
)
);
}
} TestThemeDisplay::Display();
Адаптивность
Создадим еще 2 темы. Одна будет для мобильных девайсов, а вторая для пк. У темы есть функции: MinWidth, MaxWidth, MinHeight, MaxHeight, объявив которые вы можете указать на каком размере будет работать тема.
Теме для телефонов зададим MinWidth 800px.
Теме для пк зададим MaxWidth 800px.
Создадим блок темы где в стандартном модификаторе для мобильной версии будет присваиваться цвет фона зелёный, а на пк версии — желтый. Назовем блок adaptive_color.
Добавим обе темы в документ дисплея.
Добавим ключи темы к контейнеру.
function GetMobileTheme() : Theme {
return Theme::Create()
->MinWidth(Px(800))
->ThemeBlocks(
ThemeBlock::Create()
->Keys("adaptive_color")
->Modifiers(
StandartModifier::Create()
->Parameter(BackgroundColor, Green)
)
);
}
function GetDesktopTheme() : Theme {
return Theme::Create()
->MaxWidth(Px(800))
->ThemeBlocks(
ThemeBlock::Create()
->Keys("adaptive_color")
->Modifiers(
StandartModifier::Create()
->Parameter(BackgroundColor, Red)
)
);
}
class TestThemeDisplay extends DisplayComponent
{
function Build() : Document {
return Document::Create()
->Themes([
GetMyTheme(),
GetDesktopTheme(),
GetMobileTheme()
])
->Child(
Container::Create()
->ThemeKeys(["my_container", "adaptive_color"])
->Child(
Text::Create()
->ThemeKeys("shake_text")
->Text("Error text")
)
);
}
} TestThemeDisplay::Display();
Логика
Попробуем написать простой кликер.
Для начала нам надо создать класс и наследовать его от DisplayComponent.
Создадим функцию Build() и возвратим в ней Document.
class ClickerDisplay extends DisplayComponent
{
function Build() : Element {
return Document::Create()
->Title("Clicker");
}
} ClickerDisplay::Display();
Добавим колонку в качестве ребенка документа.
Так же в качестве детей колонки добавим текст и кнопку.
class ClickerDisplay extends DisplayComponent
{
function Build() : Element {
return Document::Create()
->Title("Clicker")
->Child(
Column::Create()
->Children([
Text::Create()
->Text("Pressed 0 times"),
Button::Create()
->Text("Press")
])
);
}
} ClickerDisplay::Display();
Результат будет следующим.
Далее добавим простые ThemeParameter, чтобы сделать наш пример красивее.
class ClickerDisplay extends DisplayComponent
{
function Build() : Element {
return Document::Create()
->Title("Clicker")
->Child(
Column::Create()
->ThemeParameter(Padding, Px(15))
->Children([
Text::Create()
->ThemeParameter(PaddingBottom, Px(15))
->Text("Pressed 0 times"),
Button::Create()
->ThemeParameter(Width, Auto)
->ThemeParameter(Padding, [Px(4), Px(10)])
->ThemeParameter(BackgroundColor, Blue)
->ThemeParameter(Color, White)
->ThemeParameter(BorderRadius, Px(4))
->Text("Press")
])
);
}
} ClickerDisplay::Display();
Выглядит куда лучше в несколько простых строчек.
Теперь можно добавить логику.
Для начала нужно инициализировать функцию Initialize() и создать приватную переменную TapCount.
Аналог form в фреймворке — это Action.
Добавим Action в наше дерево элементов. Action тип пусть будет Post. В качестве детей укажем нашу колонку где находится наша кнопка.
Далее добавим click_count переменную в Action. А в качестве ее значение присвоим TapCount.
В Initialize() через Action::GetValue(name, standart_value, action_type) получим наше переменную. В качестве значения по умолчанию укажем 0, а в качестве типа укажем Post.
Добавим инкремент для нашей переменной.
В тексте выведим "Press $this->TapCount times".
Все, простой клинкер готов.
class ClickerDisplay extends DisplayComponent
{
private $TapCount;
function Initialize() {
$this->TapCount = Action::GetValue("click_count", 0 /* standart value */, ActionTypes::Post);
$this->TapCount++;
}
function Build() : Document {
return Document::Create()
->Title("Test page")
->Child(
Action::Create()
->Type(ActionTypes::Post)
->Variable("click_count", $this->TapCount)
->Child(
Column::Create()
->ThemeParameter(Padding, Px(15))
->Children([
Text::Create()
->ThemeParameter(PaddingBottom, Px(15))
->Text("Press $this->TapCount times"),
Button::Create()
->ThemeParameter(Width, Auto)
->ThemeParameter(Padding, [Px(4), Px(10)])
->ThemeParameter(BackgroundColor, Blue)
->ThemeParameter(Color, White)
->ThemeParameter(BorderRadius, Px(4))
->Text("Press")
])
)
);
}
} ClickerDisplay::Display();
Итог
Мне удалось написать простой, но достаточно мощный кодогенератор.
Он прошол путь от генерации простых таблиц до полноценного генератора html и css, на котором можно удобно верстать проекты и совмещять верстку с логикой.
На данном фреймворке я написал курсовой проект (скриншоты ниже), использовал его на экзамене и делал на нем учебные задания.
Скриншоты курсового проекта сделанного на MelonPHP
SPL
Источники
GitHub — MelonPHP
Flutter
MAUI
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, PHP, Ненормальное программирование, Программирование, Разработка веб-сайтов] Inertia.js – современный монолит
- [C#, DIY или Сделай сам, Разработка под Arduino] Использование контроллера Arduino для прерываний
- [JavaScript, Open source, Программирование] Объектно-ориентированная альтернатива jquery
- [Облачные вычисления, История IT, Исследования и прогнозы в IT] Бархатная перчатка Microsoft
- [Node.JS, PHP, Perl, Python, Информационная безопасность] Трюки с переменными среды (перевод)
- [PHP, Алгоритмы, Информационная безопасность, Криптография] Разработка собственного алгоритма симметричного шифрования на Php
- [PHP] Мне не нравится то, во что превращается PHP
- [CRM-системы, ERP-системы, Open source, PHP, Развитие стартапа] Totum — open source конструктор CRM/ERP и произвольных учетных систем (PHP + PgSQL)
- [Обработка изображений, Разработка веб-сайтов] Как мы решили оптимизировать картинки — а в процессе переделали сайт, админку и подход к интерфейсу
- [PHP] POST запрос, составное содержимое (multipart/form-data)
Теги для поиска: #_php, #_php, #_php7, #_framework, #_generators, #_codegeneration, #_ui, #_php
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 18:00
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Причины и проблемы, которые нужно было решить В этой статье я вам расскажу о том как я писал кодогенератор на php. Расскажу о пути, который он прошел от генерации простых таблиц, до довольно полноценного генератора html и css кода. Приведу примеры его использования и покажу уже сделанные проекты. В этом семестре на одном из предметов можно было использовать только PHP. После бесконеного ренейма проекта Проект получил имя MelonPHP. Чтобы люди думали о еде когда произносили его имя? Но у нас тут статья не о генерации бреда, по этому давайте я вам расскажу о причине его создания. Написать надо было много, но это не проблема. Основная проблема заключалась в выводе HTML кода через PHP. Я постараюсь объяснить проблему ниже. Например вот вывод текста через всем знакомое echo: $text = "out text";
echo "<p>$text</p>"; Выглядит просто и понятно. Давайте возьмем отрезок кода моего друга, где он генерирует таблицу: ...
$sql = "SELECT * FROM table";$result = $conn->query($sql); if($result->num_rows > 0) { echo "<b>Table table</b><br><br>"; echo "<table border=2>"; echo "<tr><td> name </td>"."<td> name </td>"."<td> name </td></tr>"; while($row = $result->fetch_assoc()) { echo "<tr><td>".$row["name"]."</td><td>".$row["name"]."</td><td>".$row["name"]."</td></tr>"; } echo "</table>"; } else { echo "0 results";} ... Это страшный код демонстрирует проблемы, которые я хотел решить:
Стоит отметить, что я относительно давно пишу на Flutter и мне очень нравится идея заложенная в его основе, связанная с написанием интерфейса с помощью постройки дерева из виджетов. Я решил позаимствовать оттуда идею с нодами (виджетами). Я был уверен, что быстрее будет написать небольшой кодогенератор, чем терпеть это. Изначально генератор занимался генерацией таблиц через функции. Но потом перерос в ней-то более масштабное. Основными идеями были следующими:
Особенности MelonPHP
Архитектура Очень печально, что в PHP отсутствует передачу параметров по имени, и по этому я решил использовать вместо них функции. Я уверен что это не менее удобный аналог (Какого было мое удивление, когда Microsoft показали MAUI, который использует ту же идею с функциями). Все классы в MelonPHP наследуется от Node. Это простой класс, который имеет только 2 функции: Generate(), static Create().
abstract class Node
{ abstract function Generate() : string; static function Create() ... } Element Element — это более высокоуровневый класс, который нужен для более комфортного написания своих элементов. Элемент в основном занимается генерацией чистого html кода. Элементами в Фреймворк являются такие сущности как контейнер, кнопка, таблица и тд. Component Основная идея компонента в том, что этот класс управляет, и состоит из дерева элементов в нем. Компонент наследуется от элемента (бредовая идея). Компонентами могут быть например дисплеи (аналог страниц в MelonPHP), списки, карточки, навигационные меню и тд. abstract class Component extends Element
{ function Initialize() ... abstract function Build() : Element; function Detach() ... } Компонент удобен тем что вам не надо генерировать строки, чем занимается Element, так же в нем есть удобные функции для использования логики. Попробуем написать простой компонент. Создадим класс ListItem наследуемый от Component. Перезапишем функции Initialize() и Build(). Initialize() вызывается при создание компонента. В ней например можно инициализировать переменные или обработать логику.
Build() вызывается при генерации элемента. В ней обязательно должен возвращаться элемент. Обязательная для перезаписи. Detach() вызывается при удалении компонента. В Initialize() пропишем значение $Text по умолчанию. Добавим функцию Text(string) в которой будет записываться значение пользователя в переменную $Text. Обязательно надо возвращать $this в функциях, которые будут вызываться в дереве.
class ListItem extends Component
{ private $Text; function Initialize() { $this->Text = "Name"; } function Build() : Element { return Container::Create() ->Child( Text::Create() ->Text($this->Text) ); } function Text(string $string) { $this->Text = $string; return $this; } } DisplayComponent DisplayComponent — это компонент, который может выводит сгенерированный код на страницу. Для генерации нужно вызвать функцию Display. Попробуем написать пример простого дисплея. В функции Build() возвратим Document и присваиваем ему Title(string). В DisplayComponent, в функции Build() всегда должен возвращаться Document. Document — это класс, который генерирует стандартную разметку HTML5.
В качестве ребенка документа вызовем BuildList() функцию. Разделение дерева из нод на функции не дает ему превратиться в макаронного монстра. Если будет ситуация что надо выполнить какую-то логику прямо в дереве, то для того есть класс Builder. Но так лучше не делать...
class ListDisplay extends DisplayComponent
{ function Build() : Document { return Document::Create() // название страницы ->Title("test page") ->Child($this->BuildList()); } function BuildList() { $column = new Column; for($i = 0; $i < 10; $i++) $column->Children( ListItem::Create() ->Text("number: $i") ); return $column; } } ListDisplay::Display(); Макеты Одна из целей преследуемых при создании фреймворка — удобство при создание макетов сайта. Container Container — это макет, который используется для декоративных целей. Может содержать только одного ребенка. Column и Row У большинства элементов есть дети. Если у элемента доступен метод Child то он может иметь только одного ребенка, а если Children то у него может быть больше одного ребенка. Так же Child перезаписывает переменную ребенка в то время как Children добавляет аргументы в верх стека. В Children если один элемент в аргументе то не обязательно его заносить в массив.
Тоесть вместо Children([Text::Create()]) можно написать Children(Text::Create()) Обращаясь к функциям CrossAlign и MainAlign можно выравнивать детей внутри колонки. Row идентичен Column, но выравнивает детей горизонтально. Stack Stack — это макет дети которого не позиционированы. Это полезно если вы хотите чтобы ноды пересекались, для создания более красивого дизайна. ScrollView, HorizontalScrollView, VerticalScrollView Эти контейнеры который является областью для скороллинга. HorizontalScrollView — в этом контейнере можно скроллить только по горизонтальной оси. VerticalScrollView — в этом контейнере можно скроллить только по вертикальной оси. ScrollView — в этом контейнере можно скроллить по всем осям. Стилизация Долга думая как лучше встроить css в фреймворк я пришел к идее с константами. Например у нас в css есть background-color. Я строку записываю в константу и в php коде можно будет использовать без "". Это намного удобнее. ...
const BackgroundBlendMode = "background-blend-mode"; const BackgroundAttachment = "background-attachment"; const Border = "border"; const BorderSpacing = "border-spacing"; const BorderRadius = "border-radius"; const BorderImage = "border-image"; ... Что качается например такой конструкции "34".Px. Тут идея с константами выглядит не читабельно. По этому я решил в таких ситуациях использовать функции для css — например Px(34). Выглядит понятно и вписывается в пхп код. Простая стилизация Для простой стилизации в элементе функция ThemeParameter(...). Первый аргумент — это название параметра, а второй аргумент — это или массив из значений/значение. Рассмотрим пример. В первом параметре мы изменим цвет фона на #f0f0f0. В втором параметре мы добавим отступы. Сверху и снизу 20px, справа и слева 15px. Значения в массиве генерируются через пробел. Если вы хотите чтобы генерация происходила через запятую, то для етого есть функция CommaLine().
...
Container::Create() ->ThemeParameter(BackgroundColor, Hex("f0f0f0")) ->ThemeParameter(Padding, [Px(20), Px(15)]); ... Как видно все очень просто и удобно, но если нам понадобятся модификаторы (hover например)? Для этого сделаны темы. Темы Темы в этом Фреймворк — это более продвинутый css, с media, keyframes, и модификаторами. Напишем тему для контейнера с модификатором hover и active. Для того надо создать класс темы и добавить в него ThemeBlock через метод ThemeBlocks. Блоку темы нужно присвоить ключ / ключи. Я назову ключ my_container. Дальше в блок темы можно добавить модификаторы. Я добавил: StandartModifier, HoverModifier, ActiveModifier. И задал для них параметры тебя через метод Parameter(...). Parameter работает так же как ThemeParameter. function GetMyTheme() : Theme {
return Theme::Create() ->ThemeBlocks([ ThemeBlock::Create() ->Keys("my_container") ->Modifiers([ StandartModifier::Create() ->Parameter(BackgroundColor, Red) ->Parameter(Padding, [Px(10), Px(12)]), HoverModifier::Create() ->Parameter(BackgroundColor, Green), ActiveModifier::Create() ->Parameter(BackgroundColor, Blue) ]) ]); } Дальше контейнеру я присвоил ключ (имя класса в css) темы через метод ThemeKeys. Но для того чтобы тему можно было использовать ее надо добавить в документ через метод Themes. class TestThemeDisplay extends DisplayComponent
{ function Build() : Document { return Document::Create() ->Themes(GetMyTheme()) ->Child( Container::Create() ->ThemeKeys("my_container") ); } } TestThemeDisplay::Display(); После можно запустить дисплей и увидеть что тема по ключу применилась. Продвинутые анимации Для продвинутой анимации есть keyframes. Для того чтобы добавить в тему keyframe, используйте метод FrameBlocks. Добавим уже в существующую тему FrameBlock. В FrameBlock есть метод Frames. Вызовем его и добавим несколько фреймов, так же для каждого фрейма надо указывать Value. Оно может быть в процентах (используйте функцию Pr(value)) или может быть константа From, To. Каждому кадру можно добавлять параметры которые будут уникальны и плавно изменятся между другими кадрами. Так же добавим блок темы в котором будет применяться эта анимация и назовем его shake_text. Создадим текст в предыдущем дисплее, в контейнере и применим ему ключ темы. Так же на примере видно что в параметре можно использовать и обычные строки. function GetMyTheme() : Theme {
return Theme::Create() ->ThemeBlocks([ ThemeBlock::Create() ->Keys("my_container") ->Modifiers([ StandartModifier::Create() ->Parameter(Padding, [Px(10), Px(12)]), HoverModifier::Create() ->Parameter(BackgroundColor, Green), ActiveModifier::Create() ->Parameter(BackgroundColor, Blue) ]), ThemeBlock::Create() ->Keys("shake_text") ->Modifiers([ StandartModifier::Create() ->Parameter(Color, Red) ->Parameter(Animation, ["shake_text_anim", ".2s", "ease-in-out", "5", "alternate-reverse"]) ]) ]) ->FrameBlocks( FrameBlock::Create() ->Key("shake_text_anim") ->Frames([ Frame::Create() ->Value(Pr(0)) ->Parameter(Transform, Translate(0, 0)), Frame::Create() ->Value(Pr(25)) ->Parameter(Color, Hex("ff4040")) ->Parameter(Filter, Blur(Px(0.5))), Frame::Create() ->Value(Pr(50)) ->Parameter(Filter, Blur(Px(1.2))), Frame::Create() ->Value(Pr(75)) ->Parameter(Color, Hex("ff4040")) ->Parameter(Filter, Blur(Px(0.5))), Frame::Create() ->Value(Pr(100)) ->Parameter(Transform, Translate(Px(10), 0)), ]) ); } class TestThemeDisplay extends DisplayComponent
{ function Build() : Document { return Document::Create() ->Themes(GetMyTheme()) ->Child( Container::Create() ->ThemeKeys("my_container") ->Child( Text::Create() ->ThemeKeys("shake_text") ->Text("Error text") ) ); } } TestThemeDisplay::Display(); Адаптивность Создадим еще 2 темы. Одна будет для мобильных девайсов, а вторая для пк. У темы есть функции: MinWidth, MaxWidth, MinHeight, MaxHeight, объявив которые вы можете указать на каком размере будет работать тема. Теме для телефонов зададим MinWidth 800px. Теме для пк зададим MaxWidth 800px. Создадим блок темы где в стандартном модификаторе для мобильной версии будет присваиваться цвет фона зелёный, а на пк версии — желтый. Назовем блок adaptive_color. Добавим обе темы в документ дисплея. Добавим ключи темы к контейнеру. function GetMobileTheme() : Theme {
return Theme::Create() ->MinWidth(Px(800)) ->ThemeBlocks( ThemeBlock::Create() ->Keys("adaptive_color") ->Modifiers( StandartModifier::Create() ->Parameter(BackgroundColor, Green) ) ); } function GetDesktopTheme() : Theme {
return Theme::Create() ->MaxWidth(Px(800)) ->ThemeBlocks( ThemeBlock::Create() ->Keys("adaptive_color") ->Modifiers( StandartModifier::Create() ->Parameter(BackgroundColor, Red) ) ); } class TestThemeDisplay extends DisplayComponent
{ function Build() : Document { return Document::Create() ->Themes([ GetMyTheme(), GetDesktopTheme(), GetMobileTheme() ]) ->Child( Container::Create() ->ThemeKeys(["my_container", "adaptive_color"]) ->Child( Text::Create() ->ThemeKeys("shake_text") ->Text("Error text") ) ); } } TestThemeDisplay::Display(); Логика Попробуем написать простой кликер. Для начала нам надо создать класс и наследовать его от DisplayComponent. Создадим функцию Build() и возвратим в ней Document. class ClickerDisplay extends DisplayComponent
{ function Build() : Element { return Document::Create() ->Title("Clicker"); } } ClickerDisplay::Display(); Добавим колонку в качестве ребенка документа. Так же в качестве детей колонки добавим текст и кнопку. class ClickerDisplay extends DisplayComponent
{ function Build() : Element { return Document::Create() ->Title("Clicker") ->Child( Column::Create() ->Children([ Text::Create() ->Text("Pressed 0 times"), Button::Create() ->Text("Press") ]) ); } } ClickerDisplay::Display(); Результат будет следующим. Далее добавим простые ThemeParameter, чтобы сделать наш пример красивее. class ClickerDisplay extends DisplayComponent
{ function Build() : Element { return Document::Create() ->Title("Clicker") ->Child( Column::Create() ->ThemeParameter(Padding, Px(15)) ->Children([ Text::Create() ->ThemeParameter(PaddingBottom, Px(15)) ->Text("Pressed 0 times"), Button::Create() ->ThemeParameter(Width, Auto) ->ThemeParameter(Padding, [Px(4), Px(10)]) ->ThemeParameter(BackgroundColor, Blue) ->ThemeParameter(Color, White) ->ThemeParameter(BorderRadius, Px(4)) ->Text("Press") ]) ); } } ClickerDisplay::Display(); Выглядит куда лучше в несколько простых строчек. Теперь можно добавить логику. Для начала нужно инициализировать функцию Initialize() и создать приватную переменную TapCount. Аналог form в фреймворке — это Action.
Далее добавим click_count переменную в Action. А в качестве ее значение присвоим TapCount. В Initialize() через Action::GetValue(name, standart_value, action_type) получим наше переменную. В качестве значения по умолчанию укажем 0, а в качестве типа укажем Post. Добавим инкремент для нашей переменной. В тексте выведим "Press $this->TapCount times". Все, простой клинкер готов. class ClickerDisplay extends DisplayComponent
{ private $TapCount; function Initialize() { $this->TapCount = Action::GetValue("click_count", 0 /* standart value */, ActionTypes::Post); $this->TapCount++; } function Build() : Document { return Document::Create() ->Title("Test page") ->Child( Action::Create() ->Type(ActionTypes::Post) ->Variable("click_count", $this->TapCount) ->Child( Column::Create() ->ThemeParameter(Padding, Px(15)) ->Children([ Text::Create() ->ThemeParameter(PaddingBottom, Px(15)) ->Text("Press $this->TapCount times"), Button::Create() ->ThemeParameter(Width, Auto) ->ThemeParameter(Padding, [Px(4), Px(10)]) ->ThemeParameter(BackgroundColor, Blue) ->ThemeParameter(Color, White) ->ThemeParameter(BorderRadius, Px(4)) ->Text("Press") ]) ) ); } } ClickerDisplay::Display(); Итог Мне удалось написать простой, но достаточно мощный кодогенератор. Он прошол путь от генерации простых таблиц до полноценного генератора html и css, на котором можно удобно верстать проекты и совмещять верстку с логикой. На данном фреймворке я написал курсовой проект (скриншоты ниже), использовал его на экзамене и делал на нем учебные задания. Скриншоты курсового проекта сделанного на MelonPHPSPLИсточники GitHub — MelonPHP Flutter MAUI =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 18:00
Часовой пояс: UTC + 5