├── .gitignore ├── LICENSE ├── README.md ├── alias.go ├── changed.go ├── changed_test.go ├── component.go ├── component_test.go ├── element.go ├── emptyBackend.go ├── external.go ├── f.go ├── gas.go ├── gas_test.go ├── gen-fcTypes.go ├── go.mod ├── go.sum ├── hooks.go ├── logo.png ├── render.go ├── update.go ├── update_test.go └── web ├── README.md ├── element.go ├── ka_notwasm.go ├── ka_wasm.go ├── object.go ├── render.go └── web.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.ignore 2 | examples/ 3 | 4 | # ignore cover files 5 | cover.html 6 | cover.out 7 | 8 | # ignore app.js files 9 | *app.js* 10 | 11 | .idea/ 12 | # Created by https://www.gitignore.io/api/go,code,webstorm 13 | # Edit at https://www.gitignore.io/?templates=go,code,webstorm 14 | 15 | ### Code ### 16 | # Visual Studio Code - https://code.visualstudio.com/ 17 | .settings/ 18 | .vscode/ 19 | tsconfig.json 20 | jsconfig.json 21 | 22 | ### Go ### 23 | # Binaries for programs and plugins 24 | *.exe 25 | *.exe~ 26 | *.dll 27 | *.so 28 | *.dylib 29 | 30 | # Test binary, build with `go test -c` 31 | *.test 32 | 33 | # Output of the go coverage tool, specifically when used with LiteIDE 34 | *.out 35 | 36 | ### Go Patch ### 37 | /vendor/ 38 | /Godeps/ 39 | 40 | ### WebStorm ### 41 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 42 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 43 | 44 | # User-specific stuff 45 | .idea/**/workspace.xml 46 | .idea/**/tasks.xml 47 | .idea/**/usage.statistics.xml 48 | .idea/**/dictionaries 49 | .idea/**/shelf 50 | 51 | # Generated files 52 | .idea/**/contentModel.xml 53 | 54 | # Sensitive or high-churn files 55 | .idea/**/dataSources/ 56 | .idea/**/dataSources.ids 57 | .idea/**/dataSources.local.xml 58 | .idea/**/sqlDataSources.xml 59 | .idea/**/dynamic.xml 60 | .idea/**/uiDesigner.xml 61 | .idea/**/dbnavigator.xml 62 | 63 | # Gradle 64 | .idea/**/gradle.xml 65 | .idea/**/libraries 66 | 67 | # Gradle and Maven with auto-import 68 | # When using Gradle or Maven with auto-import, you should exclude module files, 69 | # since they will be recreated, and may cause churn. Uncomment if using 70 | # auto-import. 71 | # .idea/modules.xml 72 | # .idea/*.iml 73 | # .idea/modules 74 | 75 | # CMake 76 | cmake-build-*/ 77 | 78 | # Mongo Explorer plugin 79 | .idea/**/mongoSettings.xml 80 | 81 | # File-based project format 82 | *.iws 83 | 84 | # IntelliJ 85 | out/ 86 | 87 | # mpeltonen/sbt-idea plugin 88 | .idea_modules/ 89 | 90 | # JIRA plugin 91 | atlassian-ide-plugin.xml 92 | 93 | # Cursive Clojure plugin 94 | .idea/replstate.xml 95 | 96 | # Crashlytics plugin (for Android Studio and IntelliJ) 97 | com_crashlytics_export_strings.xml 98 | crashlytics.properties 99 | crashlytics-build.properties 100 | fabric.properties 101 | 102 | # Editor-based Rest Client 103 | .idea/httpRequests 104 | 105 | # Android studio 3.1+ serialized cache file 106 | .idea/caches/build_file_checksums.ser 107 | 108 | ### WebStorm Patch ### 109 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 110 | 111 | # *.iml 112 | # modules.xml 113 | # .idea/misc.xml 114 | # *.ipr 115 | 116 | # Sonarlint plugin 117 | .idea/sonarlint 118 | 119 | # End of https://www.gitignore.io/api/go,code,webstorm 120 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Noskov Artem and Gas contributors 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gas - Components based frontend framework 2 | 3 | [![GoDoc](https://godoc.org/github.com/gascore/gas?status.svg)](https://godoc.org/github.com/gascore/gas) 4 | 5 | Example: [gascore.github.io](https://gascore.github.io) 6 | 7 | ![Logo](https://raw.githubusercontent.com/gascore/gas/master/logo.png) 8 | 9 | ### Structure 10 | 11 | 0. [gascore/gas](https://github.com/gascore/gas) - gas framework core 12 | 1. [gascore/gas/web](https://github.com/gascore/gas/blob/master/web) - wasm/gopherjs backend 13 | 2. [gascore/gas-cli](https://github.com/gascore/gas-cli) - projects manager for gas applications 14 | 3. [gascore/std](https://github.com/gascore/std) - standard library for gas framework 15 | 16 | [Tasks and wiki](https://www.notion.so/GAS-framework-fcec4e338c644d2fa137190cd25d5e0a) 17 | -------------------------------------------------------------------------------- /alias.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | // C alias for Component 4 | type C = Component 5 | 6 | // E alias for Element 7 | type E = Element 8 | 9 | // G alias for Gas 10 | type G = Gas 11 | 12 | // NE alias for NewElement 13 | var NE = NewElement 14 | 15 | // F alias for FunctionalComponent 16 | type F = FunctionalComponent 17 | 18 | // CL alias for ToGetComponentList 19 | var CL = ToGetComponentList 20 | 21 | type Map map[string]string 22 | -------------------------------------------------------------------------------- /changed.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Changed return isChanged? and canGoDeeper? 9 | func Changed(newEl, oldEl interface{}) (bool, bool, error) { 10 | if reflect.TypeOf(newEl) != reflect.TypeOf(oldEl) { 11 | return true, false, nil 12 | } 13 | 14 | switch newEl.(type) { 15 | case *Component: 16 | isEquals := isComponentsEquals(I2C(newEl), I2C(oldEl)) 17 | return !isEquals, isEquals, nil 18 | case *Element: 19 | isEquals, canGoDeeper := isNodesEquals(I2E(newEl), I2E(oldEl)) 20 | return !isEquals, canGoDeeper, nil 21 | case bool, string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: 22 | return newEl != oldEl, false, nil 23 | case fmt.Stringer: 24 | return newEl.(fmt.Stringer).String() != oldEl.(fmt.Stringer).String(), false, nil 25 | default: 26 | return false, false, fmt.Errorf("changed: invalid `newEl` or `oldEl`. types: %T, %T", newEl, oldEl) 27 | } 28 | } 29 | 30 | func isComponentsEquals(newC, oldC *C) bool { 31 | return newC.RefsAllowed == oldC.RefsAllowed && compareHooks(newC.Hooks, oldC.Hooks) 32 | } 33 | 34 | func isNodesEquals(newE, oldE *E) (bool, bool) { 35 | if newE.Component != nil || oldE.Component != nil { 36 | if oldE.Component == nil || newE.Component == nil { 37 | return false, false 38 | } 39 | 40 | if !isComponentsEquals(newE.Component, oldE.Component) { 41 | return false, false 42 | } 43 | } 44 | 45 | return isElementsEquals(newE, oldE) 46 | } 47 | 48 | func isElementsEquals(newE, oldE *E) (bool, bool) { 49 | canBeUpdated := newE.Tag == oldE.Tag && newE.RHTML == oldE.RHTML && reflect.ValueOf(newE.HTML).Pointer() == reflect.ValueOf(oldE.HTML).Pointer() 50 | return canBeUpdated && compareAttributes(newE.RAttrs, oldE.RAttrs), canBeUpdated 51 | } 52 | 53 | func compareAttributes(newMap, oldMap Map) bool { 54 | if len(newMap) != len(oldMap) { 55 | return false 56 | } 57 | 58 | for i, el := range newMap { 59 | if el != oldMap[i] { 60 | return false 61 | } 62 | } 63 | 64 | return true 65 | } 66 | 67 | func compareHooks(newHooks, oldHooks Hooks) bool { 68 | return compareHook(newHooks.Created, oldHooks.Created) && 69 | compareHook(newHooks.Mounted, oldHooks.Mounted) && 70 | compareHookWithControl(newHooks.BeforeCreated, oldHooks.BeforeCreated) && 71 | compareHook(newHooks.BeforeDestroy, oldHooks.BeforeDestroy) && 72 | compareHook(newHooks.BeforeUpdate, oldHooks.BeforeUpdate) && 73 | compareHook(newHooks.Updated, oldHooks.Updated) 74 | } 75 | 76 | func compareHookWithControl(newHook, oldHook HookWithControl) bool { 77 | if newHook == nil && oldHook == nil { 78 | return true 79 | } 80 | 81 | if newHook == nil || oldHook == nil { 82 | return false 83 | } 84 | 85 | return reflect.ValueOf(newHook).Pointer() == reflect.ValueOf(oldHook).Pointer() 86 | } 87 | 88 | func compareHook(newHook, oldHook Hook) bool { 89 | if newHook == nil && oldHook == nil { 90 | return true 91 | } 92 | 93 | if newHook == nil || oldHook == nil { 94 | return false 95 | } 96 | 97 | return reflect.ValueOf(newHook).Pointer() == reflect.ValueOf(oldHook).Pointer() 98 | } 99 | 100 | func DiffAttrs(newA, oldA Map) Map { 101 | diffMap := make(Map) 102 | for key, val := range oldA { 103 | if _, ok := diffMap[key]; ok { 104 | continue 105 | } 106 | 107 | if newA[key] != val { 108 | if _, ok := newA[key]; ok { 109 | diffMap[key] = newA[key] 110 | continue 111 | } 112 | 113 | diffMap[key] = "" 114 | } 115 | } 116 | 117 | for key, val := range newA { 118 | if _, ok := diffMap[key]; ok { 119 | continue 120 | } 121 | 122 | if oldA[key] != val { 123 | diffMap[key] = val 124 | } 125 | } 126 | return diffMap 127 | } 128 | -------------------------------------------------------------------------------- /changed_test.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestChanged(t *testing.T) { 8 | var counter int 9 | f := func(a, b interface{}, isChanged, canGoDeeper bool, haveError bool) { 10 | counter++ 11 | ic, cgd, err := Changed(a, b) 12 | 13 | if err == nil { 14 | if haveError { 15 | t.Errorf("%d: except error, but don't got it", counter) 16 | } 17 | 18 | if ic != isChanged { 19 | t.Errorf("%d: invalid Changed isChanged result except: %t, got: %t", counter, isChanged, ic) 20 | } 21 | 22 | if cgd != canGoDeeper { 23 | t.Errorf("%d: invalid Changed canGoDeeper resul except: %t, got: %t", counter, canGoDeeper, cgd) 24 | } 25 | } else if !haveError { 26 | t.Errorf("%d: error from Changed: %s", counter, err.Error()) 27 | } 28 | } 29 | 30 | e1 := &E{ 31 | Tag: "h1", 32 | } 33 | 34 | e2 := &E{ 35 | Tag: "h2", 36 | } 37 | 38 | c1 := &C{ 39 | RefsAllowed: true, 40 | Element: e1, 41 | } 42 | 43 | c2 := &C{ 44 | RefsAllowed: false, 45 | Element: e2, 46 | } 47 | 48 | // atomic types 49 | f("1", "1", false, false, false) 50 | f(1, 1, false, false, false) 51 | f(1.228, 1.228, false, false, false) 52 | 53 | // various types 54 | f("1", 1, true, false, false) 55 | f(&C{}, &E{}, true, false, false) 56 | 57 | // not supported type 58 | f(Gas{}, Gas{}, true, false, true) 59 | 60 | // empty stucts 61 | f(&E{}, &E{}, false, true, false) 62 | f(&C{}, &C{}, false, true, false) 63 | 64 | // Component.Element 65 | f(c1, c2, true, false, false) 66 | f(c1, c1, false, true, false) 67 | 68 | // Element.Component 69 | f(&E{Component: c1}, &E{Component: c2}, true, false, false) 70 | f(&E{Component: c1}, &E{Component: c1}, false, true, false) 71 | f(&E{Component: c1}, &E{}, true, false, false) 72 | 73 | eA1 := &E{Attrs: func() Map { return Map{"id": "wow"} }} 74 | eA1.RAttrs = eA1.Attrs() 75 | eA2 := &E{Attrs: func() Map { return Map{"id": "wow", "class": "lol"} }} 76 | eA2.RAttrs = eA2.Attrs() 77 | eA3 := &E{Attrs: func() Map { return Map{"id": "lol"} }} 78 | eA3.RAttrs = eA3.Attrs() 79 | 80 | // attrs 81 | f(eA1, eA1, false, true, false) 82 | f(eA1, eA2, true, true, false) 83 | f(eA1, eA3, true, true, false) 84 | 85 | // hooks 86 | m1 := func() error { return nil } 87 | m2 := func() (bool, error) { return false, nil } 88 | 89 | f(&C{Hooks: Hooks{Created: m1}}, &C{Hooks: Hooks{Created: m1}}, false, true, false) 90 | f(&C{Hooks: Hooks{Created: m1}}, &C{Hooks: Hooks{}}, true, false, false) 91 | 92 | f(&C{Hooks: Hooks{BeforeCreated: m2}}, &C{Hooks: Hooks{BeforeCreated: m2}}, false, true, false) // with control 93 | f(&C{Hooks: Hooks{BeforeCreated: m2}}, &C{Hooks: Hooks{}}, true, false, false) // with control 94 | } 95 | 96 | func TestDiffAttrs(t *testing.T) { 97 | f := func(newA, oldA Map, excp Map) { 98 | got := DiffAttrs(newA, oldA) 99 | if !compareAttributes(got, excp) { 100 | t.Errorf("error in DiffAttrs: excepted: %v, got: %v", excp, got) 101 | } 102 | } 103 | 104 | f(Map{"1": "1"}, Map{"1": "1"}, Map{}) 105 | f(Map{"1": "1"}, Map{"1": "2"}, Map{"1": "1"}) 106 | f(Map{"1": "3"}, Map{"1": "3"}, Map{}) 107 | f(Map{"1": "1", "2": "2"}, Map{"1": "1"}, Map{"2": "2"}) 108 | f(Map{"1": "1", "2": "2"}, Map{"1": "1", "2": "3"}, Map{"2": "2"}) 109 | f(Map{"1": "1"}, Map{"1": "1", "2": "2"}, Map{"2": ""}) 110 | f(Map{"1": "1", "2": "3"}, Map{"1": "1", "2": "2", "4": "4"}, Map{"2": "3", "4": ""}) 111 | f(Map{"1": "1", "4": "4"}, Map{"1": "1", "2": "2", "3": "3"}, Map{"4": "4", "2": "", "3": ""}) 112 | } 113 | -------------------------------------------------------------------------------- /component.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | // Component logic node 4 | type Component struct { 5 | Root interface { 6 | Render() *Element 7 | } 8 | 9 | Element *Element // last-time render by root element from 10 | Hooks Hooks 11 | 12 | RefsAllowed bool 13 | Refs map[string]*Element 14 | 15 | NotPointer bool // by default component is pointer 16 | 17 | RC *RenderCore 18 | } 19 | 20 | // RenderElement create element for elements tree 21 | func (c *Component) RenderElement() *Element { 22 | el := c.Root.Render() 23 | 24 | el.RC = c.RC 25 | el.Component = c 26 | el.IsPointer = !c.NotPointer 27 | 28 | c.Element = el 29 | 30 | return el 31 | } 32 | 33 | // UnSpliceBody extract values fromm array to component childes 34 | func UnSpliceBody(body []interface{}) []interface{} { 35 | var arr []interface{} 36 | for _, el := range body { 37 | switch el.(type) { 38 | case []interface{}: 39 | for _, c := range el.([]interface{}) { 40 | arr = append(arr, c) 41 | } 42 | continue 43 | case []*E: 44 | for _, e := range el.([]*E) { 45 | arr = append(arr, e) 46 | } 47 | continue 48 | case []*C: 49 | for _, e := range el.([]*C) { 50 | arr = append(arr, e) 51 | } 52 | continue 53 | case nil: 54 | continue 55 | default: 56 | arr = append(arr, el) 57 | } 58 | } 59 | 60 | return arr 61 | } 62 | 63 | // I2C - convert interface{} to *Component 64 | func I2C(a interface{}) *Component { 65 | return a.(*Component) 66 | } 67 | 68 | // I2E - convert interface{} to *Element 69 | func I2E(a interface{}) *E { 70 | return a.(*Element) 71 | } 72 | 73 | // IsComponent return true if interface.(type) == *Component 74 | func IsComponent(c interface{}) bool { 75 | _, ok := c.(*Component) 76 | return ok 77 | } 78 | 79 | // IsElement return true if interface.(type) == *Element 80 | func IsElement(c interface{}) bool { 81 | _, ok := c.(*Element) 82 | return ok 83 | } 84 | 85 | // IsString return true if interface is string 86 | func IsString(c interface{}) bool { 87 | _, ok := c.(string) 88 | return ok 89 | } 90 | 91 | // RemoveStrings remove all strings from []interface{} 92 | func RemoveStrings(arr []interface{}) []interface{} { 93 | var out []interface{} 94 | for _, el := range arr { 95 | if IsString(el) { 96 | continue 97 | } 98 | out = append(out, el) 99 | } 100 | return out 101 | } 102 | 103 | // EmptyRoot root for component only rendering one element 104 | type EmptyRoot struct { 105 | C *Component 106 | Element *Element 107 | } 108 | 109 | func (root *EmptyRoot) Render() *Element { 110 | return root.Element 111 | } 112 | -------------------------------------------------------------------------------- /component_test.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestComponentInit(t *testing.T) { 9 | f := func(c *C) { 10 | c.Root = &exampleRoot{} 11 | 12 | el := c.RenderElement() 13 | 14 | if el.UUID == "" { 15 | t.Errorf("empty element UUID") 16 | } 17 | 18 | if el.Tag == "" { 19 | t.Errorf("empty element tag") 20 | } 21 | 22 | el.UpdateChildes() 23 | if len(el.Childes) == 0 { 24 | t.Error("element childes are null") 25 | } 26 | } 27 | 28 | f(&C{Element: &E{Tag: "h1", Attrs: func() Map { return Map{"id": "foo"} }}}) 29 | f(&C{Element: &E{Attrs: func() Map { return Map{"id": "foo"} }}}) 30 | f(&C{Element: &E{UUID: "custom_id"}}) 31 | } 32 | 33 | func TestUnSpliceBody(t *testing.T) { 34 | f := func(in, out []interface{}) { 35 | formatedIn := UnSpliceBody(in) 36 | if !reflect.DeepEqual(formatedIn, out) { 37 | t.Errorf("formatedIn and out are not the same: want - %v, but got - %v", out, formatedIn) 38 | } 39 | } 40 | 41 | f(CL(1, 2, 3), 42 | CL(1, 2, 3)) 43 | 44 | f(CL(1, nil, 3), 45 | CL(1, 3)) 46 | 47 | f(CL(1, CL(1, 2), 3), 48 | CL(1, 1, 2, 3)) 49 | 50 | f(CL(1, 2, 3, []*E{&E{Tag: "h1"}, &E{Tag: "h2"}}), 51 | CL(1, 2, 3, &E{Tag: "h1"}, &E{Tag: "h2"})) 52 | 53 | f(CL(1, 2, 3, []*C{&C{Root: &exampleRoot{}}}), 54 | CL(1, 2, 3, &C{Root: &exampleRoot{}})) 55 | } 56 | 57 | func TestRemoveStrings(t *testing.T) { 58 | f := func(in, out []interface{}) { 59 | formatedIn := RemoveStrings(in) 60 | if !reflect.DeepEqual(formatedIn, out) { 61 | t.Errorf("formatedIn and out are not the same: want - %v, but got - %v", out, formatedIn) 62 | } 63 | } 64 | 65 | f(CL(1, 2, "3"), 66 | CL(1, 2)) 67 | 68 | f(CL(1, "2", "3"), 69 | CL(1)) 70 | 71 | f(CL(1, 2, 3, "4", 5), 72 | CL(1, 2, 3, 5)) 73 | 74 | f(CL(1, 2, 3, 4, 5), 75 | CL(1, 2, 3, 4, 5)) 76 | } 77 | 78 | func TestEmptyRoot(t *testing.T) { 79 | e := &E{Tag: "h1"} 80 | root := &EmptyRoot{Element: e} 81 | if !reflect.DeepEqual(root.Render(), e) { 82 | t.Errorf("invalid EmptyRoot.Render() result") 83 | } 84 | } 85 | 86 | func TestLittleUtils(t *testing.T) { 87 | // IsComponent 88 | if !IsComponent(&C{}) { 89 | t.Error("invalid IsComponent result #1") 90 | } 91 | if IsComponent(&E{}) { 92 | t.Error("invalid IsComponent result #2") 93 | } 94 | 95 | // IsElement 96 | if !IsElement(&E{}) { 97 | t.Error("invalid IsElement result #1") 98 | } 99 | if IsElement(&C{}) { 100 | t.Error("invalid IsElement result #2") 101 | } 102 | 103 | var cI interface{} = &C{} 104 | if !IsComponent(I2C(cI)) { 105 | t.Error("invalid I2C result") 106 | } 107 | 108 | var eI interface{} = &E{} 109 | if !IsElement(I2E(eI)) { 110 | t.Error("invalid I2E result") 111 | } 112 | } 113 | 114 | func TestElementParentComponent(t *testing.T) { 115 | f := func(e *E, haveParent bool) { 116 | p := e.ParentComponent() 117 | if (p == nil || p.Component == nil) && haveParent { 118 | t.Errorf("element parent is nil: %v", e) 119 | } 120 | if p != nil && p.Component != nil && !haveParent { 121 | t.Errorf("element parent isn't nil: %v", e) 122 | } 123 | } 124 | 125 | e1 := NE(&E{}) 126 | e2 := NE(&E{Component: &C{}}, e1) 127 | p := NE(&E{}, e2) 128 | p.UpdateChildes() 129 | 130 | f(e1, true) 131 | f(e2, false) 132 | } 133 | -------------------------------------------------------------------------------- /element.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/frankenbeanies/uuid4" 8 | ) 9 | 10 | // Element stucture for basic strucutre nodes (html elements, etc) 11 | type Element struct { 12 | UUID string 13 | IsPointer bool // by default element isn't pointer 14 | 15 | Tag string 16 | Attrs func() Map 17 | RAttrs Map // last rendered Attrs 18 | Handlers map[string]Handler // events handlers: onClick, onHover 19 | 20 | HTML func() string 21 | RHTML string 22 | 23 | Childes, OldChildes []interface{} 24 | 25 | Parent *Element // if element is root, parent is nil 26 | RefName string 27 | 28 | Component *Component // can be nil 29 | 30 | RC *RenderCore 31 | } 32 | 33 | // NewElement create new element 34 | func NewElement(el *Element, childes ...interface{}) *Element { 35 | if el.Tag == "" { 36 | el.Tag = "div" 37 | } else { 38 | el.Tag = strings.ToLower(el.Tag) 39 | } 40 | 41 | if len(el.UUID) == 0 { 42 | el.UUID = uuid4.New().String() 43 | } 44 | 45 | el.Childes = UnSpliceBody(childes) 46 | 47 | return el 48 | } 49 | 50 | // Bind dynamic component attribute 51 | type Bind func() string 52 | 53 | // HTMLDirective struct for HTML Directive - storing render function and pre rendered render 54 | type HTMLDirective struct { 55 | Render func() string 56 | 57 | Rendered string // here storing rendered html for Update functions 58 | } 59 | 60 | // Handler function for triggering event 61 | type Handler func(Event) 62 | 63 | // Object wrapper for js.Value 64 | type Object interface { 65 | String() string 66 | Int() int 67 | Float() float64 68 | 69 | Get(string) Object 70 | Set(string, interface{}) 71 | GetString(string) string 72 | GetBool(string) bool 73 | GetInt(string) int 74 | 75 | Call(string, ...interface{}) Object 76 | 77 | Raw() interface{} 78 | } 79 | 80 | // Event wrapper for dom.Event 81 | type Event interface { 82 | Object 83 | Value() string 84 | ValueInt() int 85 | ValueBool() bool 86 | } 87 | 88 | // BEElement return element in backend implementation 89 | func (e *Element) BEElement() interface{} { 90 | _el := e.RC.BE.GetElement(e) 91 | if _el == nil { 92 | e.RC.BE.ConsoleError(fmt.Sprintf("component Element: %s, returning nil", e.UUID)) 93 | return nil 94 | } 95 | 96 | return _el 97 | } 98 | 99 | // GetElementUnsafely return *dom.Element by component without warning 100 | func (e *Element) GetElementUnsafely() interface{} { 101 | return e.RC.BE.GetElement(e) 102 | } 103 | 104 | // ParentComponent return first component in element parents tree 105 | func (e *Element) ParentComponent() *Element { 106 | parent := e.Parent 107 | if parent.Parent == nil || parent.Component != nil { 108 | return parent 109 | } 110 | 111 | return parent.ParentComponent() 112 | } 113 | 114 | // ElementToComponent create static component from element 115 | func ElementToComponent(el *Element) *Component { 116 | return &Component{ 117 | Root: &EmptyRoot{ 118 | Element: el, 119 | }, 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /emptyBackend.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | import "log" 4 | 5 | // emptyBackEnd empty backend for testing backend calling methods 6 | type emptyBackEnd struct { 7 | logger func([]*RenderTask) 8 | } 9 | 10 | // GetEmptyRenderCore return epmty render core 11 | func GetEmptyRenderCore() *RenderCore { 12 | return &RenderCore{BE: emptyBackEnd{}} 13 | } 14 | 15 | // GetEmptyBackend return empty BackEnd 16 | func GetEmptyBackend() BackEnd { 17 | return emptyBackEnd{} 18 | } 19 | 20 | // ExecNode return nil 21 | func (e emptyBackEnd) ExecTasks(tasks []*RenderTask) { 22 | if e.logger != nil { 23 | e.logger(tasks) 24 | } 25 | } 26 | 27 | // ChildNodes return nil 28 | func (e emptyBackEnd) ChildNodes(i interface{}) []interface{} { 29 | return []interface{}{} 30 | } 31 | 32 | // New return nil 33 | func (e emptyBackEnd) CanRender(a string) (string, error) { 34 | return "app", nil 35 | } 36 | 37 | // Init return nil 38 | func (e emptyBackEnd) Init(g Gas) error { 39 | return nil 40 | } 41 | 42 | // UpdateComponentChildes return nil 43 | func (e emptyBackEnd) UpdateComponentChildes(c *Component, newChildes, oldChildes []interface{}) error { 44 | return nil 45 | } 46 | 47 | // ReCreate return nil 48 | func (e emptyBackEnd) ReCreate(c *Component) error { 49 | return nil 50 | } 51 | 52 | // GetElement return not nil 53 | func (e emptyBackEnd) GetElement(i *Element) interface{} { 54 | return "not nil!" 55 | } 56 | 57 | // GetGasEl return not nil 58 | func (e emptyBackEnd) GetGasEl(g *Gas) interface{} { 59 | return "not nil!" 60 | } 61 | 62 | // ConsoleLog return nil 63 | func (e emptyBackEnd) ConsoleLog(values ...interface{}) { 64 | log.Println(append([]interface{}{"LOG"}, values...)...) 65 | } 66 | 67 | // ConsoleError return nil 68 | func (e emptyBackEnd) ConsoleError(values ...interface{}) { 69 | log.Println(append([]interface{}{"ERROR"}, values...)...) 70 | } 71 | -------------------------------------------------------------------------------- /external.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | // Template function returning elements 4 | type Template func(...interface{}) []interface{} 5 | 6 | // External structure for passing values to external components 7 | type External struct { 8 | Body []interface{} 9 | Slots map[string]interface{} 10 | Templates map[string]Template 11 | Attrs func() Map 12 | } 13 | 14 | type DynamicElement func(External) *Element 15 | 16 | type DynamicComponent func(External) *Component 17 | -------------------------------------------------------------------------------- /f.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | // FunctionalComponent wrapper for Component with react hooks (in gas maner) 4 | type FunctionalComponent struct { 5 | C *Component 6 | 7 | statesCounter int 8 | states []interface{} 9 | effectsCounter int 10 | effects []effectItem 11 | cleaners map[int]func() 12 | 13 | renderer func() *Element 14 | } 15 | 16 | type effectItem struct { 17 | effect Effect 18 | i int 19 | } 20 | 21 | // Effect functional components effect 22 | type Effect func() (func(), error) 23 | 24 | // Init create *C from *F 25 | func (f *FunctionalComponent) Init(notPointer bool, renderer func() *E) *C { 26 | f.renderer = renderer 27 | 28 | c := &C{ 29 | NotPointer: notPointer, 30 | Root: f, 31 | Hooks: Hooks{ 32 | Updated: f.runEffects, 33 | Mounted: f.runEffects, 34 | BeforeDestroy: f.runCleaners, 35 | }, 36 | } 37 | f.C = c 38 | 39 | return c 40 | } 41 | 42 | // UseState create new state value 43 | func (root *FunctionalComponent) UseState(defaultVal interface{}) (func() interface{}, func(interface{})) { 44 | i := root.statesCounter 45 | root.statesCounter++ 46 | 47 | if len(root.states)-1 < i { 48 | root.states = append(root.states, defaultVal) 49 | } 50 | 51 | getVal := func() interface{} { 52 | return root.states[i] 53 | } 54 | 55 | setVal := func(newVal interface{}) { 56 | root.states[i] = newVal 57 | root.C.Update() 58 | } 59 | 60 | return getVal, setVal 61 | } 62 | 63 | // UseEffect add effect 64 | func (root *FunctionalComponent) UseEffect(effect Effect) { 65 | i := root.effectsCounter 66 | root.effectsCounter++ 67 | 68 | if len(root.effects)-1 < i { 69 | root.effects = append(root.effects, effectItem{effect: effect, i: i}) 70 | } 71 | } 72 | 73 | func (root *FunctionalComponent) runEffects() error { 74 | for _, effect := range root.effects { 75 | cleaner, err := effect.effect() 76 | if err != nil { 77 | return err 78 | } 79 | 80 | if cleaner != nil { 81 | if root.cleaners == nil { 82 | root.cleaners = make(map[int]func()) 83 | } 84 | 85 | root.cleaners[effect.i] = cleaner 86 | } 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func (root *FunctionalComponent) runCleaners() error { 93 | if len(root.cleaners) == 0 { 94 | return nil 95 | } 96 | 97 | for _, cleaner := range root.cleaners { 98 | cleaner() 99 | } 100 | 101 | return nil 102 | } 103 | 104 | // Render return functionalComponent childes 105 | func (root *FunctionalComponent) Render() *Element { 106 | root.statesCounter = 0 107 | root.effectsCounter = 0 108 | return root.renderer() 109 | } 110 | -------------------------------------------------------------------------------- /gas.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | import "fmt" 4 | 5 | // Gas - main application struct 6 | type Gas struct { 7 | Component *Component 8 | } 9 | 10 | // BackEnd interface for calling platform-specific code 11 | type BackEnd interface { 12 | ExecTasks([]*RenderTask) 13 | GetElement(*Element) interface{} 14 | ChildNodes(interface{}) []interface{} 15 | 16 | ConsoleLog(...interface{}) 17 | ConsoleError(...interface{}) 18 | } 19 | 20 | // New create new gas application with custom backend 21 | func New(c *Component, be BackEnd) *Gas { 22 | c.RC = &RenderCore{BE: be} 23 | return &Gas{c} 24 | } 25 | 26 | // ToGetComponentList return array by many parameters, because it's pretty 27 | func ToGetComponentList(childes ...interface{}) []interface{} { 28 | return childes 29 | } 30 | 31 | // WarnError log error 32 | func (c *Component) WarnError(err error) { 33 | if err == nil { 34 | return 35 | } 36 | 37 | c.ConsoleError(err.Error()) 38 | } 39 | 40 | // WarnIfNot console error if !ok 41 | func (c *Component) WarnIfNot(ok bool) { 42 | if ok { 43 | return 44 | } 45 | 46 | c.ConsoleError(fmt.Errorf("invalid Data type").Error()) 47 | } 48 | 49 | // ConsoleLog call BackEnd.ConsoleLog 50 | func (c *Component) ConsoleLog(a ...interface{}) { c.RC.BE.ConsoleLog(a...) } 51 | 52 | // ConsoleError call BackEnd.ConsoleError 53 | func (c *Component) ConsoleError(a ...interface{}) { c.RC.BE.ConsoleError(a...) } 54 | -------------------------------------------------------------------------------- /gas_test.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNew(t *testing.T) { 8 | c := &C{Root: &exampleRoot{}} 9 | 10 | gas := New(c, GetEmptyBackend()) 11 | 12 | gas.Component.Update() 13 | 14 | el := gas.Component.Element 15 | if el == nil { 16 | t.Error("component element is nil") 17 | } 18 | 19 | if len(el.Childes) == 0 { 20 | t.Error("app has no childes") 21 | } 22 | 23 | child, ok := el.Childes[0].(*E) 24 | if !ok { 25 | t.Error("invalid app first child type") 26 | } 27 | 28 | if child.Attrs()["class"] != "wow" { 29 | t.Error("app has wrong start point") 30 | } 31 | } 32 | 33 | type exampleRoot struct { 34 | c *C 35 | 36 | msg string 37 | counter int 38 | } 39 | 40 | func (root *exampleRoot) Render() *Element { 41 | return NE( 42 | &E{}, 43 | NE( 44 | &E{ 45 | Attrs: func() Map { 46 | return Map{ 47 | "class": "wow", 48 | } 49 | }, 50 | }, 51 | root.msg, 52 | func() interface{} { 53 | if root.msg == "wow" { 54 | return "Message is wow" 55 | } 56 | return nil 57 | }(), 58 | ), 59 | NE( 60 | &E{ 61 | Tag: "h1", 62 | }, 63 | root.counter, 64 | ), 65 | func() interface{} { 66 | if root.counter == 5 { 67 | return "Number is 5!" 68 | } 69 | return nil 70 | }(), 71 | NE( 72 | &E{}, 73 | func() interface{} { 74 | if root.counter == 6 { 75 | return NE(&E{Tag: "i"}, "Counter is 6") 76 | } 77 | return NE(&E{Tag: "b"}, "Counter isn't 6") 78 | }(), 79 | ), 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /gen-fcTypes.go: -------------------------------------------------------------------------------- 1 | // This file was automatically generated by genny. 2 | // Any changes will be lost if this file is regenerated. 3 | // see https://github.com/cheekybits/genny 4 | 5 | package gas 6 | 7 | func (root *FunctionalComponent) UseStateBool(defaultVal bool) (func() bool, func(bool)) { 8 | getVal, setVal := root.UseState(defaultVal) 9 | return func() bool { return getVal().(bool) }, func(newVal bool) { setVal(newVal) } 10 | } 11 | 12 | func (root *FunctionalComponent) UseStateString(defaultVal string) (func() string, func(string)) { 13 | getVal, setVal := root.UseState(defaultVal) 14 | return func() string { return getVal().(string) }, func(newVal string) { setVal(newVal) } 15 | } 16 | 17 | func (root *FunctionalComponent) UseStateInt(defaultVal int) (func() int, func(int)) { 18 | getVal, setVal := root.UseState(defaultVal) 19 | return func() int { return getVal().(int) }, func(newVal int) { setVal(newVal) } 20 | } 21 | 22 | func (root *FunctionalComponent) UseStateInt8(defaultVal int8) (func() int8, func(int8)) { 23 | getVal, setVal := root.UseState(defaultVal) 24 | return func() int8 { return getVal().(int8) }, func(newVal int8) { setVal(newVal) } 25 | } 26 | 27 | func (root *FunctionalComponent) UseStateInt16(defaultVal int16) (func() int16, func(int16)) { 28 | getVal, setVal := root.UseState(defaultVal) 29 | return func() int16 { return getVal().(int16) }, func(newVal int16) { setVal(newVal) } 30 | } 31 | 32 | func (root *FunctionalComponent) UseStateInt32(defaultVal int32) (func() int32, func(int32)) { 33 | getVal, setVal := root.UseState(defaultVal) 34 | return func() int32 { return getVal().(int32) }, func(newVal int32) { setVal(newVal) } 35 | } 36 | 37 | func (root *FunctionalComponent) UseStateInt64(defaultVal int64) (func() int64, func(int64)) { 38 | getVal, setVal := root.UseState(defaultVal) 39 | return func() int64 { return getVal().(int64) }, func(newVal int64) { setVal(newVal) } 40 | } 41 | 42 | func (root *FunctionalComponent) UseStateUint(defaultVal uint) (func() uint, func(uint)) { 43 | getVal, setVal := root.UseState(defaultVal) 44 | return func() uint { return getVal().(uint) }, func(newVal uint) { setVal(newVal) } 45 | } 46 | 47 | func (root *FunctionalComponent) UseStateUint8(defaultVal uint8) (func() uint8, func(uint8)) { 48 | getVal, setVal := root.UseState(defaultVal) 49 | return func() uint8 { return getVal().(uint8) }, func(newVal uint8) { setVal(newVal) } 50 | } 51 | 52 | func (root *FunctionalComponent) UseStateUint16(defaultVal uint16) (func() uint16, func(uint16)) { 53 | getVal, setVal := root.UseState(defaultVal) 54 | return func() uint16 { return getVal().(uint16) }, func(newVal uint16) { setVal(newVal) } 55 | } 56 | 57 | func (root *FunctionalComponent) UseStateUint32(defaultVal uint32) (func() uint32, func(uint32)) { 58 | getVal, setVal := root.UseState(defaultVal) 59 | return func() uint32 { return getVal().(uint32) }, func(newVal uint32) { setVal(newVal) } 60 | } 61 | 62 | func (root *FunctionalComponent) UseStateUint64(defaultVal uint64) (func() uint64, func(uint64)) { 63 | getVal, setVal := root.UseState(defaultVal) 64 | return func() uint64 { return getVal().(uint64) }, func(newVal uint64) { setVal(newVal) } 65 | } 66 | 67 | func (root *FunctionalComponent) UseStateFloat32(defaultVal float32) (func() float32, func(float32)) { 68 | getVal, setVal := root.UseState(defaultVal) 69 | return func() float32 { return getVal().(float32) }, func(newVal float32) { setVal(newVal) } 70 | } 71 | 72 | func (root *FunctionalComponent) UseStateFloat64(defaultVal float64) (func() float64, func(float64)) { 73 | getVal, setVal := root.UseState(defaultVal) 74 | return func() float64 { return getVal().(float64) }, func(newVal float64) { setVal(newVal) } 75 | } 76 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gascore/gas 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/frankenbeanies/uuid4 v0.0.0-20180313125435-68b799ec299a 7 | github.com/gascore/dom v0.2.4 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/frankenbeanies/uuid4 v0.0.0-20180313125435-68b799ec299a h1:oxdqt7Y3J6/prhpMQ7msE7CanS51pzHb8NF44PCSoXY= 3 | github.com/frankenbeanies/uuid4 v0.0.0-20180313125435-68b799ec299a/go.mod h1:MmPmk1AfyaEzDM+9n93pi02xKEqUgZMj+AL/oX65TDE= 4 | github.com/gascore/dom v0.2.3 h1:kFfQvNXeq3nEKJhP0/2Qy59DLHGBN1mUp9RLWet2kd0= 5 | github.com/gascore/dom v0.2.3/go.mod h1:aZt0YZyRxTJaYwAkAf9mQjzFSMvL4LTasUGKCgZ0DJk= 6 | github.com/gascore/dom v0.2.4 h1:DHgkdPCd+63b2b5wmd6SB6UbUrx17j1wwD+q/ftEnuM= 7 | github.com/gascore/dom v0.2.4/go.mod h1:Irpt3cSe8mBq1HW3hUu07mUjcFh4I/0GcDTPMVcZhis= 8 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 9 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 12 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 13 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 16 | -------------------------------------------------------------------------------- /hooks.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | // Hooks component lifecycle hooks 4 | type Hooks struct { 5 | BeforeCreated HookWithControl // When parent already rendered (appended to DOM), but component Element don't yet (you can rerender childes) 6 | Created Hook // When component has been created in golang only (Element isn't available) 7 | 8 | Mounted Hook // When component has been mounted (Element is available) 9 | 10 | BeforeDestroy Hook // Before component destroy (Element is available) 11 | 12 | BeforeUpdate Hook // When component child don't updated 13 | Updated Hook // After component child was updated 14 | } 15 | 16 | // Hook - lifecycle hook 17 | type Hook func() error 18 | 19 | // HookWithControl - lifecycle hook. Return true for rerender component childes 20 | type HookWithControl func() (rerender bool, err error) 21 | 22 | // CallBeforeCreated call component and it's childes BeforeCreated hook 23 | func CallBeforeCreated(i interface{}) error { 24 | e, ok := i.(*Element) 25 | if !ok { 26 | return nil 27 | } 28 | 29 | c := e.Component 30 | if c != nil && c.Hooks.BeforeCreated != nil { 31 | rerender, err := c.Hooks.BeforeCreated() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | if rerender { 37 | e.UpdateChildes() 38 | } 39 | } 40 | 41 | for _, child := range e.Childes { 42 | err := CallBeforeCreated(child) 43 | if err != nil { 44 | return err 45 | } 46 | } 47 | 48 | return nil 49 | } 50 | 51 | // CallMounted call component and it's childes Mounted hook 52 | func CallMounted(i interface{}) error { 53 | e, ok := i.(*Element) 54 | if !ok { 55 | return nil 56 | } 57 | 58 | c := e.Component 59 | if c != nil && c.Hooks.Mounted != nil { 60 | err := c.Hooks.Mounted() 61 | if err != nil { 62 | return err 63 | } 64 | } 65 | 66 | for _, child := range e.Childes { 67 | err := CallMounted(child) 68 | if err != nil { 69 | return err 70 | } 71 | } 72 | 73 | return nil 74 | } 75 | 76 | // CallBeforeDestroy call component and it's childes WillDestroy hook 77 | func CallBeforeDestroy(i interface{}) error { 78 | e, ok := i.(*Element) 79 | if !ok { 80 | return nil 81 | } 82 | 83 | c := e.Component 84 | if c != nil && c.Hooks.BeforeDestroy != nil { 85 | err := c.Hooks.BeforeDestroy() 86 | if err != nil { 87 | return err 88 | } 89 | } 90 | 91 | for _, child := range e.Childes { 92 | err := CallBeforeDestroy(child) 93 | if err != nil { 94 | return err 95 | } 96 | } 97 | 98 | return nil 99 | } 100 | 101 | // CallUpdated call Updated hook 102 | func CallUpdated(p *Element) error { 103 | if p == nil { 104 | return nil 105 | } 106 | 107 | c := p.Component 108 | if c == nil { 109 | c = p.ParentComponent().Component 110 | } 111 | 112 | if c.Hooks.Updated != nil { 113 | err := c.Hooks.Updated() 114 | if err != nil { 115 | return err 116 | } 117 | } 118 | 119 | return nil 120 | } 121 | 122 | // CallBeforeUpdate call BeforeUpdate hook 123 | func CallBeforeUpdate(p *Element) error { 124 | if p == nil { 125 | return nil 126 | } 127 | 128 | c := p.Component 129 | if c == nil { 130 | c = p.ParentComponent().Component 131 | } 132 | 133 | if c.Hooks.BeforeUpdate != nil { 134 | err := c.Hooks.BeforeUpdate() 135 | if err != nil { 136 | return err 137 | } 138 | } 139 | 140 | return nil 141 | } 142 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gascore/gas/454a2def6908c76b90de8b12079eee7298437a57/logo.png -------------------------------------------------------------------------------- /render.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | // RenderCore render station 4 | type RenderCore struct { 5 | BE BackEnd 6 | 7 | queue []*RenderTask 8 | counter int64 9 | } 10 | 11 | // RenderTask node storing changes 12 | type RenderTask struct { 13 | ID int64 14 | 15 | Type RenderType 16 | 17 | Parent *Element 18 | 19 | New, Old interface{} // *Element, string, int, ... 20 | NodeParent, NodeNew, NodeOld interface{} // *dom.Element 21 | 22 | ReplaceCanGoDeeper bool 23 | InReplaced bool 24 | } 25 | 26 | // RenderType RenderTask type 27 | type RenderType int 28 | 29 | const ( 30 | // RReplace type for replace node 31 | RReplace RenderType = iota 32 | 33 | // RReplaceHooks type for run after replace hooks 34 | RReplaceHooks 35 | 36 | // RCreate type for create nodes 37 | RCreate 38 | 39 | // RFirstRender type for first gas render 40 | RFirstRender 41 | 42 | // RDelete type for delete node 43 | RDelete 44 | 45 | // RRecreate type for recreate node 46 | RRecreate 47 | ) 48 | 49 | // Add push render tasks to render queue and trying to execute all queue 50 | func (rc *RenderCore) Add(task *RenderTask) { 51 | task.ID = rc.counter 52 | rc.queue = append(rc.queue, task) 53 | rc.counter++ 54 | } 55 | 56 | // GetAll return render nodes from queue 57 | func (rc *RenderCore) GetAll() []*RenderTask { 58 | return rc.queue 59 | } 60 | 61 | // Exec run all render nodes in render core 62 | func (rc *RenderCore) Exec() { 63 | tQueue := rc.queue 64 | rc.queue = []*RenderTask{} 65 | rc.BE.ExecTasks(tQueue) 66 | } 67 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // UpdateWithError update component childes 9 | func (component *Component) UpdateWithError() error { 10 | oldChild := component.Element // oldChild 11 | if oldChild == nil { // it's Gas in first render 12 | component.RC.Add(&RenderTask{ 13 | Type: RFirstRender, 14 | New: component.RC.updateChild(nil, component.RenderElement()), 15 | }) 16 | component.RC.Exec() 17 | return nil 18 | } 19 | 20 | _oldChild := oldChild.BEElement() 21 | if _oldChild == nil { 22 | return errors.New("invalid _oldChild") 23 | } 24 | 25 | p := oldChild.Parent 26 | 27 | newChild := component.RC.updateChild(p, component.RenderElement()) 28 | isChanged, canGoDeeper, err := Changed(newChild, oldChild) 29 | if err != nil { 30 | return err 31 | } 32 | if isChanged { 33 | component.RC.Add(&RenderTask{ 34 | Type: RReplace, 35 | NodeOld: _oldChild, 36 | New: newChild, 37 | Old: oldChild, 38 | ReplaceCanGoDeeper: canGoDeeper, 39 | Parent: p, 40 | }) 41 | } 42 | 43 | if canGoDeeper { 44 | newChildE, ok := newChild.(*Element) 45 | if !ok { 46 | return fmt.Errorf("unexpected newE type want: *gas.Element, got: %T", newChild) 47 | } 48 | 49 | newChildE.UUID = oldChild.UUID 50 | 51 | err := component.RC.UpdateElementChildes(_oldChild, oldChild, newChildE.Childes, getOldChildes(newChildE, oldChild), true) 52 | if err != nil { 53 | return err 54 | } 55 | } 56 | 57 | if isChanged { 58 | component.RC.Add(&RenderTask{ 59 | Type: RReplaceHooks, 60 | Parent: p, 61 | New: newChild, 62 | }) 63 | } 64 | 65 | component.RC.Exec() 66 | 67 | return nil 68 | } 69 | 70 | // Update update component childes with error warning 71 | func (component *Component) Update() { 72 | component.WarnError(component.UpdateWithError()) 73 | } 74 | 75 | // ReCreate re create element 76 | func (e *Element) ReCreate() { 77 | e.RC.Add(&RenderTask{ 78 | Type: RRecreate, 79 | New: e, 80 | Parent: e.Parent, 81 | }) 82 | go e.RC.Exec() 83 | } 84 | 85 | // UpdateChildes update element childes 86 | func (e *Element) UpdateChildes() { 87 | e.OldChildes = e.Childes 88 | 89 | for i, childExt := range e.Childes { 90 | e.Childes[i] = e.RC.updateChild(e, childExt) 91 | } 92 | 93 | return 94 | } 95 | 96 | func (rc *RenderCore) updateChild(parent *Element, child interface{}) interface{} { 97 | if _, ok := child.(*Component); ok { 98 | childC := child.(*Component) 99 | childC.RC = rc 100 | child = childC.RenderElement() 101 | } 102 | 103 | childE, ok := child.(*Element) 104 | if !ok { 105 | return child 106 | } 107 | 108 | childE.RC = rc 109 | childE.Parent = parent 110 | 111 | if childE.Attrs != nil { 112 | childE.RAttrs = childE.Attrs() 113 | } 114 | 115 | childE.UpdateChildes() 116 | 117 | if childE.HTML != nil { 118 | childE.RHTML = childE.HTML() 119 | } 120 | 121 | return child 122 | } 123 | 124 | // UpdateElementChildes compare new and old trees 125 | func (rc *RenderCore) UpdateElementChildes(_el interface{}, el *Element, new, old []interface{}, inReplaced bool) error { 126 | for i := 0; i < len(new) || i < len(old); i++ { 127 | var newEl interface{} 128 | if len(new) > i { 129 | newEl = new[i] 130 | } 131 | 132 | var oldEl interface{} 133 | if len(old) > i { 134 | oldEl = old[i] 135 | } 136 | 137 | err := rc.updateElement(_el, el, newEl, oldEl, i, inReplaced) 138 | if err != nil { 139 | return err 140 | } 141 | } 142 | 143 | return nil 144 | } 145 | 146 | // updateElement trying to update element 147 | func (rc *RenderCore) updateElement(_parent interface{}, parent *Element, new, old interface{}, index int, inReplaced bool) error { 148 | // if element has created 149 | if old == nil { 150 | rc.Add(&RenderTask{ 151 | Type: RCreate, 152 | New: new, 153 | Parent: parent, 154 | NodeParent: _parent, 155 | 156 | InReplaced: inReplaced, 157 | }) 158 | 159 | return nil 160 | } 161 | 162 | _childes := rc.BE.ChildNodes(_parent) 163 | 164 | var _el interface{} 165 | if len(_childes) > index { 166 | _el = _childes[index] 167 | } else { 168 | if IsElement(old) { 169 | _el = I2E(old).BEElement() 170 | } else { 171 | return nil 172 | } 173 | } 174 | 175 | // if element was deleted 176 | if new == nil { 177 | rc.Add(&RenderTask{ 178 | Type: RDelete, 179 | NodeParent: _parent, 180 | Parent: parent, 181 | NodeOld: _el, 182 | Old: old, 183 | InReplaced: inReplaced, 184 | }) 185 | 186 | return nil 187 | } 188 | 189 | // if element has Changed 190 | isChanged, canGoDeeper, err := Changed(new, old) 191 | if err != nil { 192 | return err 193 | } 194 | if isChanged { 195 | rc.Add(&RenderTask{ 196 | Type: RReplace, 197 | NodeParent: _parent, 198 | NodeOld: _el, 199 | Parent: parent, 200 | New: new, 201 | Old: old, 202 | ReplaceCanGoDeeper: canGoDeeper, 203 | InReplaced: inReplaced, 204 | }) 205 | } 206 | if !canGoDeeper { 207 | return nil 208 | } 209 | 210 | newE := new.(*Element) 211 | oldE := old.(*Element) 212 | newE.UUID = oldE.UUID // little hack 213 | 214 | // if old and new is equals and they have html directives => they are two commons elements 215 | if oldE.HTML != nil && newE.HTML != nil { 216 | return nil 217 | } 218 | 219 | err = rc.UpdateElementChildes(_el, newE, newE.Childes, getOldChildes(newE, oldE), isChanged) 220 | if err != nil { 221 | return err 222 | } 223 | 224 | if isChanged && !inReplaced { 225 | rc.Add(&RenderTask{ 226 | Type: RReplaceHooks, 227 | NodeParent: _parent, 228 | NodeOld: _el, 229 | Parent: parent, 230 | New: new, 231 | Old: old, 232 | }) 233 | } 234 | 235 | return nil 236 | } 237 | 238 | func getOldChildes(newE, oldE *Element) []interface{} { 239 | if newE.IsPointer { 240 | return newE.OldChildes 241 | } else { 242 | return oldE.Childes 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /update_test.go: -------------------------------------------------------------------------------- 1 | package gas 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUpdateChildes(t *testing.T) { 8 | tree := NE( 9 | &E{Tag: "main", RC: GetEmptyRenderCore()}, 10 | NE( 11 | &E{Tag: "div"}, 12 | "wow", 13 | ), 14 | NE( 15 | &E{Tag: "h1", Attrs: func() Map { return Map{"id": "wow"} }}, 16 | "Title", 17 | ), 18 | NE( 19 | &E{Tag: "p"}, 20 | "Lorem ipsum dolore", 21 | " ", 22 | NE( 23 | &E{Tag: "i", Attrs: func() Map { return Map{"id": "lol", "class": "some"} }}, 24 | "opsum", 25 | ), 26 | NE( 27 | &E{Tag: "b"}, 28 | "fote", 29 | NE( 30 | &E{Tag: "i"}, 31 | "wate", 32 | ), 33 | NE( 34 | &E{HTML: func() string { return "

some

" }}, 35 | ), 36 | ), 37 | ), 38 | ) 39 | tree.UpdateChildes() 40 | 41 | isChildValid := func(i interface{}) { 42 | e, ok := i.(*E) 43 | if !ok { 44 | t.Errorf("invalid first child type: %T", tree.Childes[0]) 45 | } 46 | if len(e.Tag) == 0 || len(e.UUID) == 0 || e.RC == nil || e.Parent == nil { 47 | t.Errorf("invalid child *Element: %v", e) 48 | } 49 | } 50 | 51 | isChildValid(tree.Childes[0]) 52 | isChildValid(tree.Childes[1]) 53 | isChildValid(tree.Childes[2]) 54 | isChildValid(tree.Childes[2].(*E).Childes[2]) 55 | isChildValid(tree.Childes[2].(*E).Childes[3]) 56 | isChildValid(tree.Childes[2].(*E).Childes[3].(*E).Childes[1]) 57 | isChildValid(tree.Childes[2].(*E).Childes[3].(*E).Childes[2]) 58 | 59 | if tree.Childes[2].(*E).Childes[3].(*E).Childes[2].(*E).RHTML == "" { 60 | t.Error("invalid html directive") 61 | } 62 | } 63 | 64 | func TestUpdateElementChildes(t *testing.T) { 65 | var nodes []*RenderTask 66 | 67 | root := &exampleRoot{ 68 | msg: "no", 69 | counter: 5, 70 | } 71 | c := &C{ 72 | RC: &RenderCore{ 73 | BE: emptyBackEnd{ 74 | logger: func(newNodes []*RenderTask) { 75 | nodes = append(nodes, newNodes...) 76 | }, 77 | }, 78 | }, 79 | NotPointer: true, 80 | Root: root, 81 | } 82 | root.c = c 83 | 84 | f := func(i int) { 85 | if len(nodes) != i { 86 | t.Errorf("not enough render nodes, want: %d, but got: %d, nodes: %v", i, len(nodes), nodes) 87 | return 88 | } 89 | nodes = []*RenderTask{} 90 | } 91 | 92 | root.c.Update() 93 | f(1) 94 | 95 | root.counter = 6 96 | root.msg = "wow" 97 | root.c.Update() 98 | f(2) 99 | } 100 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # WEB - wasm/gopherjs backend for [gas framework](https://github.com/gascore/gas) 2 | -------------------------------------------------------------------------------- /web/element.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/gascore/dom" 10 | "github.com/gascore/gas" 11 | ) 12 | 13 | // CreateElement render element 14 | func CreateElement(el interface{}) (dom.Node, error) { 15 | switch el := el.(type) { 16 | case string: 17 | return createTextNode(el) 18 | case fmt.Stringer: 19 | return createTextNode(el.String()) 20 | case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: 21 | return createTextNode(fmt.Sprintf("%v", el)) 22 | case bool: 23 | if el { 24 | return createTextNode("true") 25 | } 26 | return createTextNode("false") 27 | case *gas.Element: 28 | _node, err := createHtmlElement(el) 29 | if err != nil { 30 | return nil, fmt.Errorf("cannot create component: %s", err.Error()) 31 | } 32 | 33 | if el.Component != nil && el.Component.Hooks.Created != nil { 34 | err := el.Component.Hooks.Created() 35 | if err != nil { 36 | return nil, err 37 | } 38 | } 39 | 40 | if el.RefName != "" { 41 | p := el.ParentComponent() 42 | if p == nil || !p.Component.RefsAllowed { 43 | dom.ConsoleError("parent with allowed refs doesn't exist") 44 | } else { 45 | if p.Component.Refs == nil { 46 | p.Component.Refs = make(map[string]*gas.E) 47 | } 48 | p.Component.Refs[el.RefName] = el // append element to first true component refs 49 | } 50 | } 51 | 52 | for _, child := range el.Childes { 53 | _child, err := CreateElement(child) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | _node.AppendChild(_child) 59 | } 60 | 61 | return _node, nil 62 | default: 63 | return nil, fmt.Errorf("unsupported component type: %T", el) 64 | } 65 | } 66 | 67 | func newNodeEl(tag string) *dom.Element { 68 | switch tag { 69 | case "animate", "animatemotion", "animatetransform", "circle", "clippath", "color-profile", "defs", "desc", "discard", "ellipse", "feblend", "fecolormatrix", "fecomponenttransfer", "fecomposite", "feconvolvematrix", "fediffuselighting", "fedisplacementmap", "fedistantlight", "fedropshadow", "feflood", "fefunca", "fefuncb", "fefuncg", "fefuncr", "fegaussianblur", "feimage", "femerge", "femergenode", "femorphology", "feoffset", "fepointlight", "fespecularlighting", "fespotlight", "fetile", "feturbulence", "filter", "foreignobject", "g", "hatch", "hatchpath", "image", "line", "lineargradient", "marker", "mask", "mesh", "meshgradient", "meshpatch", "meshrow", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "set", "solidcolor", "stop", "svg", "switch", "symbol", "text", "textpath", "title", "tspan", "unknown", "use", "view", "svg-style", "svg-script", "svg-a": 70 | return dom.NewElementNS("http://www.w3.org/2000/svg", strings.TrimPrefix(tag, "svg-")) 71 | default: 72 | return dom.NewElement(tag) 73 | } 74 | } 75 | 76 | // createHtmlElement create html element without children 77 | func createHtmlElement(el *gas.Element) (*dom.Element, error) { 78 | _node := newNodeEl(el.Tag) 79 | if _node == nil { 80 | return nil, errors.New("cannot create component") 81 | } 82 | 83 | _node.SetAttribute("data-i", el.UUID) // set data-i for accept element from component methods 84 | 85 | setAttributes(_node, el.RAttrs) 86 | 87 | for handlerName, handlerBodyG := range el.Handlers { 88 | handlerBody := handlerBodyG 89 | handlerNameParsed := strings.Split(handlerName, ".") 90 | if len(handlerNameParsed) == 2 { 91 | handlerType := handlerNameParsed[0] 92 | handlerTarget := strings.ToLower(handlerNameParsed[1]) 93 | 94 | var handlerTargetIsInt bool 95 | handlerTargetInt, err := strconv.Atoi(handlerTarget) 96 | handlerTargetIsInt = err == nil 97 | 98 | switch handlerType { 99 | case "keyup": 100 | _node.AddEventListener("keyup", func(e dom.Event) { 101 | keyCode, _ := strconv.Atoi(e.KeyCode()) 102 | if handlerTarget == strings.ToLower(e.Key()) || (handlerTargetIsInt && handlerTargetInt == keyCode) { 103 | handlerBody(ToGasEvent(e, false)) 104 | } 105 | }) 106 | continue 107 | case "click": 108 | _node.AddEventListener("click", func(e dom.Event) { 109 | e.PreventDefault() // Because i don't want trigger 110 | 111 | buttonClick, err := parseInt(strings.ToLower(e.ButtonAttr())) 112 | if err != nil { 113 | return 114 | } 115 | 116 | var correctKey bool 117 | if handlerTargetIsInt { 118 | correctKey = handlerTargetInt == buttonClick 119 | } else { 120 | var parsedButtonClick string 121 | switch buttonClick { 122 | case 0: 123 | parsedButtonClick = "left" 124 | case 1: 125 | parsedButtonClick = "middle" 126 | case 2: 127 | parsedButtonClick = "right" 128 | default: 129 | parsedButtonClick = "unknown" 130 | } 131 | 132 | correctKey = handlerTarget == parsedButtonClick 133 | } 134 | 135 | if correctKey { 136 | handlerBody(ToGasEvent(e, false)) 137 | } 138 | }) 139 | 140 | continue 141 | } 142 | } 143 | 144 | isCheckbox := isInputCheckbox(el.Tag, el.RAttrs) 145 | _node.AddEventListener(handlerName, func(e dom.Event) { 146 | handlerBody(ToGasEvent(e, isCheckbox)) 147 | }) 148 | } 149 | 150 | if el.HTML != nil { 151 | _node.SetInnerHTML(_node.InnerHTML() + "\n" + el.RHTML) 152 | } 153 | 154 | return _node, nil 155 | } 156 | 157 | func setAttributes(_el *dom.Element, attrs gas.Map) { 158 | for attrKey, attrVal := range attrs { 159 | if attrKey == "checked" { 160 | if attrVal == "false" { 161 | _el.JSValue().Set("checked", false) 162 | _el.RemoveAttribute("checked") 163 | continue 164 | } 165 | 166 | _el.JSValue().Set("checked", true) 167 | } 168 | 169 | _el.SetAttribute(attrKey, attrVal) 170 | 171 | if attrKey == "value" { 172 | _el.SetValue(attrVal) 173 | } 174 | } 175 | } 176 | 177 | func isInputCheckbox(tag string, attrs gas.Map) bool { 178 | if tag != "input" { 179 | return false 180 | } 181 | 182 | for attrKey, attrVal := range attrs { 183 | if attrKey == "type" && attrVal == "checkbox" { 184 | return true 185 | } 186 | } 187 | 188 | return false 189 | } 190 | 191 | // createTextNode create TextNode by string 192 | func createTextNode(node string) (dom.Node, error) { 193 | _node := dom.Doc.CreateTextNode(node) 194 | if _node == nil { 195 | return nil, errors.New("cannot create textNode") 196 | } 197 | 198 | return _node, nil 199 | } 200 | -------------------------------------------------------------------------------- /web/ka_notwasm.go: -------------------------------------------------------------------------------- 1 | // +build !wasm 2 | 3 | package web 4 | 5 | // KeepAlive keep alive application 6 | func KeepAlive() { 7 | ch := make(chan int, 5) 8 | ch <- 1 9 | } 10 | -------------------------------------------------------------------------------- /web/ka_wasm.go: -------------------------------------------------------------------------------- 1 | // +build wasm 2 | 3 | package web 4 | 5 | var signal = make(chan struct{}) 6 | 7 | func KeepAlive() { 8 | for { 9 | <-signal 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /web/object.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/gascore/dom" 8 | "github.com/gascore/gas" 9 | "syscall/js" 10 | ) 11 | 12 | func parseInt(a string) (int, error) { 13 | if a == "" { 14 | return 0, nil 15 | } 16 | 17 | return strconv.Atoi(a) 18 | } 19 | 20 | func warnError(err error) { 21 | dom.ConsoleError(err.Error()) 22 | } 23 | 24 | type object struct{ o js.Value } 25 | 26 | func (o object) String() string { return o.o.String() } 27 | func (o object) Int() int { return o.o.Int() } 28 | func (o object) Float() float64 { return o.o.Float() } 29 | func (o object) Get(q string) gas.Object { return object{o: o.o.Get(q)} } 30 | func (o object) Set(name string, val interface{}) { o.o.Set(name, val) } 31 | func (o object) GetString(q string) string { return o.o.Get(q).String() } 32 | func (o object) GetBool(q string) bool { return o.o.Get(q).Bool() } 33 | func (o object) GetInt(q string) int { return o.o.Get(q).Int() } 34 | func (o object) Raw() interface{} { return o.o } 35 | func (o object) Call(name string, args ...interface{}) gas.Object { 36 | return object{o: o.o.Call(name, args...)} 37 | } 38 | 39 | // ToUniteObject convert dom.Value to gas.Object 40 | func ToUniteObject(e dom.Value) gas.Object { return object{o: e.JSValue()} } 41 | 42 | type event struct{ 43 | gas.Object 44 | event dom.Event 45 | isCheckbox bool 46 | } 47 | 48 | // Value reurn event value 49 | func (e event) Value() string { 50 | if e.isCheckbox { 51 | if e.ValueBool() { 52 | return "true" 53 | } 54 | return "false" 55 | } 56 | 57 | return e.event.Target().Value() 58 | } 59 | 60 | // Value reurn event value and convert it to int 61 | func (e event) ValueInt() int { 62 | val := e.Value() 63 | n, err := strconv.Atoi(val) 64 | if err != nil { 65 | dom.ConsoleError(fmt.Sprintf("cannot convert event value to int: \"%s\"", val)) 66 | } 67 | 68 | return n 69 | } 70 | 71 | // Value reurn event value and convert it to boolean 72 | func (e event) ValueBool() bool { 73 | if e.isCheckbox { 74 | return e.event.Target().JSValue().Get("checked").Bool() 75 | } 76 | 77 | val := e.Value() 78 | if val == "true" { 79 | return true 80 | } else if val != "false" { 81 | dom.ConsoleError(fmt.Sprintf("cannot convert event value to bool: \"%s\"", val)) 82 | } 83 | 84 | return false 85 | } 86 | 87 | // ToGasEvent convert dom.Event to gas.Event 88 | func ToGasEvent(domEvent dom.Event, isCheckbox bool) gas.Event { 89 | e := event{ToUniteObject(domEvent), domEvent, isCheckbox} 90 | return e 91 | } 92 | -------------------------------------------------------------------------------- /web/render.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "errors" 5 | 6 | // "fmt" 7 | 8 | "github.com/gascore/dom" 9 | "github.com/gascore/gas" 10 | ) 11 | 12 | func (w *BackEnd) Executor() { 13 | for { 14 | select { 15 | case task := <-w.queue: 16 | err := w.ExecTask(task) 17 | if err != nil { 18 | dom.ConsoleError(err.Error()) 19 | } 20 | } 21 | } 22 | } 23 | 24 | // ExecTasks execute render tasks 25 | func (w *BackEnd) ExecTasks(tasks []*gas.RenderTask) { 26 | if w.newRenderer { 27 | for _, task := range tasks { 28 | w.queue <- task 29 | } 30 | return 31 | } 32 | 33 | for _, task := range tasks { 34 | err := w.ExecTask(task) 35 | if err != nil { 36 | dom.ConsoleError(err.Error()) 37 | } 38 | } 39 | } 40 | 41 | // ExecTask execute render task 42 | func (w *BackEnd) ExecTask(task *gas.RenderTask) error { 43 | hook := func(f func(interface{}) error, el interface{}) { 44 | if !task.InReplaced { 45 | err := f(el) 46 | if err != nil { 47 | dom.ConsoleError(err.Error()) 48 | } 49 | } 50 | } 51 | 52 | if !task.InReplaced { 53 | err := gas.CallBeforeUpdate(task.Parent) 54 | if err != nil { 55 | dom.ConsoleError(err.Error()) 56 | } 57 | } 58 | 59 | switch task.Type { 60 | case gas.RReplace: 61 | hook(gas.CallBeforeCreated, task.New) 62 | hook(gas.CallBeforeDestroy, task.Old) 63 | 64 | // Update old element attributes 65 | if task.ReplaceCanGoDeeper { 66 | newE := task.New.(*gas.E) 67 | _old := task.NodeOld.(*dom.Element) 68 | 69 | setAttributes(_old, gas.DiffAttrs(newE.RAttrs, task.Old.(*gas.E).RAttrs)) 70 | _old.SetAttribute("data-i", newE.UUID) 71 | 72 | return nil 73 | } 74 | 75 | // Create new element and replace with old 76 | _new, err := CreateElement(task.New) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | _old, ok := task.NodeOld.(*dom.Element) 82 | if !ok { 83 | return errors.New("invalid NodeOld type") 84 | } 85 | 86 | if _old.ParentElement() == nil { 87 | println("PARENT IS NIL") 88 | } 89 | 90 | _old.ParentElement().ReplaceChild(_new, _old) 91 | 92 | hook(gas.CallMounted, task.New) 93 | case gas.RReplaceHooks: 94 | hook(gas.CallMounted, task.New) 95 | case gas.RCreate, gas.RFirstRender: 96 | var _parent *dom.Element 97 | if task.Type == gas.RCreate { 98 | var ok bool 99 | _parent, ok = task.NodeParent.(*dom.Element) 100 | if !ok { 101 | return errors.New("invalid NodeParent type") 102 | } 103 | } else { 104 | _parent = w.startEl 105 | } 106 | 107 | hook(gas.CallBeforeCreated, task.New) 108 | 109 | _new, err := CreateElement(task.New) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | _parent.AppendChild(_new) 115 | 116 | hook(gas.CallMounted, task.New) 117 | case gas.RDelete: 118 | _parent, ok := task.NodeParent.(*dom.Element) 119 | if !ok { 120 | return errors.New("invalid NodeParent") 121 | } 122 | 123 | _old, ok := task.NodeOld.(*dom.Element) 124 | if !ok { 125 | return errors.New("invalid NodeOld") 126 | } 127 | 128 | hook(gas.CallBeforeDestroy, task.Old) 129 | 130 | _parent.RemoveChild(_old) 131 | case gas.RRecreate: 132 | e := task.New.(*gas.Element) 133 | _e, ok := e.BEElement().(*dom.Element) 134 | if !ok { 135 | return errors.New("invalid NodeNew type") 136 | } 137 | 138 | hook(gas.CallBeforeDestroy, e) 139 | 140 | for _, _child := range _e.ChildNodes() { 141 | _e.RemoveChild(_child) 142 | } 143 | 144 | e.Childes = []interface{}{} 145 | e.OldChildes = []interface{}{} 146 | 147 | err := e.ParentComponent().Component.UpdateWithError() 148 | if err != nil { 149 | return err 150 | } 151 | } 152 | 153 | if !task.InReplaced { 154 | err := gas.CallUpdated(task.Parent) 155 | if err != nil { 156 | dom.ConsoleError(err.Error()) 157 | } 158 | } 159 | 160 | return nil 161 | } 162 | 163 | // ChildNodes return *dom.Element child nodes 164 | func (w *BackEnd) ChildNodes(el interface{}) []interface{} { 165 | var iChildes []interface{} 166 | _el, ok := el.(*dom.Element) 167 | if !ok { 168 | return iChildes 169 | } 170 | 171 | _childes := _el.ChildNodes() 172 | for _, _el := range _childes { 173 | iChildes = append(iChildes, _el) 174 | } 175 | 176 | return iChildes 177 | } 178 | -------------------------------------------------------------------------------- /web/web.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/gascore/dom" 8 | "github.com/gascore/gas" 9 | ) 10 | 11 | // BackEnd backend for core library 12 | type BackEnd struct { 13 | newRenderer bool 14 | queue chan *gas.RenderTask 15 | startEl *dom.Element 16 | } 17 | 18 | // Init initialize gas application 19 | func Init(c *gas.Component, startPoint string) error { 20 | return InitCustom(c, startPoint, false) 21 | } 22 | 23 | func InitCustom(c *gas.C, startPoint string, newRenderer bool) error { 24 | _startEl := dom.Doc.GetElementById(startPoint) 25 | if _startEl == nil { 26 | return errors.New("invalid startPoint") 27 | } 28 | 29 | be := &BackEnd{ 30 | queue: make(chan *gas.RenderTask), 31 | newRenderer: newRenderer, 32 | startEl: _startEl, 33 | } 34 | if newRenderer { 35 | go be.Executor() 36 | } 37 | 38 | gas := gas.New(c, be) 39 | gas.Component.Update() 40 | 41 | return nil 42 | } 43 | 44 | // GetElement get dom.Element by element 45 | func (w *BackEnd) GetElement(c *gas.Element) interface{} { 46 | return dom.Doc.QuerySelector(fmt.Sprintf("[data-i='%s']", c.UUID)) 47 | } 48 | 49 | // ConsoleLog console.log(a) 50 | func (w *BackEnd) ConsoleLog(a ...interface{}) { dom.ConsoleLog(a...) } 51 | 52 | // ConsoleError console.error(a) 53 | func (w *BackEnd) ConsoleError(a ...interface{}) { dom.ConsoleError(a...) } 54 | --------------------------------------------------------------------------------