Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/survey-core/src/base-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export interface ITextProcessor {
export interface ISurveyErrorOwner extends ILocalizableOwner {
getErrorCustomText(text: string, error: SurveyError): string;
}
export interface ISurveyValidatorOwner extends ISurveyErrorOwner {
createRegexValidator(validator: Base, pattern: string, flags: string): RegExp;
}
export interface IValueItemCustomPropValues {
propertyName: string;
values: Array<any>;
Expand Down Expand Up @@ -94,6 +97,7 @@ export interface ISurvey extends ITextProcessor, ISurveyErrorOwner {
focusQuestionByInstance(question: IQuestion, onError: boolean): boolean;
validateQuestion(question: IQuestion, errors: Array<SurveyError>, fireCallback: boolean): void;
validatePanel(panel: IPanel, errors: Array<SurveyError>, fireCallback: boolean): void;
createRegexValidator(question: IQuestion, validator: Base, pattern: string, flags: string): RegExp;
hasVisibleQuestionByValueName(question: IQuestion): boolean;
questionsByValueName(valueName: string): Array<IQuestion>;
processHtml(html: string, reason: string): string;
Expand Down
5 changes: 4 additions & 1 deletion packages/survey-core/src/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2747,7 +2747,7 @@ export class Question extends SurveyElement<Question>
*/
public get validators(): Array<SurveyValidator> {
return this.getArrayPropertyValue("validators", (validator: any) => {
validator.errorOwner = this;
validator.owner = this;
});
}
public set validators(val: Array<SurveyValidator>) {
Expand Down Expand Up @@ -3237,6 +3237,9 @@ export class Question extends SurveyElement<Question>
if (!!this.survey) return this.survey.getSurveyErrorCustomText(this, text, error);
return text;
}
createRegexValidator(validator: Base, pattern: string, flags: string): RegExp {
return this.survey?.createRegexValidator(this, validator, pattern, flags) || new RegExp(pattern, flags);
}
//IValidatorOwner
getValidatorTitle(): string {
return null;
Expand Down
2 changes: 1 addition & 1 deletion packages/survey-core/src/question_text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ export class QuestionTextModel extends QuestionTextBase {
if (this.inputType === "email" && !this.validators.some((v) => v.getType() === "emailvalidator")) {
const valName = this.getValidatorTitle();
const emailValidator = new EmailValidator();
emailValidator.errorOwner = this;
emailValidator.owner = this;
const validateResult = emailValidator.validate(this.value, valName);

if (!!validateResult && !!validateResult.error) {
Expand Down
15 changes: 15 additions & 0 deletions packages/survey-core/src/survey-events-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { QuestionPanelDynamicModel } from "./question_paneldynamic";
import { QuestionSignaturePadModel } from "./question_signaturepad";
import { SurveyModel } from "./survey";
import { SurveyError } from "./survey-error";
import { RegexValidator } from "./validator";
import { Trigger } from "./trigger";

export interface QuestionEventMixin {
Expand Down Expand Up @@ -322,6 +323,20 @@ export interface QuestionRemovedEvent extends QuestionEventMixin, ElementRemoved
export interface PanelAddedEvent extends PanelEventMixin, ElementAddedEvent { }
export interface PanelRemovedEvent extends PanelEventMixin, ElementRemovedEvent { }
export interface PageAddedEvent extends PageEventMixin { }
export interface CreateRegexValidatorEvent extends QuestionEventMixin {
/**
* A validator instance for which the event is raised.
*/
validator: RegexValidator;
/**
* A regular expression pattern used by the validator. You can modify this value to change the validation logic.
*/
pattern: string;
/**
* Optional regular expression flags that control additional matching behavior, such as case-insensitive searching. You can modify this value. For more information about supported flags, refer to the MDN article: [Advanced searching with flags](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#advanced_searching_with_flags).
*/
flags: string;
}
export interface ValidateQuestionEvent extends QuestionEventMixin {
/**
* A question value being validated.
Expand Down
17 changes: 16 additions & 1 deletion packages/survey-core/src/survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ import { Notifier } from "./notifier";
import {
TriggerExecutedEvent, CompletingEvent, CompleteEvent, ShowingPreviewEvent, NavigateToUrlEvent, CurrentPageChangingEvent, CurrentPageChangedEvent,
ValueChangingEvent, ValueChangedEvent, VariableChangedEvent, QuestionVisibleChangedEvent, PageVisibleChangedEvent, PanelVisibleChangedEvent, QuestionCreatedEvent,
QuestionAddedEvent, QuestionRemovedEvent, PanelAddedEvent, PanelRemovedEvent, PageAddedEvent, ValidateQuestionEvent, SettingQuestionErrorsEvent, ValidatePanelEvent,
QuestionAddedEvent, QuestionRemovedEvent, PanelAddedEvent, PanelRemovedEvent, PageAddedEvent, CreateRegexValidatorEvent,
ValidateQuestionEvent, SettingQuestionErrorsEvent, ValidatePanelEvent,
ErrorCustomTextEvent, ValidatePageEvent, ValidatedErrorsOnCurrentPageEvent, ProcessHtmlEvent, GetQuestionTitleEvent, GetTitleTagNameEvent, GetQuestionNumberEvent, GetPageNumberEvent,
GetPanelNumberEvent, GetProgressTextEvent, TextMarkdownEvent, TextRenderAsEvent, SendResultEvent, GetResultEvent, UploadFilesEvent, DownloadFileEvent, ClearFilesEvent,
ChoicesLoadedEvent, ProcessDynamicTextEvent, UpdateQuestionCssClassesEvent, UpdatePanelCssClassesEvent, UpdatePageCssClassesEvent, UpdateChoiceItemCssEvent, AfterRenderSurveyEvent,
Expand Down Expand Up @@ -486,6 +487,10 @@ export class SurveyModel extends SurveyElementCore
* @see PanelModel
*/
public onPageAdded: EventBase<SurveyModel, PageAddedEvent> = this.addEvent<SurveyModel, PageAddedEvent>();
/**
* An event that is raised when a [`RegexValidator`](https://surveyjs.io/form-library/documentation/api-reference/regexvalidator) instance is created. Use this event to customize the regular expression pattern and its flags.
*/
public onCreateRegexValidator: EventBase<SurveyModel, CreateRegexValidatorEvent> = this.addEvent<SurveyModel, CreateRegexValidatorEvent>();
/**
* An event that is raised when a question value is being validated. Use this event to add/remove/modify errors or specify a custom error message.
*
Expand Down Expand Up @@ -2278,6 +2283,16 @@ export class SurveyModel extends SurveyElementCore
getErrorCustomText(text: string, error: SurveyError): string {
return this.getSurveyErrorCustomText(this, text, error);
}
createRegexValidator(question: Question, validator: Base, pattern: string, flags: string): RegExp {
const options: CreateRegexValidatorEvent = {
question: question,
validator: <any>validator,
pattern: pattern,
flags: flags
};
this.onCreateRegexValidator.fire(this, options);
return new RegExp(options.pattern, options.flags);
}
getSurveyErrorCustomText(obj: PanelModel | Question | SurveyModel, text: string, error: SurveyError): string {
const options: ErrorCustomTextEvent = {
text: text,
Expand Down
33 changes: 18 additions & 15 deletions packages/survey-core/src/validator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Base } from "./base";
import { ISurveyErrorOwner, ISurvey, IElement, IQuestion } from "./base-interfaces";
import { ISurveyValidatorOwner, ISurvey } from "./base-interfaces";
import { SurveyError } from "./survey-error";
import { CustomError, RequreNumericError } from "./error";
import { ILocalizableOwner, LocalizableString } from "./localizablestring";
import { LocalizableString } from "./localizablestring";
import { Serializer } from "./jsonobject";
import { ConditionRunner } from "./conditions";
import { HashTable, Helpers } from "./helpers";
Expand Down Expand Up @@ -48,12 +48,14 @@ export class ValidatorResult {
* [View Demo](https://surveyjs.io/form-library/examples/javascript-form-validation/ (linkStyle))
*/
export class SurveyValidator extends Base {
public errorOwner: ISurveyErrorOwner;
public owner: ISurveyValidatorOwner;
public get errorOwner(): ISurveyValidatorOwner { return this.owner; }
public set errorOwner(val: ISurveyValidatorOwner) { this.owner = val; }
public get id(): string { return "svd" + this.uniqueId; }
public get isValidator(): boolean { return true; }
public getSurvey(live: boolean = false): ISurvey {
return !!this.errorOwner && !!(<any>this.errorOwner)["getSurvey"]
? (<any>this.errorOwner).getSurvey()
return !!this.owner && !!(<any>this.owner)["getSurvey"]
? (<any>this.owner).getSurvey()
: null;
}
/**
Expand Down Expand Up @@ -99,24 +101,24 @@ export class SurveyValidator extends Base {
return null;
}
getLocale(): string {
return !!this.errorOwner ? this.errorOwner.getLocale() : "";
return !!this.owner ? this.owner.getLocale() : "";
}
getMarkdownHtml(text: string, name: string, item?: any): string {
return !!this.errorOwner
? this.errorOwner.getMarkdownHtml(text, name, item)
return !!this.owner
? this.owner.getMarkdownHtml(text, name, item)
: undefined;
}
getRenderer(name: string): string {
return !!this.errorOwner ? this.errorOwner.getRenderer(name) : null;
return !!this.owner ? this.owner.getRenderer(name) : null;
}
getRendererContext(locStr: LocalizableString): any {
return !!this.errorOwner ? this.errorOwner.getRendererContext(locStr) : locStr;
return !!this.owner ? this.owner.getRendererContext(locStr) : locStr;
}
getProcessedText(text: string): string {
return !!this.errorOwner ? this.errorOwner.getProcessedText(text) : text;
return !!this.owner ? this.owner.getProcessedText(text) : text;
}
protected createCustomError(name: string): SurveyError {
const err = new CustomError(this.getErrorText(name), this.errorOwner);
const err = new CustomError(this.getErrorText(name), this.owner);
err.onUpdateErrorTextCallback = (err => err.text = this.getErrorText(name));
return err;
}
Expand Down Expand Up @@ -185,7 +187,7 @@ export class NumericValidator extends SurveyValidator {
if (!Helpers.isNumber(value)) {
return new ValidatorResult(
null,
new RequreNumericError(this.text, this.errorOwner)
new RequreNumericError(this.text, this.owner)
);
}
const result = new ValidatorResult(Helpers.getNumber(value));
Expand Down Expand Up @@ -425,7 +427,8 @@ export class RegexValidator extends SurveyValidator {
this.caseInsensitive = val;
}
private createRegExp(): RegExp {
return new RegExp(this.regex, this.caseInsensitive ? "i" : "");
const flags = this.caseInsensitive ? "i" : "";
return (this.owner ? this.owner.createRegexValidator(this, this.regex, flags) : null) || new RegExp(this.regex, flags);
}
}
/**
Expand Down Expand Up @@ -515,7 +518,7 @@ export class ExpressionValidator extends SurveyValidator {
this.setPropertyValue("expression", val);
}
public getValueGetterContext(): IValueGetterContext {
const owner = <any>this.errorOwner;
const owner = <any>this.owner;
if (!!owner && !!owner.getValueGetterContext) return owner.getValueGetterContext();
return super.getValueGetterContext();
}
Expand Down
16 changes: 9 additions & 7 deletions packages/survey-core/tests/helperstests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,18 @@ QUnit.test("Helpers.isValueEmpty function", function(assert) {
);
});
QUnit.test("isTwoValueEquals with validators", function(assert) {
var survey = new SurveyModel();
var validators1 = [];
var validator1 = new EmailValidator();
validator1.errorOwner = survey;
const survey = new SurveyModel();
const page = survey.addNewPage("p1");
const question = page.addNewQuestion("text", "q1");
const validators1 = [];
const validator1 = new EmailValidator();
validator1.errorOwner = question;
validator1.text = "en-text";
validators1.push(validator1);

var validators2 = [];
var validator2 = new EmailValidator();
validator2.errorOwner = survey;
const validators2 = [];
const validator2 = new EmailValidator();
validator2.errorOwner = question;
validator2.text = "en-text";
validators2.push(validator2);
survey.locale = "de";
Expand Down
21 changes: 21 additions & 0 deletions packages/survey-core/tests/surveyvalidatortests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,27 @@ QUnit.test("Regex load caseInsensitve", function(assert) {
q.value = "abc@SomeThing.com";
assert.equal(q.errors.length, 0, "#5");
});
QUnit.test("survey.onCreateRegexValidator, Issue#10766", function(assert) {
const survey = new SurveyModel({
checkErrorsMode: "onValueChanged",
elements: [{ type: "text", name: "q", validators: [{ type: "regex", regex: ".+@something." }] }]
});
survey.onCreateRegexValidator.add((sender, options) => {
assert.equal(options.validator.regex, ".+@something.", "check options.validator");
options.pattern = options.pattern + "com";
options.flags = "i";
});
const q = survey.getQuestionByName("q");
assert.equal(q.errors.length, 0, "#1");
q.value = "abc";
assert.equal(q.errors.length, 1, "#2");
q.value = "abc@something1.com";
assert.equal(q.errors.length, 1, "#3");
q.value = "abc@something.com";
assert.equal(q.errors.length, 0, "#4");
q.value = "abc@SomeThing.com";
assert.equal(q.errors.length, 0, "#5");
});

QUnit.test("question with async validators", function(assert) {
let returnResult1: (res: any) => void = (res: boolean) => {};
Expand Down