В язык программирования встроена возможность писать HTML и CSS прямо в коде. Транслятор, автоматически транслирует код в php и js, так чтобы он работал на сервере и браузере, и одинаково отображал верстку. В JS также доступны обработчики события. Обновление HTML кода на клиенте происходит путем патчинга HTML дерева через RenderDriver.
Листинг кода приведен в конце.
Чтобы отрисовать страницу нужна модель, и функция рендера. Модель хранит данные страницы.
Модель страницы - неизменяемая структура данных. Чтобы изменить страницу, нужно изменить модель, вызвав функцию this.updateModel(Dict map)
или this.setModel(CoreStruct model)
в обаботчике событий компонента. После этого будет создана новая модель, и отправлен запрос на перерисовку страницы.
Перерисовка страницы происходит через RenderDriver, используя requestAnimationFrame, путем вызова функции render у каждого компонента, который нужно перерисовать. В функции render происходить патч HTML дерева.
Данный метод не использует Virtual DOM, как это сделано, например, в React JS. Render Driver напрямую патчит HTML через функцию render компонента.
Функция static async RenderContainer IndexPage(RenderContainer container)
является точкой входа в программу. @Route{ "uri": "/" }
задает маршрут точку входа в функцию.
Данная функция создает модель IndexPageModel, и задает title для страницы.
f_inc - обновляет css и js, добавляет ?_=number к загружаемым css и js файлам.
CSS страницы описан в теге функции css()
. Выражение %content
означает, что будет сформирован content-f224, где f224 это хэш класса. Это нужно, чтобы одинаковые css селекторы разных классов не пересекались.
Чтобы прописать этот css в html теге нужно указать: <div @class='content'>
Отрисовка HTML происходит в функции: render(LayoutModel layout, IndexPageModel model, Dict params, html content)
Параметры функции:
К примеру, если вызвать компонент
<Button kind='success' @event:MouseClickEvent='onMouseClick'>Save</Button>
, то:
в params будет содержаться kind='success'
в content будет содержаться текст Save
Другой пример:
<Dialog @bind="dialog" @ref="dialog" style="promt"
@event:DialogEvent="onDialogEvent"
/>
Будет отрисован компонент Dialog.
Выражение @event:DialogEvent="onDialogEvent"
и @event:MouseClickEvent='onMouseClick'
это обработчики событий. Если Dialog вызовет событие DialogEvent, то будет вызвана функция onDialogEvent у родительского компонента, который вызвал Dialog. И соответственно onMouseClick, при нажатии мыши.
Существуют два типа событий. Синхронные и асинхронные.
Для синхронных нужно указывать @event:MouseClickEvent='onMouseClick'
Для асинхронных @eventAsync:MouseClickEvent='onMouseClickAsync'
Асинхронные позволяют делать аякс запросы к бэкенд.
Соответственно для асинхронных событий, обработчик будет выглядеть так: async void menuClick(MouseClickEvent e)
Код e.cancel();
позволяет отменить дефолтное JS событие в браузере.
Код:
this.updateModel
{
"content": this.model.content ~ "!",
};
Вносит изменения в модель, и отправляет вышестоящему компоненту событие, о том, что модель изменилась. Далее по цепочке вверх передается событие, с измененными моделями компонентов, и RenderDriver получает измененную LayoutModel. После этого RenderDriver отправляет запрос на перерисовку, используя requestAnimationFrame.
namespace App.UI;
use Runtime.MessageRPC;
use Runtime.Web.Component;
use Runtime.Web.LayoutModel;
use Runtime.Web.RenderContainer;
use Runtime.Web.RenderHelper;
use Runtime.Web.Annotations.Route;
use Runtime.Web.Annotations.RouteList;
use Runtime.Web.Annotations.Template;
use Runtime.Web.Button.Button;
use App.Model.IndexPageModel;
@RouteList{}
@Template{ "model_name": classof IndexPageModel }
class IndexPage extends Component
{
/**
* Returns module name
*/
lambda string moduleName() => "App";
/**
* Route Action
* @return WebContainer
*/
@Route{ "uri": "/" }
static async RenderContainer IndexPage(RenderContainer container)
{
IndexPageModel model = new IndexPageModel
{
"content": "Hello world",
};
/* Set title */
container <= layout <= title <= "Hello world !!!";
/* Create model */
container <= layout <= page_class <= classof IndexPage;
container <= layout <= page_model <= model;
container <= layout <= f_inc <= 1;
return container;
}
/**
* Returns required components
*/
lambda Collection components() =>
[
classof Button,
];
/**
* Component css
*/
lambda string css(Dict vars) =>
@css{
%content{
text-align: center;
padding-top: 50px;
}
%label{
padding-bottom: 5px;
}
%input{
padding: 5px 10px;
}
}
;
/**
* Component render
*/
lambda html render(LayoutModel layout, IndexPageModel model, Dict params, html content) =>
<div @class='content' @key='content'>
<div @class='label'>@{ model.content }</div>
<input @class='input' @bind="content" </input>
<Button @event:MouseClickEvent='onMouseClick'>Click Me!</Button>
</div>
;
/**
* Mouse click event
*/
void onMouseClick(MouseClickEvent e)
{
e.cancel();
this.updateModel
{
"content": this.model.content ~ "!",
};
}
}