вторник, 5 июля 2011 г.

Использование HTML5 в формах списков SharePoint

Недавно я писал про RenderingTemplate и использование перегруженного ListFieldIterator для того, чтобы изменять отображение форм списков SharePoint. В качестве примера использования этого способа, я привел скриншот проекта, где поля списка распределены по вкладкам. Также, в том посте был выложен для скачивания "базовый" проект-пример на эту тему.

Сегодня я хочу еще раз вернуться к RenderingTemplate и ListFieldIterator, рассмотрев их более тщательно и иллюстрированно, на другом примере - внедряя элементы управления HTML5 в формы списков SharePoint.
Что это дает?

Как вы наверняка знаете, HTML5 добавляет множество крайне интересных типов элементов управления, в числе которых:
Модифицированное html5-поле
Expires из стандартного списка
"Announcements",
вид из браузера Opera
  • элемент управления для выбора даты/времени
  • элемент управления для ввода телефонного номера
  • элемент управления для ввода адреса электронной почты
  • элемент управления для ввода URL
Есть и другие, полный список можно найти на W3Schools. Помимо типа элементов, добавились также и атрибуты, например, мне очень нравится встроенная валидация с помощью атрибутов required и pattern. Очень интересны также списки-подсказки (атрибут list) и текст-подсказка (атрибут placeholder). Т.о., использование HTML5 позволяет серьезно сэкономить на размере страницы, улучшить её производительность за счет отказа от javascript-решений, использовать оформление и локализацию браузера.

К сожалению, список браузеров, поддерживающих HTML5-элементы управления, очень невелик. Лучшая реализация сегодня - у Opera, на втором месте Chrome, все другие браузеры очень ощутимо отстают. Причем, в Опере SharePoint вообще ведет себя неважно - некоторые скрипты не работают, иногда неверно отображается дизайн, и т.д.

Поэтому, вынужден признать, что основная практическая ценность данной статьи - в объяснении приемов подмены полей, а не в добавлении HTML5 на формы :( Возможно, через некоторое время всё изменится, когда во все современные браузеры добавят поддержку форм HTML5. Я на это очень надеюсь (ибо возможности действительно классные)!


Rendering Template

Давайте же уже приступим к делу! Первым делом, создаем проект SharePoint Empty Project, и добавляем в него SharePoint Mapped Folder CONTROLTEMPLATES. Внутри этой папки, создаем User Control, и называем его Html5ListForm.ascx:


Копируем туда код из стандартного шаблона отображения ListForm, который вы можете найти в файле 14\TEMPLATE\CONTROLTEMPLATES\DefaultTemplates.ascx. Не забываем также скопировать и весь требуемый заголовочный код (<%@ .. %>).

Далее, ищем контрол ListFieldIterator и заменяем его на его же наследника:
<%@ Register Tagprefix="my" Namespace="Html5ListForms" Assembly="$SharePoint.Project.AssemblyFullName$" %>
<!-- skipped for clarity... -->

<SharePoint:RenderingTemplate ID="Html5ListForm" runat="server">
    <!-- skipped for clarity... -->

   <my:Html5FieldIterator ID="ListFieldIterator1" runat="server"/>
    
    <!-- skipped for clarity... -->
</SharePoint:RenderingTemplate>


FormsModernizr

Вслепую подключать HTML5 нехорошо и неправильно. Для определения, поддерживает ли браузер те или иные функции HTML5, существует специальная js-библиотека, которая даже была включена в состав ASP.Net MVC. Называется эта библиотека - Modernizr, но работает она на стороне клиента. А хотелось бы использовать ее на стороне сервера, ведь иначе никакой экономии в размере страницы не получится...


И вот недавно я как раз наткнулся на реализацию server-side modernizr на PHP. Идея довольно простая: при первой загрузке, все сведения из modernizr сохраняются в cookies, потом страница сразу же перезагружается, а после этого клиентский modernizr уже не нужен, и можно спокойно осуществлять любые проверки на стороне сервера, выпарзивая нужные сведения из cookies.

Недолго думая, написал крохотный контрол, который реализует эту идею.

Этот контрол, как не трудно догадаться, тоже следует добавить в RenderingTemplate, куда-нибудь повыше:
<my:FormsModernizr ID="FormsModernizr1" runat="server"/>

Я назвал этот контрол FormsModernizr, поскольку он позволяет определять только поддержку новых типов элементов управления и их атрибутов для html-форм. Другие возможности Modernizr'а не используются.

В этом контроле я перегрузил метод OnLoad, в котором проверяю, есть ли уже cookies с данными из клиентского Modernizr'а. Если кукисы еще не поставлены, то с помощью Page.Response.Write рендерим простейшую html-страницу, содержащую:
  1. Скрипт Modernizr, который определяет, какие фишки HTML5 доступны
  2. Функцию stringify, для преобразования объекта Modernizr в json-массив
  3. Код для простановки cookies
  4. Код для перезагрузки страницы 
Далее, вызываем Page.Response.End - страница отсылается клиенту, Modernizr определяет возможности браузера, эти сведения сериализуются и попадают в кукисы. После этого страница перегружается.

Случай disabled cookies также корректно обрабатывается ;).

Если cookies наконец-то получены, то json-строка десериализуется (с помощью DataContractJsonSerializer) в статический объект FormsModernizr.Data. Ок, теперь можно использовать этот объект в классе, ответственном за отображение полей.


Html5FieldIterator

Следующий шаг - это создание класса Html5FieldIterator, который собственно и будет отображать поля. Идея состоит в перегрузке метода Render, и использовании коллекции SPContext.Current.FormContext.FieldControlCollection для стандартного отображения полей.

Для того, чтобы пометить некоторые нестандартные типы полей, которые нельзя определить по FieldType (например, поле для хранения номера телефона: у него FieldType = Text, а html5 type='tel'), мы будем использовать метаданные. Про метаданные для SPField я рассказывал в предыдущем посте.

Обычно, я еще дополнительно делаю ApplicationPage для настройки метаданных полей, и подключаю ссылку на неё на страницу настроек списка (List Settings). Но в этом примере, метаданные для полей можно просто расставить вручную, что я и сделал в FeatureReceiver.

Тип поля определили, но вот как теперь срендерить наши замены для полей?

Сделать это на самом деле довольно просто, можно воспользоваться даже несколькими разными способами. Например, довольно перспективно выглядит вариант с использованием Html Agility Pack, когда все контролы рендерятся стандартными средствами, после чего результат загружается в объект HtmlDocument, выбираются нужные элементы типа input, и им выставляется нужный тип...

Можно пойти более примитивным, но одновременно - более надежным путем, не полагаясь на html-парзинг, и полностью срендерив нужные input'ы. При этом, чтобы форма не сломалась, этим input'ам следует проставить правильные атрибуты id и name, которые бы корректно понимались SharePoint'ом. Брать эти атрибуты следует как раз из той самой коллекции правильных контролов, SPContext.Current.FormContext.FieldControlCollection, на серверной стороне они называются UniqueId и ClientId.

Код метода, заменяющего поле, у меня получился вот каким:

private static void RenderHtml5Field(HtmlTextWriter output, BaseFieldControl fieldControl, string fieldType)
{
    if (fieldControl.Field.Required && FormsModernizr.Data.Input.Required)
    {
        output.AddAttribute("required""required");
    }
 
    output.AddAttribute("type", fieldType);
    output.AddAttribute("id", fieldControl.ClientID);
    output.AddAttribute("name", fieldControl.UniqueID);
    output.RenderBeginTag(HtmlTextWriterTag.Input);
    output.RenderEndTag();
}

Перед вызовом этого метода, fieldType определяется через метаданные поля, и проверяется, поддерживается ли такой тип поля браузером - через FormsModernizr.Data. Если тип не поддерживается, рендерится стандартный контрол. Более подробно можно посмотреть в проекте-примере.


Итоги

Итак, сегодня мы детально изучили процесс подмены контролов на формах списков SharePoint, познакомились с тем, как можно использовать Modernizr на стороне сервера, и может быть, немного подизучили HTML5 :)

Замечание: Решение не будет работать в Office365 и вообще в песочнице, поскольку ascx-файл с нашим RenderingTemplate'ом должен попасть в папку CONTROLTEMPLATES, а эта возможность для Sandboxed Solutions заблокирована.

6 комментариев:

  1. А что будет, если пользователь пользуется разными браузерами?

    ОтветитьУдалить
  2. ну ребята ну в самом деле... статья для вас написана целиком, а не фрагментами. читайте целиком - тогда и таких вопросов не будет...

    если браузер не поддерживает html5, будет срендерен стандартный контрол. если поддерживает - срендерится html5. если частично поддерживает, то срендерятся частично стандартные контролы, а частично - html5

    ОтветитьУдалить
  3. Я не про это. Ну да ладно :)

    ОтветитьУдалить
  4. для одного пользователя это все тоже справедливо. главное - какой браузер используется в текущий момент.

    ОтветитьУдалить
  5. Спасибо за статьи на тему кастомизации форм. Очень полезно и интересно =)
    Такой вопрос: попробовал активировать различные HTML5 контролы и разницу увидел только в календаре. Всякие телефоны и e-mail'ы выглядели как обычные текстовые поля. При этом в коде следил, использование html5 было разрешено. У меня был браузер не очень(проверял на Chrome) или они действительно на Sharepoint'е не отличаются?

    ОтветитьУдалить
    Ответы
    1. Александр, да, десктопные браузеры плохо поддерживают типы полей HTML5. Более-менее полная поддержка, как ни смешно, только в Опере (но даже там - не все типы).

      Однако, польза всё-таки есть от HTML5 полей. Например, поле типа "tel" (телефон) в браузерах мобильных устройств должно вызывать цифровую экранную клавиатуру, а не общую. Ну и вообще, с каждым месяцем всё больше и больше добавляют поддержки HTML5, думаю и полями скоро займутся.

      Удалить

Внимание! Реклама и прочий спам будут беспощадно удаляться.