При работе с 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/
Добавить комментарий