Поговорим о действиях коллекций?

72 1

Работа с коллекциями - неотъемлемая часть разработки в Directum RX. В данной статье будет рассмотрен как момент с доступностью базовых действий коллекции, так и пример с реализацией «множественного выбора» для ячейки коллекции.

Запрет добавления, удаления или копирования строк коллекции

В каждом типе сущности есть действия, которые относятся ко всем коллекциям типа

  1. AddChildEntity – действие «Добавить строку».
  2. CopyChildEntity – действие «Копирование строки».
  3. DeleteChildEntity – действие «Удаление строки».

Предположим, что у нас есть одна доступная пользователю коллекция в карточке объекта. Тогда, чтобы запретить добавлять, удалять или копировать сущности коллекции, достаточно для действий AddChildEntity, CopyChildEntity или DeleteChildEntity установить обработчик возможности выполнения “return false;” и у коллекции не будут отображаться её базовые действий.

А что если у нас 2 коллекции или более? Описанный выше вариант скроет действия для всех коллекций без исключения. Тут можно предложить вариант, чтобы при попытке изменения состава коллекции пользователю отображалась ошибка о невозможности добавить/удалить строки коллекции. Тогда для коллекции, у которой необходимо ограничить возможность добавления/удаления строк, в событиях «Добавления в коллекцию» и «Удаление из коллекции» добавим следующий код:

throw AppliedCodeException.Create(“Нельзя добавлять новые строки для данной коллекции.”);

Данный подход решает проблему, но, кажется, можно найти подход и получше.

Теперь предположим, что у нас есть тип объекта с именем «Tests» и в нём 2 коллекции: «Collection1» и «Collection2». Необходимо для коллекции «Collection1» ограничить доступность действия «Добавить строку», а для «Collection2» ограничить доступность действий «Копирование строки» и «Удаление строки». В таком случае код для возможности выполнения действия AddChildEntity будет выглядеть следующим образом:

public override bool CanAddChildEntity(Sungero.Domain.Client.CanExecuteChildCollectionActionArgs e)
{
    // Ограничение доступности базового действия для определённой коллекции.
    var internalType = _all.GetType().GetGenericArguments()[0];
    if (internalType.Name == "ITestsCollection1")
      return false;

    return base.CanAddChildEntity(e);
}

В свою очередь, код для события выполнения действий «Удаление строки» и «Копирование строки» будет одинаковым и рассмотрим его на примере первого действия, соответственно:

public override bool CanDeleteChildEntity(Sungero.Domain.Client.CanExecuteChildCollectionActionArgs e)
{
    // Ограничение доступности базового действия для определённой коллекции.
    var internalType = _all.GetType().GetGenericArguments()[0];
    if (internalType.Name == "ITestsCollection2")
      return false;

    return base.CanDeleteChildEntity(e);
}

В примерах выше мы получаем наименование интерфейса типа коллекции и сравниваем его с подходящим интерфейсом, но в виде строки.

Работа с кастомными действиями для коллекции

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

Понадобится создать:

  • Коллекция: Roles (Исполнители)
  • Свойства коллекции:
  1. Role (Роль) – перечисление. Видимое.
  2. PerformersName (Исполнители) – строка. Видимое.
  3. PerformersIds (Id исполнителей)- строка. Не видимое.
  • Действия:
  1. AddPerformers (Добавить)
  2. EditPerformers (Изменить)
  3. DeletePerformers (Удалить)

Рисунок 1. Пример настройки действия для дочерней коллекции

Все действия настраиваются, как на рисунке 1, кроме действия «Добавить». Для него необходимо поставить галочку для параметра «Доступно при отсутствии выделенных записей».

Далее, для нашего типа объекта, добавим клиентскую функцию:

/// <summary>
/// Создать или изменить существующую строку коллекции.
/// </summary>
/// <param name="newRow">Строка коллекции.</param>
public void DialogForRolesCollection(ITestsRoles row)
{
    var isNew = row == null;

    // Если строки коллекции нет, то создать.
    if (row == null)
      row = _obj.Roles.AddNew();

    // Предопределённые переменные.
    var rolesEnumeration = row.RoleAllowedItems;
    var rolesLocalization = rolesEnumeration.Select(x => 
    Testses.Info.Properties.Roles.Properties.Role.GetLocalizedValue(x)).ToArray();

    // Создать диалог.
    var dialog = Dialogs.CreateInputDialog("Диалог работы с ролью.");
    var role = dialog.AddSelect("Роль", true).From(rolesLocalization);
    var performers = dialog.AddSelectMany("Исполнители", true, Sungero.Company.Employees.Null);

    // Предзаполнить диалог.
    role.Value = Testses.Info.Properties.Roles.Properties.Role.GetLocalizedValue(row.Role);
    if (!string.IsNullOrEmpty(row.PerformersIds))
    {
      var listIds = row.PerformersIds.Split(';').ToList();
      performers.Value = Sungero.Company.Employees.GetAll().Where( x => 
      listIds.Contains(x.Id.ToString()));
    }

    if (dialog.Show() == DialogButtons.Ok)
    {
      row.Role = rolesEnumeration.FirstOrDefault(x => 
      Testses.Info.Properties.Roles.Properties.Role.GetLocalizedValue(x) == role.Value);
      row.PerformersName = string.Join("; ", performers.Value.Select(x => x.DisplayValue).ToArray());
      row.PerformersIds = string.Join(";", performers.Value.Select(x => x.Id).ToArray());
    }
    else
    {
      if (isNew)
        _obj.Roles.Remove(row);
    }
}

