| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  | import {UIElement} from "../UIElement"; | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  | import {InputElement} from "./InputElement"; | 
					
						
							| 
									
										
										
										
											2020-07-21 00:38:03 +02:00
										 |  |  | import Translations from "../i18n/Translations"; | 
					
						
							| 
									
										
										
										
											2020-08-17 17:23:15 +02:00
										 |  |  | import {UIEventSource} from "../../Logic/UIEventSource"; | 
					
						
							| 
									
										
										
										
											2020-08-22 02:12:46 +02:00
										 |  |  | import * as EmailValidator from "email-validator"; | 
					
						
							|  |  |  | import {parsePhoneNumberFromString} from "libphonenumber-js"; | 
					
						
							|  |  |  | import {TagRenderingOptions} from "../../Customizations/TagRenderingOptions"; | 
					
						
							|  |  |  | import {CustomLayoutFromJSON} from "../../Customizations/JSON/CustomLayoutFromJSON"; | 
					
						
							|  |  |  | import {Tag} from "../../Logic/TagsFilter"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class ValidatedTextField { | 
					
						
							|  |  |  |     public static inputValidation = { | 
					
						
							|  |  |  |         "$": (str) => true, | 
					
						
							|  |  |  |         "string": (str) => true, | 
					
						
							|  |  |  |         "date": (str) => true, // TODO validate and add a date picker
 | 
					
						
							|  |  |  |         "int": (str) => str.indexOf(".") < 0 && !isNaN(Number(str)), | 
					
						
							|  |  |  |         "nat": (str) => str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0, | 
					
						
							|  |  |  |         "float": (str) => !isNaN(Number(str)), | 
					
						
							|  |  |  |         "pfloat": (str) => !isNaN(Number(str)) && Number(str) > 0, | 
					
						
							|  |  |  |         "email": (str) => EmailValidator.validate(str), | 
					
						
							|  |  |  |         "url": (str) => str, | 
					
						
							|  |  |  |         "phone": (str, country) => { | 
					
						
							|  |  |  |             return parsePhoneNumberFromString(str, country.toUpperCase())?.isValid() ?? false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static formatting = { | 
					
						
							|  |  |  |         "phone": (str, country) => { | 
					
						
							|  |  |  |             console.log("country formatting", country) | 
					
						
							|  |  |  |             return parsePhoneNumberFromString(str, country.toUpperCase()).formatInternational() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static TagTextField(value: UIEventSource<Tag[]> = undefined, allowEmpty: boolean) { | 
					
						
							|  |  |  |         allowEmpty = allowEmpty ?? false; | 
					
						
							|  |  |  |         return new TextField<Tag[]>({ | 
					
						
							|  |  |  |                 placeholder: "Tags", | 
					
						
							|  |  |  |                 fromString: str => { | 
					
						
							|  |  |  |                     const tags = CustomLayoutFromJSON.TagsFromJson(str); | 
					
						
							|  |  |  |                     if (tags === []) { | 
					
						
							|  |  |  |                         if (allowEmpty) { | 
					
						
							|  |  |  |                             return [] | 
					
						
							|  |  |  |                         } else { | 
					
						
							|  |  |  |                             return undefined; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return tags; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 , | 
					
						
							|  |  |  |                 toString: (tags: Tag[]) => { | 
					
						
							|  |  |  |                     if (tags === undefined) { | 
					
						
							|  |  |  |                         return undefined; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     if (tags === []) { | 
					
						
							|  |  |  |                         if (allowEmpty) { | 
					
						
							|  |  |  |                             return ""; | 
					
						
							|  |  |  |                         } else { | 
					
						
							|  |  |  |                             return undefined; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return tags.map(tag =>  | 
					
						
							|  |  |  |                         tag.invertValue ? tag.key + "!=" + tag.value :  | 
					
						
							|  |  |  |                         tag.key + "=" + tag.value).join("&") | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 value: value, | 
					
						
							|  |  |  |                 startValidated: true | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ValidatedTextField(type: string, options: { value?: UIEventSource<string>, country?: string }) | 
					
						
							|  |  |  |         : TextField<string> { | 
					
						
							|  |  |  |         let isValid = ValidatedTextField.inputValidation[type]; | 
					
						
							|  |  |  |         if (isValid === undefined | 
					
						
							|  |  |  |         ) { | 
					
						
							|  |  |  |             throw "Invalid type for textfield: " + type | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         let formatter = ValidatedTextField.formatting[type] ?? ((str) => str); | 
					
						
							|  |  |  |         return new TextField<string>({ | 
					
						
							|  |  |  |             placeholder: type, | 
					
						
							|  |  |  |             toString: str => str, | 
					
						
							|  |  |  |             fromString: str => isValid(str, options?.country) ? formatter(str, options.country) : undefined, | 
					
						
							|  |  |  |             value: options.value, | 
					
						
							|  |  |  |             startValidated: true | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  | export class TextField<T> extends InputElement<T> { | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-22 02:12:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  |     private value: UIEventSource<string>; | 
					
						
							|  |  |  |     private mappedValue: UIEventSource<T>; | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Pings and has the value data | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public enterPressed = new UIEventSource<string>(undefined); | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  |     private _placeholder: UIElement; | 
					
						
							|  |  |  |     private _fromString?: (string: string) => T; | 
					
						
							|  |  |  |     private _toString: (t: T) => string; | 
					
						
							| 
									
										
										
										
											2020-08-22 02:12:46 +02:00
										 |  |  |     private startValidated: boolean; | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-23 17:32:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  |     constructor(options: { | 
					
						
							| 
									
										
										
										
											2020-07-23 17:32:18 +02:00
										 |  |  |         /** | 
					
						
							|  |  |  |          * Shown as placeholder | 
					
						
							|  |  |  |          */ | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  |         placeholder?: string | UIElement, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:32:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         /** | 
					
						
							|  |  |  |          * Converts the T to a (canonical) string | 
					
						
							|  |  |  |          * @param t | 
					
						
							|  |  |  |          */ | 
					
						
							| 
									
										
										
										
											2020-07-20 18:24:00 +02:00
										 |  |  |         toString: (t: T) => string, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:32:18 +02:00
										 |  |  |         /** | 
					
						
							|  |  |  |          * Converts the string to a T | 
					
						
							|  |  |  |          * Returns undefined if invalid | 
					
						
							|  |  |  |          * @param string | 
					
						
							|  |  |  |          */ | 
					
						
							| 
									
										
										
										
											2020-07-20 18:24:00 +02:00
										 |  |  |         fromString: (string: string) => T, | 
					
						
							| 
									
										
										
										
											2020-08-22 02:12:46 +02:00
										 |  |  |         value?: UIEventSource<T>, | 
					
						
							|  |  |  |         startValidated?: boolean, | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  |     }) { | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  |         super(undefined); | 
					
						
							| 
									
										
										
										
											2020-07-20 21:39:07 +02:00
										 |  |  |         const self = this; | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  |         this.value = new UIEventSource<string>(""); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 21:39:07 +02:00
										 |  |  |         this.mappedValue = options?.value ?? new UIEventSource<T>(undefined); | 
					
						
							|  |  |  |         this.mappedValue.addCallback(() => self.InnerUpdate()); | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  |         // @ts-ignore
 | 
					
						
							|  |  |  |         this._fromString = options.fromString ?? ((str) => (str)) | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  |         this.value.addCallback((str) => this.mappedValue.setData(options.fromString(str))); | 
					
						
							|  |  |  |         this.mappedValue.addCallback((t) => this.value.setData(options.toString(t))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 00:38:03 +02:00
										 |  |  |         this._placeholder = Translations.W(options.placeholder ?? ""); | 
					
						
							|  |  |  |         this.ListenTo(this._placeholder._source); | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  |         this._toString = options.toString ?? ((t) => ("" + t)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.mappedValue.addCallback((t) => { | 
					
						
							| 
									
										
										
										
											2020-07-20 18:24:00 +02:00
										 |  |  |             if (t === undefined || t === null) { | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const field = document.getElementById('text-' + this.id); | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  |             if (field === undefined || field === null) { | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  |             // @ts-ignore
 | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  |             field.value = options.toString(t); | 
					
						
							| 
									
										
										
										
											2020-08-22 02:12:46 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  |         this.startValidated = options.startValidated ?? false; | 
					
						
							| 
									
										
										
										
											2020-07-05 18:59:47 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     GetValue(): UIEventSource<T> { | 
					
						
							| 
									
										
										
										
											2020-07-20 13:28:45 +02:00
										 |  |  |         return this.mappedValue; | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 21:03:55 +02:00
										 |  |  |     ShowValue(t: T): boolean { | 
					
						
							|  |  |  |         if (!this.IsValid(t)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.mappedValue.setData(t); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     InnerRender(): string { | 
					
						
							| 
									
										
										
										
											2020-07-23 17:32:18 +02:00
										 |  |  |         return `<form onSubmit='return false' class='form-text-field'>` + | 
					
						
							|  |  |  |             `<input type='text' placeholder='${this._placeholder.InnerRender()}' id='text-${this.id}'>` + | 
					
						
							|  |  |  |             `</form>`; | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 21:39:07 +02:00
										 |  |  |     InnerUpdate() { | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |         const field = document.getElementById('text-' + this.id); | 
					
						
							| 
									
										
										
										
											2020-07-20 21:03:55 +02:00
										 |  |  |         if (field === null) { | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-23 17:32:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.mappedValue.addCallback((data) => { | 
					
						
							|  |  |  |             field.className = this.mappedValue.data !== undefined ? "valid" : "invalid"; | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-08-22 02:12:46 +02:00
										 |  |  |          | 
					
						
							|  |  |  |         field.className = this.mappedValue.data !== undefined ? "valid" : "invalid"; | 
					
						
							| 
									
										
										
										
											2020-07-23 17:32:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |         const self = this; | 
					
						
							|  |  |  |         field.oninput = () => { | 
					
						
							| 
									
										
										
										
											2020-07-05 18:59:47 +02:00
										 |  |  |             // @ts-ignore
 | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |             self.value.setData(field.value); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         field.addEventListener("keyup", function (event) { | 
					
						
							|  |  |  |             if (event.key === "Enter") { | 
					
						
							| 
									
										
										
										
											2020-07-05 18:59:47 +02:00
										 |  |  |                 // @ts-ignore
 | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |                 self.enterPressed.setData(field.value); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-07-05 18:59:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 21:03:55 +02:00
										 |  |  |         if (this.IsValid(this.mappedValue.data)) { | 
					
						
							| 
									
										
										
										
											2020-07-20 21:39:07 +02:00
										 |  |  |             const expected = this._toString(this.mappedValue.data); | 
					
						
							| 
									
										
										
										
											2020-07-20 21:03:55 +02:00
										 |  |  |             // @ts-ignore
 | 
					
						
							| 
									
										
										
										
											2020-07-20 21:39:07 +02:00
										 |  |  |             if (field.value !== expected) { | 
					
						
							|  |  |  |                 // @ts-ignore
 | 
					
						
							|  |  |  |                 field.value = expected; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-07-20 21:03:55 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-05 18:59:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 15:54:50 +02:00
										 |  |  |     IsValid(t: T): boolean { | 
					
						
							|  |  |  |         if(t === undefined || t === null){ | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const result = this._toString(t); | 
					
						
							|  |  |  |         return result !== undefined && result !== null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |     Clear() { | 
					
						
							|  |  |  |         const field = document.getElementById('text-' + this.id); | 
					
						
							|  |  |  |         if (field !== undefined) { | 
					
						
							| 
									
										
										
										
											2020-07-05 18:59:47 +02:00
										 |  |  |             // @ts-ignore
 | 
					
						
							| 
									
										
										
										
											2020-07-01 02:12:33 +02:00
										 |  |  |             field.value = ""; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |