Skip to main content

Кастомизация

Кастомизация - это способ расширить UI возможности N2O через создание собственной темы стилей или визуальных компонентов.

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

Настройка окружения

Для кастомизации вам потребуется установить следующее программное обеспечение:

Создание React проекта

Перед тем как подключить кастомизацию необходимо создать React проект c N2O Framework.

Сборка React проекта выполняется менеджером пакетов yarn.

В корне N2O проекта выполните команду:

npm init react-app frontend

Эта команда создаст папку frontend, в ней создаст необходимые для начала работы файлы и папки, включая index.js.

Перейдите в папку /frontend:

cd frontend

Затем установите библиотеку n2o-framework:

yarn add n2o-framework

Далее необходимо удалить лишние файлы, созданные после инициализации. Удалите файлы App.css, App.js, App.test.js, logo.svg, reportWebVitals.js.

Отредактируйте файл /frontend/src/index.js. Он должен выглядеть следующим образом:

import React from 'react';
import ReactDOM from 'react-dom';
import N2O from 'n2o-framework';
import createFactoryConfig from "n2o-framework/lib/core/factory/createFactoryConfig";
import authProvider from "n2o-framework/lib/core/auth/authProvider";

import 'n2o-framework/dist/n2o.css';
import './index.css';

const config = {
security: {
authProvider,
externalLoginUrl: '/'
}
};

ReactDOM.render(
<React.StrictMode>
<N2O {...createFactoryConfig(config)} />
</React.StrictMode>,
document.getElementById('root')
);

Теперь N2O React проект готов.

Для сборки проекта в папке /frontend выполните команду:

yarn

А затем:

yarn build

В результате в папке /frontend/build появится index.html и другие статические ресурсы.

Чтобы подключить статические ресурсы к N2O серверу, в файле pom.xml в разделе <plugins> добавьте maven-resources-plugin:

<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>Copy frontend build</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/classes/public</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>${project.basedir}/frontend/build</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>

Теперь N2O сервер будет раздавать статические ресурсы собранные в React проекте в папке /frontend/build.

Подключение кастомизаций

Кастомизации передаются в N2O с помощью конфигурационного объекта:

note

Минимальный вид конфигурационного объекта, с обязательным свойством security

const config = {
// ... кастомизации
security: {
authProvider,
externalLoginUrl: './',
},
}
frontend/src/index.js
import React from "react";
import N2O from "n2o-framework";
import authProvider from "n2o-framework/lib/core/auth/authProvider";
import createFactoryConfig from "n2o-framework/lib/core/factory/createFactoryConfig";

const config = {
// ... кастомизации
security: {
authProvider,
externalLoginUrl: './',
},
}

ReactDOM.render(
<React.StrictMode>
<N2O {...createFactoryConfig(config)} />
</React.StrictMode>,
document.getElementById('root')
);

Конфигурационный объект содержит наборы компонентов, разбитые по типам кастомизации:

const config = {
controls,
widgets,
regions,
pages,
headers,
cells,
fieldsets,
fields,
actions,
snippets,
buttons,
templates: {
header,
footer,
sidebar,
},
security
};
note

Для кастомизации header footer sidebar необходимо в свойство templates конфигурационного объекта, передать кастомные компоненты.

Пример описывающий как реализовать данную возможность:

Пример кастомного Header frontend/src/components/plugins/CustomHeader.js
import SimpleHeader from 'n2o-framework/lib/plugins/Header/SimpleHeader/SimpleHeader'
export function CustomHeader(config) {
return (
<SimpleHeader { ...config } />
)
}
Пример кастомного Footer frontend/src/components/plugins/CustomFooter.js
import Footer from 'n2o-framework/lib/plugins/Footer/Footer'
export function CustomFooter(config) {
return (
<Footer { ...config } />
)
}
Пример кастомного Sidebar frontend/src/components/plugins/CustomSidebar.js
import SideBar from 'n2o-framework/lib/plugins/SideBar/SideBar'
export function CustomSidebar(config) {
return (
<SideBar { ...config } />
)
}

Имена переданных в N2O компонентов header footer sidebar задаются с помощью атрибута src в application.xml

Пример application.xml
<application xmlns="http://n2oapp.net/framework/config/schema/application-3.0"
welcome-page-id="index">
<header src="CustomHeader" />
<sidebar src="CustomSidebar" />
<footer src="CustomFooter" />
</application>

Передача кастомных компонентов в свойство templates конфигурационного объекта

frontend/src/index.js
import CustomHeader from "./components/plugins/CustomHeader";
import CustomSidebar from "./components/plugins/CustomSidebar";
import CustomFooter from "./components/plugins/CustomFooter";

