Перейти к содержанию

Поток стадий — горизонтальная полоса, которая показывает жизненный цикл объекта. Пройденные стадии залиты цветом текущей стадии, оставшиеся — цветом фона. Пользователь переводит объект на другую стадию нажатием на нее.

Поток размещают на странице отдельного объекта — заявки, документа, задания, — когда пользователю нужно видеть, где объект находится сейчас, и менять это одним нажатием. Набор стадий задают в коде при создании потока.

Расширение только отрисовывает переданные данные и сообщает о выборе пользователя. Текущую стадию оно не переставляет и запросы к серверу не отправляет: обработчик пишет разработчик и после ответа сервера сам вызывает метод setCurrentStageId().

Успешную и провальные стадии поток показывает одной стадией в конце полосы. По нажатию на нее расширение спрашивает результат завершения и передает выбранную стадию в обработчик.

В Bitrix Framework за поток стадий отвечает расширение ui.stageflow. Оно экспортирует объект StageFlow с двумя классами:

  • Chart — весь поток: хранит стадии, отрисовывает полосу и вызывает обработчик смены стадии.

  • Stage — одна стадия потока. Объекты этого класса создает сам поток, разработчик получает их в обработчике и через методы чтения.

Подключить расширение

Если вы подключаете поток из PHP, загрузите расширение ui.stageflow.

\Bitrix\Main\UI\Extension::load('ui.stageflow');

Вместе с расширением подключаются его зависимости: main.core, main.popup, ui.buttons и ui.fonts.opensans. Загружать их отдельно не нужно.

Если вы работаете в модульном JavaScript, импортируйте объект StageFlow из ui.stageflow.

import { StageFlow } from 'ui.stageflow';

Классы лежат внутри этого объекта: StageFlow.Chart и StageFlow.Stage. Вне модульного JavaScript тот же объект доступен как BX.UI.StageFlow.

Серверной части у расширения нет: ни класса, ни компонента, ни хелпера. Разметку контейнера и данные о стадиях готовит разработчик.

Собрать поток стадий

Чтобы показать поток на странице, выполните основные действия:

  1. Подготовьте контейнер и задайте ему высоту и позиционирование.

  2. Создайте экземпляр Chart. Первым аргументом передайте параметры потока, вторым — массив стадий.

  3. Вызовите метод render() и вставьте полученный узел в контейнер.

Пример. Шаблон страницы загружает расширение.

<?php

\Bitrix\Main\UI\Extension::load('ui.stageflow');

Пример. Разметка выводит пустой контейнер потока.

<div class="document-stageflow" id="document-stageflow"></div>

Пример. Стили задают контейнеру высоту и делают его точкой отсчета для потока.

.document-stageflow
{
    position: relative;
    height: 32px;
}

Внимание

Полосу стадий расширение растягивает по всем четырем сторонам ближайшего позиционированного предка. Задайте контейнеру свойство position: relative и высоту. Без них поток либо не виден, либо растягивается по всей странице.

Пример. Поток из пяти стадий документа выводится в узел document-stageflow, текущая стадия — «На проверке».

import { StageFlow } from 'ui.stageflow';

const stages = [
    { id: 1, name: 'Черновик', color: 'a8adb4' },
    { id: 2, name: 'На проверке', color: '47d1e2' },
    { id: 3, name: 'Утвержден', color: '7bd500', isSuccess: true },
    { id: 4, name: 'Отклонен', color: 'ff5752', isFail: true },
    { id: 5, name: 'Отозван', color: 'cb3e3e', isFail: true },
];

const chart = new StageFlow.Chart({
    backgroundColor: 'd3d7dc',
    currentStage: 2,
    isActive: true,
    onStageChange: (stage) => {
        console.log(stage.getName());
    },
}, stages);

document.getElementById('document-stageflow').appendChild(chart.render());

В полосе появятся три стадии: «Черновик», «На проверке» и стадия завершения. Успешную и две провальные стадии поток свернул в нее — как это работает, описано в разделе Показать финальную стадию.

Метод render() собирает разметку и возвращает корневой узел. При повторном вызове он очищает уже созданный контейнер и заполняет его заново, поэтому вставлять узел в страницу второй раз не нужно.

Если имя стадии не помещается в свою часть полосы, поток обрезает его многоточием, а при наведении расширяет стадию до полной ширины имени.

Передать параметры

Первый аргумент конструктора — параметры всего потока.

type ChartOptions = {
    backgroundColor: string;
    currentStage?: number | string;
    isActive?: boolean;
    onStageChange?: Function;
    labels?: Object;
};

|

|| Параметр | Тип | Описание || || backgroundColor | string | Задает цвет непройденных стадий. Расширение принимает только строку ровно из шести символов, остальные значения игнорирует. По умолчанию значение не задано || || currentStage | number | string | Задает идентификатор текущей стадии. Поток заливает цветом эту стадию и все предыдущие. Значение приводится к целому числу. По умолчанию 0 || || isActive | boolean | Разрешает пользователю менять стадию. Расширение принимает только логическое значение. По умолчанию false || || onStageChange | Function | Задает обработчик смены стадии. Расширение вызывает его и передает выбранную стадию. По умолчанию значение не задано || || labels | Object | Задает подписи стадии завершения и окон выбора результата. Переданные ключи расширение накладывает поверх значений по умолчанию, поэтому можно задать только часть подписей. По умолчанию подписи берутся из языкового файла расширения || |#

Параметр isActive управляет всем взаимодействием сразу. Со значением false поток не подсвечивает стадии при наведении, не расширяет их до полного имени и не реагирует на нажатия — получается полоса только для просмотра. Такое значение подходит для карточки нового объекта, который еще не сохранен, и для пользователя без прав на изменение.

Без параметра currentStage поток рисует все стадии цветом фона и заливку не показывает. Передавайте идентификатор текущей стадии всегда, даже если объект стоит на первой стадии.

Цвет текста на стадии расширение подбирает само по яркости заливки: на темной ставит белый, на светлой — темно-серый. Задать его нельзя.

Внимание

Цвет в параметре backgroundColor и в поле color каждой стадии задают шестизначным HEX-кодом без решетки, например d3d7dc. Значение с решеткой или другой длины в параметре backgroundColor расширение отбрасывает, и создание потока прерывается ошибкой. Решетка в поле color к ошибке не приведет, но стадия получит неверный цвет.

Описать стадию

Второй аргумент конструктора — массив стадий. Каждый элемент массива описывает одну стадию объекта.

|

|| Поле | Тип | Описание || || id | number | string | Задает идентификатор стадии. Значение приводится к целому числу и должно быть больше нуля. По этому идентификатору стадию находят методы чтения || || name | string | Задает название стадии. Поток показывает его в полосе и в окнах выбора результата || || color | string | Задает цвет стадии шестизначным HEX-кодом без решетки. Этим цветом поток заливает стадию, когда она пройдена || || isSuccess | boolean | Помечает стадию успешным завершением. Такая стадия в полосе не рисуется. По умолчанию false || || isFail | boolean | Помечает стадию провальным завершением. Такая стадия в полосе не рисуется. По умолчанию false || || isDisable | boolean | Помечает стадию заблокированной. Стадия становится полупрозрачной, курсор над ней показывает запрет, но нажатие по ней поток все равно передает в обработчик. По умолчанию false || |#

Стадии поток рисует в том порядке, в котором они идут в массиве. Сам он их не сортирует и поле с индексом сортировки не читает — выстраивайте массив в нужном порядке до создания потока.

У стадии есть семантика: она либо успешная, либо провальная, либо промежуточная. Успешная стадия в потоке одна, провальных может быть сколько угодно, остальные стадии — рабочие этапы.

Поля backgroundColor, fillingColor, isFilled и events расширение записывает в данные стадии само, передавать их не нужно.

Свои обработчики наведения и нажатия через поле events задать нельзя. Их переопределяют в наследнике класса Chart — смотрите раздел Запретить переход в стадию.

Внимание

Поля id, name и color обязательны для каждой стадии, а идентификатор должен быть больше нуля. Если у стадии нет хотя бы одного из этих полей, создание потока прерывается ошибкой.

Внимание

Задавайте признак isSuccess хотя бы одной стадии. Поток без успешной стадии либо прервет создание ошибкой, либо не откроет окно выбора результата: имя успешной стадии расширение подставляет и в стадию завершения, и в кнопку успешного результата.

Показать финальную стадию

Стадии с признаком isSuccess или isFail поток в полосе не рисует. Вместо них он добавляет в конец одну стадию завершения с подписью «Завершить», окрашенную в цвет успешной стадии.

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

