Создание нового типа поля для MS SharePoint на примере простого проверяемого поля

от автора

При работе с SharePoint часто возникает необходимость сделать свое собственное поле для каких-либо специфических задач. Одна из таких задач — возможность проверки текстовых полей, например на правильность заполнения Email или каких-либо данных об Организации: ИНН, КПП и д.р.



Самым простым и удобным в этом случае является применение регулярных выражений, поэтому на них и остановимся.
Начнем с внутреннего представления поля в Sharepoint. Это класс отнаследованный от класса SPField. Сами поля хранятся в базе Sharepoint в виде зашифрованного XML. На основании ХML(схемы поля) создается объект отнаследованный от SPField. Схема обычного текстового поля выглядит так:

<Field ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="Title" Group="_Hidden" Type="Text" DisplayName="Наименование" Required="TRUE" FromBaseType="TRUE" /> 

Для начала создадим класс нашего нового поля:

class RegExpField : SPFieldText     {         public RegExpField(SPFieldCollection fields, string fieldName) : base(fields, fieldName) 		{ 		}         public RegExpField(SPFieldCollection fields, string typeName, string displayName)             : base(fields, typeName, displayName) 		{ 		} } 

У поля должно быть 2 настраиваемых свойства — это свойство хранящие регулярное выражения для его валидации и свойство, хранящие текст ошибки выводящиеся в случаи несоответствии строки регулярному выражению.
С кастомными свойства у SharePoint полей есть определенные сложности, самым оптимальным является использования да простят меня Боги рефлексии. Нам нужно добавить 2 метода в наше поле:

//Записываем значение поля в текущую схему через рефлексивный вызов метода private void SetFieldAttribute(string attribute, string value) {     Type baseType;     BindingFlags flags;     MethodInfo mi;      baseType = typeof(RegExpField);     flags = BindingFlags.Instance | BindingFlags.NonPublic;     mi = baseType.GetMethod("SetFieldAttributeValue", flags);     mi.Invoke(this, new object[] { attribute, value }); } //Считываем значение поля из текущей схемы через рефлексивный вызов метода  private string GetFieldAttribute(string attribute) {     Type baseType;     BindingFlags flags;     MethodInfo mi;      baseType = typeof(RegExpField);     flags = BindingFlags.Instance | BindingFlags.NonPublic;     mi = baseType.GetMethod("GetFieldAttributeValue",                                 flags,                                 null,                                 new Type[] { typeof(String) },                                 null);      object obj = mi.Invoke(this, new object[] { attribute });      if (obj == null)         return "";     else         return obj.ToString(); }  

Добавим 2 свойства в поле:

public string ValidRegExp {     get     {         return GetFieldAttribute("ValidRegExp");     }     set     {         SetFieldAttribute("ValidRegExp", value);     } } public string ErrorMessage {     get     {         return GetFieldAttribute("ErrorMessage");     }     set     {         SetFieldAttribute("ErrorMessage", value);     } } 

Теперь нужно добавить возможность редактировать настройки поля пользователю. Для этого создаем UserControl RegExpFieldEdit.ascx и наследуем его от интерфейса IFieldEditor. У интерфейса одно свойство и два метода которые нужно переопределить. Выглядеть это будет так:

public bool DisplayAsNewSection {     get     {         return true;     } }  //Устанавливаем значения поля в контролы public void InitializeWithField(SPField field) {     if (!IsPostBack)             {                 if (field is RegExpField)                 {                           var Validfield = field as RegExpField;                         RegExp.Text = Validfield.ValidRegExp;                         ErrorMessage.Text = Validfield.ErrorMessage;                 }             } }  //Переносим значения контролов в поле public void OnSaveChange(SPField field, bool isNewField) {     if (field is RegExpField)     {         var Validfield = field as RegExpField;         Validfield.ValidRegExp = RegExp.Text;         Validfield.ErrorMessage = ErrorMessage.Text;     } } 

Следующим шагом нужно сказать SharePoint, что все, что мы создали — является полем. Для этого создадим XML в которым пропишем новый тип поля. Для начала «замапим» папку XML из SharePoint в наш проект и создадим новый XML файл c именем Fldtypes_RegExpField.xml, и содержимым:

<FieldTypes>   <FieldType>     <Field Name="TypeName">RegExpField</Field>     <Field Name="ParentType">Text</Field>     <Field Name="TypeDisplayName">Проверяемое текстовое поля</Field>     <Field Name="TypeShortDescription">Проверяемое текстовое поля</Field>     <Field Name="FieldTypeClass">RegExpField.RegExpField, $SharePoint.Project.AssemblyFullName$</Field>     <Field Name="FieldEditorUserControl">/_controltemplates/15/RegExpFieldEdit.ascx</Field>   </FieldType> </FieldTypes> 

Теперь нужно разобраться с методами рендеринга поля. В SharePoint есть два метода отрисовки поля:

  • Серверный рендеринг – для этого используется некий WebControl отнаследованый от BaseFieldControl, данная методика является устаревший в рамках SharePoint 2013. Поэтому мы не будет создавать этот контрол.
  • Клиентский рендеринг (Client side rendering) – под ним понимается отриcовка поля непосредственно использующая JavaScript в браузере клиента. Он появился в SharePoint 2013 и повсеместно там используется.

Для нашего поля будем использовать клиентский рендеринг. Для начала создадим некоторый JS файл RegExpField.js в котором будет производиться отрисовка поля на клиенте. Для привязки файла к нашему полю используется механизм JSLink. У каждого поля есть свойство JSLink. В нем указываются JS файлы которые необходимы загружать на клиент для работы отрисовки поля. Перегрузим свойство в нашем классе поля.

public override string JSLink {     get     {         return "/_layouts/15/RegExpField/RegExpField.js";     } } 

Еще нужно передавать наши новые параметры на клиент. Для этого есть специальный метод в SPField. Перегрузим и добавим в него наши параметры. Вот как это выглядит:

public override Dictionary<string, object> GetJsonClientFormFieldSchema(SPControlMode mode)         {             var formtctx = base.GetJsonClientFormFieldSchema(mode);             formtctx["ValidRegExp"] = ValidRegExp;             formtctx["ErrorMessage"] = ErrorMessage;             return formtctx;         } 

Здесь мы берем уже сформированный контекст и добавляем в него наши свойства. Данные из этого метода сериализуются SharePoint’ом в JSON при помощи класса JavaScriptSerializer.
Теперь нужно зарегистрировать шаблон отрисвоки поля на клиенте для этого напишем в JS файле следующий код:

RegExpFieldTemplate = function RegExpFieldTemplate () { }  RegExpFieldTemplate.$$cctor = function RegExpFieldTemplate $$$cctor() {     if (typeof (SPClientTemplates) != "undefined")         SPClientTemplates.TemplateManager.RegisterTemplateOverrides(RegExpFieldTemplate.createRenderContextOverride()); // регистрируется объект с функциями отрисовки } RegExpFieldTemplate.createRenderContextOverride = function () {     var RegExpFieldTemplateContext = {};     RegExpFieldTemplateContext.Templates = {};     RegExpFieldTemplateContext.Templates['Fields'] = {         RegExpField: {             View: RegExpFieldTemplate.renderViewControl,             DisplayForm: RegExpFieldTemplate.renderDisplayControl,             NewForm: RegExpFieldTemplate.renderEditControl,             EditForm: RegExpFieldTemplate.renderEditControl,          }//создаем объект в котором есть поле с именем нашего типа поля, в этом объекте определены функции отрисовки для каждого возможного варианта      };     return RegExpFieldTemplateContext; }  function RegExpField_init() {     RegExpFieldTemplate.$$cctor(); }; RegExpField_init(); 

Теперь разберем отрисовку каждого варианта отображения в отдельности.
Начнем c отрисовки на отображении(View)

RegExpFieldTemplate.renderViewControl = function (renderCtx, field, item, list) {      if (renderCtx.inGridMode === true) {         field.AllowGridEditing = false; // Отключаем режим редактирования поля если выбран режим GridView     }      return STSHtmlEncode(item[field.Name]);//Берем значения поля из item. Предварительно производим Encode Html символов и отправляем значение в рендеринг  } 

В качестве возвращаемого значения функции возвращается html разметка в виде строки.
На форме просмотра будет использовать следующий код:

RegExpFieldTemplate.renderDisplayControl = function (renderCtx) {      return STSHtmlEncode(renderCtx.CurrentFieldValue);//Берем значение из контекста сформированного для поля и производим Encode Html } 

Остался рендеринг поля на форме редактирования и на форме создания элемента. Вот что получилось:

//Создаем объект валидатора поля RegExpFieldTemplate.ValidatorValue = function (stringRegExp, errorMessage) {     //в объекте должна быть всего 1 функция она производит валидацию     RegExpFieldTemplate.ValidatorValue.prototype.Validate = function (value) {         //Проверяем наличие данных в поле и наличие регулярного выражения         if (value && stringRegExp) {              var reg = new RegExp(stringRegExp);                     //Проверяем проходит ли валидацию значение поля             if (!reg.test(value)) {                         //В качестве возвращаемого значения создается объект,                         // в котором первый параметр показывает наличие ошибки, второй текст ошибки                         return new SPClientFormsInclude.ClientValidation.ValidationResult(true, errorMessage);//                 }             }         return new SPClientForms.ClientValidation.ValidationResult(false);     }; }  RegExpFieldTemplate.renderEditControl = function (rCtx) {     if (rCtx == null)         return '';     var frmData = SPClientTemplates.Utility.GetFormContextForCurrentField(rCtx);// Получение данных формы, функция по контексту      //возвращает специальный объект с данными и функциями для регистрации событий поля      if (frmData == null || frmData.fieldSchema == null)         return '';     var _inputElt;     var _value = frmData.fieldValue != null ? frmData.fieldValue : '';// В случаи если с сервера придут не корректные данные     var _inputId = frmData.fieldName + '_' + '_$RegExp' + rCtx.FormUniqueId;//Формируется Id input'а ввода     var validators = new Eos.Fields.ClientControls.ClientValidation.ValidatorSet();//Создается объект валидатора, в него нужно будет записать все используемые в поле валидаторы     if (frmData.fieldSchema.Required) {//Проверка на наличие включеной настройки обязательности заполнения данного поля         // Если поле является обязательным к заполнению нужно добавить специальный валидатор проверки заполненности поля         validators.RegisterValidator(new Eos.Fields.ClientControls.ClientValidation.RequiredValidator());     }     //Здесь происходит регистрация нашего валидатора указанного в настройках поля      validators.RegisterValidator(new RegExpFieldTemplate.ValidatorValue(rCtx.CurrentFieldSchema.ValidRegExp,rCtx.CurrentFieldSchema.ErrorMessage));     //Регистрация объекта валидации     frmData.registerClientValidator(frmData.fieldName, validators);      //регистрируется функция вызываемая после добавления HTML разметки в DOM     frmData.registerInitCallback(frmData.fieldName, InitControl);     //регистрируется функция вызываемая при необходимости фокусировки на данном поле, допустим       //после того как поле не прошло валидацию     frmData.registerFocusCallback(frmData.fieldName, function () {         if (_inputElt != null) {             _inputElt.focus();             if (browseris.ie8standard) {                 var range = _inputElt.createTextRange();                  range.collapse(true);                 range.moveStart('character', 0);                 range.moveEnd('character', 0);                 range.select();             }         }     });     //регистрируется функция вызываемая для вывода ошибки поля     frmData.registerValidationErrorCallback(frmData.fieldName, function (errorResult) {         //Стандартная функция рисующая ошибку у заданного элемента, рисуется в виде span'a внизу поля         SPFormControl_AppendValidationErrorMessage(_inputId, errorResult);     });     //регистрируется функция вызываемая для получения значений из поля     frmData.registerGetValueCallback(frmData.fieldName, function () {         return _inputElt == null ? '' : _inputElt.value;     });      //обновляет значение поля хранящиеся в скрытом hidden (На самом деле так и не понял зачем это делается, но решил добавить)     frmData.updateControlValue(frmData.fieldName, _value);     //Формируем разметку поля на основании контекста     var result = '<span dir="' + STSHtmlEncode(frmData.fieldSchema.Direction) + '">';     result += '<input type="text" value="' + STSHtmlEncode(_value) + '" maxlength="' + STSHtmlEncode(frmData.fieldSchema.MaxLength) + '" ';     result += 'id="' + STSHtmlEncode(_inputId) + '" title="' + STSHtmlEncode(frmData.fieldSchema.Title);     result += '" class="ms-long ms-spellcheck-true ' + (rCtx.CurrentFieldSchema.DoubleWidth ? 'InputDoubleWidth' : '') + ' " />';     result += '<br /></span>';//      return result;     //Описываем функцию которая срабатывает после добавления разметки в DOM     function InitControl() {         //Получаем наш Input         _inputElt = document.getElementById(_inputId);         if (_inputElt != null)             //Добавляем событие изменения             AddEvtHandler(_inputElt, "onchange", OnValueChanged);     }     //Описываем функцию изменения в input     function OnValueChanged() {         if (_inputElt != null)             //обновляет значение поля хранящиеся в скрытом hidden (На самом деле так и не понял зачем это делается, но решил добавить)             frmData.updateControlValue(frmData.fieldName, _inputElt.value);     }   } 

Тут я постарался максимально описать все в комментариях к коду, поэтому думаю дополнительно описывать тут нечего.
В общем и целом, наше поле готово. Осталось развернуть решение на SharePoint и настроить наше новое поле. Поле в Sharepoint будет выглядеть так:

Создание нового типа поля такого уровня занимает пару часов. И оно может сильно облегчить работу с формами пользователей. Его так же можно легко расширить добавив в него маску ввода, допустим с помошью библиотеки jQuery Masked Input

Все исходники доступны на GitHub.

ссылка на оригинал статьи http://habrahabr.ru/post/262187/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *