├── .gitignore ├── example ├── components │ ├── App.css │ └── App.go ├── favicon.ico ├── main.wasm ├── build_from_go.sh ├── index.go ├── build_from_tinygo.sh ├── master.css ├── index.html └── .air.toml ├── assets └── logo.png ├── go.mod ├── go.sum ├── LICENSE ├── utils └── utils.go ├── gooroo_test.go ├── dom └── dom.go ├── README.md └── gooroo.go /.gitignore: -------------------------------------------------------------------------------- 1 | /**/tmp 2 | /**/*.js 3 | /**/.vscode -------------------------------------------------------------------------------- /example/components/App.css: -------------------------------------------------------------------------------- 1 | .title { 2 | color: cornflowerblue; 3 | } -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matbabs/Gooroo/HEAD/assets/logo.png -------------------------------------------------------------------------------- /example/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matbabs/Gooroo/HEAD/example/favicon.ico -------------------------------------------------------------------------------- /example/main.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matbabs/Gooroo/HEAD/example/main.wasm -------------------------------------------------------------------------------- /example/build_from_go.sh: -------------------------------------------------------------------------------- 1 | cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" . && GOOS=js GOARCH=wasm go build -o ./main.wasm -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Matbabs/Gooroo 2 | 3 | go 1.18 4 | 5 | require github.com/lithammer/shortuuid v3.0.0+incompatible 6 | 7 | require github.com/google/uuid v1.3.0 // indirect 8 | -------------------------------------------------------------------------------- /example/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | o "github.com/Matbabs/Gooroo" 5 | "github.com/Matbabs/Gooroo/example/components" 6 | ) 7 | 8 | func main() { 9 | o.Render(func() { 10 | o.Html( 11 | components.App(), 12 | ) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /example/build_from_tinygo.sh: -------------------------------------------------------------------------------- 1 | cp $(tinygo env TINYGOROOT)/targets/wasm_exec.js . && tinygo build -o main.wasm -target wasm --no-debug index.go 2 | file_to_modify="wasm_exec.js" 3 | string_to_replace="console.error('syscall/js.finalizeRef not implemented');" 4 | sed -i "s@$string_to_replace@@" "$file_to_modify" -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 2 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 3 | github.com/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w= 4 | github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHTK2Tciko1/vKuIKS9dSkDrA4w= 5 | -------------------------------------------------------------------------------- /example/master.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap"); 2 | 3 | *, 4 | ::before, 5 | ::after { 6 | box-sizing: border-box; 7 | font-family: "JetBrains Mono", monospace; 8 | } 9 | 10 | html, 11 | body { 12 | margin: 0; 13 | padding: 0; 14 | overflow: hidden; 15 | } 16 | 17 | input, 18 | button, 19 | select { 20 | padding: 8px; 21 | } 22 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Gooroo 4 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/.air.toml: -------------------------------------------------------------------------------- 1 | root = "." 2 | testdata_dir = "testdata" 3 | tmp_dir = "tmp" 4 | 5 | [build] 6 | args_bin = [] 7 | bin = "/bin/echo 'Gooroo ready !'" 8 | cmd = "cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" . && GOOS=js GOARCH=wasm go build -o ./main.wasm" 9 | delay = 0 10 | exclude_dir = ["assets", "tmp", "vendor", "testdata"] 11 | exclude_file = [] 12 | exclude_regex = ["_test.go"] 13 | exclude_unchanged = false 14 | follow_symlink = false 15 | full_bin = "" 16 | include_dir = [] 17 | include_ext = ["go", "tpl", "tmpl", "html"] 18 | kill_delay = "0s" 19 | log = "build-errors.log" 20 | send_interrupt = false 21 | stop_on_error = true 22 | 23 | [color] 24 | app = "" 25 | build = "blue" 26 | main = "magenta" 27 | runner = "cyan" 28 | watcher = "yellow" 29 | 30 | [log] 31 | time = false 32 | 33 | [misc] 34 | clean_on_exit = false 35 | 36 | [screen] 37 | clear_on_rebuild = false 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 BABONNEAU Matisse 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | // This package describes the private utility and generic functions to the other methods of the main gooroo package. 2 | package utils 3 | 4 | import ( 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // Check for the presence of a 'string' in a '[]string'. 10 | func Contains(s []string, str string) bool { 11 | for _, v := range s { 12 | if v == str { 13 | return true 14 | } 15 | } 16 | return false 17 | } 18 | 19 | // Initializes a value in a map if and only if it does not exist. 20 | func MapInit[T any](key string, _map map[string]T, value T) { 21 | _, isPresent := _map[key] 22 | if !isPresent { 23 | _map[key] = value 24 | } 25 | } 26 | 27 | // Initializes a function (value) in a map if and only if it does not exist. 28 | func MapInitCallback[T any](key string, _map map[string]T, callback func() T) { 29 | _, isPresent := _map[key] 30 | if !isPresent { 31 | _map[key] = callback() 32 | } 33 | } 34 | 35 | // Formatting a string for key management 36 | func CallerToKey(file string, no int) string { 37 | splitFile := strings.Split(file, "/") 38 | return fmt.Sprintf("%s#%d", splitFile[len(splitFile)-1], no) 39 | } 40 | 41 | // Convert 'any' type to 'string'. 42 | func AnyStr(v any) string { 43 | return fmt.Sprintf("%v", v) 44 | } 45 | -------------------------------------------------------------------------------- /example/components/App.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "fmt" 5 | "syscall/js" 6 | 7 | o "github.com/Matbabs/Gooroo" 8 | ) 9 | 10 | type Person struct { 11 | name string 12 | age string 13 | } 14 | 15 | func App() o.DomComponent { 16 | 17 | o.Css("components/App.css") 18 | 19 | name, _ := o.UseState("Paul") 20 | age, _ := o.UseState("42") 21 | p, setP := o.UseState(Person{(*name).(string), (*age).(string)}) 22 | 23 | o.UseEffect(func() { 24 | fmt.Println("When person changed !") 25 | }, p) 26 | 27 | handleChange := func(_ js.Value) { 28 | fmt.Println("OnChange callback") 29 | fmt.Println(*name) 30 | } 31 | 32 | handleSubmit := func(_ js.Value) { 33 | setP(Person{(*name).(string), (*age).(string)}) 34 | } 35 | 36 | return o.Div(o.Style("margin: auto; padding: 100px; width: 800px"), 37 | o.H1( 38 | "Tuto Gooroo web front Go ! v0.1.8", 39 | o.ClassName("title"), 40 | ), 41 | o.H2("Form"), 42 | o.Div(o.GridLayout(3, 0, "20px"), 43 | o.Span("Name"), 44 | o.Span("Age"), 45 | o.Span(""), 46 | o.Input(o.OnChange(name, handleChange)), 47 | o.Input(o.OnChange(age), o.Type("number")), 48 | o.Button("Click", o.OnClick(handleSubmit), o.Title("")), 49 | ), 50 | o.H2("From Store"), 51 | o.Div(o.FlexLayout("row", "left", "center", "20px"), 52 | o.P((*name).(string)), 53 | o.P((*age).(string)), 54 | ), 55 | o.H2("From State"), 56 | o.Div(o.FlexLayout("row", "left", "center", "20px"), 57 | o.P((*p).(Person).name), 58 | o.P((*p).(Person).age), 59 | ), 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /gooroo_test.go: -------------------------------------------------------------------------------- 1 | // GOOS=js GOARCH=wasm go test -cover -o example/main.wasm 2 | 3 | package gooroo 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "syscall/js" 9 | "testing" 10 | 11 | "github.com/Matbabs/Gooroo/dom" 12 | ) 13 | 14 | type test struct { 15 | name string 16 | function func(t *testing.T) 17 | } 18 | 19 | var head js.Value 20 | var body js.Value 21 | 22 | var tests = []test{ 23 | { 24 | "Css", 25 | func(t *testing.T) { 26 | path := "./master.css" 27 | Css(path) 28 | href := head.Get(dom.JS_CHILDREN).Get("4").Get(dom.JS_HREF).String() 29 | if !strings.Contains(href, "master.css") { 30 | t.Error("master.css not exist in href") 31 | } 32 | }, 33 | }, 34 | { 35 | "Html void", 36 | func(t *testing.T) { 37 | Html() 38 | length := body.Get(dom.JS_CHILDREN).Get(dom.JS_LENGTH).String() 39 | if !strings.Contains(length, "number: 1") { 40 | t.Error("Body has different number of child than expected") 41 | } 42 | }, 43 | }, 44 | { 45 | "Html children", 46 | func(t *testing.T) { 47 | title := "Test title !" 48 | paragraph := "Paragraph test" 49 | Html( 50 | Div( 51 | H1(title), 52 | P(paragraph), 53 | ), 54 | ) 55 | div := body.Get(dom.JS_CHILDREN).Get("1").Get(dom.JS_CHILDREN).Get("0") 56 | h1 := div.Get(dom.JS_CHILDREN).Get("0").Get(dom.JS_INNER_HTML) 57 | p := div.Get(dom.JS_CHILDREN).Get("1").Get(dom.JS_INNER_HTML) 58 | if title != h1.String() || paragraph != p.String() { 59 | t.Error("Innerhtml does not contain expected value") 60 | } 61 | }, 62 | }, 63 | { 64 | "sanitizeHtml", 65 | func(t *testing.T) { 66 | str := "" 67 | sanitizeHtml(&str) 68 | if strings.Contains(str, " 427 | 436 | 437 | 438 | 439 | 440 | ``` -------------------------------------------------------------------------------- /gooroo.go: -------------------------------------------------------------------------------- 1 | // The Gooroo package gathers a set of cumulative functions allowing you 2 | // to create web applications on the Frontend side. 3 | // To do this purpose, it implements DOM manipulation features based on syscall/js 4 | // and webassembly. 5 | // Its objective is to explore the possibilities of a modern, lightweight and 6 | // javascript independent web library. 7 | package gooroo 8 | 9 | import ( 10 | "fmt" 11 | "runtime" 12 | "strings" 13 | "syscall/js" 14 | 15 | "github.com/Matbabs/Gooroo/dom" 16 | "github.com/Matbabs/Gooroo/utils" 17 | ) 18 | 19 | // DomComponent represents an element of the DOM. This element can be a tag, an attribute, 20 | // a layout or even a binding. Most DomComponents can be nested within each other thanks to 21 | // variadic parameters. 22 | type DomComponent func() string 23 | 24 | // DomBinding is a structure that allows to retain the link between an event, a callback function 25 | // and a potential value to update when the event is triggered. 26 | // All binding is reapplied during rendering. 27 | type domBinding struct { 28 | event string 29 | callback js.Func 30 | value *any 31 | } 32 | 33 | // DomStore allows to keep the state of change of a value in the store. 34 | type domStore struct { 35 | value any 36 | hasChanged bool 37 | } 38 | 39 | var ( 40 | // Represents the global variable "document" of a website, useful for DOM manipulation and some 41 | // JavaScript interraction. 42 | document js.Value = js.Global().Get(dom.HTML_DOCUMENT) 43 | 44 | // List of paths to add CSS style sheets already imported into the website. 45 | stylesheets = []string{} 46 | 47 | // List of paths to add Js scripts already imported into the website. 48 | scripts = []string{} 49 | 50 | // Communication channel that generates a new rendering for each message sent within it. 51 | state = make(chan bool, 1) 52 | 53 | // List of DomBindings registered for the application rendering. 54 | bindings = make(map[string][]domBinding) 55 | 56 | // Store the last domComponent that have been focused 57 | lastDomComponentFocused = "" 58 | 59 | // Store of local variables recorded in the application state. 60 | store = make(map[string]*domStore) 61 | 62 | // Store of memoized variables. 63 | storeMemo = make(map[string]any) 64 | 65 | // Store of memoized functions. 66 | storeCallback = make(map[string]*func(...any) any) 67 | ) 68 | 69 | // Manipulate DOM 70 | 71 | // Hangs a CSS file in the content of the website. 72 | func Css(filepath string) { 73 | if !utils.Contains(stylesheets, filepath) { 74 | stylesheets = append(stylesheets, filepath) 75 | elem := document.Call(dom.JS_CREATE_ELEMENT, dom.HTML_LINK) 76 | document.Get(dom.HTML_HEAD).Call(dom.JS_APPEND_CHILD, elem) 77 | elem.Set(dom.JS_REL, dom.HTML_STYLESHEET) 78 | elem.Set(dom.JS_HREF, filepath) 79 | } 80 | } 81 | 82 | // Hangs a Js file in the content of the website. 83 | func Js(filepath string) { 84 | if !utils.Contains(scripts, filepath) { 85 | scripts = append(scripts, filepath) 86 | elem := document.Call(dom.JS_CREATE_ELEMENT, dom.HTML_SCRIPT) 87 | document.Get(dom.HTML_HEAD).Call(dom.JS_APPEND_CHILD, elem) 88 | elem.Set(dom.JS_SRC, filepath) 89 | } 90 | } 91 | 92 | // Triggers a rendering of the DOM, of all the DomComponents declared in parameters. 93 | func Html(domComponents ...DomComponent) { 94 | for i := range domComponents { 95 | elem := document.Call(dom.JS_CREATE_ELEMENT, dom.HTML_DIV) 96 | document.Get(dom.HTML_BODY).Call(dom.JS_APPEND_CHILD, elem) 97 | elem.Set(dom.JS_INNER_HTML, domComponents[i]()) 98 | } 99 | } 100 | 101 | // Clean the content of a string to prevent injections related to innerHtml attributes. 102 | func sanitizeHtml(htmlStr *string) { 103 | tmp := document.Call(dom.JS_CREATE_ELEMENT, dom.HTML_DIV) 104 | tmp.Set(dom.JS_INNER_HTML, *htmlStr) 105 | *htmlStr = tmp.Get(dom.JS_TEXT_CONTENT).String() 106 | } 107 | 108 | // Removes the whole rendering from the DOM, including the DomComponents passed as parameters 109 | // in the Html() function. 110 | func clearContext() { 111 | document.Get(dom.HTML_BODY).Set(dom.JS_INNER_HTML, "") 112 | } 113 | 114 | // Create a functional DomBinding set on its parameters. 115 | func generateBinding(id string, event string, value *any, callbacks ...func(js.Value)) domBinding { 116 | return domBinding{ 117 | event, 118 | js.FuncOf( 119 | func(_ js.Value, args []js.Value) any { 120 | needToChanged := false 121 | switch event { 122 | case dom.JS_EVENT_CHANGE, dom.JS_EVENT_KEYUP, dom.JS_EVENT_KEYDOWN: 123 | // change value when event is emitted before callbacks calls 124 | *value = args[0].Get(dom.JS_TARGET).Get(dom.JS_VALUE).String() 125 | needToChanged = true 126 | case dom.JS_EVENT_FOCUS: 127 | // set last focused 128 | lastDomComponentFocused = id 129 | } 130 | if event == dom.JS_EVENT_CHANGE || event == dom.JS_EVENT_CLICK { 131 | for i := range callbacks { 132 | callbacks[i](args[0]) 133 | } 134 | } 135 | if needToChanged { 136 | // force state change but keep updated value 137 | setHasChanged(value, *value) 138 | } 139 | return nil 140 | }, 141 | ), 142 | value, 143 | } 144 | } 145 | 146 | // Applies all the bindings to the DOM elements concerned. 147 | func setBindings() { 148 | for id := range bindings { 149 | elem := document.Call(dom.JS_GET_ELEMENT_BY_CLASSNAME, id).Index(0) 150 | for i := range bindings[id] { 151 | // add event listener 152 | elem.Call(dom.JS_ADD_EVENT_LISTENER, bindings[id][i].event, bindings[id][i].callback) 153 | switch bindings[id][i].event { 154 | case dom.JS_EVENT_CHANGE: 155 | // add actual value if defined in input 156 | elem.Set(dom.JS_VALUE, *(bindings[id][i].value)) 157 | case dom.JS_EVENT_FOCUS: 158 | // reset focus to input if last focused 159 | if id == lastDomComponentFocused { 160 | elem.Call(dom.JS_EVENT_FOCUS) 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | // Deletes all the DomBindings stored locally. 168 | func unsetBindings() { 169 | bindings = make(map[string][]domBinding) 170 | } 171 | 172 | // Change variable from store & updateState 173 | func setHasChanged(variable *any, setVal any) { 174 | for key := range store { 175 | if variable == &store[key].value { 176 | store[key].value = setVal 177 | store[key].hasChanged = true 178 | updateState() 179 | } 180 | } 181 | } 182 | 183 | // Checks if one or more variables in the store have been changed. 184 | func detectHasChanged(variables ...*any) bool { 185 | for i := range variables { 186 | for key := range store { 187 | if variables[i] == &store[key].value && store[key].hasChanged { 188 | return true 189 | } 190 | } 191 | } 192 | return false 193 | } 194 | 195 | // Reset to zero of all the changes of each variable stored in the store. 196 | func clearHasChange() { 197 | for key := range store { 198 | if store[key].hasChanged { 199 | store[key].hasChanged = false 200 | } 201 | } 202 | } 203 | 204 | // Starts the library's renderer. Allows to re-trigger the renderings when the 205 | // state changes (with a UseSate variable for example), through the state channel. 206 | // Must take a lambda function func() containing the call to Html() as parameter 207 | // to execute a rendering context. 208 | func Render(context func()) { 209 | updateState() 210 | for { 211 | <-state 212 | clearContext() 213 | unsetBindings() 214 | context() 215 | clearHasChange() 216 | setBindings() 217 | } 218 | } 219 | 220 | // Called to trigger in parallel a message sending in the chan state and consequently 221 | // request the new rendering of the application. 222 | func updateState() { 223 | state <- true 224 | } 225 | 226 | // Returns a stateful value, and a function to update it. 227 | // During the initial render, the returned state (state) is the same as the value 228 | // passed as the first argument (initialState). 229 | // The setState function is used to update the state. It accepts a new state value 230 | // and enqueues a re-render of the DOM. 231 | func UseState(initialValue any) (actualValue *any, f func(setterValue any)) { 232 | _, file, no, _ := runtime.Caller(1) 233 | key := utils.CallerToKey(file, no) 234 | utils.MapInit(key, store, &domStore{initialValue, false}) 235 | return &store[key].value, func(setVal any) { 236 | setHasChanged(&store[key].value, setVal) 237 | } 238 | } 239 | 240 | // Accepts a function that contains imperative, possibly effectful code. 241 | // The default behavior for effects is to fire the effect after every completed render. 242 | // That way an effect is always recreated if one of its variables changes (in variadics params). 243 | func UseEffect(callback func(), variables ...*any) { 244 | if len(variables) == 0 || detectHasChanged(variables...) { 245 | callback() 246 | } 247 | } 248 | 249 | // Pass an inline callback and an array of dependencies. useCallback will return a memoized 250 | // version of the callback that only changes if one of the dependencies has changed. 251 | func UseCallback(callback func(...any) any, variables ...*any) *func(...any) any { 252 | _, file, no, _ := runtime.Caller(1) 253 | key := utils.CallerToKey(file, no) 254 | utils.MapInit(key, storeCallback, &callback) 255 | if len(variables) == 0 || detectHasChanged(variables...) { 256 | storeCallback[key] = &callback 257 | } 258 | return storeCallback[key] 259 | } 260 | 261 | // Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized 262 | // value when one of the dependencies has changed. This optimization helps to avoid expensive 263 | // calculations on every render. 264 | func UseMemo(callback func() any, variables ...*any) any { 265 | _, file, no, _ := runtime.Caller(1) 266 | key := utils.CallerToKey(file, no) 267 | utils.MapInitCallback(key, storeMemo, callback) 268 | if len(variables) == 0 || detectHasChanged(variables...) { 269 | storeMemo[key] = callback() 270 | } 271 | return storeMemo[key] 272 | } 273 | 274 | // Generate code for the DOM 275 | 276 | // Fires again if one of the insiders has a dom.ELEMENT_PARAM anchor, to hook 277 | // the parameter as an attribute of the HTML element passed to the DOM. 278 | func insertDomComponentParams(opener string, insiders ...DomComponent) (string, []DomComponent) { 279 | var insidersWithoutParam []DomComponent 280 | for _, insider := range insiders { 281 | if strings.Contains(insider(), dom.ELEMENT_PARAM) { 282 | split := (strings.Split(opener, dom.ELEMENT_PARAM_SPLIT)) 283 | opener = fmt.Sprintf("%s %s%s%s", split[0], strings.Split(insider(), dom.ELEMENT_PARAM)[1], dom.ELEMENT_PARAM_SPLIT, split[1]) 284 | } else { 285 | insidersWithoutParam = append(insidersWithoutParam, insider) 286 | } 287 | } 288 | return opener, insidersWithoutParam 289 | } 290 | 291 | // Returns the html rendering of the DomComponent reproduced recursively with all its DomComponents insiders 292 | func htmlDomComponent(opener string, closer string, insiders ...DomComponent) DomComponent { 293 | if len(insiders) > 0 { 294 | htmlStr, insiders := insertDomComponentParams(opener, insiders...) 295 | for _, insider := range insiders { 296 | htmlStr += insider() 297 | } 298 | htmlStr += closer 299 | return func() string { return htmlStr } 300 | } 301 | return func() string { return fmt.Sprintf("%s%s", opener, closer) } 302 | } 303 | 304 | // Same function as htmlDomComponent() but only if the condition in parameter is valid. 305 | func If(condition bool, insiders ...DomComponent) DomComponent { 306 | if condition { 307 | htmlStr := "" 308 | for _, insider := range insiders { 309 | htmlStr += insider() 310 | } 311 | return func() string { return htmlStr } 312 | } 313 | return func() string { return "" } 314 | } 315 | 316 | // Same operation as htmlDomComponent() but applies the function passed in parameter for the 317 | // whole array. The "key" element is used to make the link with the elements within the function. 318 | func For[T string | int | int32 | int64 | float32 | float64 | bool | any](elements []T, keyDomComponent func(i int) DomComponent) DomComponent { 319 | if len(elements) > 0 { 320 | htmlStr := "" 321 | for i := range elements { 322 | htmlStr += keyDomComponent(i)() 323 | } 324 | return func() string { return htmlStr } 325 | } 326 | return func() string { return "" } 327 | } 328 | 329 | // DomComponents 330 | 331 | // Declare an html element with the
tag. 332 | func Div(insiders ...DomComponent) DomComponent { 333 | return htmlDomComponent(dom.HTML_DIV_OPENER, dom.HTML_DIV_CLOSER, insiders...) 334 | } 335 | 336 | // Declare an html element with the

tag. 337 | func P[T string | int | int32 | int64 | float32 | float64 | bool](text T, insiders ...DomComponent) DomComponent { 338 | textStr := utils.AnyStr(text) 339 | sanitizeHtml(&textStr) 340 | return htmlDomComponent(fmt.Sprintf("%s%s", dom.HTML_P_OPENER, textStr), dom.HTML_P_CLOSER, insiders...) 341 | } 342 | 343 | // Declare an html element with the tag. 344 | func Span[T string | int | int32 | int64 | float32 | float64 | bool](text T, insiders ...DomComponent) DomComponent { 345 | textStr := utils.AnyStr(text) 346 | sanitizeHtml(&textStr) 347 | return htmlDomComponent(fmt.Sprintf("%s%s", dom.HTML_SPAN_OPENER, textStr), dom.HTML_SPAN_CLOSER, insiders...) 348 | } 349 | 350 | // Declare an html element with the