Вот что требуется клиентам и что сделано нами на сайте под 1С-Битрикс .NET для одного из них:
1. Список новостей должен иметь фильтры - по году публикации, по тегам и др. связанным элементам инфоблоков. Фильтры могут располагаться физически как перед списком, так и после списка. При этом должна сохраняться возможность редактировать список через стандартный Битрикс-интерфейс для инфоблоков.
Что сделано нами
Известно, что в .NET данная возможность уже присутствует в виде веб-частей (WebParts) и связей между ними. Мы создали надстройку над Битрикс-компонентами - WebPartComponent. Такой компонент поддерживает создание связей между ним и другими веб-парт компонентами. Одновременно он является обычным Битрикс-компонентом.
Лично я бы предпочитал просто сделать поддержку добавления Битрикс-компонентов на страницу как веб-частей (внутрь WebPartZone), но из-за странной реализации Битрикс-компонентов (создание дочерних элементов производится везде в Page_Init, а не в CreateChildControls, как этого требует нормальный подход к .NET-программированию) такой подход не является возможным (особенно если нужна еще связь между компонентами). Поэтому и создан просто новый класс WebPartComponent, унаследованный от BXComponent. Заодно создан класс WebPartComponentWrapper, унаследованный уже от WebPartComponent, который позволяет, написав три строчки кода, сделать свой компонент, являющийся оболочкой для любого уже существующего Битрикс-компонента (достаточно только переопределить свойство WrappedComponentName, возвратив имя компонента, например, "bitrix:system.menu").
Наряду с созданием самого класса WebPartComponent, созданы и другие вещи, полностью реализующие инфраструктуру WebPart-компонентов в стаднатрном .NET-интерфейсе редактирования веб-частей:
- Загрузка и сохранение значений переметров компонента в/из общего хранилища персонализации.
- Реализация редактора пользовательских свойств веб-частей. Наш редактор позволяет редактировать все свойства комонента в стандартном интерфейсе свойств веб-части.
- Инфраструктура веб-частей в виде компонента переключения режимов отображения и компонента зон редактирвоания веб-частей. Эти компоненты расположены на Mater Page (читай: в шаблоне).
- Автоматическое переключение инфраструктуры веб-частей в режим редактирования при переключении на вкладки "Редактирование" и "Разработка" в админской панели Битрикс, также переключение при этом WebPart-компонентов в битрикс-понимание режима дизайна.
- Элемент управления каталога WebPart-компонентов - помещается внутрь CatalogZone и содержит список доступных для помещения на страницу WebPart-компонентов из определенного физического каталога.
- Ну и напоследок - реализация своего компонента списка, поддерживающего фильтры. Это могла бы быть 98%-ная копия компонента списка новостей, но мы сделали гибрид компонентов "Список новостей" и "Лента новостей", чтобы он удовлетворял всем (изощренным и не очень) запросам клиента.
При этом, если WebPart-компоненты поддерживают соединения, то эти компоненты и соединения можно добавить на ASPX-страницу статически (как .NET позволяет сделать стандартно). При этом, конечно, нельзя использовать BX:IncludeComponent, т.к. .NET для поддержки связей требует, чтобы они были организованы между реальными классами, а не элементами управления, их содержащими. Поэтому код ASPX-страницы будет выглядеть примерно так (опускаю все неинтересные части):
Здесь parts:BitrixYearFilter - компонент фильтра по годам, parts:BitrixList - универсальный компонент списка элементов инфоблока (помните списки из SharePoint?). Можно также расположить на странице и другие компоненты фильтров.
В визуальном редакторе Битрикс, правда, при редактировании такой страницы появляется JS-сообщение с ошибкой о невозможности прочитать содержимое тега StaticConnections, после чего редактировать страницу становится невозможно. Это баг визуального редактора, который, надеюсь, в будущем специалисты Битрикс исправят. С другой стороны, использование стандартного редактора из Visual Studio / VWD Express на данной странице более полезно, чем использование редактора Битрикс.
Всего в нашем пространстве имен Individ.WebParts насчитывается 27 различных классов. Я буду рад, если данная функциональность будет интегрирована в Битрикс, и все Битрикс-компоненты будут основаны не на BXComponent, а на WebPartComponent или его производных (например, наш класс WebPartListComponent уже содержит реализацию приведенных в примере способов связывания, а также стандартное содержимое PreLoadComponentDefinition; класс BXFilterWebPart<ValueType> является очень полезным базовым классом для создания компонентов-фильтров). Лично я большинство своих компонентов наследую от WebPartComponent.
Интересно, что один раз написанный компонент фильтра может использоваться для разных компонентов списка, а к компоненту списка можно применять любые фильтры, в том числе те, которые будут созданы позже. Одно условие - это что список и фильтр должны использовать одну схему данных (например, инфоблоки). В основном это заслуга Битрикс, в арсенале которого есть такой хороший интерфейс - IBXFilterItem.
В общем, WebPart-инфраструктура делает Битрикс гораздо ближе к SharePoint.
2. Формы. Такого модуля в Битрикс .NET пока нет, и, насколько я понял, в ближайшее время не планируется. Клиенту же требуется такая функциональность: на сайте должна отображаться форма, которую может конструировать клиент. Данные формы должны сохраняться в бэкофисе и отсылаться на Email при отправке формы пользователем. Т.к. клиенту нрасится Битрикс, редактирование формы должно быть в интерфейсе Битрикс.
К реализации таких требований есть два подхода. Первый - простой - повторно использовать уже имеющийся в поставке компонент ~/bitrix/controls/Main/CustomFieldEdit.ascx. При этом будет один недостаток - дизайн формы будет предопределен. Конечно, при этом будут учитываться заданные клиентом ширина, высота полей и т.п., но клиентам обычно больше нравится, если они это не задают, а дизайн сайта при этом является стандартным.
Поэтому в угоду дизайну нам пришлось выбрать более сложный второй способ - это всё сделать самим. Результатом явилось создание компонента "InfoBlockForm" и расширяемая инфраструктура типов полей под него - пространство имен Individ.Forms. В настоящее время мы самостоятельно реализовали не так много типов полей, но это не так страшно (как я уже сказал, инфораструктура расширяемая).
3. Поиск. Здесь есть две проблемы. Одна проблема у Битрикс, другая - у клиента. Рассмотрим каждую проблему.
3.1. Проблема Битрикс - модуль поиска хорошо справляется с индексированием контента (причем произвольного), но он не знает, как строить ссылки на элементы инфоблоков. Поэтому найденные элементы инфоблоков в результатах поиска выводятся без ссылок. Насколько я знаю, в PHP-Битриксе подобной проблемы нет, там в настройках каждого инфоблока можно указать адрес его элементов. В .NET-же версии Битрикс исходя из каких-то соображений этого сделано не было. Изучение Битрикс показало, что мы можем сделать это самостоятельно, реализовав обработчик Битрикс-события "Bitrix.Search.ProvideUrl". И мы такой обработчик для инфоблоков реализовали. Т.к. в бэкофис мы не вмешиваемся, конфигурация в данном случае хранится в ~/App_Data/ в виде xml-файла. Т.к. в VB.NET присутствует замечательная удобная реализация LINQ-to-Xml, реализация обработчика была хорошим поводом попрактиковаться в VB.NET (хотя обычно пишу на C#). Надеюсь, что все же неполноценность поиска в будущем исправится штатными средствами.
3.2. Проблема клиента - это своё понимание поиска. Им недостаточно простого поиска, они хотят еще фильтровать результаты поиска по различным свойствам элементов инфоблока. Да, я понимаю, что поиск в Битрикс универсальный и об инфоблоках понятия не имеет и не умеет фильтровать по их свойствам. Однако же для клиента фильтр важен.
И здесь, как говорится, см. п.1 - всё необходимое у нас уже есть, осталось его чуть-чуть доработать. В компонент BitrixList мы добавили режим поиска. В этом режиме он некоторые свои свойства игнорирует и вместо них использует настройки xml-файла из п.3.1. Т.е. он выводит элементы из тех инфоблоков, для которых он может построить ссылки на основе xml-файла (кроме того, в данный файл мы добавили возможность укажания только конкретных инфоблоков, по которым выполняется поиск). И ссылки строятся не исходя из параметра DetailUrl, а исходя из результатов работы команды "Bitrix.Search.ProvideUrl".
Также мы создали фильтр для инфоблоков по строке для поиска. К сожалению, API парсинга строки поиска (SearchExpression) в модуле поиска Битрикс является закрытым, поэтому нам пришлось его продублировать. Открытые части API типа BXSearchLanguages.StemWord мы используем. Разобраная строка поиска у нас транслируется не в SQL-запрос напрямую, как это сделано в модуле поиска, а в фильтр (IBXFilterItem), что пригодно для наших WebPart-фильтров.
Не решенная проблема, в решении которой специалисты Битрикс, возможно, могли бы нам помочь, заключается в некоторых ограничениях класса BXFilterItem. Во-первых, он не содержит экранирования символа "%" для запросов типа LIKE. Во-вторых, мы хотим искать подстроку с начала слова. Фильтры же Like и StartsLike нам здесь помочь не могут, нам нужно что-то типа CONTAINS, но этот оператор через BXFilterItem не поддерживается. В итоге мы пока удовлетворились поиском подстроки в произвольной части слова (кстати, если поичковый термин введен в кавычках, то так делает и стандартная реализация поиска). Для реализации требуемого поведения поиска нам потребуется написать свою реализацию IBXFilterItem, а это, как я понял, довольно трудоёмко.
1. Список новостей должен иметь фильтры - по году публикации, по тегам и др. связанным элементам инфоблоков. Фильтры могут располагаться физически как перед списком, так и после списка. При этом должна сохраняться возможность редактировать список через стандартный Битрикс-интерфейс для инфоблоков.
Что сделано нами
Известно, что в .NET данная возможность уже присутствует в виде веб-частей (WebParts) и связей между ними. Мы создали надстройку над Битрикс-компонентами - WebPartComponent. Такой компонент поддерживает создание связей между ним и другими веб-парт компонентами. Одновременно он является обычным Битрикс-компонентом.
Лично я бы предпочитал просто сделать поддержку добавления Битрикс-компонентов на страницу как веб-частей (внутрь WebPartZone), но из-за странной реализации Битрикс-компонентов (создание дочерних элементов производится везде в Page_Init, а не в CreateChildControls, как этого требует нормальный подход к .NET-программированию) такой подход не является возможным (особенно если нужна еще связь между компонентами). Поэтому и создан просто новый класс WebPartComponent, унаследованный от BXComponent. Заодно создан класс WebPartComponentWrapper, унаследованный уже от WebPartComponent, который позволяет, написав три строчки кода, сделать свой компонент, являющийся оболочкой для любого уже существующего Битрикс-компонента (достаточно только переопределить свойство WrappedComponentName, возвратив имя компонента, например, "bitrix:system.menu").
Наряду с созданием самого класса WebPartComponent, созданы и другие вещи, полностью реализующие инфраструктуру WebPart-компонентов в стаднатрном .NET-интерфейсе редактирования веб-частей:
- Загрузка и сохранение значений переметров компонента в/из общего хранилища персонализации.
- Реализация редактора пользовательских свойств веб-частей. Наш редактор позволяет редактировать все свойства комонента в стандартном интерфейсе свойств веб-части.
- Инфраструктура веб-частей в виде компонента переключения режимов отображения и компонента зон редактирвоания веб-частей. Эти компоненты расположены на Mater Page (читай: в шаблоне).
- Автоматическое переключение инфраструктуры веб-частей в режим редактирования при переключении на вкладки "Редактирование" и "Разработка" в админской панели Битрикс, также переключение при этом WebPart-компонентов в битрикс-понимание режима дизайна.
- Элемент управления каталога WebPart-компонентов - помещается внутрь CatalogZone и содержит список доступных для помещения на страницу WebPart-компонентов из определенного физического каталога.
- Ну и напоследок - реализация своего компонента списка, поддерживающего фильтры. Это могла бы быть 98%-ная копия компонента списка новостей, но мы сделали гибрид компонентов "Список новостей" и "Лента новостей", чтобы он удовлетворял всем (изощренным и не очень) запросам клиента.
При этом, если WebPart-компоненты поддерживают соединения, то эти компоненты и соединения можно добавить на ASPX-страницу статически (как .NET позволяет сделать стандартно). При этом, конечно, нельзя использовать BX:IncludeComponent, т.к. .NET для поддержки связей требует, чтобы они были организованы между реальными классами, а не элементами управления, их содержащими. Поэтому код ASPX-страницы будет выглядеть примерно так (опускаю все неинтересные части):
Код |
---|
<%@ Page ... %> <%@ Register TagPrefix="parts" TagName="BitrixList" Src="~/bitrix/components/parts/BitrixList/component.ascx" %> <%@ Register TagPrefix="parts" TagName="BitrixYearFilter" Src="~/bitrix/components/parts/BitrixYearFilter/component.ascx" %> ... <asp:Content ID="Content2" ContentPlaceHolderID="bxcontent" runat="server"> <asp:UpdatePanel ID="ZoneMainUpdatePanel" runat="server"> <ContentTemplate> <asp:WebPartZone ID="ZoneMain" runat="server" HeaderText="Основное содержимое"> <ZoneTemplate> <parts:BitrixYearFilter ID="YearFilter" runat="server" Name="parts:BitrixYearFilter" WebPartTitle="Фильтр по годам" BXParam_ValuesStorageType="QS" BXParam_ValuesGetParameterName="year" BXParam_AllItemsText="Все" /> <parts:BitrixList ID="BitrixList" runat="server" Name="parts:BitrixList" WebPartTitle="Список Битрикс" BXParam_IBlockTypeId="18" BXParam_IBlockIds="34" BXParam_DetailUrl="Item.aspx?id=#ELEMENT_ID#" /> </ZoneTemplate> </asp:WebPartZone> <asp:ProxyWebPartManager ID="ProxyWebPartmanager" runat="server"> <StaticConnections> <asp:WebPartConnection ID="YearFilterGetIBlockIds" ProviderID="BitrixList" ProviderConnectionPointID="BXIBlockIdsProvider" ConsumerID="YearFilter" ConsumerConnectionPointID="BXIBlockIdsConsumer" /> <asp:WebPartConnection ID="YearFilterConnection" ProviderID="YearFilter" ProviderConnectionPointID="IBXFilterProvider" ConsumerID="BitrixList" ConsumerConnectionPointID="IBXFilterConsumer" /> </StaticConnections> </asp:ProxyWebPartManager> </ContentTemplate> </asp:UpdatePanel> </asp:Content> |
Здесь parts:BitrixYearFilter - компонент фильтра по годам, parts:BitrixList - универсальный компонент списка элементов инфоблока (помните списки из SharePoint?). Можно также расположить на странице и другие компоненты фильтров.
В визуальном редакторе Битрикс, правда, при редактировании такой страницы появляется JS-сообщение с ошибкой о невозможности прочитать содержимое тега StaticConnections, после чего редактировать страницу становится невозможно. Это баг визуального редактора, который, надеюсь, в будущем специалисты Битрикс исправят. С другой стороны, использование стандартного редактора из Visual Studio / VWD Express на данной странице более полезно, чем использование редактора Битрикс.
Всего в нашем пространстве имен Individ.WebParts насчитывается 27 различных классов. Я буду рад, если данная функциональность будет интегрирована в Битрикс, и все Битрикс-компоненты будут основаны не на BXComponent, а на WebPartComponent или его производных (например, наш класс WebPartListComponent уже содержит реализацию приведенных в примере способов связывания, а также стандартное содержимое PreLoadComponentDefinition; класс BXFilterWebPart<ValueType> является очень полезным базовым классом для создания компонентов-фильтров). Лично я большинство своих компонентов наследую от WebPartComponent.
Интересно, что один раз написанный компонент фильтра может использоваться для разных компонентов списка, а к компоненту списка можно применять любые фильтры, в том числе те, которые будут созданы позже. Одно условие - это что список и фильтр должны использовать одну схему данных (например, инфоблоки). В основном это заслуга Битрикс, в арсенале которого есть такой хороший интерфейс - IBXFilterItem.
В общем, WebPart-инфраструктура делает Битрикс гораздо ближе к SharePoint.
2. Формы. Такого модуля в Битрикс .NET пока нет, и, насколько я понял, в ближайшее время не планируется. Клиенту же требуется такая функциональность: на сайте должна отображаться форма, которую может конструировать клиент. Данные формы должны сохраняться в бэкофисе и отсылаться на Email при отправке формы пользователем. Т.к. клиенту нрасится Битрикс, редактирование формы должно быть в интерфейсе Битрикс.
К реализации таких требований есть два подхода. Первый - простой - повторно использовать уже имеющийся в поставке компонент ~/bitrix/controls/Main/CustomFieldEdit.ascx. При этом будет один недостаток - дизайн формы будет предопределен. Конечно, при этом будут учитываться заданные клиентом ширина, высота полей и т.п., но клиентам обычно больше нравится, если они это не задают, а дизайн сайта при этом является стандартным.
Поэтому в угоду дизайну нам пришлось выбрать более сложный второй способ - это всё сделать самим. Результатом явилось создание компонента "InfoBlockForm" и расширяемая инфраструктура типов полей под него - пространство имен Individ.Forms. В настоящее время мы самостоятельно реализовали не так много типов полей, но это не так страшно (как я уже сказал, инфораструктура расширяемая).
3. Поиск. Здесь есть две проблемы. Одна проблема у Битрикс, другая - у клиента. Рассмотрим каждую проблему.
3.1. Проблема Битрикс - модуль поиска хорошо справляется с индексированием контента (причем произвольного), но он не знает, как строить ссылки на элементы инфоблоков. Поэтому найденные элементы инфоблоков в результатах поиска выводятся без ссылок. Насколько я знаю, в PHP-Битриксе подобной проблемы нет, там в настройках каждого инфоблока можно указать адрес его элементов. В .NET-же версии Битрикс исходя из каких-то соображений этого сделано не было. Изучение Битрикс показало, что мы можем сделать это самостоятельно, реализовав обработчик Битрикс-события "Bitrix.Search.ProvideUrl". И мы такой обработчик для инфоблоков реализовали. Т.к. в бэкофис мы не вмешиваемся, конфигурация в данном случае хранится в ~/App_Data/ в виде xml-файла. Т.к. в VB.NET присутствует замечательная удобная реализация LINQ-to-Xml, реализация обработчика была хорошим поводом попрактиковаться в VB.NET (хотя обычно пишу на C#). Надеюсь, что все же неполноценность поиска в будущем исправится штатными средствами.
3.2. Проблема клиента - это своё понимание поиска. Им недостаточно простого поиска, они хотят еще фильтровать результаты поиска по различным свойствам элементов инфоблока. Да, я понимаю, что поиск в Битрикс универсальный и об инфоблоках понятия не имеет и не умеет фильтровать по их свойствам. Однако же для клиента фильтр важен.
И здесь, как говорится, см. п.1 - всё необходимое у нас уже есть, осталось его чуть-чуть доработать. В компонент BitrixList мы добавили режим поиска. В этом режиме он некоторые свои свойства игнорирует и вместо них использует настройки xml-файла из п.3.1. Т.е. он выводит элементы из тех инфоблоков, для которых он может построить ссылки на основе xml-файла (кроме того, в данный файл мы добавили возможность укажания только конкретных инфоблоков, по которым выполняется поиск). И ссылки строятся не исходя из параметра DetailUrl, а исходя из результатов работы команды "Bitrix.Search.ProvideUrl".
Также мы создали фильтр для инфоблоков по строке для поиска. К сожалению, API парсинга строки поиска (SearchExpression) в модуле поиска Битрикс является закрытым, поэтому нам пришлось его продублировать. Открытые части API типа BXSearchLanguages.StemWord мы используем. Разобраная строка поиска у нас транслируется не в SQL-запрос напрямую, как это сделано в модуле поиска, а в фильтр (IBXFilterItem), что пригодно для наших WebPart-фильтров.
Не решенная проблема, в решении которой специалисты Битрикс, возможно, могли бы нам помочь, заключается в некоторых ограничениях класса BXFilterItem. Во-первых, он не содержит экранирования символа "%" для запросов типа LIKE. Во-вторых, мы хотим искать подстроку с начала слова. Фильтры же Like и StartsLike нам здесь помочь не могут, нам нужно что-то типа CONTAINS, но этот оператор через BXFilterItem не поддерживается. В итоге мы пока удовлетворились поиском подстроки в произвольной части слова (кстати, если поичковый термин введен в кавычках, то так делает и стандартная реализация поиска). Для реализации требуемого поведения поиска нам потребуется написать свою реализацию IBXFilterItem, а это, как я понял, довольно трудоёмко.