[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>