forked from MapComplete/MapComplete
		
	Intermediary refactoring
This commit is contained in:
		
							parent
							
								
									dcf5d24002
								
							
						
					
					
						commit
						93db813cfc
					
				
					 15 changed files with 280 additions and 178 deletions
				
			
		|  | @ -1,17 +1,18 @@ | |||
| import {UIElement} from "../UI/UIElement"; | ||||
| import {UIEventSource} from "../UI/UIEventSource"; | ||||
| import {And, Tag, TagsFilter, TagUtils} from "../Logic/TagsFilter"; | ||||
| import {UIRadioButton} from "../UI/Base/UIRadioButton"; | ||||
| import {FixedUiElement} from "../UI/Base/FixedUiElement"; | ||||
| import {SaveButton} from "../UI/SaveButton"; | ||||
| import {Changes} from "../Logic/Changes"; | ||||
| import {TextField} from "../UI/Base/TextField"; | ||||
| import {UIInputElement} from "../UI/Base/UIInputElement"; | ||||
| import {UIRadioButtonWithOther} from "../UI/Base/UIRadioButtonWithOther"; | ||||
| import {VariableUiElement} from "../UI/Base/VariableUIElement"; | ||||
| import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor"; | ||||
| import {OnlyShowIfConstructor} from "./OnlyShowIf"; | ||||
| import {UserDetails} from "../Logic/OsmConnection"; | ||||
| import {TextField} from "../UI/Input/TextField"; | ||||
| import {InputElement} from "../UI/Input/InputElement"; | ||||
| import {InputElementWrapper} from "../UI/Input/InputElementWrapper"; | ||||
| import {FixedInputElement} from "../UI/Input/FixedInputElement"; | ||||
| import {RadioButton} from "../UI/Input/RadioButton"; | ||||
| 
 | ||||