const config = {
// ... кастомизации
templates: {
CustomHeader,
CustomSidebar,
CustomFooter,
},
security
}

ReactDOM.render(
<React.StrictMode>
<N2O {...createFactoryConfig(config)} />
</React.StrictMode>,
document.getElementById('root')
);
note

Хорошей практикой будет создать папку components в frontend/src для размещения в ней всех ваших компонентов. В папке components разделить компоненты по типам кастомизаций, например: /pages, /widgets, /cells. В каждой из этих папок будут находиться:

  • папки с кастомными компонентами
  • файл index.js, в котором будут экспортироваться эти компоненты
frontend/src/components/pages/index.js
import HelloPage from './HelloPage/HelloPage.js';

export default {
HelloPage
};

В config объект передать импортированные наборы компонентов:

frontend/src/index.js
import pages from './components/pages';

const config = {
pages,
security
}

Переданные в N2O компоненты можно использовать в конфигурациях с помощью атрибута src:

Подключение кастомного компонента
<?xml version='1.0' encoding='UTF-8'?>
<simple-page xmlns="http://n2oapp.net/framework/config/schema/page-4.0"
src="HelloPage">
...
</simple-page>

Отладка

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

Проксирование запросов на сервер N2O осуществляется с помощью библиотеки http-proxy-middleware. Для подключения http-proxy-middleware к проекту выполните команду:

yarn add http-proxy-middleware --dev

URL целевого сервера указывается в файле setupProxy.js. Создайте его в папке src и укажите цель для проксирования:

frontend/src/setupProxy.js
const {createProxyMiddleware} = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
'/n2o',
createProxyMiddleware({
// URL целевого сервера (локальный или удаленный)
target: "http://localhost:8080",
changeOrigin: true
})
);
};

Запуск приложения выполняется командой:

yarn start

Приложение откроется по адресу http://localhost:3000.

Кастомизация стилей

Кастомизация стилей предназначена для изменения общего внешнего оформления приложения в соответствии с вашими пожеланиями. Кастомизация стилей заключается в переопределении стилей стандартных css классов и css-переменных N2O и Bootstrap на свои собственные.

Стандартные стили N2O находятся в папке frontend/node_modules/n2o-framework/src/sass:

frontend/node_modules/n2o-framework/src/sass
/n2o             // папка со основными стилями N2O
/theme // папка со стилями темы N2O
bootstrap.scss // корневой файл всех стилей, импортированных из библиотеки bootstrap
n2o.scss // корневой файл всех стилей N2O
variables.scss // корневой файл всех css-переменных N2O

Сборка стилей N2O выполняется с помощью библиотеки sass. Для ее подключения выполните команду:

yarn add sass --dev

После этого файл index.css, расположенный в папке src проекта, необходимо переименовать в index.scss.

Чтобы передать управление стилями проекту, необходимо отредактировать директивы import в файле index.js:

src/index.js
//import 'n2o-framework/dist/n2o.css';  // эту директиву необходимо удалить
import './index.scss'; // файл index.css необходимо переименовать в index.scss
...

Файл index.scss является корневым файлом стилей вашего проекта, поэтому все файлы стилей должны подключаться в него. Первым должен быть подключен корневой файл стилей N2O:

frontend/src/index.scss
@import "n2o-framework/src/sass/n2o.scss";
...

Теперь необходимо скопировать в ваш проект файлы со стилями и css-переменными, которые вы будете переопределять, из frontend/node_modules/n2o-framework/src/sass.

note

Хорошей практикой считается хранение файлов стилей проекта в отдельной папке, например /sass. Рекомендуется сохранять такие же названия папкам, которые вы будете копировать из n2o-framework.

Для подключения измененных стилей укажите относительный путь до файла с изменениями в index.scss:

frontend/src/index.scss
// стандартные стили N2O
@import "n2o-framework/src/sass/n2o.scss";

// файлы общих стилей вашего проекта
@import "./sass/variables.scss";
@import "./sass/theme/variables";
...
// файлы стилей компонентов вашего проекта
@import "./sass/someComponent.scss";
@import "./sass/anotherComponent.scss"
note

При подключении файлов стилей к проекту решающее значение имеет очередность подключения. Следует строго придерживаться следующего порядка:

  1. файл стилей N2O
  2. файлы с css-переменными вашего проекта
  3. файлы стилей компонентов, контролов, темы вашего проекта

Если запустить проект в режиме разработки, все изменения в файлах стилей будут подхватываться и сразу отображаться.

Кастомное действие

Кастомное действие позволяет расширить логику работы с n2o store. Для вызова с сервера, в реакт проекте не требуется дополнительных действий. Обработка кастомного действия зависит от бизнес-требования.

Если необходимо расширить n2o store новыми ключами данных, следует написать reducer.

note

Хорошей практикой будет создать папку reducers в frontend/src для размещения в ней всех ваших reducers. Импорт в config вашего App (app/src/App.jsx) производить из общего файла импорта index.js

Ключ в config должен быть customReducers

index.js
import { customReducer1 } from './reducers/custom1/store'
import { customReducer2 } from './reducers/custom2/store'

export const customReducers = { custom1: customReducer, custom2: customReducer2 }

Если нет необходимости расширять n2o store, достаточно реализовать saga effect для манипуляции на кастомным действием.

note

Хорошей практикой будет создать папку sagas в frontend/src для размещения в ней всех ваших sagas. Импорт в config вашего App (app/src/App.jsx) производить из общего файла импорта index.js

Ключ в config должен быть customSagas

index.js
import { saga } from './sagas/action1/saga1'
import { saga2 } from './sagas/action2/saga2'

export const customSagas = [
saga,
saga2,
]

Импорт в n2o config

app/src/App.jsx
const config = createFactoryConfig({
// ... кастомизации
customReducers,
customSagas,
})

ReactDOM.render(
<React.StrictMode>
<N2O {...config} />
</React.StrictMode>,
document.getElementById('root')
);

Для расширения существующих объектов данных из n2o store необходимо использовать функцию

export const reducersManager = (state, action, n2oSlice, customSlice) => {
const { type } = action
const { actions = {}, name } = customSlice

const customActions = Object.keys(actions).map(action => `${name}/${action}`)

if (customActions.includes(type)) {
return customSlice.reducer(state, action)
}

return n2oSlice(state, action)
}
note

Не является безопасной практикой. Мы не можем отвечать за работоспособность функционала при изменении данных из n2o-framework store. Рекомендуется только добавление новых ключей данных, а не мутации уже существующих.

Пример. Чтобы расширить объект page, выполняем импорт pages reducers из n2o-framework. Создаем slice используя name из n2o (префикс n2o/ + reducerName) Применяем reducersManager передав в него нужные данные.

note

Список reducers можно увидеть по пути n2o-framework/lib/reducers

import pages from 'n2o-framework/lib/ducks/pages/store'

const pagesSlice = createSlice({
name: 'n2o/pages',
initialState: {},
reducers: {
// ... ваша логика
}
})

export const pagesReducers = (state, action) => reducersManager(state, action, pages, pagesSlice)

При экспорте, в customReducers, необходимо использовать ключ reducer из n2o-framework.

export const customReducers = { pages: pagesReducers }
note

Клиентские действия реализуются идентично сервер/клиент действиям.

Для конфигурации кастомного действия в xml используется элемент <action>. В данном теге необходимо указать Redux тип действия, а также можно настроить поведение приложения после выполнения действия. Полезная нагрузка Redux действия конфигурируется с помощью дочернего для <action> элемента <payload>. Тег <payload> поддерживает возможность использования любых атрибутов, а также возможность преобразованием в json-объекты атрибуты, использующие "дефис" в именовании.

xml конфигурация кастомного действия
<action type="n2o/actionImpl/START_INVOKE"
redirect-url="/test" redirect-target="application">
<payload dataProvider-url="n2o/data/mi2"
dataProvider-method="POST"
datasource="_main"
model="resolve"
pageId="_"
widgetId="_main"/>
</action>
json, полученный в результате компиляции
"action": {
"type": "n2o/actionImpl/START_INVOKE",
"payload": {
"datasource": "_main",
"widgetId": "_main",
"model": "resolve",
"dataProvider": {
"method": "POST",
"url": "n2o/data/mi2"
},
"pageId": "_"
},
"meta": {
"success": {
"refresh": {
"datasources": [
"_main"
]
},
"redirect": {
"path": "/test",
"pathMapping": {},
"queryMapping": {},
"target": "application"
}
},
"fail": {}
}
}

Смена favicon и других статичных элементов

Чтобы поменять иконку приложения favicon, необходимо:

  1. Написать реализацию StaticCustomizationController
  2. Указать путь расположения новой иконки, реализовав метод faviconPath()

Пример:

@Controller
public class MyFaviconController extends StaticCustomizationController {

@Value("${n2o.favicon.path}")
private String path;

@Override
protected String faviconPath() {
return path;
}
}

Также с помощью реализации StaticCustomizationController можно переопределить получение других статичных элементов.