Form Flow
Терминология
- Приложение - фронтенд-приложение на базе фреймворка Angular, Redux или другого.
- Страницы Приложения - страницы, между которыми выполняется навигация в Приложении и которые имеют предопределённый маршрут.
- Сервис Form Flow - вспомогательный класс, реализующий механизм Form Flow.
- Хранилище - единое хранилище данных Приложения, хранящее всё его состояние и имеющее один экземпляр.
Архитектура Form Flow
Механизм Form Flow реализуется посредством Сервиса Form Flow, осуществляющего 2 типа взаимодействия между Страницами Приложения:
- Навигация между Страницами приложения согласно предопределённому алгоритму;
- Предоставление Страницам данных из Хранилища и сохранение их в Хранилище.
В качестве хранилища используется Redux Store.
Хранилище взаимодействует с внешними источниками (например, хранилище браузера или REST API), то есть:
- Получает данные из источников при инициализации хранилища;
- Сохраняет их в источниках в какой-то момент времени.
Цепочки Шагов
Механизм Form Flow осуществляется за счёт навигации между Страницами Приложения на основе предопределённых правил в рамках определённого процесса. Набор таких Страниц в совокупности с правилами навигации в рамках процесса называется Цепочкой Шагов, одна страница такого набора называется Шагом бизнес-процесса.
Конфигурация Form Flow
Механизм Form Flow осуществляется на основе конфигурации, заданной в формате JSON, пример которой представлен ниже:
processes: { // объект, описывающий бизнес-процессы
openDeposit: { // ключ - системное название бизнес-процесса
title: 'Процесс: Создание депозитного продукта', // заголовок бизнес-процесса
steps: { // каждый элемент - объект, описывающий Шаг бизнес-процесса
openDepositForm: { // системное название Шага
title: 'Депозитный продукт: кастомный заголовок', // заголовок бизнес-процесса
route: 'new-deposit', // маршрут Страницы, соответствующей этому Шагу
onNext: ['validate'], // какие функции выполнить при переходе на следующий Шаг
onCancel: ['abort'], // какие функции выполнить при отмене Шага,
transitions: { // объект, описывающий, на какие Шаги нужно перейти по заданному действию (ключ - действие, значение - имя Шага)
next: 'reportingForm', // следующий Шаг в массиве steps
prev: 'home', // предыдущий Шаг - home - "системный" Шаг, описанный в объекте systemSteps
cancel: 'home' // при отмене заполнения формы будет сделан переход на системный Шаг home
},
data: { // начальные данные, которые будут переданы компоненту Шага
form: { // форма Шага получит эти данные для инициализации полей
signedByStaff: 'ДАВИДЕНКО НАТАЛЬЯ КОНСТАНТИНОВНА',
isEnterPromoCode: false
},
user: 'vasya' // будет использовано компонентом Шага в своих целях
}
},
reportingForm: {
title: 'Печать отчётов: кастомный заголовок',
type: 'form',
route: 'report-printing',
onNext: ['validate'],
onCancel: ['abort'],
transitions: {
next: 'home',
prev: 'openDepositForm',
cancel: 'home'
},
data: {
form: {
signedByStaff: 'МЕРЗЛИКИН ИЛЬЯ АЛЕКСАНДРОВИЧ',
isEnterPromoCode: false
},
user: 'vasya'
}
}
}
}
}
Использование в приложении (Angular)
При использовании в Angular-приложении рекомендуется класть конфиг в ts-файл, чтобы иметь возможность типизации и автодополнения. Пример файла приведён в блоке ниже.
import {FormFlowConfig} from '@diasoft/qpalette-form-flow';
const baseUrl = '/development/libraries/form-flow';
export const formFlowConfig: FormFlowConfig = {
processes: { // объект, описывающий бизнес-процессы
openDeposit: { // ключ - системное название бизнес-процесса
title: 'Процесс: Создание депозитного продукта', // заголовок бизнес-процесса
startFrom: 'openDepositForm',
defaultRoute: baseUrl,
systemRoutes: {
home: baseUrl
},
steps: { // каждый элемент - объект, описывающий Шаг бизнес-процесса
openDepositForm: { // системное название Шага
title: 'Депозитный продукт: кастомный заголовок', // заголовок бизнес-процесса
route: baseUrl + '/new-deposit', // маршрут Страницы, соответствующей этому Шагу
onNext: ['validate'], // какие функции выполнить при переходе на следующий Шаг
onPrev: [],
onCancel: ['abort'], // какие функции выполнить при отмене Шага,
transitions: { // объект, описывающий, на какие Шаги нужно перейти по заданному действию (ключ - действие, значение - имя Шага)
prev: 'home',
next: 'reportingForm', // следующий Шаг в массиве steps
cancel: 'home',
}
},
reportingForm: {
title: 'Печать отчётов: кастомный заголовок',
route: baseUrl + '/report-printing',
onNext: ['validate'],
onCancel: ['abort'],
transitions: {
prev: 'openDepositForm',
next: 'home',
cancel: 'home',
}
}
}
}
}
};
Для использования движка в компоненте необходимо получить экземпляр FormFlowService и вызвать у него метод attachProcess, передав в него название нужного бизнес-процесса:
formGroup = this.formBuilder.group(...)
constructor(
private formBuilder: FormBuilder,
private formFlowService: FormFlowService
) {
this.processManager = this.formFlowService.attachProcess('openDeposit', {
form: {
signedByStaff: 'ДАВИДЕНКО НАТАЛЬЯ КОНСТАНТИНОВНА',
isEnterPromoCode: false
}
}, this.formGroup);
this.processManager.process$.subscribe(process => {
console.log('Данные шага reportingForm процесса openDeposit: ', process.steps['reportingForm'].data);
});
this.processManager.currentStepData$.subscribe(stepData => {
this.formGroup.patchValue(stepData.data.form ?? {});
});
}
Если бизнес-процесс с таким названием уже стартовал ранее, он будет запущен с того Шага, на котором он был приостановлен в предыдущий раз. В противном случае, будет стартован новый бизнес-процесс с первого Шага.
Вторым параметром вattachProcess()
можно передать начальные данные процесса, если это необходимо. В дальнейшем их можно будет получить из поляcurrentStepData$
экземпляра диспетчера процессовFormFlowProcessManager
(см. выше пример работы с экземпляром). Данные передаются в виде объекта, например:
{
form: {
signedByStaff: 'ДАВИДЕНКО НАТАЛЬЯ КОНСТАНТИНОВНА',
isEnterPromoCode: false
}
}
При этом если заполнено полеform
, его поля будут использованы для инициализации формы, привязанной процессу (см. ниже описание третьего параметраattachProcess()
).
Третьим параметром вattachProcess()
передаётся форма Angular (объект типаFormGroup
), которую нужно "привязать" к этому шагу процесса - она будет проинициализирована данными из второго параметраattachProcess()
и будет провалидирована в случае наличия функции'validate'
в конфигурацииonNext()|onPrev()
шага процесса.
МетодattachProcess()
вернёт экземпляр диспетчера процессовFormFlowProcessManager
, с которым можно работать для управления текущим бизнес-процессом.
У экземпляра диспетчера процессов есть полеprocess$
, подписавшись на которое, можно получить данные всего процесса. Структура данных следующая:
{
name: string;
currentStep: string;
steps: {
[key: string]: FormFlowProcessStep;
};
}
name
- Имя процесса.currentStep
- Имя текущего шага.steps
- Данные всех шагов процесса в виде объекта, в котором ключ - это имя шага процесса, значение - данные шага. Формат данных шага соответствует структуреFormFlowProcessStep
, описанной выше.
Например, как видно в примере выше, можно подписаться на поле диспетчераprocess$
и получить из него данные конкретного шага.
СтруктураFormFlowProcessStep
следующая
{
data: FormFlowProcessStepData;
previousStep?: string;
previousUrl?: string;
nextStep?: string;
urlParams?: FormFlowStepUrlParams;
}
data
- Данные шага процесса, представленные типомFormFlowProcessStepData
, описание ниже.previousStep
- Имя предыдущего шага (если есть).previousUrl
- URL-адрес предыдущего шага (если есть).nextStep
- Имя следующего шага (если есть).urlParams
- GET-параметры URL-адреса текущего шага (если они были переданы в один из методов навигации диспетчераFormFlowProcessManager
, описание методов см. ниже).
Например, как видно в примере выше, можно подписаться на поле диспетчераcurrentStepData$
и воспользоваться им для инициализации данных формы текущего шага. В данном случае, поскольку в полеdata.form
лежат данные формы (как видно из рассмотренной ранее конфигурации), мы можем ими проинициализировать форму компонента, представленную свойствомformGroup
.
Помимо этого, у диспетчера можно вызвать следующие методы:
this.processManager.nextStep(data?: any, stepUrlParams?: FormFlowStepUrlParams); // переход на следующий шаг
this.processManager.prevStep(data?: any, stepUrlParams?: FormFlowStepUrlParams); // переход на предыдущий шаг
this.processManager.cancelStep(data?: any, stepUrlParams?: FormFlowStepUrlParams); // отменить текущий шаг
this.processManager.navigate(step: string, data?: any, stepUrlParams?: FormFlowStepUrlParams); // перейти на произвольный шаг с заданным именем
this.processManager.validateStep(); // выполнить валидацию формы
Первые 4 метода - методы навигации между шагами. В каждый из них можно передать параметры (все необязательные):
data
- Объект, содержащий данные для следующего шага. В случае передачи параметра, объект будет использован для инициализации данных следующего шага. Это значит, что- В случае наличия объекта в поле
data
шага в конфигурации процесса - его поля будут перезатёрты полями из переданного объекта в случае совпадения ключей. - Итоговый объект можно будет получить после навигации из диспетчера процессов (см. примеры получения через
process$
иcurrentStepData$
выше). - В случае наличия в переданном объекте поля
form
оно будет использовано для инициализации формы шага, на который осуществляется переход (см. описание второго параметра методаattachProcess()
выше).
- В случае наличия объекта в поле
stepUrlParams
- GET-параметры URL-адреса шага, на который осуществляется переход. Их можно будет получить из данных шага через диспетчерFormFlowProcessManager
, см. описание поляurlParams
объектаFormFlowProcessStep
выше.
Помимо перечисленных параметров методnavigate()
принимает параметрstep
- имя шага, на который осуществляется переход.
Например, в рассматриваемом выше примере:
- Вызов метода
nextStep()
приведёт к валидации формы (onNext: ['validate']
в конфигурации) и открытию шага с именемreportingForm
с маршрутом['report-printing']
(transitions.next
в конфигурации). - Вызов метода
cancelStep()
приведёт к остановке бизнес-процесса (onCancel: ['abort']
в конфигурации) и переходу на системный шаг с именемhome
с маршрутом '/' (systemSteps.home
в конфигурации). - Вызов метода
validateStep()
приведёт к валидации формы, привязанной к этому шагу.
Алгоритм переходов по Цепочке Шагов
Алгоритм работы метода attachProcess()
Метод запускает процесс с заданным именем, если он не был ранее запущен, либо выполняет навигацию на текущий шаг процесса, сохранённый в Хранилище, в противном случае, и восстанавливает данные шага из Хранилища.
Вызывайте метод в компоненте, который реализует первый шаг какого-либо процесса.
Алгоритм работы метода представлен ниже:
Этап 1. Получение метаданных процесса.
Если процесс не найден в метаданных, бросается ошибка.
Этап 2. Определение текущего шага процесса:
- Если процесс уже проинициализирован в хранилище, текущий шаг берётся из поля
currentStep
процесса (последний сохранённый в хранилище шаг). - Иначе текущий шаг берётся из поля
startFrom
метаданных процесса. - В случае, если шаг не удалось определить, бросается ошибка.
Этап 3. Получение метаданных текущего шага процесса.
Если метаданных нет, бросается ошибка.
Этап 4. Валидация текущего URL.
Если текущий URL-адрес не соответствует URL-адресу текущего шага процесса, определённому на этапе 2, (например, при попытке прямой навигации на произвольный шаг процесса), выводится сообщение об ошибке ("...Меняйте шаг средствами Form Flow вместо прямой навигации по маршруту...") и выполняется редирект на URL-адрес текущего шага процесса.
Этап 5. Инициализация шага процесса.
- Если процесс уже проинициализирован в хранилище и в
attachProcess
переданы новые данные шага, они будут сохранены в хранилище. - Если процесс не проинициализирован в хранилище, выполняется его инициализация, при этом:
- Параметр
currentStep
процесса в хранилище становится равным параметруstartFrom
метаданных процесса. - В параметре
steps[currentStep]
процесса в хранилище сохраняются параметры шага:- В
previousUrl
сохраняется предыдущий URL, сохранённый сервисом Form Flow (если есть). - В
data
сохраняются данные, переданные при вызовеattachProcess
.
- В
- Параметр
- Поле
currentProcess
(текущий активный процесс) в хранилище становится равным активированному процессу.
Алгоритм работы метода attachCurrentProcess()
Метод позволяет "присоединиться" к уже запущенному, текущему, процессу. Алгоритм работы - аналогичноattachProcess()
, за исключением того, что имя процесса передавать не нужно - оно будет взято из Хранилища.
Вызывайте метод в тех компонентах, которые реализуют один из следующих шагов уже запущенного процесса, а не запускают новый процесс.
Алгоритм работы секции transitions метаданных процесса
prev
Этот параметр задаёт предыдущий шаг процесса. Используется при:
- вызове метода
prevStep()
менеджера процесса; - вызове события "Назад" в браузере.
Если не указан, то:
- Если в хранилище сохранился предыдущий шаг, с которого пользователь пришёл на текущий - выполняется навигация не него.
- Иначе выполняется навигация на первый установленный URL из следующего списка:
- Предыдущий URL, сохранённый в хранилище, с которого пришёл пользователь;
- Предыдущий URL, заданный в параметре
defaultRoute
конфигурации процесса; - Корневой маршрут ('/').
next
Этот параметр указывает следующий шаг процесса