вторник, 23 апреля 2013 г.

Загадочный список Survey

Список типа "Опрос" в SharePoint - очень интересный и особенный. Прежде всего, уникальность его заключается в том, что он позволяет вставлять разделители страниц (Page separator), т.е. по сути разбивать форму создания элемента списка на страницы!

Стоит ли говорить, что в реальном мире многостраничные формы и визарды - это очень частая и востребованная опция, повсеместно используемая. К примеру, вы обязательно столкнетесь с необходимостью заполнения многостраничной формы, если будете оформлять загранпаспорт через интернет, или же через интернет покупать билеты на поезд на сайте РЖД, и т.д.


Page Separator - это особый тип поля (SPFieldType.PageSeparator), доступный только для списков типа Survey. По сути это обычная колонка списка, не содержащая никаких данных. Если при выводе полей на форму встречается колонка этого типа, это считается окончанием страницы и следующие поля не выводятся. Также для реализации этого механизма используется параметр командной строки "FirstField", который определяет, начиная с какого поля выводить форму:


Более того, переходы на разные страницы можно даже сделать условными с помощью фишки, которая называется "Branching logic":

Т.е. в зависимости от выбора пользователя, можно пропустить следующую страницу опроса, и т.д.

Конечно, список типа "Survey" ориентирован прежде всего на создание опросов. Однако, в некоторых ситуациях его вполне можно использовать и для других бизнес нужд. Живой пример: регистрации на встречи Russian SharePoint User Group на сайте rusug.net всегда были реализованы именно с помощью обычного списка "Survey" (хоть и без разбития на страницы).

Однако, если вы хотите сделать свой список и свою форму на основе Survey, необходимо учитывать следующие основные ограничения этого списка:
  1. Ответить на опрос можно только один раз. Это автоматически означает, что один пользователь может заполнить форму лишь единожды, т.е. создать только 1 элемент списка. Для регистрации на событие это вполне нормально, а вот оформление заказов таким образом уже не сделаешь...
    Update: Спасибо Алексею за комментарий! Оказывается, при создании списка есть дополнительная опция "Allow multiple responses", которая решает эту проблему.
  2. Бэкэнд заточен под создание вопросов и ответов, и везде используется соответствующая терминология. Если вы планируете давать клиенту возможность изменять этот список (добавлять в него поля и т.п.), надо готовиться, что придется пускаться в пространные объяснения, что это там за вопросы и ответы такие :)
  3. Веб-интерфейс настроек списка сильно урезан. Например, через настройки списка в браузере нельзя создавать представления (хотя через SharePoint Designer или программно представления создаются без проблем).
  4. Survey - это, пожалуй, единственный тип списка, у которого до сих пор остался старый тип тулбара, из 2007го SharePoint'а. Интеграция с риббоном отсутствует.
Проверки на тип списка, как водится, захардкожены по всему SharePoint'у :) Поэтому обойти эти ограничения непросто, если вообще возможно.

В основном из-за первого ограничения, довольно сложно использовать Survey-списки для каких-то своих нужд в неизменном виде. Я постарался разобраться во внутреннем устройстве этого типа списка, и выяснить, можно ли обойти его ограничения и использовать сходный подход для создания многостраничной формы.

Как устроен Survey List


Оказывается, реализация многостраничности в опросах довольно простая. Она основывается на переопределенном RenderingTemplate. Вы можете найти этот шаблон в файле CONTROLTEMPLATES/DefaultTemplates.ascx, называется SurveyForm:

<SharePoint:RenderingTemplate id="SurveyForm" runat="server">
    <Template>
        <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop" RightButtonSeparator="&amp;#160;" runat="server">
            <Template_RightButtons>
                <SharePoint:NextPageButton runat="server"/>
                <SharePoint:SaveButton Text="<%$Resources:wss,tb_survey_save%>" accesskey="<%$Resources:wss,tb_survey_save_AK%>" runat="server"/>
                <SharePoint:MultiPageGoBackButton runat="server"/>
             </Template_RightButtons>
        </wssuc:ToolBar>
        <SharePoint:FormToolBar runat="server"/>
        <SharePoint:ItemValidationFailedMessage runat="server"/>
        <table class="ms-formtable" style="margin-top: 8px;" border="0" cellpadding="0" cellspacing="0" width="100%">
        <SharePoint:SurveyFieldIterator runat="server"/>
        </table>
        <table cellpadding="0" cellspacing="0" width="100%"><tr><td class="ms-formline"><img src="/_layouts/15/images/blank.gif?rev=23" width='1' height='1' alt="" /></td></tr></table>
        <table cellpadding="0" cellspacing="0" width="100%" style="padding-top: 7px"><tr><td width="100%">
        <SharePoint:ItemHiddenVersion runat="server"/>
        <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&amp;#160;" runat="server">
                <Template_Buttons>
                        <SharePoint:InitContentType runat="server"/>
                        <SharePoint:CreatedModifiedInfo runat="server"/>
                </Template_Buttons>
                <Template_RightButtons>
                    <SharePoint:NextPageButton runat="server"/>
                    <SharePoint:SaveButton Text="<%$Resources:wss,tb_survey_save%>" accesskey="<%$Resources:wss,tb_survey_save_AK%>" runat="server"/>
                    <SharePoint:MultiPageGoBackButton runat="server"/>
                </Template_RightButtons>
        </wssuc:ToolBar>
        </td></tr></table>
    </Template>
</SharePoint:RenderingTemplate>

Для многостраничной логики, здесь задействованы контролы, обозначенные жирным. Наиболее интересным на первый взгляд здесь представляется контрол SurveyFieldIterator. Он реализует логику отображения полей на форме. Если подсмотреть на этот класс через рефлектор или его аналог, вы увидите, что логика отображения на удивление простая и даже в некотором роде элегантная (если элегантный говнокод существует - то это как раз он! :) ).

Хорошие новости: SurveyFieldIterator будет работать для любого списка - т.е. довольно легко его задействовать в каком-нибудь другом, кастомном списке. Плохие новости: в контролы NextPageButton и SaveButton захардкожены проверки на шаблон списка (SPListTemplateType.Survey), и использовать их в кастомном списке не выйдет :(

Заменить эти контролы очень непросто, потому что они используют некоторые методы, помеченные как internal (т.е. только через Reflection, чего я стараюсь избегать). Обойтись без них тоже никак - ведь именно кнопка "NextPageButton" обеспечивает промежуточное сохранение элемента списка и реализует кучу другой интересной логики. В общем, после нескольких часов ковыряния в рефлекторе, я так и не смог найти нормального способа сделать многостраничный список в SharePoint таким же способом, как сделан список Survey, т.е. через ListFieldIterator... :(

Помимо невозможности замены NextPageButton, есть кстати и другие ограничения. К примеру, поле PageSeparator через веб-интерфейс доступно только для списков типа Survey. Впрочем, это поле очень легко добавить программно, или например вот таким простейшим PowerShell-скриптом:

$w = get-spweb http://localhost
$l = $w.Lists["My List"]
$l.Fields.Add("PageSeparator", [Microsoft.SharePoint.SPFieldType]::PageSeparator, $false, $false, $null)

После добавления этого поля, можно перейти в настройки списка и изменить порядок колонок, так чтобы PageSeparator был расположен в нужном вам месте формы. Как правило, число шагов визарда изменяется крайне редко, поэтому давать возможность пользователям добавлять PageSeparator через интерфейс скорее всего не потребуется.

Branching Logic иногда тоже было бы полезно использовать, но и эта фишка в UI доступна только для списков типа "Опрос". Так что тут снова придется либо действовать PowerShell-ом, либо писать отдельный UI - если требуется, чтобы пользователи сами могли настраивать логику отображения формы (что, кстати, требуется крайне редко).

Также, следует отметить, что стандартные кнопки Save и Cancel работать не должны. Впрочем, убрать их с Ribbon довольно легко следующим Custom Action:

<CustomAction Id="RemoveRibbonButton" Location="CommandUI.Ribbon">
  <CommandUIExtension>
    <CommandUIDefinitions>
     <CommandUIDefinition Location="Ribbon.ListForm.Edit.Commit" />
    </CommandUIDefinitions>
  </CommandUIExtension>
</CustomAction>

На этом пункте, я посчитал свое исследование законченным, и надеюсь результаты этого исследования вам было интересно читать, пусть мне и не удалось реализовать изначальную мою идею :)

Заключение


В SharePoint много вещей, которые мало кто использует и о которых мало кто знает. На удивление, даже программисты с 3-4 летним стажем разработки под SharePoint часто не знают про некоторые базовые функции платформы... Нередко эти вещи можно использовать очень эффективно, сокращая трудозатраты на реализацию того или иного функционала в десятки раз.

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

Поэтому, когда хочется использовать какую-то возможность SharePoint, обязательно сначала создайте проект-пример, и чем больше он будет приближенным к тому что вам в итоге нужно - тем лучше. Не бойтесь потратить на исследования даже 1-2 дня, потому что вы потратите как минимум в два раза больше, если начнете писать этот код сразу же в основной проект.

Что же касается многостраничных форм - реализовывать их в SharePoint сейчас на удивление тяжело - на мой взгляд это в некотором роде прокол со стороны MS, ведь бизнес-кейс это очень частый. Есть у них конечно полуживой InfoPath и малополезный Scenario Framework, но InfoPath - действительно полуживой и полон собственных ограничений и багов, а Scenario Framework - мало что дает по сравнению с обычным ASP.Net-визардом, созданным "вручную". В настоящий момент наиболее перспективным подходом к задаче многостраничных форм мне лично видится использование Client Side Rendering (CSR) из SharePoint 2013 - но это пока что подход не проверенный. Если будет шанс реализовать многостраничную форму на CSR, я этим обязательно с вами поделюсь, а на сегодня у меня все. Удачи!

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

  1. В настройках версий 2007-2010 есть "Разрешить отвечать несколько раз", так что надо проверить, прав ли ты, говоря, что "Ответить на опрос можно только один раз..."

    ОтветитьУдалить
    Ответы
    1. уточняю: при создании Survey на 2007 есть такой вопрос:
      Show user names in survey results?
      Yes No

      Allow multiple responses?
      Yes No

      Удалить
    2. Ого, здорово, не знал! Спасибо :) Я обновлю пост!

      Удалить
  2. Андрей, не подскажите как восстановить графическую сводку в опросе? Этот вид просмотра результатов голосования перестает работать после изменения веб-части. Если перейти в вид "Графическая сводка ответов" - нажать "Изменить страницу" - выбрать "Свойства веб-части" - верхнее меню "Select current view" меняем на любое значение - возникает предупреждение "Переключение модет деактивировать подключения веб-части зависящие от колонок-полей этого вида просмотра"
    Вот скриншот: http://s017.radikal.ru/i432/1503/da/e3ac2c13a31b.png

    ОтветитьУдалить
    Ответы
    1. Вот этот способ для ShP2007 не помогло "Issues caused by removing web parts from standard Survey pages" http://blogs.msdn.com/b/bettertogether/archive/2013/12/04/10063258.aspx

      Удалить
    2. Удалось решить проблему нерабочего просмотра списка survey в виде графической сводки, правкой файла sumary.aspx, подробно рассказал в своем блоге http://aendrous.blogspot.de/2015/04/survey.html

      Удалить

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

Примечание. Отправлять комментарии могут только участники этого блога.