forked from MapComplete/MapComplete
		
	Butchering the UI framework
This commit is contained in:
		
							parent
							
								
									8d404b1ba9
								
							
						
					
					
						commit
						6415e195d1
					
				
					 90 changed files with 1012 additions and 3101 deletions
				
			
		
							
								
								
									
										222
									
								
								UI/UIElement.ts
									
										
									
									
									
								
							
							
						
						
									
										222
									
								
								UI/UIElement.ts
									
										
									
									
									
								
							|  | @ -1,25 +1,19 @@ | |||
| import {UIEventSource} from "../Logic/UIEventSource"; | ||||
| import {Utils} from "../Utils"; | ||||
| import BaseUIElement from "./BaseUIElement"; | ||||
| 
 | ||||
| export abstract class UIElement extends UIEventSource<string> { | ||||
| export abstract class UIElement extends BaseUIElement{ | ||||
| 
 | ||||
|     private static nextId: number = 0; | ||||
|     public readonly id: string; | ||||
|     public readonly _source: UIEventSource<any>; | ||||
|     public dumbMode = false; | ||||
|     private clss: Set<string> = new Set<string>(); | ||||
|     private style: string; | ||||
|     private _hideIfEmpty = false; | ||||
| 
 | ||||
|     private lastInnerRender: string; | ||||
|     private _onClick: () => void; | ||||
|     private _onHover: UIEventSource<boolean>; | ||||
| 
 | ||||
|     protected constructor(source: UIEventSource<any> = undefined) { | ||||
|         super(""); | ||||
|         this.id = "ui-element-" + UIElement.nextId; | ||||
|         super() | ||||
|         this.id = `ui-${this.constructor.name}-${UIElement.nextId}`; | ||||
|         this._source = source; | ||||
|         UIElement.nextId++; | ||||
|         this.dumbMode = true; | ||||
|         this.ListenTo(source); | ||||
|     } | ||||
| 
 | ||||
|  | @ -27,183 +21,97 @@ export abstract class UIElement extends UIEventSource<string> { | |||
|         if (source === undefined) { | ||||
|             return this; | ||||
|         } | ||||
|         this.dumbMode = false; | ||||
|         const self = this; | ||||
|         source.addCallback(() => { | ||||
|             self.lastInnerRender = undefined; | ||||
|             self.Update(); | ||||
|             if(self._constructedHtmlElement !== undefined){ | ||||
|                 self.UpdateElement(self._constructedHtmlElement); | ||||
|             } | ||||
|              | ||||
|         }) | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public onClick(f: (() => void)) { | ||||
|         this.dumbMode = false; | ||||
|         this._onClick = f; | ||||
|         this.SetClass("clickable") | ||||
|         this.Update(); | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public IsHovered(): UIEventSource<boolean> { | ||||
|         this.dumbMode = false; | ||||
|         if (this._onHover !== undefined) { | ||||
|             return this._onHover; | ||||
|         } | ||||
|         // Note: we just save it. 'Update' will register that an eventsource exist and install the necessary hooks
 | ||||
|         this._onHover = new UIEventSource<boolean>(false); | ||||
|         return this._onHover; | ||||
|     } | ||||
| 
 | ||||
|     Update(): void { | ||||
|         if (Utils.runningFromConsole) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         let element = document.getElementById(this.id); | ||||
|         if (element === undefined || element === null) { | ||||
|             // The element is not painted or, in the case of 'dumbmode' this UI-element is not explicitely present
 | ||||
|             if (this.dumbMode) { | ||||
|                 // We update all the children anyway
 | ||||
|                 this.UpdateAllChildren(); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|         const newRender = this.InnerRender(); | ||||
|         if (newRender !== this.lastInnerRender) { | ||||
|             this.lastInnerRender = newRender; | ||||
|             this.setData(this.InnerRender()); | ||||
|             element.innerHTML = this.data; | ||||
|         } | ||||
| 
 | ||||
|         if (this._hideIfEmpty) { | ||||
|             if (element.innerHTML === "") { | ||||
|                 element.parentElement.style.display = "none"; | ||||
|             } else { | ||||
|                 element.parentElement.style.display = ""; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this._onClick !== undefined) { | ||||
|             const self = this; | ||||
|             element.onclick = (e) => { | ||||
|                 // @ts-ignore
 | ||||
|                 if (e.consumed) { | ||||
|                     return; | ||||
|                 } | ||||
|                 self._onClick(); | ||||
|                 // @ts-ignore
 | ||||
|                 e.consumed = true; | ||||
|             } | ||||
|             element.style.pointerEvents = "all"; | ||||
|             element.style.cursor = "pointer"; | ||||
|         } | ||||
| 
 | ||||
|         if (this._onHover !== undefined) { | ||||
|             const self = this; | ||||
|             element.addEventListener('mouseover', () => self._onHover.setData(true)); | ||||
|             element.addEventListener('mouseout', () => self._onHover.setData(false)); | ||||
|         } | ||||
| 
 | ||||
|         this.InnerUpdate(element); | ||||
|         this.UpdateAllChildren(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     HideOnEmpty(hide: boolean): UIElement { | ||||
|         this._hideIfEmpty = hide; | ||||
|         this.Update(); | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     Render(): string { | ||||
|         this.lastInnerRender = this.InnerRender(); | ||||
|         if (this.dumbMode) { | ||||
|             return this.lastInnerRender; | ||||
|         } | ||||
| 
 | ||||
|         let style = ""; | ||||
|         if (this.style !== undefined && this.style !== "") { | ||||
|             style = `style="${this.style}" `; | ||||
|         } | ||||
|         let clss = ""; | ||||
|         if (this.clss.size > 0) { | ||||
|             clss = `class='${Array.from(this.clss).join(" ")}' `; | ||||
|         } | ||||
|         return `<span ${clss}${style}id='${this.id}' gen="${this.constructor.name}">${this.lastInnerRender}</span>` | ||||
|         return "Don't use Render!" | ||||
|     } | ||||
| 
 | ||||
|     AttachTo(divId: string) { | ||||
|         this.dumbMode = false; | ||||
|         let element = document.getElementById(divId); | ||||
|         if (element === null) { | ||||
|             throw "SEVERE: could not attach UIElement to " + divId; | ||||
|         } | ||||
|         element.innerHTML = this.Render(); | ||||
|         this.Update(); | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public abstract InnerRender(): string; | ||||
|     public InnerRenderAsString(): string { | ||||
|         let rendered = this.InnerRender(); | ||||
|         if (typeof rendered !== "string") { | ||||
|             let html = rendered.ConstructElement() | ||||
|             return html.innerHTML | ||||
|         } | ||||
|         return rendered | ||||
|     } | ||||
| 
 | ||||
|     public IsEmpty(): boolean { | ||||
|         return this.InnerRender() === ""; | ||||
|         return this.InnerRender() === undefined || this.InnerRender() === ""; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Adds all the relevant classes, space seperated | ||||
|      * @param clss | ||||
|      * @constructor | ||||
|      * Should be overridden for specific HTML functionality | ||||
|      */ | ||||
|     public SetClass(clss: string) { | ||||
|         this.dumbMode = false; | ||||
|         const all = clss.split(" "); | ||||
|         let recordedChange = false; | ||||
|         for (const c of all) { | ||||
|             if (this.clss.has(clss)) { | ||||
|                 continue; | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         // Uses the old fashioned way to construct an element using 'InnerRender'
 | ||||
|         const innerRender = this.InnerRender(); | ||||
|         if (innerRender === undefined || innerRender === "") { | ||||
|             return undefined; | ||||
|         } | ||||
|         const el = document.createElement("span") | ||||
|         if (typeof innerRender === "string") { | ||||
|             el.innerHTML = innerRender | ||||
|         } else { | ||||
|             const subElement = innerRender.ConstructElement(); | ||||
|             if (subElement === undefined) { | ||||
|                 return undefined; | ||||
|             } | ||||
|             this.clss.add(c); | ||||
|             recordedChange = true; | ||||
|             el.appendChild(subElement) | ||||
|         } | ||||
|         if (recordedChange) { | ||||
|             this.Update(); | ||||
|         } | ||||
|         return this; | ||||
|         return el; | ||||
|     } | ||||
| 
 | ||||
|     public RemoveClass(clss: string): UIElement { | ||||
|         if (this.clss.has(clss)) { | ||||
|             this.clss.delete(clss); | ||||
|             this.Update(); | ||||
|         } | ||||
|         return this; | ||||
|     } | ||||
|     protected UpdateElement(el: HTMLElement) : void{ | ||||
|         const innerRender = this.InnerRender(); | ||||
| 
 | ||||
|     public SetStyle(style: string): UIElement { | ||||
|         this.dumbMode = false; | ||||
|         this.style = style; | ||||
|         this.Update(); | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     // Called after the HTML has been replaced. Can be used for css tricks
 | ||||
|     protected InnerUpdate(htmlElement: HTMLElement) { | ||||
|     } | ||||
| 
 | ||||
|     private UpdateAllChildren() { | ||||
|         for (const i in this) { | ||||
|             const child = this[i]; | ||||
|             if (child instanceof UIElement) { | ||||
|                 child.Update(); | ||||
|             } else if (child instanceof Array) { | ||||
|                 for (const ch of child) { | ||||
|                     if (ch instanceof UIElement) { | ||||
|                         ch.Update(); | ||||
|                     } | ||||
|                 } | ||||
|         if (typeof innerRender === "string") { | ||||
|             if(el.innerHTML !== innerRender){ | ||||
|                 el.innerHTML = innerRender     | ||||
|             } | ||||
|         } else { | ||||
|             const subElement = innerRender.ConstructElement(); | ||||
|             if(el.children.length === 1 && el.children[0] === subElement){ | ||||
|                 return; // Nothing changed
 | ||||
|             } | ||||
| 
 | ||||
|             while (el.firstChild) { | ||||
|                 el.removeChild(el.firstChild); | ||||
|             }            | ||||
|              | ||||
|             if (subElement === undefined) { | ||||
|                 return; | ||||
|             } | ||||
|             el.appendChild(subElement) | ||||
|         } | ||||
|          | ||||
|     } | ||||
|      | ||||
|    | ||||
| 
 | ||||
|     /** | ||||
|      * @deprecated The method should not be used | ||||
|      */ | ||||
|     protected abstract InnerRender(): string | BaseUIElement; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue