logo

[ELMA3] Реализация возможности закрепления объекта на странице

В статье приведен пример реализации возможности закрепления объекта на странице. Существует множество вариантов использования данного функционала. В данной статье приведено два примера возможного использования:

  1. Открытие конкретного экземпляра объекта IPaperPinExampleObject при переходе в модуль. IPaperPinExampleObject – простой объект-справочник со стандартным набором полей: Наименование, Автор, Дата создания, Дата Изменения, Автор изменения.
  2. Запись прикрепленных объектов (в примере используются документы) в портлет.

Пример отображения данных

Рис. 1. Отображение «булавки» на форме объекта

Рис. 2. Портлет, отображающий прикрепленные документы

Методы расширения (интерфейса)

Точка расширения (интерфейс) IPaperPinProvider имеет следующие методы:

/// <summary>
/// Доступна ли булавка для данного объекта
/// </summary>
/// <param name="typeUid">GUID типа объекта</param>
/// <param name="entityId">Идентификатор объекта</param>
/// <returns><c>true</c>, если для указанного типа объекта будет отображаться булавка</returns>
bool IsAvailable(Guid typeUid, object entityId = null);
        
/// <summary>
/// Все поддерживаемые типы.
/// </summary>
/// <returns>Список поддерживаемых типов</returns>
List<Guid> AvailableTypes();

/// <summary>
/// Использовать для закрепления только один экземпляр для данного типа. Все остальные экземпляры открепляются.
/// </summary>
/// <returns><c>true</c>, если использовать для закрепления только один экземпляр данного типа</returns>
bool OnlyOneInstance();

/// <summary>
/// Описание на кнопке, когда объект не закреплён.
/// </summary>
string TooltipText(Guid typeUid, object entityId = null);

/// <summary>
/// Описание на кнопке, когда объект закреплён.
/// </summary>
string TooltipTextPin(Guid typeUid, object entityId = null);

/// <summary>
/// Текст предупреждения при закреплении объекта
/// </summary>
/// <param name="typeUid">GUID типа объекта</param>
/// <param name="entityId">Идентификатор объекта</param>
string ConfirmText(Guid typeUid, object entityId = null);

Данная точка расширения реализована при помощи базового класса BasePaperPinProvider

BasePaperPinProvider имеет следующие методы:

/// <summary>
/// Доступна ли булавка для данного объекта
/// </summary>
/// <param name="typeUid">GUID типа объекта</param>
/// <param name="entityId">Идентификатор объекта</param>
/// <returns><c>true</c>, если для указанного типа объекта будет отображаться булавка</returns>
public virtual bool IsAvailable(Guid typeUid, object entityId = null)
{
    var availableTypes = AvailableTypes();
    return availableTypes.Any(uid => uid == typeUid);
}

/// <summary>
/// Все поддерживаемые типы.
/// </summary>
/// <returns>Список поддерживаемых типов</returns>
public abstract List<Guid> AvailableTypes();

/// <summary>
/// Использовать для закрепления только один экземпляр для данного типа. Все остальные экземпляры открепляются
/// </summary>
/// <returns><c>true</c>, если использовать для закрепления только один экземпляр данного типа</returns>
public virtual bool OnlyOneInstance()
{
    return false;
}

/// <summary>
/// Описание на кнопке, когда объект не закреплён.
/// </summary>
/// <param name="typeUid">GUID типа объекта</param>
/// <param name="entityId">Идентификатор объекта</param>
public abstract string TooltipText(Guid typeUid, object entityId = null);

/// <summary>
/// Описание на кнопке, когда объект закреплён.
/// </summary>
/// <param name="typeUid">GUID типа объекта</param>
/// <param name="entityId">Идентификатор объекта</param>
public abstract string TooltipTextPin(Guid typeUid, object entityId = null);

/// <summary>
/// Текст предупреждения при закреплении объекта
/// </summary>
/// <param name="typeUid">GUID типа объекта</param>
/// <param name="entityId">Идентификатор объекта</param>
public abstract string ConfirmText(Guid typeUid, object entityId = null);

Пример 1. Открытие конкретного экземпляра объекта IPaperPinExampleObject

Пример класса точки расширения

[Component]
public class ObjectPaperPinExample : BasePaperPinProvider
{
    public IEntityActionHandler EntityActionHandler { get; set; }

    /// <summary>
    /// Условие при котором "булавка" будет доступна
    /// </summary>
    /// <param name="typeUid"></param>
    /// <param name="entityId"></param>
    /// <returns></returns>
    public override bool IsAvailable(Guid typeUid, object entityId = null)
    {
        if (!base.IsAvailable(typeUid, entityId))
            return false;

        if (entityId == null)
            return false;

        return typeUid == InterfaceActivator.UID<IPaperPinExampleObject>();
    }

    /// <summary>
    /// Все поддерживаемые типы.
    /// </summary>
    /// <returns></returns>
    public override List<Guid> AvailableTypes()
    {
        return AllObjectTypes();
    }

    public static List<Guid> AllObjectTypes()
    {
        return new List<Guid> { InterfaceActivator.UID<IPaperPinExampleObject>() };
    }

    /// <summary>
    /// Использовать для закрепления только один экземпляр для данного типа. Все остальные экземпляры открепляются
    /// </summary>
    /// <returns></returns>
    public override bool OnlyOneInstance()
    {
        return true;
    }

    /// <summary>
    /// Описание на кнопке, когда объект не закреплён.
    /// </summary>
    public override string TooltipText(Guid typeUid, object entityId = null)
    {
        return SR.T("После нажатия на кнопку, данный объект будет открываться сразу при выборе пункта меню \"Мои объекты\"");
    }

    /// <summary>
    /// Описание на кнопке, когда объект закреплён.
    /// </summary>
    public override string TooltipTextPin(Guid typeUid, object entityId = null)
    {
        return SR.T("После нажатия на кнопку, при выборе пункта меню \"Мои объекты\", будет открываться главная страница модуля \"Мои объекты\"");
    }

    /// <summary>
    /// Текст предупреждения при закреплении объекта
    /// </summary>
    /// <param name="typeUid"></param>
    /// <param name="entityId"></param>
    public override string ConfirmText(Guid typeUid, object entityId = null)
    {
        return SR.T("Объекты будет открываться сразу при выборе пункта меню \"Мои объекты\"");
    }
}
Примечание
После реализации точки расширения у объекта появится булавка, затем необходимо реализовать логику работы данной булавки.

В данном примере был создан пункт меню, который переводит пользователя на список объектов:

protected IUser CurrentUser
{
    get
    {
        return AuthenticationService.GetCurrentUser<IUser>();
    }
}

[ContentItem]
public ActionResult Grid()
{
    // если есть закреплённый объект, то открываем сразу его
    var paperPinManager = PaperPinManager.Instance;
    var allObjectTypes = ObjectPaperPinExample.AllObjectTypes();
    var pin = paperPinManager.FirstPaperPinByUser(CurrentUser, allObjectTypes);
    if (pin != null && !string.IsNullOrEmpty(pin.EntityId))
    {
        long entityId;
        if (long.TryParse(pin.EntityId, out entityId))
            return RedirectToAction("ViewItem", new { id = entityId });
    }

    var filter = CreateFilter();
    var data = CreateGridData(new GridCommand(), filter);
    return View(data);
}

public ActionResult ViewItem(long id)
{
    var model = PaperPinExampleObjectManager.Instance.Load(id);
    return View(model);
}

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

О том, как создать свой пункт меню, можно подробнее прочитать в данной статье.

Примечание
При реализации карточки объекта необходимо добавить в заголовок следующий код, где Model – это объект IPaperPinExampleObject
@{
    var title = SR.T("Объект - {0}", Model.Name);
    Html.Header(title, Model);
}

Благодаря данному коду на странице появится булавка.

Пример 2. Запись прикрепленных объектов (в примере используются документы) в портлет

Пример класса точки расширения

    private static List<Guid> _documentAvailableTypes;

    public static List<Guid> DocumentAvailableTypes
    {
        get
        {
            if (_documentAvailableTypes == null)
            {
                _documentAvailableTypes = Locator.GetServiceNotNull<IMetadataRuntimeService>()
                    .GetMetadataList()
                    .OfType<DocumentMetadata>()
                    .Select(documentMetadata => documentMetadata.Uid).ToList();
            }
            return _documentAvailableTypes;
        }
    } 


[Component]
public class DocumentPaperPinExample : BasePaperPinProvider
{
    public IEntityActionHandler EntityActionHandler { get; set; }

    /// <summary>
    /// Условие при котором "булавка" будет доступна
    /// </summary>
    /// <param name="typeUid"></param>
    /// <param name="entityId"></param>
    /// <returns></returns>
    public override bool IsAvailable(Guid typeUid, object entityId = null)
    {
        if (!base.IsAvailable(typeUid, entityId))
            return false;

        if (entityId == null)
            return false;

        return DocumentAvailableTypes.FirstOrDefault(a => a == typeUid) != null;
    }

    /// <summary>
    /// Все поддерживаемые типы.
    /// </summary>
    /// <returns></returns>
    public override List<Guid> AvailableTypes()
    {
        return DocumentAvailableTypes;
    }

    /// <summary>
    /// Описание на кнопке, когда объект не закреплён.
    /// </summary>
    public override string TooltipText(Guid typeUid, object entityId = null)
    {
        return SR.T("После нажатия на кнопку, документ добавится в портлет \"Список прикрепленных документов\"");
    }

    /// <summary>
    /// Описание на кнопке, когда объект закреплён.
    /// </summary>
    public override string TooltipTextPin(Guid typeUid, object entityId = null)
    {
        return SR.T("Документ отображается в портлете \"Список прикрепленных документов\"");
    }

    /// <summary>
    /// Текст предупреждения при закреплении объекта
    /// </summary>
    /// <param name="typeUid"></param>
    /// <param name="entityId"></param>
    public override string ConfirmText(Guid typeUid, object entityId = null)
    {
        return SR.T("Документ будет добавлен в портлет \"Список прикрепленных документов\"");
    }
}

Создадим портлет в модуле. Подробнее о том, как создавать портлеты в модуле, Вы можете прочитать здесь.

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

@model PaperPinExampleModule.Web.Portlets.PaperPinDocumentsPersonalization
@using PaperPinExampleModule.Web

@Html.Action("PortletView", "Home", new {area = RouteProvider.AreaName})

В представлении вызывается метод PortletView контроллера HomeController

Код метода контроллера:

protected IUser CurrentUser
{
    get
    {
        return AuthenticationService.GetCurrentUser<IUser>();
    }
}

public ActionResult PortletView()
{
    var documentManager = DocumentManager.Instance;
    //Получаем все идентификаторы документов, у которых прицеплена булавка для текущего пользователя
    var paperPinManager = PaperPinManager.Instance;
    var allObjectTypes = DocumentPaperPinExample.DocumentAvailableTypes;
    var pins = paperPinManager.GetAllPaperPinsByUser(CurrentUser, allObjectTypes);
            
    List<long> documentIdsArray = new List<long>();
    foreach (var pin in pins)
    {
        long documentId;
        if (long.TryParse(pin.EntityId, out documentId))
            documentIdsArray.Add(documentId);
    }
    var docFilter = InterfaceActivator.Create<IDocumentFilter>();
    docFilter.Ids = documentIdsArray;
    
    var fetchOptions = new FetchOptions
    {
         SelectColumns = new List<string>{"Id", "Name"}
    };
    
    var documents = documentManager.Find(docFilter, fetchOptions).Select(doc => new DocumentPortletModel()
    {
        Name = doc.Name,
        Id = doc.Id
    }).ToList();
    
    return PartialView("PortletView", documents);
}

Где DocumentPortletModel - это класс, представляющий простую модель со свойствами Name, Id:

public class DocumentPortletModel
{
    /// <summary>
    /// Наименование документа
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Идентификатор документа
    /// </summary>
    public long Id { get; set; }
}

Как можно заметить, в список документов добавляются только те документы, которые прикреплены текущим пользователем, соответственно, в портлете появятся прикрепленные документы только для текущего пользователя. Далее контроллер возвращает частичное представление PortletView. Код частичного представления:

@model System.Collections.Generic.List<PaperPinExampleModule.Web.Models.DocumentPortletModel>

<style>
    .tableTd {
        padding: 3px 5px !important;
        border-bottom: 1px solid #e6e6e6 !important;
    }
</style>

<table class="t-grid-table" style="width: 100%">
    <thead class="t-grid-header">
        <tr>
            <th scope="col" class="t-header "><span class="t-header-content">@SR.T("Название документа")</span></th>
        </tr>
    </thead>
    @if (Model.Count > 0)
    {
        foreach (var doc in (dynamic)Model)
        {
            if (doc != null)
            {
                <tr>
                    <td class="tableTd">
                        <a href="@Url.Action("View", "Document", new {area = "EleWise.ELMA.Documents.Web", id = doc.Id})">@doc.Name</a>
                    </td>
                </tr>
            }
        }
    }
</table>
Важное примечание
В примере 2 метод OnlyOneInstance отсутствует. Это связано с тем, что базовый класс BasePaperPinProvider определяет данный метод как false, так как в портлет должны попадать все прикрепленные документы пользователя, а не только один.

Ссылки на Базу знаний

Создание портлета в модуле;

IMenuItemsProvider.