forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			189 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * A thin wrapper around a html element, which allows to generate a HTML-element.
 | |
|  *
 | |
|  * Assumes a read-only configuration, so it has no 'ListenTo'
 | |
|  */
 | |
| import { Utils } from "../Utils"
 | |
| 
 | |
| export default abstract class BaseUIElement {
 | |
|     protected _constructedHtmlElement: HTMLElement
 | |
|     protected isDestroyed = false
 | |
|     protected readonly clss: Set<string> = new Set<string>()
 | |
|     protected style: string
 | |
|     private _onClick: () => void | Promise<void>
 | |
| 
 | |
|     public onClick(f: () => void) {
 | |
|         this._onClick = f
 | |
|         this.SetClass("cursor-pointer")
 | |
|         if (this._constructedHtmlElement !== undefined) {
 | |
|             this._constructedHtmlElement.onclick = f
 | |
|         }
 | |
|         return this
 | |
|     }
 | |
| 
 | |
|     AttachTo(divId: string) {
 | |
|         let element = document.getElementById(divId)
 | |
|         if (element === null) {
 | |
|             if (Utils.runningFromConsole) {
 | |
|                 this.ConstructElement()
 | |
|                 return
 | |
|             }
 | |
|             throw "SEVERE: could not attach UIElement to " + divId
 | |
|         }
 | |
| 
 | |
|         let alreadyThere = false
 | |
|         const elementToAdd = this.ConstructElement()
 | |
|         const childs = Array.from(element.childNodes)
 | |
|         for (const child of childs) {
 | |
|             if (child === elementToAdd) {
 | |
|                 alreadyThere = true
 | |
|                 continue
 | |
|             }
 | |
|             element.removeChild(child)
 | |
|         }
 | |
| 
 | |
|         if (elementToAdd !== undefined && !alreadyThere) {
 | |
|             element.appendChild(elementToAdd)
 | |
|         }
 | |
| 
 | |
|         return this
 | |
|     }
 | |
| 
 | |
|     public ScrollIntoView() {
 | |
|         if (this._constructedHtmlElement === undefined) {
 | |
|             return
 | |
|         }
 | |
|         this._constructedHtmlElement?.scrollIntoView({
 | |
|             behavior: "smooth",
 | |
|             block: "start",
 | |
|         })
 | |
|     }
 | |
|     /**
 | |
|      * Adds all the relevant classes, space separated
 | |
|      */
 | |
|     public SetClass(clss: string) {
 | |
|         if (clss == undefined) {
 | |
|             return this
 | |
|         }
 | |
|         const all = clss.split(" ").map((clsName) => clsName.trim())
 | |
|         let recordedChange = false
 | |
|         for (let c of all) {
 | |
|             c = c.trim()
 | |
|             if (this.clss.has(clss)) {
 | |
|                 continue
 | |
|             }
 | |
|             if (c === undefined || c === "") {
 | |
|                 continue
 | |
|             }
 | |
|             this.clss.add(c)
 | |
|             recordedChange = true
 | |
|         }
 | |
|         if (recordedChange) {
 | |
|             this._constructedHtmlElement?.classList.add(...Array.from(this.clss))
 | |
|         }
 | |
|         return this
 | |
|     }
 | |
| 
 | |
|     public RemoveClass(classes: string): BaseUIElement {
 | |
|         const all = classes.split(" ").map((clsName) => clsName.trim())
 | |
|         for (let clss of all) {
 | |
|             if (this.clss.has(clss)) {
 | |
|                 this.clss.delete(clss)
 | |
|                 this._constructedHtmlElement?.classList.remove(clss)
 | |
|             }
 | |
|         }
 | |
|         return this
 | |
|     }
 | |
| 
 | |
|     public HasClass(clss: string): boolean {
 | |
|         return this.clss.has(clss)
 | |
|     }
 | |
| 
 | |
|     public SetStyle(style: string): BaseUIElement {
 | |
|         this.style = style
 | |
|         if (this._constructedHtmlElement !== undefined) {
 | |
|             this._constructedHtmlElement.style.cssText = style
 | |
|         }
 | |
|         return this
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * The same as 'Render', but creates a HTML element instead of the HTML representation
 | |
|      */
 | |
|     public ConstructElement(): HTMLElement {
 | |
|         if (typeof window === undefined) {
 | |
|             return undefined
 | |
|         }
 | |
| 
 | |
|         if (this._constructedHtmlElement !== undefined) {
 | |
|             return this._constructedHtmlElement
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             const el = this.InnerConstructElement()
 | |
| 
 | |
|             if (el === undefined) {
 | |
|                 return undefined
 | |
|             }
 | |
| 
 | |
|             this._constructedHtmlElement = el
 | |
|             const style = this.style
 | |
|             if (style !== undefined && style !== "") {
 | |
|                 el.style.cssText = style
 | |
|             }
 | |
|             if (this.clss?.size > 0) {
 | |
|                 try {
 | |
|                     el.classList.add(...Array.from(this.clss))
 | |
|                 } catch (e) {
 | |
|                     console.error(
 | |
|                         "Invalid class name detected in:",
 | |
|                         Array.from(this.clss).join(" "),
 | |
|                         "\nErr msg is ",
 | |
|                         e
 | |
|                     )
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (this._onClick !== undefined) {
 | |
|                 const self = this
 | |
|                 el.onclick = async (e) => {
 | |
|                     // @ts-ignore
 | |
|                     if (e.consumed) {
 | |
|                         return
 | |
|                     }
 | |
|                     const v = self._onClick()
 | |
|                     if (typeof v === "object") {
 | |
|                         await v
 | |
|                     }
 | |
|                     // @ts-ignore
 | |
|                     e.consumed = true
 | |
|                 }
 | |
|                 el.classList.add("cursor-pointer")
 | |
|             }
 | |
| 
 | |
|             return el
 | |
|         } catch (e) {
 | |
|             const domExc = e as DOMException
 | |
|             if (domExc) {
 | |
|                 console.error(
 | |
|                     "An exception occured",
 | |
|                     domExc.code,
 | |
|                     domExc.message,
 | |
|                     domExc.name,
 | |
|                     domExc
 | |
|                 )
 | |
|             }
 | |
|             console.error(e)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public AsMarkdown(): string {
 | |
|         throw "AsMarkdown is not implemented; implement it in the subclass"
 | |
|     }
 | |
| 
 | |
|     public Destroy() {
 | |
|         this.isDestroyed = true
 | |
|     }
 | |
| 
 | |
|     protected abstract InnerConstructElement(): HTMLElement
 | |
| }
 |