И вызовем её в выполнении событий новых действий:

public virtual void DeletePerformers(Sungero.Domain.Client.ExecuteChildCollectionActionArgs e)
{
    _obj.Tests.Roles.Remove(_obj);
}

public static void AddPerformers(Sungero.Domain.Client.ExecuteChildCollectionActionArgs e)
{
    var entity = Testses.As(e.RootEntity);
    Functions.Tests.DialogForRolesCollection(entity, null);
}

public virtual void EditPerformers(Sungero.Domain.Client.ExecuteChildCollectionActionArgs e)
{
    Functions.Tests.DialogForRolesCollection(_obj.Tests, _obj);
}

Теперь для получения сотрудников создадим в серверных функциях метод GetPerformers, который будет принимать список ИД сотрудников и возвращать список сотрудников:

/// <summary>
/// Получить список сотрудников по списку Ид'ешников.
/// </summary>
/// <param name="ids">Список Ид'ешников.</param>
/// <returns>Список сотрудников.</returns>
[Remote]
public static List<Sungero.Domain.Shared.IEntity> GetPerformers(List<int> ids)
{
    return Sungero.Company.Employees.GetAll()
              .Where(x => ids.Contains(x.Id))
              .ToList()
              .Cast<Sungero.Domain.Shared.IEntity>()
              .ToList();
}

На будущее: для удобства получения сотрудников по роли также добавим метод GetPerformersOnRole, который позволит получать список сотрудников по роли:

/// <summary>
/// Получить список сотрудников по роли.
/// </summary>
/// <param name="role">Роль.</param>
/// <returns>Список сотрудников.</returns>
public List<Sungero.Domain.Shared.IEntity> GetPerformersOnRole(Enumeration? role)
{
    var row = _obj.Roles.FirstOrDefault(x => Equals(x.Role, role));
    if (row == null)
      return new List<Sungero.Domain.Shared.IEntity>();
    var idsPerformers = Array.ConvertAll(row.PerformersIds.Split(';'), int.Parse).ToList();
    return Sungero.Company.Employees.GetAll()
               .Where(x => idsPerformers.Contains(x.Id))
               .ToList()
               .Cast<Sungero.Domain.Shared.IEntity>()
               .ToList();
}

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

public virtual void PreviewPerformers(Sungero.Domain.Client.ExecuteChildCollectionActionArgs e)
{
    var ids = Array.ConvertAll(row.PerformersIds.Split(';'), int.Parse).ToList();
    var performers = Functions.Tests.Remote.GetPerformers(ids);
    performers.ShowModal("Предпросмотр");
}

А обработчик возможности выполнения так:

public virtual bool CanPreviewPerformers(Sungero.Domain.Client.CanExecuteChildCollectionActionArgs e)
{
    return !string.IsNullOrEmpty(_obj.PerformersIds);
}

Рисунок 2. Демонстрация проделанной работы

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

Юлия Литвинюк

Отличная статья  немного дополню.

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

  • e.RootEntity - сущность, для которой вызывается действие
  • _all - коллекция, для которой вызывается действие
  • _obj - строка коллекции, для которой вызывается действие (CopyChildEntity)
  • _objs - строки коллекции, для которых вызывается действие (DeleteChildEntity)

Таким образом для сущности Dossier, у которой есть 2 коллекции MainDocs и OtherDocs код будет следующим:

Запретить добавлять строки в коллекцию MainDocs и разрешить добавлять в коллекцию OtherDocs

public override bool CanAddChildEntity(Sungero.Domain.Client.CanExecuteChildCollectionActionArgs e)
    {
      var dossier = Dossiers.As(e.RootEntity);
      if (dossier.MainDocs == _all)
        return false;
      
       return base.CanAddChildEntity(e);
    }

Запретить в коллекции MainDocs удалять записи, с признаком ведущий документ (свойство IsMain == true) или счет, остальные строки в этой коллекции удалять можно

 public override bool CanDeleteChildEntity(Sungero.Domain.Client.CanExecuteChildCollectionActionArgs e)
    {
      var dossier = Dossiers.As(e.RootEntity);
      if (dossier.MainDocs == _all)
      {
        // Нельзя удалять ведущий документ или счет.
        if (_objs.Cast<IDossierMainDocs>().Any(d => d.IsMain == true || (d.Doc != null && IncomingInvoices.Is(d.Doc))))
          return false;
      }
      return base.CanDeleteChildEntity(e);
    }
Юлия Литвинюк: обновлено 21.03.2023 в 12:45

Авторизуйтесь, чтобы написать комментарий