The cog package provides functionality to develop reusable components (cogs) for Isomorphic Go web applications.

uxcog.go 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. // The UXToolkit Project
  2. // Copyright (c) Wirecog, LLC. All rights reserved.
  3. // Use of this source code is governed by a BSD-style
  4. // license, which can be found in the LICENSE file.
  5. package cog
  6. import (
  7. "errors"
  8. "os"
  9. "path/filepath"
  10. "reflect"
  11. "strings"
  12. "go.isomorphicgo.org/go/isokit"
  13. "go.isomorphicgo.org/uxtoolkit/reconcile"
  14. _ "golang.org/x/net/html"
  15. "honnef.co/go/js/dom"
  16. )
  17. type UXCog struct {
  18. Cog
  19. cogType reflect.Type
  20. cogPrefixName string
  21. cogPackagePath string
  22. cogTemplatePath string
  23. templateSet *isokit.TemplateSet
  24. Props map[string]interface{}
  25. element *dom.Element
  26. id string
  27. hasBeenRendered bool
  28. parseTree *reconcile.ParseTree
  29. cleanupFunc func()
  30. }
  31. func (u *UXCog) getCogPrefixName() string {
  32. if u.cogType != nil {
  33. result := strings.Split(u.cogType.PkgPath(), string(os.PathSeparator))
  34. return "cog:" + result[len(result)-1]
  35. } else {
  36. return ""
  37. }
  38. }
  39. func (u *UXCog) ID() string {
  40. return u.id
  41. }
  42. func (u *UXCog) SetID(id string) {
  43. u.id = id
  44. }
  45. func (u *UXCog) SetCleanupFunc(cleanupFunc func()) {
  46. u.cleanupFunc = cleanupFunc
  47. }
  48. func (u *UXCog) SetElement(element *dom.Element) {
  49. u.element = element
  50. }
  51. func (u *UXCog) Element() *dom.Element {
  52. return u.element
  53. }
  54. func (u *UXCog) CogInit(ts *isokit.TemplateSet) {
  55. u.hasBeenRendered = false
  56. u.Props = make(map[string]interface{})
  57. if ts != nil {
  58. u.templateSet = ts
  59. }
  60. u.cogTemplatePath = filepath.Join(DefaultGoSourcePath, u.cogType.PkgPath(), DefaultTemplatesDirectoryName)
  61. u.cogPrefixName = u.getCogPrefixName()
  62. if isokit.OperatingEnvironment() == isokit.ServerEnvironment {
  63. u.RegisterCogTemplates()
  64. }
  65. }
  66. func (u *UXCog) TemplateSet() *isokit.TemplateSet {
  67. return u.templateSet
  68. }
  69. func (u *UXCog) SetTemplateSet(ts *isokit.TemplateSet) {
  70. u.templateSet = ts
  71. }
  72. func (u *UXCog) CogType() reflect.Type {
  73. return u.cogType
  74. }
  75. func (u *UXCog) SetCogType(cogType reflect.Type) {
  76. u.cogType = cogType
  77. }
  78. func (u *UXCog) CogTemplatePath() string {
  79. return u.cogTemplatePath
  80. }
  81. func (u *UXCog) SetCogTemplatePath(path string) {
  82. u.cogTemplatePath = path
  83. }
  84. func (u *UXCog) RegisterCogTemplates() {
  85. u.templateSet.GatherCogTemplates(u.cogTemplatePath, u.cogPrefixName, ".tmpl")
  86. }
  87. func (u *UXCog) GetProps() map[string]interface{} {
  88. return u.Props
  89. }
  90. func (u *UXCog) SetProp(key string, value interface{}) {
  91. u.Props[key] = value
  92. if ReactivityEnabled == true && u.hasBeenRendered == true {
  93. u.Render()
  94. }
  95. }
  96. func (u *UXCog) BatchPropUpdate(props map[string]interface{}) {
  97. for k, v := range props {
  98. u.Props[k] = v
  99. }
  100. if ReactivityEnabled == true && u.hasBeenRendered == true {
  101. u.Render()
  102. }
  103. }
  104. func (u *UXCog) RenderCogTemplate() {
  105. var populateRenderedContent bool
  106. if u.hasBeenRendered == false {
  107. populateRenderedContent = true
  108. } else {
  109. populateRenderedContent = false
  110. }
  111. rp := isokit.RenderParams{Data: u.Props, Disposition: isokit.PlacementReplaceInnerContents, Element: *u.element, ShouldPopulateRenderedContent: populateRenderedContent}
  112. u.templateSet.Render(filepath.Join(u.getCogPrefixName(), strings.Split(u.getCogPrefixName(), ":")[1]), &rp)
  113. if u.hasBeenRendered == false {
  114. u.hasBeenRendered = true
  115. D := dom.GetWindow().Document()
  116. cogRoot := D.GetElementByID(u.id).FirstChild().(*dom.HTMLDivElement)
  117. contents := cogRoot.InnerHTML()
  118. parseTree, err := reconcile.NewParseTree([]byte(contents))
  119. if err != nil {
  120. println("Encountered an error: ", err)
  121. } else {
  122. u.parseTree = parseTree
  123. }
  124. }
  125. }
  126. func (u *UXCog) Render() error {
  127. document := dom.GetWindow().Document()
  128. e := document.GetElementByID(u.ID())
  129. if u.hasBeenRendered == true && e == nil {
  130. if u.cleanupFunc != nil {
  131. u.cleanupFunc()
  132. return nil
  133. }
  134. }
  135. if strings.ToLower(e.GetAttribute("data-component")) != "cog" {
  136. return errors.New("The cog container div must have a \"data-component\" attribute with a value specified as \"cog\".")
  137. }
  138. if u.hasBeenRendered == false {
  139. // Initial Render
  140. u.SetElement(&e)
  141. u.RenderCogTemplate()
  142. return nil
  143. } else if u.element != nil {
  144. // Re-render
  145. if VDOMEnabled == true {
  146. rp := isokit.RenderParams{Data: u.Props, Disposition: isokit.PlacementReplaceInnerContents, Element: *u.element, ShouldPopulateRenderedContent: true, ShouldSkipFinalRenderStep: true}
  147. u.templateSet.Render(filepath.Join(u.getCogPrefixName(), strings.Split(u.getCogPrefixName(), ":")[1]), &rp)
  148. D := dom.GetWindow().Document()
  149. cogRoot := D.GetElementByID(u.id).FirstChild().(*dom.HTMLDivElement)
  150. //contents := cogRoot.InnerHTML()
  151. newTree, err := reconcile.NewParseTree([]byte(rp.RenderedContent))
  152. if err != nil {
  153. println("Encountered an error: ", err)
  154. }
  155. changes, err := u.parseTree.Compare(newTree)
  156. if err != nil {
  157. println("Encountered an error: ", err)
  158. }
  159. if len(changes) > 0 {
  160. changes.ApplyChanges(cogRoot)
  161. u.parseTree = newTree
  162. }
  163. } else {
  164. u.RenderCogTemplate()
  165. }
  166. return nil
  167. }
  168. return nil
  169. }