«Friendly Open Space» — очень молодой фреймворк, но бегать уже умеет :-)
В данной статье по освоению «Friendly Open Space», мы освоим рендеринг шаблона в браузере и запуск приложения на локальной файловой БД.
Ядро фреймворка поддерживает два типа сборки шаблона на клиенте:
- Рендеринг полностью выполняется на стороне клиента
- Выполняется запрос на рендеринг шаблона серверу, с последующим выводом его в окне браузера.
Первый режим имеет одну особенность, данные необходимые для отрисовки шаблона запрашиваются у сервера, т.е. клиент выполняет FSQL запросы. Особой опасности в этом нет, если вы используете ограничение доступа и модуль fosAccess, но есть возможность выгрузки сырых данных копипастерами. Однако, такой подход существенно снимает нагрузку на сервер.
Второй тип рендеринга уже лишен данной особенности. Клиент отправляет параметры шаблона и принимает уже от сервера HTML. Да, нагрузка на сервер конечно возрастет, но зато такой метод больше подходит для открытых интернет решений.
Подготовка приложения и его настройка
И так, перед нами стоит задача создать одностраничное приложение, которое будет рендерить 2-а шаблона (окошки) на стороне клиента. В виду достаточно простой задачи мы обойдемся без базы данных MySQL и проекций, поэтому всю работу FSQL мы направим в файл, т.е. будем использовать БД на файле для работы внутренних механизмов фреймворка. Ну, приступим.
Создадим каталоги:
templates — директория шаблонов
css — директория css файлов
fos — скачиваем последнюю beta friendlyopenspace.site/ru/download
Разместим надоедающий favicon.ico в корневом каталоге приложения: favicon.ico
Так же сразу разместим файлы стилей в каталоге css: window.css и styles.css
Далее, создаем файл самого приложения application.js:
var fos = require("./fos/fos");
fos.module({
name: "application.js",
dependencies: [
"fos:NServer/Application.js",
],
module: function(application) {
application.setSettings({
port: 3001,
packages: [],
dynamicLoading: true,
clientRenderingMode: "client",
dataClient: {
defaultConnection: "default",
connections: {
default: {
type: "file",
file: "data.fosdb"
}
},
},
onInitialize: function(a_event) {
if (!a_event.error) {
application.run();
} else {
console.error(a_event.error);
}
}
});
application.getRouter().add([
{
route: "",
controller: "fos:NServer/NControllers/Tmpl.js",
source: "templates/page.tmpl",
},
{
route: "/css/*",
controller: "fos:NServer/NControllers/File.js",
source: "css",
},
{
route: "/templates/*",
controller: "fos:NServer/NControllers/File.js",
source: "templates",
},
{
route: "favicon.ico",
controller: "fos:NServer/NControllers/File.js",
source: "favicon.ico",
},
]);
application.initialize();
}
});
Теперь давайте разберемся с содержимым файла application.js
Мы отключили все пакеты, за их ненадобностью (параметр packages метода setSettings):
...
application.setSettings({
port: 3001,
packages: [],
dynamicLoading: true,
clientRenderingMode: "client",
...
Новый для нас параметр приложения clientRenderingMode, отвечает за тип рендеринга на клиенте и имеет два значения:
«client» — рендеринг выполняется полностью средствами браузера. Клиент самостоятельно подгружает зависимости и выполняет FSQL запросы к серверу, после чего собирает сам HTML
«server» — клиент выполняет запрос к серверу на рендеринг шаблона и получает в качестве ответа готовый HTML
...
packages: [],
dynamicLoading: true,
clientRenderingMode: "client",
dataClient: {
defaultConnection: "default",
...
И последнее для нас новшество — это подключение базы данных на файле. Фреймворк не может полноценно работать без возможности обработки конфигурационных переменных, которые имеют табличную структуру. Поэтому, взамен MYSQL мы будем использовать обычный файл.
...
defaultConnection: "default",
connections: {
default: {
type: "file",
file: "data.fosdb"
}
},
},
...
Как видно из вышеприведенной выдержки, в этом случае тип базы данных (параметр type) равен «file», а единственный параметр соединения file должен содержать путь к файлу данных. Если файл отсутствует, приложение самостоятельно его создаст.
Создание шаблона страницы
Теперь пришло время создать файл шаблона страницы приложения templates/page.tmpl, который у нас прописан в корневом маршруте URL.
//~OPTIONS
{
args:{
fosInclude: ["css/styles.css"],
}
}
//~BLOCK main default
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
%{{ render.renderHTMLHeader(); }}%
<script>
function onClientSideRendering(a_event){
a_event.preventDefault();
fos.application.render({
template: "templates/window.tmpl",
owner: document.body,
args: { title: "Client side rendering", context: "Simple example of client side rendering" },
onResult: function(a_error, a_template){
}
});
}
function onServerSideRendering(a_event){
a_event.preventDefault();
fos.application.render({
template: "templates/window.tmpl",
owner: document.body,
renderMode: "server",
args: { title: "Server side rendering", context: "Simple example of server side rendering" },
onResult: function(a_error, a_template){
}
});
}
</script>
</head>
<body>
<div class="mainwrapper">
<div class="header markup">
Client Side Rendering
</div>
<div class="body-wrapper markup">
<div class="body markup">
<p class="example-link--container"><a onclick="onClientSideRendering(event);">Client side rendering</a></p>
<p class="example-link--container"><a onclick="onServerSideRendering(event);">Server side rendering</a></p>
</div>
<div class="clear-body"></div>
</div>
</div>
</body>
</html>
Шаблон страницы имеет две ссылки «Client side rendering» & «Server side rendering». При нажатии на которые будут отображаться окна, которых пока нет. Но есть код их вызова. И так давайте разберемся с кодом шаблона.
При нажатии на ссылку «Client side rendering» мы вызываем функцию onClientSideRendering(), которая выполняет рендеринг шаблона «templates/window.tmpl». Полностью на стороне клиента, т.к. в настройках приложения параметр clientRenderingMode был установлен в значение «client». Не смотря на то, что это значение по умолчанию :-).
function onClientSideRendering(a_event){
a_event.preventDefault();
fos.application.render({
template: "templates/window.tmpl",
owner: document.body,
args: { title: "Client side rendering", context: "Simple example of client side rendering" },
onResult: function(a_error, a_template){
}
});
}
Собственно метод render и выполняет рендеринг нашего окна и помещает его в тело страницы, как указано в свойстве аргумента owner. В шаблон окна мы передаем 2-а аргумента: title & context, собственно то, что будет отображаться в выводимом окошке. Более подробную информацию о методе см. fos::NClient::Application::render
Вторая ссылка вызывает функцию onServerSideRendering(), которая аналогично рендерит тот же шаблон, но на стороне сервера, а клиент получает уже готовый HTML. Данный режим задается в свойстве аргумента renderMode метода render значением «server».
function onServerSideRendering(a_event){
a_event.preventDefault();
fos.application.render({
template: "templates/window.tmpl",
owner: document.body,
renderMode: "server",
args: { title: "Server side rendering", context: "Simple example of server side rendering" },
onResult: function(a_error, a_template){
}
});
}
Создание шаблона всплывающего окна и написание враппера шаблона
Сам шаблон всплывающего окошка очень прост. Создайте файл templates/window.tmpl. Ниже приведено его содержимое.
//~OPTIONS
{
args:{
fosWrapper: true,
fosClass: "window",
fosInclude: ["css/window.css"],
title: "",
context: "",
}
}
//~BLOCK main default
<div class="window-container">
<div class="window-close" name="close">x</div>
<div class="window-title">${{args.title}}$</div>
<div class="window-context">${{args.context}}$</div>
</div>
Здесь для вас есть два новых параметра fosWrapper и fosClass.
Начнем с fosWrapper. Если данный флаг установлен в true, то HTML шаблон помещается в контейнер span и для него создается объект обертки fos::NRender::Wrapper.
Запустите приложение, перейдите по адресу localhost:3001 и выполните клик по ссылке «Client side rendering». Всплывающие окошко и есть наш шаблон. Всплытие окна реализовано полностью средствами css (файл css/window.css) — это я просто упомянул, что бы вы не искали скрытый JS :-).
Откройте DevTools браузера (Ctrl+Alt+i), перейдите на вкладку Elements и рассмотрите структуру нашего окошка.

Синей линией на рисунке помечен наш оберточный контейнер span c которым связан объект fos::NRender::Wrapper.
Следующий системный аргумент шаблона — fosClass. Он просто добавляет CSS класс к span контейнеру враппера.
И так, все сделано хорошо, но если мы попробуем закрыть наше всплывающие окошко, то ничего у нас из этого не выйдет. Да, операцию закрытия мы еще не писали!
Как мы говорили ранее, если системный аргумент fosWrapper равен true, то для шаблона создается объект враппера fos::NRender::Wrapper. Он предоставляет штатный интерфейс взаимодействия с шаблоном на клиенте. Что бы переопределить штатный враппер для шаблона достаточно создать модуль с именем соответствующему следующему формату [НАИМЕНОВАНИЕ_ШАБЛОНА].wrapper.js, при этом модуль должен быть унаследован от класса fos::NRender::Wrapper.
Теперь создадим файл врапера для шаблона templates/window.tmpl. Новый класс должен будет закрывать наше всплывающие окошко, при нажатии на символ «x».
Файл templates/window.wrapper.js:
fos.module({
name: "templates/window.wrapper.js",
dependencies: ["fos:NRender/Wrapper.js"],
module: function(Wrapper){
return function(a_initializeOptions) {
var self = this;
Wrapper.call(this, a_initializeOptions);
var parentAttach = this._attach;
this._attach = function(a_cb) {
parentAttach.call(this, function(){
fos.addDomListener(fos.select(self.getDomElement(), "[name=close]")[0], "click", function(){
self.getDomElement().classList.add("window-hide");
setTimeout(function() { self.destroy(); }, 2000);
});
a_cb();
});
}
}
}
});
Разберем содержимое нашего модуля. Сначала мы подключаем базовый класс fos::NRender::Wrapper и наследуемся от него через методом call.
fos.module({
name: "templates/window.wrapper.js",
dependencies: ["fos:NRender/Wrapper.js"],
module: function(Wrapper){
return function(a_initializeOptions) {
var self = this;
Wrapper.call(this, a_initializeOptions);
...
После переопределяем метод fos::NRender::Wrapper::_attach, который вызывается при связывании шаблона с объектом. В данном методе мы будем подключать событие нажатия на ссылку закрытия окна. Переопределение выполняется просто объявлением метода, но в начале мы должны сохранить ссылку на родительскую реализацию. Метод имеет «асинхронную природу», поэтому реализацию наших действий мы помещаем в функцию обратного вызова родительской реализации.
...
var parentAttach = this._attach;
this._attach = function(a_cb) {
parentAttach.call(this, function(){
fos.addDomListener(fos.select(self.getDomElement(), "[name=close]")[0], "click", function(){
self.getDomElement().classList.add("window-hide");
setTimeout(function() { self.destroy(); }, 2000);
});
a_cb();
});
}
...
Подключение к событию нажатия на символ «x» мы выполняем вызовом fos::addDomListener, который не сбрасывает подключенные события при смене родителя DOM элемента.
В обработчике события мы и выполняем закрытие окна. Вначале красиво убираем окошко с экрана, добавлением CSS класса «window-hide». После завершения анимации вызываем метод fos::NRender::Wrapper::destroy, который удаляет шаблон и объект враппера.
ВСЁ ПРИЛОЖЕНИЕ НАПИСАНО, ЗАПУСКАЕМ И РАДУЕМСЯ!!!
node application.js
Официальный сайт разработки
Ссылка на пример
Комментарии (9)
erlyeagle Автор
25.10.2019 21:41Ну это микро баги, подправим спасибо, бывают и опечатки по ночам. Над TypeScript даже пока не думал, на все это нужно время. Большое спасибо за советы!
staticlab
Почему стиль кода выглядит как привет из 2005? Не используются ни возможности возможности новых версий JS, ни даже модули Node.js. Да и с клиентской стороны ужас-ужас: прямая манипуляция DOM. В чём удобство и эффективность по сравнению с современными клиентскими и серверными фреймворками на JS?
Carduelis
Да, там и само ядро описано в 2005 стиле. Своя реализация require.js, сплошные
var
даfor in
.erlyeagle Автор
Это учебный пример, поэтому на стили пока, сильно внимания не обращаю. Новые возможности JS, не используются из-за повышенной совместимости (в скором будущем), просто опыт показал у некоторых заказчиков бывают требования на IE. Основная задача — это доступ к данным из шаблона через проекцию БД и структуризация сложных архитектур проектов, правда данная тематика в данном примере не раскрыта. Но спасибо будем лучше читать и улучшать.
Carduelis
Но… ведь есть babel, который следует не просто хотелкам, а настоящему и активно развивающемуся стандарту.
Более того, код самой библиотеки имеет смысл писать на TypeScript. Ведь библиотека должна быть пуленепробиваемой, а типизация очень хорошо помогает самим авторам, да еще и разработчикам, которые случайно не ту переменную передают.
А у вас проверка на типы реализована еще и слабо:
А если передать туда
null
, который тожеtypeof null === 'object'
, все сломается, да?staticlab
GraphQL?
erlyeagle Автор
Нет, своя разработка. Для выборки из нескольких таблиц, формирования списков, деревьев в рамках одного запроса и одной записи результата запроса, для облегчения работы с БД. Многие вещи еще даже не описаны. В планах — это полноценная проекция на несколько таблиц БД, с возможностью их модификации, а обращение происходит по FSQL (свой язык, очень похожий на SQL, но с отличиями). Для удобства выборки из GUI. На базе данных проекций формируются не только запросы, но и элементы GUI.
staticlab
Вот и я говорю, что вместо общепринятого проверенного решения вы соорудили собственный велосипед.
erlyeagle Автор
Велик, не велик, ездить учится, а потом и реактивный двигатель поставим. Вещь которая в задумке мне нравится, все из коробки сел и полетел. Ладно, все напишу, самому приятно будет использовать. Потом, мир прекрасен в своем многообразии, цветов и оттенков! Плюс FSQL почти тот-же SQL, более привычен и короче в написании.