Что видит пользователь при нажатии на стадию завершения, зависит от того, есть ли в потоке провальные стадии:

  • Провальных стадий нет. Поток сразу передает успешную стадию в обработчик, а вместо подписи «Завершить» в полосе стоит имя успешной стадии.

  • Провальные стадии есть. Поток открывает окно «Выберите результат завершения» с двумя кнопками. На первой стоит имя успешной стадии, на второй — имя единственной провальной стадии или подпись «Провал», если провальных стадий больше одной.

Кнопка успеха сразу передает успешную стадию в обработчик. Кнопка провала открывает второе окно.

Во втором окне пользователь подтверждает выбор кнопкой сохранения или отказывается от него кнопкой отмены. Список провальных стадий появляется в окне, только если их больше одной. При единственной провальной стадии выбирать не из чего, и окно сводится к подтверждению.

Переключатель в шапке второго окна открывает меню из двух пунктов — имени успешной стадии и результата провала. Через него пользователь меняет результат, не закрывая окно.

Кнопки окон расширение берет из ui.buttons, а сами окна — из main.popup.

Если объект уже стоит на успешной или провальной стадии, стадия завершения берет ее имя и цвет. Полоса при этом залита целиком.

Подписи стадии завершения и окон задают параметром labels.

Ключ Значение по умолчанию Где видит пользователь
finalStageName Завершить Подпись стадии завершения в полосе
finalStagePopupTitle Выберите результат завершения Заголовок окна выбора результата
finalStagePopupFail Провал Подпись кнопки провала, когда провальных стадий больше одной
finalStageSelectorTitle Завершение: Подпись переключателя результата в шапке окна выбора провальной стадии

Пример. Поток документа получает свои подписи вместо значений по умолчанию.

import { Loc } from 'main.core';
import { StageFlow } from 'ui.stageflow';

const chart = new StageFlow.Chart({
    backgroundColor: 'd3d7dc',
    currentStage: 2,
    isActive: true,
    labels: {
        finalStageName: Loc.getMessage('DOCUMENT_FINAL_STAGE_NAME'),
        finalStagePopupTitle: Loc.getMessage('DOCUMENT_FINAL_STAGE_POPUP_TITLE'),
    },
}, stages);

Внимание

Окна выбора результата расширение создает один раз на страницу с постоянными идентификаторами. Два потока на одной странице будут обращаться к одним и тем же окнам, и выбор результата попадет не в тот поток. Размещайте на странице один поток стадий.

Обработать смену стадии

Своих событий расширение не бросает. Единственная точка связи — функция из параметра onStageChange.

Поток вызывает ее и передает объект Stage — стадию, которую выбрал пользователь. В обработчике доступны методы этой стадии:

  • getId() — возвращает идентификатор стадии.

  • getName() — возвращает название стадии.

  • isSuccess() — возвращает true, если стадия завершает работу с объектом успешно.

  • isFail() — возвращает true, если стадия завершает работу с объектом провалом.

Обработчик не вызывается в двух случаях: поток создан со значением false в параметре isActive или пользователь нажал на текущую стадию.

Текущую стадию поток сам не переставляет. Порядок работы такой: отправьте изменение на сервер, а после успешного ответа вызовите метод setCurrentStageId(). Он меняет текущую стадию и сразу перекрашивает полосу, поэтому вызывать render() следом не нужно.

Пока запрос выполняется, полоса остается на прежней стадии. При ошибке достаточно ничего не менять: поток уже показывает прежнее состояние.

Пример. Обработчик отправляет новую стадию документа на сервер и перекрашивает полосу по ответу.

import { StageFlow } from 'ui.stageflow';

let isSaving = false;

const chart = new StageFlow.Chart({
    backgroundColor: 'd3d7dc',
    currentStage: 2,
    isActive: true,
    onStageChange: (stage) => {
        if (isSaving)
        {
            return;
        }
        isSaving = true;

        BX.ajax.runAction('example.Document.setStage', {
            data: {
                id: 1,
                stageId: stage.getId(),
            },
        }).then((response) => {
            isSaving = false;
            chart.setCurrentStageId(response.data.stageId);
        }).catch(() => {
            isSaving = false;
        });
    },
}, stages);

Метод setCurrentStageId() ищет стадию по числовому идентификатору. Если стадии с таким идентификатором нет, метод ничего не меняет и возвращает undefined — цепочку вызовов через его результат строить нельзя.

Историю смены стадий показывают рядом с потоком в статье Лента событий ui.timeline: у нее есть готовый тип записи для смены стадии.

Получить стадии потока

Стадии потока доступны через методы чтения класса Chart. Они нужны, когда логика вокруг потока должна знать, где стоит объект и чем процесс может закончиться.

|

|| Метод | Описание || || getStages() | Возвращает все стадии объектом Map. Ключ — числовой идентификатор стадии, у стадии завершения ключ final || || getStageById(id) | Возвращает стадию по числовому идентификатору. Возвращает undefined, если стадии с таким идентификатором нет || || getSuccessStage() | Возвращает успешную стадию и null, если ее нет. Если успешных стадий несколько, метод вернет последнюю в массиве || || getFailStages() | Возвращает массив провальных стадий. Возвращает пустой массив, если их нет || || getFirstFailStage() | Возвращает первую провальную стадию в порядке массива и null, если провальных стадий нет || || getFinalStage() | Возвращает стадию завершения — ту, что поток нарисовал вместо успешной и провальных стадий || |#

Стадию завершения метод getStageById() не находит: у нее нечисловой ключ. Для нее есть отдельный метод getFinalStage().

Пример. Код проверяет, завершена ли работа с документом.

const currentStage = chart.getStageById(2);
const isFinished = currentStage ? currentStage.isFinal() : false;

Метод isFinal() возвращает true и для успешной, и для провальной стадии. Для промежуточных стадий он возвращает false.

Изменить стадии после отрисовки

Часть данных стадии можно поменять у отрисованного потока, не создавая его заново. Стадию для этого получают методами чтения, а меняют ее собственными методами:

  • setName(name) — меняет название стадии и сразу обновляет текст в полосе.

  • setColor(color) — меняет цвет в данных стадии, но полосу не перекрашивает.

  • setDisable(isDisable) — помечает стадию заблокированной или снимает пометку.

Перекрашивает полосу метод addBackLightUpToStage() класса Chart. Он заливает цветом все стадии до указанной включительно, а без аргументов берет текущую стадию и ее цвет. Вызывайте его после того, как поменяли цвета стадий.

Пример. Код меняет название стадии, задает ей другой цвет и перекрашивает полосу.

const stage = chart.getStageById(2);

stage.setName('На согласовании');
stage.setColor('ffa900');

chart.addBackLightUpToStage();

Заменить набор стадий у созданного потока нельзя: методов добавления и удаления стадии у класса Chart нет. Если набор стадий изменился, создайте новый объект и заново вставьте результат метода render() в контейнер.

Запретить переход в стадию

Переход в стадию закрывают, когда у пользователя нет прав на нее или объект еще не готов к этому шагу.

Внимание

Стадия с признаком isDisable выглядит заблокированной: она полупрозрачная, курсор над ней показывает запрет. Но нажатие по ней проходит и вызывает обработчик из параметра onStageChange. Проверяйте признак блокировки в наследнике класса Chart, иначе запрет останется только внешним.

Унаследуйте свой класс от StageFlow.Chart и переопределите нужные методы. Точки переопределения:

  • onStageClick(stage) — нажатие на обычную стадию.

  • onFinalStageClick(stage) — нажатие на стадию завершения.

  • onStageMouseHover(stage) — наведение на стадию и подсветка пройденного пути.

  • onStageMouseLeave(stage) — уход курсора со стадии и снятие подсветки.

В переопределенном методе сделайте свою проверку, а затем вызовите реализацию родителя через super. Так остальная логика потока — подсветка, окна выбора результата, вызов обработчика — продолжит работать.

Остальные методы класса Chart собирают окна выбора результата и их содержимое. Это внутреннее устройство диалога, поэтому здесь они не описаны: переопределяйте их, только если стандартного выбора результата не хватает.

Пример. Наследник не пропускает переход в заблокированную стадию.

import { StageFlow } from 'ui.stageflow';

class DocumentChart extends StageFlow.Chart
{
    onStageClick(stage)
    {
        if (stage.isDisabled())
        {
            return;
        }

        super.onStageClick(stage);
    }
}

Пример. Код блокирует стадию «На проверке», пока документ не заполнен.

const chart = new DocumentChart({
    backgroundColor: 'd3d7dc',
    currentStage: 1,
    isActive: true,
}, stages);

chart.getStageById(2).setDisable();

document.getElementById('document-stageflow').appendChild(chart.render());

Метод isDisabled() возвращает текущий признак блокировки стадии. Метод setDisable() работает и до первой отрисовки, и после нее: у отрисованной стадии он меняет оформление сразу, а до отрисовки признак применится при вызове render().

Связанные материалы