четверг, 7 марта 2013 г.

Динамическая форма списка в SharePoint Designer

Как известно, SharePoint Designer (далее SPD) генерит формы списков в “статическом” виде (Lists and libraries –> [ваш список] –> Forms –> New…). На каждое поле списка в XSLT разметке генерится отдельный тэг <tr>, в который захардкожены значения для заголовка и названия поля. Выглядит один такой <tr> следующим образом:
<tr>
    <td width="190px" valign="top" class="ms-formlabel">
        <H3 class="ms-standardheader">
            <nobr>Title<span class="ms-formvalidation"> *</span>
            </nobr>
        </H3>
    </td>
    <td width="400px" valign="top" class="ms-formbody">
        <SharePoint:FormField runat="server" id="FormField1" ControlMode="Edit" FieldName="Title" __designer:bind="{ddwrt:DataBind('u',concat('ff1',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Title')}"/>
        <SharePoint:FieldDescription runat="server" id="FieldDescription1" FieldName="Title" ControlMode="Edit"/>
    </td>
</tr>

Таких кусков в XSLT коде столько, сколько у вас на форме полей.

Этот подход приводит к очевидным неприятностям: если в список добавилось новое поле, форму придется перегенерить. Что неприятно: списки они ж ведь на то и списки, чтобы пользователи сами туда могли добавлять поля!…

Оказывается, решить эту проблему – очень легко.


В чем сложность?


Итак, по большому счету, нам нужно заменить статические “захардкоженные” поля на цикл в XSLT. Для этого нужно где-то получить информацию об этих полях – список всех полей на форме, заголовки полей, их описания, являются ли поля обязательными для заполнения и т.п.

Однако, если проанализировать XML, который приходит на вход к DataFormWebPart (SharePoint Designer генерит форму в виде DataFormWebPart) – то мы обнаружим, что в этом XML содержатся только значения полей и их Internal Name. Никаких дополнительных сведений о самих полях во входных данных нет.

Пример входного XML:
<dsQueryResponse>
    <Rows>
        <Row ID="2" 
            ContentType="Task" 
            Title="test19" 
            Predecessors="" 
            Priority="(2) Normal" 
            Status="Not Started" 
            PercentComplete="" 
            AssignedTo="" 
            Body="<div></div>" 
            StartDate="2012-11-29T08:00:00Z" 
            DueDate="">
        </Row>
    </Rows>
</dsQueryResponse>

Таким образом, нам необходимо откуда-то получить недостающие данные и написать простенький XSTL который бы все эти данные отображал.

Решение


Прокидывать откуда-то данные о полях – довольно сложно, хотя и вполне реально. Но обратите внимание, что при формировании XSLT используется разметка для серверных контролов! Это известная “фишка” XSLT-веб частей SharePoint’а – почти во всех OOTB веб-частях вы можете создавать серверные ASPX контролы прямо в коде XSLT, – и эта фишка нам поможет и в этом случае!

Помимо контролов FormField и FieldDescription, которые вы видели выше, в SharePoint на самом деле очень много разных OOTB контролов (о которых кстати мало кто знает). Есть, например, контрол ListItemProperty, который позволяет выводить значения полей текущего контекстного элемента списка, и есть контролы, позволяющие выводить значения полей из User Profiles, и много-много других полезных контролов. Среди них можно найти FieldLabel – вау, этот контрол выводит заголовок поля и даже красную звездочку, если поле Required!

Ну что ж, теперь дело за малым: вместо всех статических <tr>-ов вставить цикл. Вот готовый код:
<xsl:for-each select="@*">
    <xsl:variable name="FieldPos" select="position()" />
    <xsl:variable name="FieldName" select="name()" />
    <xsl:if test="$FieldName != 'ContentType' and $FieldName != 'ID'">
    <tr>
        <td width="190px" valign="top" class="ms-formlabel">
            <H3 class="ms-standardheader">
                <nobr><SharePoint:FieldLabel runat="server" id="FieldLabel1" FieldName="{$FieldName}" />
                </nobr>
            </H3>
        </td>
        <td width="400px" valign="top" class="ms-formbody">
            <SharePoint:FormField runat="server" id="FormField1" FieldName="{$FieldName}" ControlMode="Edit" __designer:bind="{ddwrt:DataBind('u',concat('ff',$FieldPos,$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),concat('@',$FieldName))}" />
            <SharePoint:FieldDescription runat="server" id="FieldDescription1" FieldName="{$FieldName}" ControlMode="Edit" />
        </td>
    </tr>
    </xsl:if>
</xsl:for-each>

Обратите внимание: атрибут __designer:bind нельзя удалять из разметки! Он нужен не только для SPD, без него форма просто не будет сохраняться.

Также, вы можете заметить, что я добавил проверку полей ID и ContentType, чтобы не отображать системные поля.

Если вам потребуются какие-то другие свойства полей, вы можете воспользоваться контролом FieldProperty, который дает возможность вывести значение любого свойства объекта SPField. К сожалению, значения из серверных контролов, сгенерированных внутри XSLT, в самом XSLT для анализа недоступны. Так что бить на вкладки на основе метаданных поля – придется скорее всего уже javascript’ом.

Заключение


Формы, которые генерит SharePoint Designer, часто недооценивают. Люди просто плохо знают SharePoint’овский XSLT или не понимают, как много в SharePoint можно сделать без всякого кода. На самом деле, большинство кажущихся ограничений этого подхода легко обходятся, решения по кастомизации форм удается создавать быстрее и эффективнее, и безусловно интеграция этих решений с SharePoint получается намного глубже, чем решений на основе C# кода и кастомных ASPX страниц. Наконец, кастомизации форм на основе DataFormWebPart (в отличие от некоторых других способов) прекрасно работают в Office365 и SharePoint 2013 Apps.

В общем, по-моему, стоящий подход! Попробуйте :)

P.S. Справедливости ради стоит отметить: если говорить про SharePoint 2013, использование Client Side Rendering на текущий момент представляется более правильным подходом к кастомизации форм списков.

4 комментария:

  1. Андрей, спасибо за подход. Обязательно попытаюсь применить.

    Посты на данном блоге - на вес золото. Подписался на rss. Андрей, есть ли у тебя в гугл ридере (или другой читалке) список хороших ресурсов по SP? Не msdn, а блоги где выкладывают лучшие практики, методики кастомизации SP и т.д. ?

    Можешь поделиться со своими читателями списком ресурсов, на которые подписан сам (ru\en)?

    Заранее благодарю.

    ОтветитьУдалить
    Ответы
    1. Добавил из этого списка 2 блога. Остальные уже в подписке. Спасибо.

      Удалить
  2. Список тех, на кого ты подписан уже изучаю =)
    https://twitter.com/amarkeev/following

    ОтветитьУдалить

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