[ELMA3] Реализация возможности закрепления объекта на странице
В статье приведен пример реализации возможности закрепления объекта на странице. Существует множество вариантов использования данного функционала. В данной статье приведено два примера возможного использования:
- Открытие конкретного экземпляра объекта IPaperPinExampleObject при переходе в модуль. IPaperPinExampleObject – простой объект-справочник со стандартным набором полей: Наименование, Автор, Дата создания, Дата Изменения, Автор изменения.
- Запись прикрепленных объектов (в примере используются документы) в портлет.
Пример отображения данных
Рис. 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); }
Как можно заметить, при выборе пункта меню, если будет найден первый объект, который прикреплен, то будет совершен переход на запись этого объекта.
О том, как создать свой пункт меню, можно подробнее прочитать в данной статье.
@{ 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>