github.com/vugu/vugu@v0.3.5/component.go (about) 1 package vugu 2 3 import ( 4 "bytes" 5 ) 6 7 // BuildIn is the input to a Build call. 8 type BuildIn struct { 9 10 // the overall build environment 11 BuildEnv *BuildEnv 12 13 // a stack of position hashes, the last one can be used by a component to get a unique hash for overall position 14 PositionHashList []uint64 15 } 16 17 // CurrentPositionHash returns the hash value that can be used by a component to 18 // mix into it's own hash values to achieve uniqueness based on overall position 19 // in the output tree. Basically you should XOR this with whatever unique reference 20 // ID within the component itself used during Build. The purpose is to ensure 21 // that we use the same ID for the same component in the same position within 22 // the overall tree but a different one for the same component in a different position. 23 func (bi *BuildIn) CurrentPositionHash() uint64 { 24 if len(bi.PositionHashList) == 0 { 25 return 0 26 } 27 return bi.PositionHashList[len(bi.PositionHashList)-1] 28 } 29 30 // BuildOut is the output from a Build call. It includes Out as the DOM elements 31 // produced, plus slices for CSS and JS elements. 32 type BuildOut struct { 33 34 // output element(s) - usually just one that is parent to the rest but slots can have multiple 35 Out []*VGNode 36 37 // components that need to be built next - corresponding to each VGNode in Out with a Component value set, 38 // we make the Builder populate this so BuildEnv doesn't have to traverse Out to gather up each node with a component 39 // FIXME: should this be called ChildComponents, NextComponents? something to make it clear that these are pointers to more work to 40 // do rather than something already done 41 Components []Builder 42 43 // optional CSS style or link tag(s) 44 CSS []*VGNode 45 46 // optional JS script tag(s) 47 JS []*VGNode 48 } 49 50 // AppendCSS will append a unique node to CSS (nodes match exactly will not be added again). 51 func (b *BuildOut) AppendCSS(nlist ...*VGNode) { 52 // FIXME: we really should consider doing this dedeplication with a map or something, but for now this works 53 nlistloop: 54 for _, n := range nlist { 55 for _, css := range b.CSS { 56 if !ssNodeEq(n, css) { 57 continue 58 } 59 if ssText(n) != ssText(css) { 60 continue 61 } 62 // these are the same, skip duplicate add 63 continue nlistloop 64 } 65 // not a duplicate, add it 66 b.CSS = append(b.CSS, n) 67 } 68 } 69 70 // AppendJS will append a unique node to JS (nodes match exactly will not be added again). 71 func (b *BuildOut) AppendJS(nlist ...*VGNode) { 72 nlistloop: 73 for _, n := range nlist { 74 for _, js := range b.JS { 75 if !ssNodeEq(n, js) { 76 continue 77 } 78 if ssText(n) != ssText(js) { 79 continue 80 } 81 // these are the same, skip duplicate add 82 continue nlistloop 83 } 84 // not a duplicate, add it 85 b.JS = append(b.JS, n) 86 } 87 } 88 89 // returns text content of child of script/style tag (assumes only one level of children and all text) 90 func ssText(n *VGNode) string { 91 // special case 0 children 92 if n.FirstChild == nil { 93 return "" 94 } 95 // special case 1 child 96 if n.FirstChild.NextSibling == nil { 97 return n.FirstChild.Data 98 } 99 // more than one child 100 var buf bytes.Buffer 101 for childN := n.FirstChild; childN != nil; childN = childN.NextSibling { 102 buf.WriteString(childN.Data) 103 } 104 return buf.String() 105 } 106 107 // script/style node compare - only care about Type, Data and Attributes, does not examine children 108 func ssNodeEq(n1, n2 *VGNode) bool { 109 if n1.Type != n2.Type { 110 return false 111 } 112 if n1.Data != n2.Data { 113 return false 114 } 115 if len(n1.Attr) != len(n2.Attr) { 116 return false 117 } 118 for i := 0; i < len(n1.Attr); i++ { 119 if n1.Attr[i] != n2.Attr[i] { 120 return false 121 } 122 } 123 return true 124 } 125 126 // FIXME: CSS and JS are now full element nodes not just blocks of text (style, link, or script tags), 127 // and we should have some sort of AppendCSS and AppendJS methods that understand that and also perform 128 // deduplication. There is no reason to wait and deduplicate later, we should be doing it here during 129 // the Build. 130 131 // func (b *BuildOut) AppendCSS(css string) { 132 // // FIXME: should we be deduplicating here?? 133 // vgn := &VGNode{Type: ElementNode, Data: "style"} 134 // vgn.AppendChild(&VGNode{Type: TextNode, Data: css}) 135 // b.CSS = append(b.CSS, vgn) 136 // } 137 138 // func (b *BuildOut) AppendJS(js string) { 139 // // FIXME: should we be deduplicating here?? 140 // vgn := &VGNode{Type: ElementNode, Data: "script"} 141 // vgn.AppendChild(&VGNode{Type: TextNode, Data: js}) 142 // b.JS = append(b.JS, vgn) 143 // } 144 145 // Builder is the interface that components implement. 146 type Builder interface { 147 // Build is called to construct a tree of VGNodes corresponding to the DOM of this component. 148 // Note that Build does not return an error because there is nothing the caller can do about 149 // it except for stop rendering. This way we force errors during Build to either be handled 150 // internally or to explicitly result in a panic. 151 Build(in *BuildIn) (out *BuildOut) 152 } 153 154 // builderFunc is a Build-like function that implements Builder. 155 type builderFunc func(in *BuildIn) (out *BuildOut) 156 157 // Build implements Builder. 158 func (f builderFunc) Build(in *BuildIn) (out *BuildOut) { return f(in) } 159 160 // NewBuilderFunc returns a Builder from a function that is called for Build. 161 func NewBuilderFunc(f func(in *BuildIn) (out *BuildOut)) Builder { 162 // NOTE: for now, all components have to be struct pointers, so we wrap 163 // this function in a struct pointer. Would be nice to fix this at some point. 164 return &struct { 165 Builder 166 }{ 167 Builder: builderFunc(f), 168 } 169 } 170 171 // BeforeBuilder is deprecated. It is replaced by the Compute lifecycle callback. 172 type BeforeBuilder interface { 173 BeforeBuild() 174 }