| export class TagRenderingOptions implements TagDependantUIElementConstructor { | ||||
| 
 | ||||
|  | @ -129,12 +130,9 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { | |||
| class TagRendering extends UIElement implements TagDependantUIElement { | ||||
| 
 | ||||
| 
 | ||||
|     private _priority: number; | ||||
|     private _userDetails: UIEventSource<UserDetails>; | ||||
|     private _priority: number; | ||||
| 
 | ||||
|     Priority(): number { | ||||
|         return this._priority; | ||||
|     } | ||||
| 
 | ||||
|     private _question: string; | ||||
|     private _primer: string; | ||||
|  | @ -150,8 +148,8 @@ class TagRendering extends UIElement implements TagDependantUIElement { | |||
|         extraTags?: TagsFilter | ||||
|     }; | ||||
| 
 | ||||
|     private readonly _questionElement: UIElement; | ||||
|     private readonly _textField: TextField<TagsFilter>; // Only here to update
 | ||||
| 
 | ||||
|     private readonly _questionElement: InputElement<TagsFilter>; | ||||
| 
 | ||||
|     private readonly _saveButton: UIElement; | ||||
|     private readonly _skipButton: UIElement; | ||||
|  | @ -238,60 +236,12 @@ class TagRendering extends UIElement implements TagDependantUIElement { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Map radiobutton choice and textfield answer onto tagfilter. That tagfilter will be pushed into the changes later on
 | ||||
|         const pickChoice = (i => { | ||||
|             if (i === undefined || i === null) { | ||||
|                 return undefined | ||||
|             } | ||||
|             return self._mapping[i].k | ||||
|         }); | ||||
|         const pickString = | ||||
|             (string) => { | ||||
|                 if (string === "" || string === undefined) { | ||||
|                     return undefined; | ||||
|                 } | ||||
|                 const tag = new Tag(self._freeform.key, string); | ||||
|                 if (self._freeform.extraTags === undefined) { | ||||
|                     return tag; | ||||
|                 } | ||||
|                 return new And([ | ||||
|                         self._freeform.extraTags, | ||||
|                         tag | ||||
|                     ] | ||||
|                 ); | ||||
|             }; | ||||
| 
 | ||||
| 
 | ||||
|         // Prepare the actual input element -> pick an appropriate implementation
 | ||||
|         let inputElement: UIInputElement<TagsFilter>; | ||||
| 
 | ||||
|          | ||||
|         if (this._freeform !== undefined && this._mapping !== undefined) { | ||||
|             // Radio buttons with 'other'
 | ||||
|             inputElement = new UIRadioButtonWithOther( | ||||
|                 choices, | ||||
|                 this._freeform.template, | ||||
|                 this._freeform.placeholder, | ||||
|                 pickChoice, | ||||
|                 pickString | ||||
|             ); | ||||
|             this._questionElement = inputElement; | ||||
|         } else if (this._mapping !== [] && this._mapping.length > 0) { | ||||
|             // This is a classic radio selection element
 | ||||
|             inputElement = new UIRadioButton(new UIEventSource(choices), pickChoice, false) | ||||
|             this._questionElement = inputElement; | ||||
|         } else if (this._freeform !== undefined) { | ||||
|             this._textField = new TextField(new UIEventSource<string>(this._freeform.placeholder), pickString); | ||||
|             inputElement = this._textField; | ||||
|             this._questionElement = new FixedUiElement( | ||||
|                 "<div>" + this._freeform.template.replace("$$$", inputElement.Render()) + "</div>") | ||||
|         } else { | ||||
|             throw "Invalid questionRendering, expected at least choices or a freeform" | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         this._questionElement = this.InputElementFor(options); | ||||
|         const save = () => { | ||||
|             const selection = inputElement.GetValue().data; | ||||
|             const selection = self._questionElement.GetValue().data; | ||||
|             if (selection) { | ||||
|                 changes.addTag(tags.data.id, selection); | ||||
|             } | ||||
|  | @ -305,21 +255,16 @@ class TagRendering extends UIElement implements TagDependantUIElement { | |||
|         } | ||||
| 
 | ||||
|         // Setup the save button and it's action
 | ||||
|         this._saveButton = new SaveButton(inputElement.GetValue()) | ||||
|         this._saveButton = new SaveButton(this._questionElement.GetValue()) | ||||
|             .onClick(save); | ||||
| 
 | ||||
|         this._editButton = new FixedUiElement(""); | ||||
|         if (this._question !== undefined) { | ||||
|             this._editButton = new FixedUiElement("<img class='editbutton' src='./assets/pencil.svg' alt='edit'>") | ||||
|                 .onClick(() => { | ||||
|                     console.log("Click", self._editButton); | ||||
|                     if (self._textField) { | ||||
|                         self._textField.value.setData(self._source.data["name"] ?? ""); | ||||
|                     } | ||||
| 
 | ||||
|                     self._questionElement.GetValue().setData(self.CurrentValue()); | ||||
|                     self._editMode.setData(true); | ||||
|                 }); | ||||
|         } else { | ||||
|             this._editButton = new FixedUiElement(""); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -332,15 +277,95 @@ class TagRendering extends UIElement implements TagDependantUIElement { | |||
|         }); | ||||
|         // And at last, set up the skip button
 | ||||
|         this._skipButton = new VariableUiElement(cancelContents).onClick(cancel); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private InputElementFor(options: { | ||||
|         freeform?: { | ||||
|             key: string, template: string, | ||||
|             renderTemplate: string | ||||
|             placeholder?: string, | ||||
|             extraTags?: TagsFilter, | ||||
|         }, | ||||
|         mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[] | ||||
|     }): | ||||
|         InputElement<TagsFilter> { | ||||
| 
 | ||||
|         const elements = []; | ||||
| 
 | ||||
|         if (options.mappings !== undefined) { | ||||
|             for (const mapping of options.mappings) { | ||||
|                 elements.push(this.InputElementForMapping(mapping)); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (options.freeform !== undefined) { | ||||
|             elements.push(this.InputForFreeForm(options.freeform)); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         if (elements.length == 0) { | ||||
|             throw "NO TAGRENDERINGS!" | ||||
|         } | ||||
|         if (elements.length == 1) { | ||||
|             return elements[0]; | ||||
|         } | ||||
| 
 | ||||
|         return new RadioButton(elements, false); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private ApplyTemplate(template: string): string { | ||||
|         const tags = this._tagsPreprocessor(this._source.data); | ||||
|         return TagUtils.ApplyTemplate(template, tags); | ||||
| 
 | ||||
|     private InputElementForMapping(mapping: { k: TagsFilter, txt: string }) { | ||||
|         return new FixedInputElement(mapping.txt, mapping.k); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private InputForFreeForm(freeform): InputElement<TagsFilter> { | ||||
|         if (freeform === undefined) { | ||||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         const pickString = | ||||
|             (string) => { | ||||
|                 if (string === "" || string === undefined) { | ||||
|                     return undefined; | ||||
|                 } | ||||
|                 const tag = new Tag(freeform.key, string); | ||||
|                 if (freeform.extraTags === undefined) { | ||||
|                     return tag; | ||||
|                 } | ||||
|                 return new And([ | ||||
|                         freeform.extraTags, | ||||
|                         tag | ||||
|                     ] | ||||
|                 ); | ||||
|             }; | ||||
| 
 | ||||
|         const toString = | ||||
|             (tag) => { | ||||
|                 if (tag instanceof And) { | ||||
|                     return toString(tag.and[0]) | ||||
|                 } else if (tag instanceof Tag) { | ||||
|                     return tag.value | ||||
|                 } | ||||
|                 return undefined; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|         let inputElement: InputElement<TagsFilter>; | ||||
|         const textField = new TextField({ | ||||
|             placeholder: this._freeform.placeholder, | ||||
|             fromString: pickString, | ||||
|             toString: toString | ||||
|         }); | ||||
| 
 | ||||
|         const prepost = freeform.template.split("$$$"); | ||||
|         return new InputElementWrapper(prepost[0], textField, prepost[1]); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     IsKnown(): boolean { | ||||
|         const tags = TagUtils.proprtiesToKV(this._source.data); | ||||
| 
 | ||||
|  | @ -353,6 +378,22 @@ class TagRendering extends UIElement implements TagDependantUIElement { | |||
|         return this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined; | ||||
|     } | ||||
| 
 | ||||
|     private CurrentValue(): TagsFilter { | ||||
|         const tags = TagUtils.proprtiesToKV(this._source.data); | ||||
| 
 | ||||
|         for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) { | ||||
|             if (oneOnOneElement.k === null || oneOnOneElement.k.matches(tags)) { | ||||
|                 return oneOnOneElement.k; | ||||
|             } | ||||
|         } | ||||
|         if (this._freeform === undefined) { | ||||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|         return new Tag(this._freeform.key, this._source.data[this._freeform.key]); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     IsQuestioning(): boolean { | ||||
|         if (this.IsKnown()) { | ||||
|             return false; | ||||
|  | @ -441,13 +482,15 @@ class TagRendering extends UIElement implements TagDependantUIElement { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     InnerUpdate(htmlElement: HTMLElement) { | ||||
|         super.InnerUpdate(htmlElement); | ||||
|         this._questionElement.Update(); | ||||
|         this._saveButton.Update(); | ||||
|         this._skipButton.Update(); | ||||
|         this._textField?.Update(); | ||||
|         this._editButton.Update(); | ||||
| 
 | ||||
|     Priority(): number { | ||||
|         return this._priority; | ||||
|     } | ||||
| 
 | ||||
|     private ApplyTemplate(template: string): string { | ||||
|         const tags = this._tagsPreprocessor(this._source.data); | ||||
|         return TagUtils.ApplyTemplate(template, tags); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -123,6 +123,7 @@ export class AddButton extends UIElement { | |||
|         const self = this; | ||||
| 
 | ||||
|         htmlElement.onclick = function (event) { | ||||
|             // @ts-ignore
 | ||||
|             if(event.consumed){ | ||||
|                 return; | ||||
|             } | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ export class FixedUiElement extends UIElement { | |||
| 
 | ||||
|     constructor(html: string) { | ||||
|         super(undefined); | ||||
|         this._html = html; | ||||
|         this._html = html ?? ""; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerRender(): string { | ||||
|  |  | |||
|  | @ -1,10 +0,0 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| 
 | ||||
| export abstract class UIInputElement<T> extends UIElement{ | ||||
|      | ||||
|     abstract GetValue() : UIEventSource<T>; | ||||
|      | ||||
|      | ||||
|      | ||||
| } | ||||
|  | @ -115,4 +115,6 @@ export class FeatureInfoBox extends UIElement { | |||
|             "</div>"; | ||||
|     } | ||||
|      | ||||
|      | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import {UIEventSource} from "./UIEventSource"; | |||
| import $ from "jquery" | ||||
| import {Imgur} from "../Logic/Imgur"; | ||||
| import {UserDetails} from "../Logic/OsmConnection"; | ||||
| import {DropDownUI} from "./Base/DropDownUI"; | ||||
| import {DropDown} from "./Input/DropDown"; | ||||
| import {VariableUiElement} from "./Base/VariableUIElement"; | ||||
| 
 | ||||
| export class ImageUploadFlow extends UIElement { | ||||
|  | @ -31,7 +31,7 @@ export class ImageUploadFlow extends UIElement { | |||
|         this._uploadOptions = uploadOptions; | ||||
|         this.ListenTo(this._isUploading); | ||||
| 
 | ||||
|         const licensePicker = new DropDownUI("Jouw foto wordt gepubliceerd ", | ||||
|         const licensePicker = new DropDown("Jouw foto wordt gepubliceerd ", | ||||
| 
 | ||||
|             [ | ||||
|                 {value: "CC0", shown: "in het publiek domein"}, | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| 
 | ||||
| export class DropDownUI extends UIElement { | ||||
| export class DropDown extends UIElement { | ||||
| 
 | ||||
|     selectedElement: UIEventSource<string> | ||||
|     private _label: string; | ||||
|  | @ -1,10 +1,10 @@ | |||
| import {UIInputElement} from "./UIInputElement"; | ||||
| import {InputElement} from "./InputElement"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| import {FixedUiElement} from "./FixedUiElement"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 
 | ||||
| 
 | ||||
| export class FixedInputElement<T> extends UIInputElement<T> { | ||||
| export class FixedInputElement<T> extends InputElement<T> { | ||||
|     private rendering: UIElement; | ||||
|     private value: UIEventSource<T>; | ||||
| 
 | ||||
|  | @ -22,4 +22,8 @@ export class FixedInputElement<T> extends UIInputElement<T> { | |||
|         return this.rendering.Render(); | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: T): boolean { | ||||
|         return t === this.value.data; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										11
									
								
								UI/Input/InputElement.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								UI/Input/InputElement.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 
 | ||||
| export abstract class InputElement<T> extends UIElement{ | ||||
|      | ||||
|     abstract GetValue() : UIEventSource<T>; | ||||
|      | ||||
|     abstract IsValid(t: T) : boolean; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										37
									
								
								UI/Input/InputElementWrapper.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								UI/Input/InputElementWrapper.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import {InputElement} from "./InputElement"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 
 | ||||
| 
 | ||||
| export class InputElementWrapper<T> extends InputElement<T>{ | ||||
|     private pre: UIElement ; | ||||
|     private input: InputElement<T>; | ||||
|     private post: UIElement ; | ||||
|      | ||||
|     constructor( | ||||
|         pre: UIElement | string, | ||||
|         input: InputElement<T>, | ||||
|         post: UIElement | string | ||||
|          | ||||
|     ) { | ||||
|         super(undefined); | ||||
|         this.pre = typeof(pre) === 'string' ?  new FixedUiElement(pre) : pre | ||||
|         this.input = input; | ||||
|         this.post =typeof(post) === 'string' ?  new FixedUiElement(post) : post | ||||
|     } | ||||
|      | ||||
|      | ||||
|     GetValue(): UIEventSource<T> { | ||||
|         return this.input.GetValue(); | ||||
|     } | ||||
| 
 | ||||
|     protected InnerRender(): string { | ||||
|         return this.pre.Render() + this.input.Render() + this.post.Render(); | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: T): boolean { | ||||
|         return this.input.IsValid(t); | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | @ -1,31 +1,27 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import {UIInputElement} from "./UIInputElement"; | ||||
| import {InputElement} from "./InputElement"; | ||||
| 
 | ||||
| export class UIRadioButton<T> extends UIInputElement<T> { | ||||
| export class RadioButton<T> extends InputElement<T> { | ||||
| 
 | ||||
|     public readonly SelectedElementIndex: UIEventSource<number> | ||||
|     private readonly _selectedElementIndex: UIEventSource<number> | ||||
|         = new UIEventSource<number>(null); | ||||
| 
 | ||||
|     private value: UIEventSource<T>; | ||||
|     private readonly _elements: UIInputElement<T>[] | ||||
|     private readonly _elements: InputElement<T>[] | ||||
|     private _selectFirstAsDefault: boolean; | ||||
|     private _valueMapping: (i: number) => T; | ||||
| 
 | ||||
| 
 | ||||
|     constructor(elements: UIInputElement<T>[], | ||||
|     constructor(elements: InputElement<T>[], | ||||
|                 selectFirstAsDefault = true) { | ||||
|         super(undefined); | ||||
|         this._elements = elements; | ||||
|         this._selectFirstAsDefault = selectFirstAsDefault; | ||||
|         const self = this; | ||||
|         this.SelectedElementIndex.addCallback(() => { | ||||
|             self.InnerUpdate(undefined); | ||||
|         }) | ||||
| 
 | ||||
| 
 | ||||
|         this.value = | ||||
|             UIEventSource.flatten(this.SelectedElementIndex.map( | ||||
|             UIEventSource.flatten(this._selectedElementIndex.map( | ||||
|                 (selectedIndex) => { | ||||
|                     if (selectedIndex !== undefined && selectedIndex !== null) { | ||||
|                         return elements[selectedIndex].GetValue() | ||||
|  | @ -34,14 +30,27 @@ export class UIRadioButton<T> extends UIInputElement<T> { | |||
|             ), elements.map(e => e.GetValue())) | ||||
|         ; | ||||
| 
 | ||||
|         this.value.addCallback((t) => { | ||||
|             self.SetCorrectValue(t); | ||||
|         }) | ||||
| 
 | ||||
|         for (let i = 0; i < elements.length; i ++){ | ||||
|             elements[i].onClick(( ) => { | ||||
|                 self.SelectedElementIndex.setData(i); | ||||
| 
 | ||||
|         for (let i = 0; i < elements.length; i++) { | ||||
|             // If an element is clicked, the radio button corresponding with it should be selected as well
 | ||||
|             elements[i].onClick(() => { | ||||
|                 self._selectedElementIndex.setData(i); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: T): boolean { | ||||
|         for (const inputElement of this._elements) { | ||||
|             if (inputElement.IsValid(t)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     GetValue(): UIEventSource<T> { | ||||
|  | @ -70,6 +79,24 @@ export class UIRadioButton<T> extends UIInputElement<T> { | |||
|         return "<form id='" + this.id + "-form'>" + body + "</form>"; | ||||
|     } | ||||
| 
 | ||||
|     private SetCorrectValue(t: T) { | ||||
|         if (t === undefined) { | ||||
|             return; | ||||
|         } | ||||
|         // We check that what is selected matches the previous rendering
 | ||||
|         for (let i = 0; i < this._elements.length; i++) { | ||||
|             const e = this._elements[i]; | ||||
|             if (e.IsValid(t)) { | ||||
|                 this._selectedElementIndex.setData(i); | ||||
|                 e.GetValue().setData(t); | ||||
|                 // @ts-ignore
 | ||||
|                 document.getElementById(this.IdFor(i)).checked = true; | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     InnerUpdate(htmlElement: HTMLElement) { | ||||
|         const self = this; | ||||
| 
 | ||||
|  | @ -78,7 +105,7 @@ export class UIRadioButton<T> extends UIInputElement<T> { | |||
|                 const el = document.getElementById(self.IdFor(i)); | ||||
|                 // @ts-ignore
 | ||||
|                 if (el.checked) { | ||||
|                     self.SelectedElementIndex.setData(i); | ||||
|                     self._selectedElementIndex.setData(i); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -91,8 +118,9 @@ export class UIRadioButton<T> extends UIInputElement<T> { | |||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         if (this.SelectedElementIndex.data == null) { | ||||
|             if (this._selectFirstAsDefault) { | ||||
|         if (this._selectFirstAsDefault) { | ||||
|             this.SetCorrectValue(this.value.data); | ||||
|             if (this._selectedElementIndex.data === null || this._selectedElementIndex.data === undefined) { | ||||
|                 const el = document.getElementById(this.IdFor(0)); | ||||
|                 if (el) { | ||||
|                     // @ts-ignore
 | ||||
|  | @ -100,30 +128,10 @@ export class UIRadioButton<T> extends UIInputElement<T> { | |||
|                     checkButtons(); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
| 
 | ||||
|             // We check that what is selected matches the previous rendering
 | ||||
|             var checked = -1; | ||||
|             var expected = this.SelectedElementIndex.data; | ||||
|             if (expected) { | ||||
| 
 | ||||
|                 for (let i = 0; i < self._elements.length; i++) { | ||||
|                     const el = document.getElementById(self.IdFor(i)); | ||||
|                     // @ts-ignore
 | ||||
|                     if (el.checked) { | ||||
|                         checked = i; | ||||
|                     } | ||||
|                 } | ||||
|                 if (expected != checked) { | ||||
|                     const el = document.getElementById(this.IdFor(expected)); | ||||
|                     // @ts-ignore
 | ||||
|                     el.checked = true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -1,9 +1,10 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import {UIInputElement} from "./UIInputElement"; | ||||
| import {InputElement} from "./InputElement"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 
 | ||||
| 
 | ||||
| export class TextField<T> extends UIInputElement<T> { | ||||
| export class TextField<T> extends InputElement<T> { | ||||
| 
 | ||||
|     private value: UIEventSource<string>; | ||||
|     private mappedValue: UIEventSource<T>; | ||||
|  | @ -11,28 +12,32 @@ export class TextField<T> extends UIInputElement<T> { | |||
|      * Pings and has the value data | ||||
|      */ | ||||
|     public enterPressed = new UIEventSource<string>(undefined); | ||||
|     private _placeholder: UIEventSource<string>; | ||||
|     private _pretext: string; | ||||
|     private _fromString: (string: string) => T; | ||||
|     private _placeholder: UIElement; | ||||
|     private _fromString?: (string: string) => T; | ||||
|     private _toString: (t: T) => string; | ||||
| 
 | ||||
|     constructor(options: { | ||||
|         placeholder?: UIEventSource<string>, | ||||
|         toString: (t: T) => string, | ||||
|         fromString: (string: string) => T, | ||||
|         pretext?: string, | ||||
|         placeholder?: string | UIElement, | ||||
|         toString?: (t: T) => string, | ||||
|         fromString?: (string: string) => T, | ||||
|         value?: UIEventSource<T> | ||||
|     }) { | ||||
|         super(options?.placeholder); | ||||
|         super(undefined); | ||||
|         this.value = new UIEventSource<string>(""); | ||||
|         this.mappedValue = options?.value ?? new UIEventSource<T>(undefined); | ||||
| 
 | ||||
| 
 | ||||
|         // @ts-ignore
 | ||||
|         this._fromString = options.fromString ?? ((str) => (str)) | ||||
|         this.value.addCallback((str) => this.mappedValue.setData(options.fromString(str))); | ||||
|         this.mappedValue.addCallback((t) => this.value.setData(options.toString(t))); | ||||
| 
 | ||||
| 
 | ||||
|         this._placeholder = options?.placeholder ?? new UIEventSource<string>(""); | ||||
|         this._pretext = options?.pretext ?? ""; | ||||
|         this._placeholder =  | ||||
|             typeof(options.placeholder) === "string" ? new FixedUiElement(options.placeholder) : | ||||
|                 (options.placeholder ?? new FixedUiElement("")); | ||||
|         this._toString = options.toString ?? ((t) => ("" + t)); | ||||
| 
 | ||||
| 
 | ||||
|         const self = this; | ||||
|         this.mappedValue.addCallback((t) => { | ||||
|  | @ -40,9 +45,10 @@ export class TextField<T> extends UIInputElement<T> { | |||
|                 return; | ||||
|             } | ||||
|             const field = document.getElementById('text-' + this.id); | ||||
|             if (field === undefined && field === null) { | ||||
|             if (field === undefined || field === null) { | ||||
|                 return; | ||||
|             } | ||||
|             // @ts-ignore
 | ||||
|             field.value = options.toString(t); | ||||
|         }) | ||||
|     } | ||||
|  | @ -52,13 +58,16 @@ export class TextField<T> extends UIInputElement<T> { | |||
|     } | ||||
| 
 | ||||
|     protected InnerRender(): string { | ||||
|         return this._pretext + "<form onSubmit='return false' class='form-text-field'>" + | ||||
|             "<input type='text' placeholder='" + (this._placeholder.data ?? "") + "' id='text-" + this.id + "'>" + | ||||
|         return "<form onSubmit='return false' class='form-text-field'>" + | ||||
|             "<input type='text' placeholder='" + this._placeholder.InnerRender() + "' id='text-" + this.id + "'>" + | ||||
|             "</form>"; | ||||
|     } | ||||
| 
 | ||||
|     InnerUpdate(htmlElement: HTMLElement) { | ||||
|         const field = document.getElementById('text-' + this.id); | ||||
|         if(field === null){ | ||||
|             return; | ||||
|         } | ||||
|         const self = this; | ||||
|         field.oninput = () => { | ||||
|             // @ts-ignore
 | ||||
|  | @ -75,6 +84,14 @@ export class TextField<T> extends UIInputElement<T> { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: T): boolean { | ||||
|         if(t === undefined || t === null){ | ||||
|             return false; | ||||
|         } | ||||
|         const result = this._toString(t); | ||||
|         return result !== undefined && result !== null; | ||||
|     } | ||||
| 
 | ||||
|     Clear() { | ||||
|         const field = document.getElementById('text-' + this.id); | ||||
|         if (field !== undefined) { | ||||
|  | @ -1,5 +1,5 @@ | |||
| import {UIElement} from "./UIElement"; | ||||
| import {TextField} from "./Base/TextField"; | ||||
| import {TextField} from "./Input/TextField"; | ||||
| import {UIEventSource} from "./UIEventSource"; | ||||
| import {FixedUiElement} from "./Base/FixedUiElement"; | ||||
| import {Geocoding} from "../Logic/Geocoding"; | ||||
|  | @ -9,7 +9,10 @@ import {Basemap} from "../Logic/Basemap"; | |||
| export class SearchAndGo extends UIElement { | ||||
| 
 | ||||
|     private _placeholder = new UIEventSource("Zoek naar een locatie...") | ||||
|     private _searchField = new TextField(this._placeholder); | ||||
|     private _searchField = new TextField<string>({ | ||||
|             placeholder: this._placeholder | ||||
|         } | ||||
|     ); | ||||
| 
 | ||||
|     private _foundEntries = new UIEventSource([]); | ||||
|     private _map: Basemap; | ||||
|  | @ -33,7 +36,7 @@ export class SearchAndGo extends UIElement { | |||
| 
 | ||||
|     // Triggered by 'enter' or onclick
 | ||||
|     private RunSearch() { | ||||
|         const searchString = this._searchField.value.data; | ||||
|         const searchString = this._searchField.GetValue().data; | ||||
|         this._searchField.Clear(); | ||||
|         this._placeholder.setData("Bezig met zoeken..."); | ||||
|         const self = this; | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import {UIEventSource} from "./UIEventSource"; | ||||
| import instantiate = WebAssembly.instantiate; | ||||
| 
 | ||||
| export abstract class UIElement { | ||||
|      | ||||
|  | @ -121,5 +120,6 @@ export abstract class UIElement { | |||
|     public IsEmpty(): boolean { | ||||
|         return this.InnerRender() === ""; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										24
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -7,33 +7,19 @@ import {OsmLink} from "./Customizations/Questions/OsmLink"; | |||
| import {ConfirmDialog} from "./UI/ConfirmDialog"; | ||||
| import {Imgur} from "./Logic/Imgur"; | ||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement"; | ||||
| import {UIRadioButton} from "./UI/Base/UIRadioButton"; | ||||
| import {FixedInputElement} from "./UI/Base/FixedInputElement"; | ||||
| import {TextField} from "./UI/Base/TextField"; | ||||
| import {TextField} from "./UI/Input/TextField"; | ||||
| import {FixedInputElement} from "./UI/Input/FixedInputElement"; | ||||
| import {RadioButton} from "./UI/Input/RadioButton"; | ||||
| 
 | ||||
| 
 | ||||
| const buttons = new UIRadioButton<number>( | ||||
| const buttons = new RadioButton<number>( | ||||
|     [new FixedInputElement("Five", 5), | ||||
|         new FixedInputElement("Ten", 10), | ||||
|         new TextField<number>({ | ||||
|             fromString: (str) => parseInt(str), | ||||
|             toString: (i) => ("" + i), | ||||
|         }) | ||||
|     ] | ||||
|     ], false | ||||
| ).AttachTo("maindiv"); | ||||
| 
 | ||||
| buttons.GetValue().addCallback(console.log); | ||||
| buttons.GetValue().setData(10) | ||||
| 
 | ||||
| const value = new TextField<number>({ | ||||
|     fromString: (str) => parseInt(str), | ||||
|     toString: (i) => { | ||||
|         if(isNaN(i)){ | ||||
|             return "" | ||||
|         } | ||||
|         return ("" + i) | ||||
|     }, | ||||
| }).AttachTo("extradiv").GetValue(); | ||||
| 
 | ||||
| value.setData(42); | ||||
| value.addCallback(console.log) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue