SPFieldIterator наше все или как изменить внешний вид List Form в SharePoint

Если вы уже сталкивались с задачами кастомизацией видимости полей в элементе или задачами связанными с расширением формы элемента, то вы наверное уже заходили на блоги именитых в Рунете блоггеров, которые публиковали подобного рода статьи. И во многих идет перевес в сторону использования SPD (в простонародье SharePointDesigner ).
Хочу немного поделиться своими впечатлениями по работе с данной кастомизацией и покажу небольшой пример.




Для начала немного из теории


Шаблон по которому рисуются формы списков находится в файле DefaultTemplates.ascx (папка CONTROLTEMPLATES) и называется ListForm (Листинг 1).

<SharePoint:RenderingTemplate id="ListForm" runat="server">
    <Template>
        <span id='part1'>
            <SharePoint:InformationBar runat="server"/>
            <div id="listFormToolBarTop">
            <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop" RightButtonSeparator="&#160;" runat="server">
                    <Template_RightButtons>
                        <SharePoint:NextPageButton runat="server"/>
                        <SharePoint:SaveButton runat="server"/>
                        <SharePoint:GoBackButton runat="server"/>
                    </Template_RightButtons>
            </wssuc:ToolBar>
            </div>
            <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:ChangeContentType runat="server"/>
            <SharePoint:FolderFormFields runat="server"/>
            <SharePoint:ListFieldIterator runat="server"/>
            <SharePoint:ApprovalStatus runat="server"/>
            <SharePoint:FormComponent TemplateName="AttachmentRows" runat="server"/>
            </table>
            <table cellpadding="0" cellspacing="0" width="100%"><tr><td class="ms-formline"><img src="/_layouts/images/blank.gif" 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"/>
            <SharePoint:ParentInformationField runat="server"/>
            <SharePoint:InitContentType runat="server"/>
            <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&#160;" runat="server">
                    <Template_Buttons>
                        <SharePoint:CreatedModifiedInfo runat="server"/>
                    </Template_Buttons>
                    <Template_RightButtons>
                        <SharePoint:SaveButton runat="server"/>
                        <SharePoint:GoBackButton runat="server"/>
                    </Template_RightButtons>
            </wssuc:ToolBar>
            </td></tr></table>
        </span>
        <SharePoint:AttachmentUpload runat="server"/>
    </Template>
</SharePoint:RenderingTemplate>

Листинг 1. Шаблон по которому рисуются формы списков — ListForm

Контрол 
<SharePoint:ListFieldIterator runat=«server»/>
отвечает за вывод полей. Идея заключается в том, что бы сделать свой ListFieldIterator и подключить его для нужных списков.

Можно сделать несколько ListFieldIterator-оров для разных списков, можно даже для каждой формы (NewForm.aspx, DispForm.aspx, EditForm.aspx) одного и того же списка назначить свой итератор.

Делаем свой итератор

1. Сделать наследника ListFieldIterator — Листинг 2.

public class MyFieldIterator : ListFieldIterator
{
    protected override void CreateChildControls()
    {
        // массивы названий полей можно получать динамически
        // здесь, для примера, они прохардкожены

        var readOnlyFieldsInternalNames = new string[] { "Title", "Status" };
        var excludeFieldsInternalNames = new string[] { "SalesPoint" };


        for (int ii = 0; ii < base.Fields.Count; ii++)
        {
            var currentFiledInternalName = base.Fields[ii].InternalName;

            if (base.IsFieldExcluded(base.Fields[ii]) || 
                                excludeFieldsInternalNames.Contains(currentFiledInternalName))
                continue;


            var fieldIterator = new ListFieldIterator()
            {
                ControlMode = readOnlyFieldsInternalNames.Contains(currentFiledInternalName) ? 
                                SPControlMode.Display : 
                                SPContext.Current.FormContext.FormMode,

                ExcludeFields = string.Join(";#", base.Fields.Cast<SPField>()
                            .Where(ff => ff.InternalName != currentFiledInternalName)
                            .Select(ff => ff.InternalName).ToArray())
            };

            this.Controls.Add(fieldIterator);
        }
    }
}

Листинг 2. Наследник ListFieldIterator который прячет и переводит в ReadOnly заданные поля

Ничего сложного. Здесь для каждого поля, которое нужно отобразить, создается свой ListFieldIterator с нужным ControlMode. В ExcludeFields передается строка содержащая поля которые нужно исключить, т.е. все кроме текущего.

2. Сделать файл MyListForm.aspx и положить его в папку CONTROLTEMPLATES
В MyListForm.aspx скопируйте весь <SharePoint:RenderingTemplate id=«ListForm» runat=«server»>, замените id=«ListForm» на id=«MyListForm”, <SharePoint:ListFieldIterator runat=»server"/> на <My:MyFieldIterator runat=«server»> — Листинг 3.

...
<%@ Register TagPrefix="My" Assembly="$SharePoint.Project.AssemblyFullName$" namespace="My" %>
<SharePoint:RenderingTemplate id="MyListForm" runat="server"><%--<SharePoint:ListFieldIterator runat="server"/>--%>
            <My:MyFieldIterator  runat="server" />
...
</SharePoint:RenderingTemplate>

Листинг 3. Свой шаблон вывода формы списков

Отметим, что MyListForm.aspx обязательно должен лежать в корне CONTROLTEMPLATES, если положить в под-папку не взлетит.

3. Указать для какой формы использовать шаблон MyListForm.
Для этого в ListDefinition укажите Template для нужной формы — Листинг 4.

...
    <Forms>
      <Form Type="DisplayForm" Url="DispForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
      <Form Type="EditForm" Url="EditForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" Template="MyListForm" />
      <Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" Template="MyListForm"/>
...

Листинг 4. ListDefinition с указанием шаблонов для вывода форм

Выводы



Использование итераторов по моему мнению, это штука более динамичная и когда делать кастомизацию надо исходить из того чтобы параметры для работы итератора были описаны где нибудь в независимом месте, я всегда использую для этих дел XML и описываю соответствующую схему или генерирую ее динамически. Должен получаться своего рода контроллер. А если нужно что-то единичное сделать, то можно использовать кучу разных простых и быстрых вариантов (SPD, JS.)

Небольшой пример из жизни.

Задача заключалась в том, что есть папка определенного типа контента и все элементы создаваемые внутри папки наследуют некоторые атрибуты и их необходимо скрыть при создании элемента внутри папки, но надо не забывать что элементы из этого типа контента могут создаются и не в папках с наследованием элементов и в этом случае отображать столбцы для заполнения

Мы используем уже существующий пример и добавляем в него - Листинг 5.



private static bool IsParentItem(SPList list, string rootFolderUrl)
        {
            var parentItem = false;
            var check = list.ParentWeb.GetFolder(rootFolderUrl).Exists;
            if (check)
            {
                bool checkItm = list.ParentWeb.GetFolder(rootFolderUrl).Item != null;
                if (checkItm)
                {
                    parentItem = list.ParentWeb.GetFolder(rootFolderUrl).Item.ContentType.Parent.Id == {ContentTypeId};
                }
            }
            return parentItem;
        }

Листинг 5. Проверка родителя

И добавляем дополнительное условие - Листинг 5


 if (base.IsFieldExcluded(base.Fields[ii]) || 
                                (excludeFieldsInternalNames.Contains(currentFiledInternalName)&& isParent))
                continue;

Листинг 5. Добавление в условие из Листнига 2 доп условия

В итоге мы скроем поля которые создались внутри папки и покажем их при создание вне папок.

Для того чтобы не повторяться и не было критики на плагиаты вот источник (Который ссылается тоже на источник).

Комментарии

Популярные сообщения из этого блога

Полезности под рукой: Приятные SharePoint уведомления для пользователя

Полезности под рукой: Отправка писем при помощи REST API в SharePoint 2013