forked from MapComplete/MapComplete
		
	Nailed phone number input
This commit is contained in:
		
							parent
							
								
									585a4243f7
								
							
						
					
					
						commit
						07c85bb218
					
				
					 3 changed files with 52 additions and 19 deletions
				
			
		|  | @ -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); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue