<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 на текущий момент представляется более правильным подходом к кастомизации форм списков.
Андрей, спасибо за подход. Обязательно попытаюсь применить.
ОтветитьУдалитьПосты на данном блоге - на вес золото. Подписался на rss. Андрей, есть ли у тебя в гугл ридере (или другой читалке) список хороших ресурсов по SP? Не msdn, а блоги где выкладывают лучшие практики, методики кастомизации SP и т.д. ?
Можешь поделиться со своими читателями списком ресурсов, на которые подписан сам (ru\en)?
Заранее благодарю.
Спасибо за отзыв :)
УдалитьБлоги Waldek Mastykarz, Wictor Wilen, Stefan Stanev, Marc D Anderson, Christian Stahl, Christophe Humbert, Bjørn Furuknap, для начала хватит?
Добавил из этого списка 2 блога. Остальные уже в подписке. Спасибо.
УдалитьСписок тех, на кого ты подписан уже изучаю =)
ОтветитьУдалитьhttps://twitter.com/amarkeev/following