html.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. package app
  2. import (
  3. "context"
  4. "io"
  5. "reflect"
  6. "github.com/maxence-charriere/go-app/v9/pkg/errors"
  7. )
  8. type htmlElement struct {
  9. tag string
  10. xmlns string
  11. isSelfClosing bool
  12. attributes attributes
  13. eventHandlers eventHandlers
  14. parent UI
  15. children []UI
  16. context context.Context
  17. contextCancel func()
  18. dispatcher Dispatcher
  19. jsElement Value
  20. this UI
  21. }
  22. func (e *htmlElement) Kind() Kind {
  23. return HTML
  24. }
  25. func (e *htmlElement) JSValue() Value {
  26. return e.jsElement
  27. }
  28. func (e *htmlElement) Mounted() bool {
  29. return e.context != nil && e.context.Err() == nil
  30. }
  31. func (e *htmlElement) name() string {
  32. return e.tag
  33. }
  34. func (e *htmlElement) self() UI {
  35. return e.this
  36. }
  37. func (e *htmlElement) setSelf(v UI) {
  38. e.this = v
  39. }
  40. func (e *htmlElement) getContext() context.Context {
  41. return e.context
  42. }
  43. func (e *htmlElement) getDispatcher() Dispatcher {
  44. return e.dispatcher
  45. }
  46. func (e *htmlElement) getAttributes() attributes {
  47. return e.attributes
  48. }
  49. func (e *htmlElement) getEventHandlers() eventHandlers {
  50. return e.eventHandlers
  51. }
  52. func (e *htmlElement) getParent() UI {
  53. return e.parent
  54. }
  55. func (e *htmlElement) setParent(p UI) {
  56. e.parent = p
  57. }
  58. func (e *htmlElement) getChildren() []UI {
  59. return e.children
  60. }
  61. func (e *htmlElement) mount(d Dispatcher) error {
  62. if e.Mounted() {
  63. return errors.New("html element is already mounted").
  64. Tag("tag", e.tag).
  65. Tag("kind", e.Kind())
  66. }
  67. e.context, e.contextCancel = context.WithCancel(context.Background())
  68. e.dispatcher = d
  69. jsElement, err := Window().createElement(e.tag, e.xmlns)
  70. if err != nil {
  71. return errors.New("mounting js element failed").
  72. Tag("kind", e.Kind()).
  73. Tag("tag", e.tag).
  74. Tag("xmlns", e.xmlns).
  75. Wrap(err)
  76. }
  77. e.jsElement = jsElement
  78. e.attributes.Mount(jsElement, d.resolveStaticResource)
  79. e.eventHandlers.Mount(e)
  80. for i, c := range e.children {
  81. if err := mount(d, c); err != nil {
  82. return errors.New("mounting child failed").
  83. Tag("index", i).
  84. Tag("child", c.name()).
  85. Tag("child-kind", c.Kind()).
  86. Wrap(err)
  87. }
  88. c.setParent(e.self())
  89. e.JSValue().appendChild(c)
  90. }
  91. return nil
  92. }
  93. func (e *htmlElement) dismount() {
  94. for _, c := range e.children {
  95. dismount(c)
  96. }
  97. for _, eh := range e.eventHandlers {
  98. eh.Dismount()
  99. }
  100. e.contextCancel()
  101. }
  102. func (e *htmlElement) canUpdateWith(v UI) bool {
  103. return e.Mounted() &&
  104. e.Kind() == v.Kind() &&
  105. e.name() == v.name()
  106. }
  107. func (e *htmlElement) updateWith(v UI) error {
  108. if !e.canUpdateWith(v) {
  109. return errors.New("cannot update html element with given element").
  110. Tag("current", reflect.TypeOf(e.self())).
  111. Tag("new", reflect.TypeOf(v))
  112. }
  113. if e.attributes == nil && v.getAttributes() != nil {
  114. e.attributes = v.getAttributes()
  115. e.attributes.Mount(e.jsElement, e.dispatcher.resolveStaticResource)
  116. } else if e.attributes != nil {
  117. e.attributes.Update(
  118. e.jsElement,
  119. v.getAttributes(),
  120. e.getDispatcher().resolveStaticResource,
  121. )
  122. }
  123. if e.eventHandlers == nil && v.getEventHandlers() != nil {
  124. e.eventHandlers = v.getEventHandlers()
  125. e.eventHandlers.Mount(e)
  126. } else if e.eventHandlers != nil {
  127. e.eventHandlers.Update(e, v.getEventHandlers())
  128. }
  129. childrenA := e.children
  130. childrenB := v.getChildren()
  131. i := 0
  132. for len(childrenA) != 0 && len(childrenB) != 0 {
  133. a := childrenA[0]
  134. b := childrenB[0]
  135. if canUpdate(a, b) {
  136. if err := update(a, b); err != nil {
  137. return errors.New("updating child failed").
  138. Tag("child", reflect.TypeOf(a)).
  139. Tag("new-child", reflect.TypeOf(b)).
  140. Tag("index", i).
  141. Wrap(err)
  142. }
  143. } else {
  144. if err := e.replaceChildAt(i, b); err != nil {
  145. return errors.New("replacing child failed").
  146. Tag("child", reflect.TypeOf(a)).
  147. Tag("new-child", reflect.TypeOf(b)).
  148. Tag("index", i).
  149. Wrap(err)
  150. }
  151. }
  152. childrenA = childrenA[1:]
  153. childrenB = childrenB[1:]
  154. i++
  155. }
  156. for len(childrenA) != 0 {
  157. if err := e.removeChildAt(i); err != nil {
  158. return errors.New("removing child failed").
  159. Tag("child", reflect.TypeOf(childrenA[0])).
  160. Tag("index", i).
  161. Wrap(err)
  162. }
  163. childrenA = childrenA[1:]
  164. }
  165. for len(childrenB) != 0 {
  166. b := childrenB[0]
  167. if err := e.appendChild(b); err != nil {
  168. return errors.New("appending child failed").
  169. Tag("child", reflect.TypeOf(b)).
  170. Tag("index", i).
  171. Wrap(err)
  172. }
  173. childrenB = childrenB[1:]
  174. }
  175. return nil
  176. }
  177. func (e *htmlElement) replaceChildAt(idx int, new UI) error {
  178. old := e.children[idx]
  179. if err := mount(e.getDispatcher(), new); err != nil {
  180. return errors.New("replacing child failed").
  181. Tag("name", e.name()).
  182. Tag("kind", e.Kind()).
  183. Tag("index", idx).
  184. Tag("old-name", old.name()).
  185. Tag("old-kind", old.Kind()).
  186. Tag("new-name", new.name()).
  187. Tag("new-kind", new.Kind()).
  188. Wrap(err)
  189. }
  190. e.children[idx] = new
  191. new.setParent(e.self())
  192. e.JSValue().replaceChild(new, old)
  193. dismount(old)
  194. return nil
  195. }
  196. func (e *htmlElement) removeChildAt(i int) error {
  197. if i < 0 || i >= len(e.children) {
  198. return errors.New("index out of range").
  199. Tag("index", i).
  200. Tag("children-count", len(e.children))
  201. }
  202. child := e.children[i]
  203. e.jsElement.removeChild(child)
  204. dismount(child)
  205. children := e.children
  206. copy(children[i:], children[i+1:])
  207. children[len(children)-1] = nil
  208. e.children = children[:len(children)-1]
  209. return nil
  210. }
  211. func (e *htmlElement) appendChild(v UI) error {
  212. if err := mount(e.getDispatcher(), v); err != nil {
  213. return errors.New("mounting element failed").
  214. Tag("element", reflect.TypeOf(v)).
  215. Wrap(err)
  216. }
  217. v.setParent(e.self())
  218. e.JSValue().appendChild(v)
  219. e.children = append(e.children, v)
  220. return nil
  221. }
  222. func (e *htmlElement) setAttr(name string, value any) {
  223. if e.attributes == nil {
  224. e.attributes = make(attributes)
  225. }
  226. e.attributes.Set(name, value)
  227. }
  228. func (e *htmlElement) setEventHandler(event string, h EventHandler, scope ...any) {
  229. if e.eventHandlers == nil {
  230. e.eventHandlers = make(eventHandlers)
  231. }
  232. e.eventHandlers.Set(event, h, scope...)
  233. }
  234. func (e *htmlElement) setChildren(v ...UI) {
  235. if e.isSelfClosing {
  236. panic(errors.New("cannot set children of a self closing element").
  237. Tag("element", e.tag),
  238. )
  239. }
  240. e.children = FilterUIElems(v...)
  241. }
  242. func (e *htmlElement) preRender(p Page) {
  243. for _, c := range e.getChildren() {
  244. c.preRender(p)
  245. }
  246. }
  247. func (e *htmlElement) onComponentEvent(le any) {
  248. for _, c := range e.getChildren() {
  249. c.onComponentEvent(le)
  250. }
  251. }
  252. func (e *htmlElement) html(w io.Writer) {
  253. io.WriteString(w, "<")
  254. io.WriteString(w, e.tag)
  255. for k, v := range e.attributes {
  256. io.WriteString(w, " ")
  257. io.WriteString(w, k)
  258. if v != "" {
  259. io.WriteString(w, `="`)
  260. io.WriteString(w, resolveAttributeURLValue(k, v, func(s string) string {
  261. if e.dispatcher != nil {
  262. return e.dispatcher.resolveStaticResource(v)
  263. }
  264. return v
  265. }))
  266. io.WriteString(w, `"`)
  267. }
  268. }
  269. io.WriteString(w, ">")
  270. if e.isSelfClosing {
  271. return
  272. }
  273. for _, c := range e.children {
  274. io.WriteString(w, "\n")
  275. if c.self() == nil {
  276. c.setSelf(c)
  277. }
  278. c.html(w)
  279. }
  280. if len(e.children) != 0 {
  281. io.WriteString(w, "\n")
  282. }
  283. io.WriteString(w, "</")
  284. io.WriteString(w, e.tag)
  285. io.WriteString(w, ">")
  286. }
  287. func (e *htmlElement) htmlWithIndent(w io.Writer, indent int) {
  288. writeIndent(w, indent)
  289. io.WriteString(w, "<")
  290. io.WriteString(w, e.tag)
  291. for k, v := range e.attributes {
  292. io.WriteString(w, " ")
  293. io.WriteString(w, k)
  294. if v != "" {
  295. io.WriteString(w, `="`)
  296. io.WriteString(w, resolveAttributeURLValue(k, v, func(s string) string {
  297. if e.dispatcher != nil {
  298. return e.dispatcher.resolveStaticResource(v)
  299. }
  300. return v
  301. }))
  302. io.WriteString(w, `"`)
  303. }
  304. }
  305. io.WriteString(w, ">")
  306. if e.isSelfClosing {
  307. return
  308. }
  309. for _, c := range e.children {
  310. io.WriteString(w, "\n")
  311. if c.self() == nil {
  312. c.setSelf(c)
  313. }
  314. c.htmlWithIndent(w, indent+1)
  315. }
  316. if len(e.children) != 0 {
  317. io.WriteString(w, "\n")
  318. writeIndent(w, indent)
  319. }
  320. io.WriteString(w, "</")
  321. io.WriteString(w, e.tag)
  322. io.WriteString(w, ">")
  323. }