Nailed phone number input

This commit is contained in:
Pieter Vander Vennet 2020-09-26 21:00:03 +02:00
parent 585a4243f7
commit 07c85bb218
3 changed files with 52 additions and 19 deletions

View file

@ -11,14 +11,14 @@ export class TextField extends InputElement<string> {
private readonly _isArea: boolean; private readonly _isArea: boolean;
private readonly _textAreaRows: number; private readonly _textAreaRows: number;
private readonly _isValid: (string) => boolean; private readonly _isValid: (string, country) => boolean;
constructor(options?: { constructor(options?: {
placeholder?: string | UIElement, placeholder?: string | UIElement,
value?: UIEventSource<string>, value?: UIEventSource<string>,
textArea?: boolean, textArea?: boolean,
textAreaRows?: number, textAreaRows?: number,
isValid?: ((s: string) => boolean) isValid?: ((s: string, country?: string) => boolean)
}) { }) {
super(undefined); super(undefined);
const self = this; const self = this;
@ -28,7 +28,7 @@ export class TextField extends InputElement<string> {
this.value = options?.value ?? new UIEventSource<string>(undefined); this.value = options?.value ?? new UIEventSource<string>(undefined);
this._textAreaRows = options.textAreaRows; this._textAreaRows = options.textAreaRows;
this._isValid = options.isValid ?? ((str) => true); this._isValid = options.isValid ?? ((str, country) => true);
this._placeholder = Translations.W(options.placeholder ?? ""); this._placeholder = Translations.W(options.placeholder ?? "");
this.ListenTo(this._placeholder._source); this.ListenTo(this._placeholder._source);
@ -37,6 +37,7 @@ export class TextField extends InputElement<string> {
self.IsSelected.setData(true) self.IsSelected.setData(true)
}); });
this.value.addCallback((t) => { this.value.addCallback((t) => {
console.log("Setting actual value to", t);
const field = document.getElementById("txt-"+this.id); const field = document.getElementById("txt-"+this.id);
if (field === undefined || field === null) { if (field === undefined || field === null) {
return; return;
@ -74,10 +75,12 @@ export class TextField extends InputElement<string> {
const field = document.getElementById("txt-" + this.id); const field = document.getElementById("txt-" + this.id);
const self = this; const self = this;
field.oninput = () => { field.oninput = () => {
// How much characters are on the right, not including spaces?
// @ts-ignore // @ts-ignore
const endDistance = field.value.length - field.selectionEnd; const endDistance = field.value.substring(field.selectionEnd).replace(/ /g,'').length;
// @ts-ignore // @ts-ignore
const val: string = field.value; let val: string = field.value;
if (!self.IsValid(val)) { if (!self.IsValid(val)) {
self.value.setData(undefined); self.value.setData(undefined);
} else { } else {
@ -86,8 +89,21 @@ export class TextField extends InputElement<string> {
// Setting the value might cause the value to be set again. We keep the distance _to the end_ stable, as phone number formatting might cause the start to change // Setting the value might cause the value to be set again. We keep the distance _to the end_ stable, as phone number formatting might cause the start to change
// See https://github.com/pietervdvn/MapComplete/issues/103 // See https://github.com/pietervdvn/MapComplete/issues/103
// We reread the field value - it might have changed! // We reread the field value - it might have changed!
// @ts-ignore // @ts-ignore
self.SetCursorPosition(field.value.length - endDistance); val = field.value;
let newCursorPos = endDistance;
while(newCursorPos >= 0 &&
// We count the number of _actual_ characters (non-space characters) on the right of the new value
// This count should become bigger then the end distance
val.substr(newCursorPos).replace(/ /g, '').length <= endDistance
){
newCursorPos --;
}
newCursorPos++;
// @ts-ignore
self.SetCursorPosition(newCursorPos);
}; };
if (this.value.data !== undefined && this.value.data !== null) { if (this.value.data !== undefined && this.value.data !== null) {
@ -127,7 +143,7 @@ export class TextField extends InputElement<string> {
if (t === undefined || t === null) { if (t === undefined || t === null) {
return false return false
} }
return this._isValid(t); return this._isValid(t, undefined);
} }
} }

View file

@ -140,9 +140,7 @@ export default class ValidatedTextField {
} }
return parsePhoneNumberFromString(str, country?.toUpperCase())?.isValid() ?? false return parsePhoneNumberFromString(str, country?.toUpperCase())?.isValid() ?? false
}, },
(str, country: any) => { (str, country: any) => parsePhoneNumberFromString(str, country?.toUpperCase()).formatInternational()
return parsePhoneNumberFromString(str, country?.toUpperCase()).formatInternational()
}
) )
] ]
@ -170,21 +168,38 @@ export default class ValidatedTextField {
value?: UIEventSource<string>, value?: UIEventSource<string>,
textArea?: boolean, textArea?: boolean,
textAreaRows?: number, textAreaRows?: number,
isValid?: ((s: string) => boolean) isValid?: ((s: string, country: string) => boolean),
country?: string
}): InputElement<string> { }): InputElement<string> {
options = options ?? {}; options = options ?? {};
options.placeholder = options.placeholder ?? type; options.placeholder = options.placeholder ?? type;
const tp: TextFieldDef = ValidatedTextField.AllTypes[type] const tp: TextFieldDef = ValidatedTextField.AllTypes[type]
let isValid = tp.isValid; const isValidTp = tp.isValid;
let isValid;
if (options.isValid) { if (options.isValid) {
const optValid = options.isValid; const optValid = options.isValid;
isValid = (str, country) => { isValid = (str, country) => {
return ValidatedTextField.AllTypes[type](str, country) && optValid(str); if(str === undefined){
return false;
} }
return isValidTp(str, country ?? options.country) && optValid(str, country ?? options.country);
}
}else{
isValid = isValidTp;
} }
options.isValid = isValid; options.isValid = isValid;
let input: InputElement<string> = new TextField(options); let input: InputElement<string> = new TextField(options);
if (tp.reformat) {
input.GetValue().addCallbackAndRun(str => {
if (!options.isValid(str, options.country)) {
return;
}
const formatted = tp.reformat(str, options.country);
input.GetValue().setData(formatted);
})
}
if (tp.inputHelper) { if (tp.inputHelper) {
input = new CombinedInputElement(input, tp.inputHelper(input.GetValue())); input = new CombinedInputElement(input, tp.inputHelper(input.GetValue()));
} }
@ -252,11 +267,12 @@ export default class ValidatedTextField {
startValidated?: boolean, startValidated?: boolean,
textArea?: boolean, textArea?: boolean,
textAreaRows?: number, textAreaRows?: number,
isValid?: ((string: string) => boolean) isValid?: ((string: string) => boolean),
country?: string
}): InputElement<T> { }): InputElement<T> {
let textField: InputElement<string>; let textField: InputElement<string>;
if (options.type) { if (options.type) {
textField = ValidatedTextField.InputForType(options.type); textField = ValidatedTextField.InputForType(options.type, options);
} else { } else {
textField = new TextField(options); textField = new TextField(options);
} }

View file

@ -326,15 +326,13 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
} }
let formatter = ValidatedTextField.AllTypes[type].reformat ?? ((str) => str);
const pickString = const pickString =
(string: any) => { (string: any) => {
if (string === "" || string === undefined) { if (string === "" || string === undefined) {
return undefined; return undefined;
} }
const tag = new Tag(freeform.key, formatter(string, this._source.data._country)); const tag = new Tag(freeform.key, string);
if (freeform.extraTags === undefined) { if (freeform.extraTags === undefined) {
return tag; return tag;
@ -361,11 +359,14 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
return undefined; return undefined;
} }
console.log("Creating a freeform input element for ", this._source.data._country);
return ValidatedTextField.Mapped(pickString, toString, { return ValidatedTextField.Mapped(pickString, toString, {
placeholder: this._freeform.placeholder, placeholder: this._freeform.placeholder,
type: type, type: type,
isValid: (str) => (str.length <= 255), isValid: (str) => (str.length <= 255),
textArea: isTextArea textArea: isTextArea,
country: this._source.data._country
}) })
} }