├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples ├── 1 │ ├── app.go │ ├── counter │ │ ├── counter.go │ │ └── counter_test.go │ └── index.html ├── 2 │ ├── app.go │ ├── counter │ │ ├── counter.go │ │ └── counter_test.go │ ├── counterpair │ │ ├── counterpair.go │ │ └── counterpair_test.go │ └── index.html ├── 3 │ ├── app.go │ ├── counter │ │ ├── counter.go │ │ └── counter_test.go │ ├── counterlist │ │ ├── counterlist.go │ │ └── counterlist_test.go │ └── index.html ├── clearfield │ ├── app.go │ ├── clearfield │ │ ├── clearfield.go │ │ └── clearfield_test.go │ └── index.html ├── inception │ ├── app.go │ ├── counter │ │ ├── counter.go │ │ └── counter_test.go │ ├── counterpair │ │ ├── counterpair.go │ │ └── counterpair_test.go │ ├── counterpairpair │ │ ├── counterpairpair.go │ │ └── counterpairpair_test.go │ └── index.html ├── label │ ├── app.go │ ├── index.html │ └── label │ │ ├── label.go │ │ └── label_test.go └── reverse │ ├── app.go │ ├── index.html │ └── reverse │ ├── reverse.go │ └── reverse_test.go ├── go.mod ├── go.sum ├── html ├── html.go └── html_test.go └── start └── start.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated JavaScript files 2 | *.js 3 | *.js.map 4 | *.DS_Store 5 | 6 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 7 | *.o 8 | *.a 9 | *.so 10 | 11 | # Folders 12 | _obj 13 | _test 14 | 15 | # Architecture specific extensions/prefixes 16 | *.[568vq] 17 | [568vq].out 18 | 19 | *.cgo1.go 20 | *.cgo2.c 21 | _cgo_defun.c 22 | _cgo_gotypes.go 23 | _cgo_export.* 24 | 25 | _testmain.go 26 | 27 | *.exe 28 | *.test 29 | *.prof 30 | *.sh 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - "1.11.x" 5 | - "1.10.x" 6 | - tip 7 | env: 8 | global: 9 | - GO111MODULE=on 10 | matrix: 11 | allow_failures: 12 | - go: tip 13 | fast_finish: true 14 | install: 15 | - # Do not install yet, since we want it to happen inside the script step below. 16 | script: 17 | - go get -t -v ./... 18 | - diff -u <(echo -n) <(gofmt -d -s .) # Make sure formatting is correct. 19 | - go generate -x ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) # Check that go generate ./... produces a zero diff; clean up any changes afterwards. 20 | - go tool vet . 21 | - go test -v -race ./... 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement] 6 | (https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | Before you start working on a larger contribution, you should get in touch with 15 | us first through the issue tracker with your idea so that we can help out and 16 | possibly guide you. Coordinating up front makes it much easier to avoid 17 | frustration later on. 18 | 19 | ### Code reviews 20 | All submissions, including submissions by project members, require review. We 21 | use Github pull requests for this purpose. 22 | 23 | ### The small print 24 | Contributions made by corporations are covered by a different agreement than 25 | the one above, the 26 | [Software Grant and Corporate Contributor License Agreement] 27 | (https://cla.developers.google.com/about/google-corporate). 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-frp - Functional Reactive Programming in Go 2 | 3 | ## Status 4 | [![GoDoc](https://godoc.org/github.com/gmlewis/go-frp?status.svg)](https://godoc.org/github.com/gmlewis/go-frp) 5 | [![Build Status](https://travis-ci.org/gmlewis/go-frp.png)](https://travis-ci.org/gmlewis/go-frp) 6 | 7 | ## Overview 8 | 9 | Functional Reactive Programming as demonstrated by [React][react], 10 | [React Native][react-native] and [Flux][flux] appear to be game-changing 11 | technologies for web development as well as native app development for both 12 | Android and iOS. 13 | 14 | [react]: http://facebook.github.io/react 15 | [react-native]: http://facebook.github.io/react-native 16 | [flux]: http://facebook.github.io/flux 17 | 18 | While investigating these technologies, I came across the 19 | [Elm Programming Language][elm] and was impressed by the simple web app model 20 | and the idea that a single language could provide all the HTML, CSS, *and* 21 | JavaScript necessary to create a full web application. I read through the 22 | tutorials and examples and although I'm a big fan of functional programming 23 | languages, it dawned on me that with the advent of [GopherJS][gopherjs], I could 24 | do all this in Go. 25 | 26 | [elm]: http://elm-lang.org/ 27 | [gopherjs]: https://github.com/gopherjs/gopherjs 28 | 29 | This is not an official Google product. 30 | 31 | ## Why Go? 32 | 33 | There are a number of reasons for using [Go][]: 34 | 35 | * Go is easy to read and understand, 36 | * [goimports][] (and its integration with text editors) is fantastic, 37 | * if your code compiles, chances are good that it is correct, and 38 | * Go makes programming fun again. 39 | 40 | [Go]: http://www.golang.org/ 41 | [gofmt]: https://golang.org/cmd/gofmt 42 | [goimports]: https://godoc.org/golang.org/x/tools/cmd/goimports 43 | 44 | ## Installation 45 | 46 | ```bash 47 | $ go get github.com/gopherjs/gopherjs 48 | $ go get github.com/gmlewis/go-frp/v2 49 | ``` 50 | 51 | ## Getting started 52 | 53 | ### Run the examples 54 | 55 | ```bash 56 | $ cd examples/1 57 | $ gopherjs build -m app.go 58 | ``` 59 | 60 | ## License 61 | 62 | Copyright 2015 Google Inc. All Rights Reserved. 63 | 64 | Licensed under the Apache License, Version 2.0 (the "License"); 65 | you may not use this file except in compliance with the License. 66 | You may obtain a copy of the License at 67 | 68 | http://www.apache.org/licenses/LICENSE-2.0 69 | 70 | Unless required by applicable law or agreed to in writing, software 71 | distributed under the License is distributed on an "AS IS" BASIS, 72 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 73 | See the License for the specific language governing permissions and 74 | limitations under the License. 75 | -------------------------------------------------------------------------------- /examples/1/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/gmlewis/go-frp/v2/examples/1/counter" 9 | "github.com/gmlewis/go-frp/v2/start" 10 | ) 11 | 12 | func main() { 13 | m := counter.Model(0) 14 | start.Start(m, counter.Updater(m)) 15 | } 16 | -------------------------------------------------------------------------------- /examples/1/counter/counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package counter is an example using go-frp modeled after the Elm example found at: 6 | // https://github.com/evancz/elm-architecture-tutorial/blob/master/examples/1/Counter.elm 7 | package counter 8 | 9 | import ( 10 | "fmt" 11 | 12 | h "github.com/gmlewis/go-frp/v2/html" 13 | ) 14 | 15 | // MODEL 16 | 17 | type Model int 18 | 19 | func (m Model) String() string { return fmt.Sprintf("%v", int(m)) } 20 | 21 | // UPDATE 22 | 23 | type Action func(Model) Model 24 | 25 | func Updater(model Model) func(action Action) Model { 26 | return func(action Action) Model { return model.Update(action) } 27 | } 28 | func (m Model) Update(action Action) Model { return action(m) } 29 | 30 | func Increment(model Model) Model { return model + 1 } 31 | func Decrement(model Model) Model { return model - 1 } 32 | 33 | // VIEW 34 | 35 | func (m Model) View(rootUpdateFunc, wrapFunc interface{}) h.HTML { 36 | style := [][]string{ 37 | {"font-size", "20px"}, 38 | {"font-family", "monospace"}, 39 | {"display", "inline-block"}, 40 | {"width", "50px"}, 41 | {"text-align", "center"}, 42 | } 43 | return h.Div( 44 | h.Button(h.Text("-")).OnClick(rootUpdateFunc, Updater(m), Decrement), 45 | h.Div(h.Text(m.String())).Style(style), 46 | h.Button(h.Text("+")).OnClick(rootUpdateFunc, Updater(m), Increment), 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /examples/1/counter/counter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package counter 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestView(t *testing.T) { 13 | m := Model(0) 14 | v := m.View(Updater(m), nil) 15 | want := []string{ 16 | "
", "
", 17 | `
0
`, 18 | "-", 19 | "+", 20 | } 21 | got, _ := v.Render() 22 | for _, w := range want { 23 | if !strings.Contains(got, w) { 24 | t.Errorf("View = %q, want %q", got, w) 25 | } 26 | } 27 | } 28 | 29 | func TestIncrement(t *testing.T) { 30 | m := Model(0) 31 | for i := 0; i < 10; i++ { 32 | m = Increment(m) 33 | } 34 | if got, want := int(m), 10; got != want { 35 | t.Errorf("Increment = %v, want %v", got, want) 36 | } 37 | } 38 | 39 | func TestDecrement(t *testing.T) { 40 | m := Model(0) 41 | for i := 0; i < 10; i++ { 42 | m = Decrement(m) 43 | } 44 | if got, want := int(m), -10; got != want { 45 | t.Errorf("Decrement = %v, want %v", got, want) 46 | } 47 | } 48 | 49 | func TestUpdate(t *testing.T) { 50 | m := Model(0) 51 | m = m.Update(Increment) 52 | if got, want := int(m), 1; got != want { 53 | t.Errorf("Increment = %v, want %v", got, want) 54 | } 55 | m = m.Update(Decrement) 56 | if got, want := int(m), 0; got != want { 57 | t.Errorf("Decrement = %v, want %v", got, want) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Counter Example 1 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/2/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/gmlewis/go-frp/v2/examples/2/counterpair" 9 | "github.com/gmlewis/go-frp/v2/start" 10 | ) 11 | 12 | func main() { 13 | m := counterpair.Init(0, 0) 14 | start.Start(m, counterpair.Updater(m)) 15 | } 16 | -------------------------------------------------------------------------------- /examples/2/counter/counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package counter is an example using go-frp modeled after the Elm example found at: 6 | // https://github.com/evancz/elm-architecture-tutorial/blob/master/examples/2/Counter.elm 7 | package counter 8 | 9 | import ( 10 | "fmt" 11 | 12 | h "github.com/gmlewis/go-frp/v2/html" 13 | ) 14 | 15 | // MODEL 16 | 17 | type Model int 18 | 19 | func (m Model) String() string { return fmt.Sprintf("%v", int(m)) } 20 | func Init(count int) Model { return Model(count) } 21 | 22 | // UPDATE 23 | 24 | type Action func(Model) Model 25 | 26 | func Updater(model Model) func(action Action) Model { 27 | return func(action Action) Model { return model.Update(action) } 28 | } 29 | func (m Model) Update(action Action) Model { return action(m) } 30 | 31 | func Increment(model Model) Model { return model + 1 } 32 | func Decrement(model Model) Model { return model - 1 } 33 | 34 | type WrapFunc func(model Model) interface{} 35 | 36 | func wrapper(model Model, wrapFunc WrapFunc) func(action Action) interface{} { 37 | return func(action Action) interface{} { 38 | newModel := model.Update(action) 39 | return wrapFunc(newModel) 40 | } 41 | } 42 | 43 | // VIEW 44 | 45 | func (m Model) View(rootUpdateFunc interface{}, wrapFunc WrapFunc) h.HTML { 46 | style := [][]string{ 47 | {"font-size", "20px"}, 48 | {"font-family", "monospace"}, 49 | {"display", "inline-block"}, 50 | {"width", "50px"}, 51 | {"text-align", "center"}, 52 | } 53 | return h.Div( 54 | h.Button(h.Text("-")).OnClick(rootUpdateFunc, wrapper(m, wrapFunc), Decrement), 55 | h.Div(h.Text(m.String())).Style(style), 56 | h.Button(h.Text("+")).OnClick(rootUpdateFunc, wrapper(m, wrapFunc), Increment), 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /examples/2/counter/counter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package counter 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestView(t *testing.T) { 13 | m := Model(0) 14 | v := m.View(Updater(m), nil) 15 | want := []string{ 16 | "
", "
", 17 | `
0
`, 18 | "-", 19 | "+", 20 | } 21 | got, _ := v.Render() 22 | for _, w := range want { 23 | if !strings.Contains(got, w) { 24 | t.Errorf("View = %q, want %q", got, w) 25 | } 26 | } 27 | } 28 | 29 | func TestIncrement(t *testing.T) { 30 | m := Model(0) 31 | for i := 0; i < 10; i++ { 32 | m = Increment(m) 33 | } 34 | if got, want := int(m), 10; got != want { 35 | t.Errorf("Increment = %v, want %v", got, want) 36 | } 37 | } 38 | 39 | func TestDecrement(t *testing.T) { 40 | m := Model(0) 41 | for i := 0; i < 10; i++ { 42 | m = Decrement(m) 43 | } 44 | if got, want := int(m), -10; got != want { 45 | t.Errorf("Decrement = %v, want %v", got, want) 46 | } 47 | } 48 | 49 | func TestUpdate(t *testing.T) { 50 | m := Model(0) 51 | m = m.Update(Increment) 52 | if got, want := int(m), 1; got != want { 53 | t.Errorf("Increment = %v, want %v", got, want) 54 | } 55 | m = m.Update(Decrement) 56 | if got, want := int(m), 0; got != want { 57 | t.Errorf("Decrement = %v, want %v", got, want) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/2/counterpair/counterpair.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package counterpair is an example using go-frp modeled after the Elm example found at: 6 | // https://github.com/evancz/elm-architecture-tutorial/blob/master/examples/2/CounterPair.elm 7 | package counterpair 8 | 9 | import ( 10 | "github.com/gmlewis/go-frp/v2/examples/2/counter" 11 | h "github.com/gmlewis/go-frp/v2/html" 12 | ) 13 | 14 | // MODEL 15 | 16 | type Model struct { 17 | top counter.Model 18 | bottom counter.Model 19 | } 20 | 21 | func Init(top, bottom int) Model { 22 | return Model{ 23 | top: counter.Init(top), 24 | bottom: counter.Init(bottom), 25 | } 26 | } 27 | 28 | // UPDATE 29 | 30 | type Action func(Model) Model 31 | 32 | func Updater(model Model) func(action Action) Model { 33 | return func(action Action) Model { return model.Update(action) } 34 | } 35 | func (m Model) Update(action Action) Model { return action(m) } 36 | 37 | func Reset(model Model) Model { return Init(0, 0) } 38 | 39 | func top(model Model) counter.WrapFunc { 40 | return func(cm counter.Model) interface{} { 41 | return Model{ 42 | top: cm, 43 | bottom: model.bottom, 44 | } 45 | } 46 | } 47 | 48 | func bottom(model Model) counter.WrapFunc { 49 | return func(cm counter.Model) interface{} { 50 | return Model{ 51 | top: model.top, 52 | bottom: cm, 53 | } 54 | } 55 | } 56 | 57 | // VIEW 58 | 59 | func (m Model) View(rootUpdateFunc, wrapFunc interface{}) h.HTML { 60 | return h.Div( 61 | m.top.View(rootUpdateFunc, top(m)), 62 | m.bottom.View(rootUpdateFunc, bottom(m)), 63 | h.Button(h.Text("Reset")).OnClick(rootUpdateFunc, rootUpdateFunc, Reset), 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /examples/2/counterpair/counterpair_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package counterpair 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestView(t *testing.T) { 13 | m := Init(1, 2) 14 | v := m.View(Updater(m), nil) 15 | want := []string{ 16 | "
", "
", 17 | `
1
`, 18 | `
2
`, 19 | "-", 20 | "+", 21 | "Reset", 22 | } 23 | got, _ := v.Render() 24 | for _, w := range want { 25 | if !strings.Contains(got, w) { 26 | t.Errorf("View = %q, want %q", got, w) 27 | } 28 | } 29 | } 30 | 31 | func TestReset(t *testing.T) { 32 | m := Init(10, 100) 33 | m = Updater(m)(Reset) 34 | if got, want := int(m.top), 0; got != want { 35 | t.Errorf("Top: Reset = %v, want %v", got, want) 36 | } 37 | if got, want := int(m.bottom), 0; got != want { 38 | t.Errorf("Bottom: Reset = %v, want %v", got, want) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Counter Example 2 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/3/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/gmlewis/go-frp/v2/examples/3/counterlist" 9 | "github.com/gmlewis/go-frp/v2/start" 10 | ) 11 | 12 | func main() { 13 | m := counterlist.Init(0, 0, 0) 14 | start.Start(m, counterlist.Updater(m)) 15 | } 16 | -------------------------------------------------------------------------------- /examples/3/counter/counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package counter is an example using go-frp modeled after the Elm example found at: 6 | // https://github.com/evancz/elm-architecture-tutorial/blob/master/examples/3/Counter.elm 7 | package counter 8 | 9 | import ( 10 | "fmt" 11 | 12 | h "github.com/gmlewis/go-frp/v2/html" 13 | ) 14 | 15 | // MODEL 16 | 17 | type Model int 18 | 19 | func (m Model) String() string { return fmt.Sprintf("%v", int(m)) } 20 | func Init(count int) Model { return Model(count) } 21 | 22 | // UPDATE 23 | 24 | type Action func(Model) Model 25 | 26 | func Updater(model Model) func(action Action) Model { 27 | return func(action Action) Model { return model.Update(action) } 28 | } 29 | func (m Model) Update(action Action) Model { return action(m) } 30 | 31 | func Increment(model Model) Model { return model + 1 } 32 | func Decrement(model Model) Model { return model - 1 } 33 | 34 | type WrapFunc func(model Model) interface{} 35 | 36 | func wrapper(model Model, wrapFunc WrapFunc) func(action Action) interface{} { 37 | return func(action Action) interface{} { 38 | newModel := model.Update(action) 39 | return wrapFunc(newModel) 40 | } 41 | } 42 | 43 | // VIEW 44 | 45 | func (m Model) View(rootUpdateFunc interface{}, wrapFunc WrapFunc) h.HTML { 46 | style := [][]string{ 47 | {"font-size", "20px"}, 48 | {"font-family", "monospace"}, 49 | {"display", "inline-block"}, 50 | {"width", "50px"}, 51 | {"text-align", "center"}, 52 | } 53 | return h.Div( 54 | h.Button(h.Text("-")).OnClick(rootUpdateFunc, wrapper(m, wrapFunc), Decrement), 55 | h.Div(h.Text(m.String())).Style(style), 56 | h.Button(h.Text("+")).OnClick(rootUpdateFunc, wrapper(m, wrapFunc), Increment), 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /examples/3/counter/counter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package counter 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestView(t *testing.T) { 13 | m := Model(0) 14 | v := m.View(Updater(m), nil) 15 | want := []string{ 16 | "
", "
", 17 | `
0
`, 18 | "-", 19 | "+", 20 | } 21 | got, _ := v.Render() 22 | for _, w := range want { 23 | if !strings.Contains(got, w) { 24 | t.Errorf("View = %q, want %q", got, w) 25 | } 26 | } 27 | } 28 | 29 | func TestIncrement(t *testing.T) { 30 | m := Model(0) 31 | for i := 0; i < 10; i++ { 32 | m = Increment(m) 33 | } 34 | if got, want := int(m), 10; got != want { 35 | t.Errorf("Increment = %v, want %v", got, want) 36 | } 37 | } 38 | 39 | func TestDecrement(t *testing.T) { 40 | m := Model(0) 41 | for i := 0; i < 10; i++ { 42 | m = Decrement(m) 43 | } 44 | if got, want := int(m), -10; got != want { 45 | t.Errorf("Decrement = %v, want %v", got, want) 46 | } 47 | } 48 | 49 | func TestUpdate(t *testing.T) { 50 | m := Model(0) 51 | m = m.Update(Increment) 52 | if got, want := int(m), 1; got != want { 53 | t.Errorf("Increment = %v, want %v", got, want) 54 | } 55 | m = m.Update(Decrement) 56 | if got, want := int(m), 0; got != want { 57 | t.Errorf("Decrement = %v, want %v", got, want) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/3/counterlist/counterlist.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package counterlist is an example using go-frp modeled after the Elm example found at: 6 | // https://github.com/evancz/elm-architecture-tutorial/blob/master/examples/3/CounterList.elm 7 | package counterlist 8 | 9 | import ( 10 | c "github.com/gmlewis/go-frp/v2/examples/3/counter" 11 | h "github.com/gmlewis/go-frp/v2/html" 12 | ) 13 | 14 | // MODEL 15 | 16 | type Model struct { 17 | counters []Counter 18 | nextID ID 19 | } 20 | 21 | type ID int 22 | 23 | type Counter struct { 24 | id ID 25 | counter c.Model 26 | } 27 | 28 | func Init(values ...int) Model { 29 | m := Model{nextID: ID(len(values))} 30 | for id, value := range values { 31 | m.counters = append(m.counters, Counter{id: ID(id), counter: c.Model(value)}) 32 | } 33 | return m 34 | } 35 | 36 | // UPDATE 37 | 38 | type Action func(Model) Model 39 | 40 | func Updater(model Model) func(action Action) Model { 41 | return func(action Action) Model { return model.Update(action) } 42 | } 43 | func (m Model) Update(action Action) Model { return action(m) } 44 | 45 | func Remove(model Model) Model { 46 | var counters []Counter 47 | var nextID ID 48 | if len(model.counters) > 1 { 49 | counters = model.counters[:len(model.counters)-1] 50 | nextID = model.nextID - 1 51 | } 52 | return Model{ 53 | counters: counters, 54 | nextID: nextID, 55 | } 56 | } 57 | 58 | func Insert(model Model) Model { 59 | counters := model.counters[:] // Copy counters 60 | return Model{ 61 | counters: append(counters, Counter{id: model.nextID, counter: c.Model(0)}), 62 | nextID: model.nextID + 1, 63 | } 64 | } 65 | 66 | func cWrapper(model Model, id int) c.WrapFunc { 67 | return func(cm c.Model) interface{} { 68 | var counters []Counter 69 | for i := 0; i < len(model.counters); i++ { 70 | if i == id { 71 | counters = append(counters, Counter{id: ID(id), counter: cm}) 72 | continue 73 | } 74 | counters = append(counters, model.counters[i]) 75 | } 76 | return Model{ 77 | counters: counters, 78 | nextID: model.nextID, 79 | } 80 | } 81 | } 82 | 83 | func wrapper(model Model) func(action Action) interface{} { 84 | return func(action Action) interface{} { 85 | return model.Update(action) 86 | } 87 | } 88 | 89 | // VIEW 90 | 91 | func (m Model) View(rootUpdateFunc, wrapFunc interface{}) h.HTML { 92 | remove := h.Button(h.Text("Remove")).OnClick(rootUpdateFunc, wrapper(m), Remove) 93 | insert := h.Button(h.Text("Add")).OnClick(rootUpdateFunc, wrapper(m), Insert) 94 | p := []h.HTML{remove, insert} 95 | for id, counter := range m.counters { 96 | p = append(p, counter.counter.View(rootUpdateFunc, cWrapper(m, id))) 97 | } 98 | return h.Div(p...) 99 | } 100 | -------------------------------------------------------------------------------- /examples/3/counterlist/counterlist_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package counterlist 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestView(t *testing.T) { 13 | m := Init(1, 2, 3) 14 | v := m.View(Updater(m), nil) 15 | want := []string{ 16 | "
", "
", 17 | `
1
`, 18 | `
2
`, 19 | `
3
`, 20 | "-", 21 | "+", 22 | "Remove", 23 | "Add", 24 | } 25 | got, _ := v.Render() 26 | for _, w := range want { 27 | if !strings.Contains(got, w) { 28 | t.Errorf("View = %q, want %q", got, w) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Counter Example 3 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/clearfield/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/gmlewis/go-frp/v2/examples/clearfield/clearfield" 9 | "github.com/gmlewis/go-frp/v2/start" 10 | ) 11 | 12 | func main() { 13 | m := clearfield.Model("Hello world!") 14 | start.Start(m, clearfield.Updater(m)) 15 | } 16 | -------------------------------------------------------------------------------- /examples/clearfield/clearfield/clearfield.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package clearfield is an example using go-frp modeled after the example found in 6 | // the book "Functional Reactive Programming" by Stephen Blackheath and Anthony Jones: 7 | // http://www.manning.com/books/functional-reactive-programming 8 | package clearfield 9 | 10 | import h "github.com/gmlewis/go-frp/v2/html" 11 | 12 | // MODEL 13 | 14 | type Model string 15 | 16 | // UPDATE 17 | 18 | type Action func(Model) Model 19 | 20 | func Updater(model Model) func(action Action) Model { 21 | return func(action Action) Model { return model.Update(action) } 22 | } 23 | func (m Model) Update(action Action) Model { return action(m) } 24 | 25 | func Clear(model Model) Model { return Model("") } 26 | 27 | // VIEW 28 | 29 | func (m Model) View(rootUpdateFunc, wrapFunc interface{}) h.HTML { 30 | return h.Div( 31 | h.Input(string(m)), 32 | h.Button(h.Text("Clear")).OnClick(rootUpdateFunc, Updater(m), Clear), 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /examples/clearfield/clearfield/clearfield_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package clearfield 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestView(t *testing.T) { 13 | m := Model("") 14 | v := m.View(Updater(m), nil) 15 | want := []string{ 16 | "
", "
", 17 | ``, 18 | "Clear", 19 | } 20 | got, _ := v.Render() 21 | for _, w := range want { 22 | if !strings.Contains(got, w) { 23 | t.Errorf("View = %q, want %q", got, w) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/clearfield/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Functional Reactive Programming Book Clearfield Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/inception/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/gmlewis/go-frp/v2/examples/inception/counterpairpair" 9 | "github.com/gmlewis/go-frp/v2/start" 10 | ) 11 | 12 | func main() { 13 | m := counterpairpair.Init(1, 2, 3, 4) 14 | start.Start(m, counterpairpair.Updater(m)) 15 | } 16 | -------------------------------------------------------------------------------- /examples/inception/counter/counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package counter is an example using go-frp modeled after the Elm example found at: 6 | // https://github.com/evancz/elm-architecture-tutorial/blob/master/examples/inception/Counter.elm 7 | package counter 8 | 9 | import ( 10 | "fmt" 11 | 12 | h "github.com/gmlewis/go-frp/v2/html" 13 | ) 14 | 15 | // MODEL 16 | 17 | type Model int 18 | 19 | func (m Model) String() string { return fmt.Sprintf("%v", int(m)) } 20 | func Init(count int) Model { return Model(count) } 21 | 22 | // UPDATE 23 | 24 | type Action func(Model) Model 25 | 26 | func Updater(model Model) func(action Action) Model { 27 | return func(action Action) Model { return model.Update(action) } 28 | } 29 | func (m Model) Update(action Action) Model { return action(m) } 30 | 31 | func Increment(model Model) Model { return model + 1 } 32 | func Decrement(model Model) Model { return model - 1 } 33 | 34 | type WrapFunc func(model Model) interface{} 35 | 36 | func wrapper(model Model, wrapFunc WrapFunc) func(action Action) interface{} { 37 | return func(action Action) interface{} { 38 | newModel := model.Update(action) 39 | return wrapFunc(newModel) 40 | } 41 | } 42 | 43 | // VIEW 44 | 45 | func (m Model) View(rootUpdateFunc interface{}, wrapFunc WrapFunc) h.HTML { 46 | style := [][]string{ 47 | {"font-size", "20px"}, 48 | {"font-family", "monospace"}, 49 | {"display", "inline-block"}, 50 | {"width", "50px"}, 51 | {"text-align", "center"}, 52 | } 53 | return h.Div( 54 | h.Button(h.Text("-")).OnClick(rootUpdateFunc, wrapper(m, wrapFunc), Decrement), 55 | h.Div(h.Text(m.String())).Style(style), 56 | h.Button(h.Text("+")).OnClick(rootUpdateFunc, wrapper(m, wrapFunc), Increment), 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /examples/inception/counter/counter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package counter 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestView(t *testing.T) { 13 | m := Model(0) 14 | v := m.View(Updater(m), nil) 15 | want := []string{ 16 | "
", "
", 17 | `
0
`, 18 | "-", 19 | "+", 20 | } 21 | got, _ := v.Render() 22 | for _, w := range want { 23 | if !strings.Contains(got, w) { 24 | t.Errorf("View = %q, want %q", got, w) 25 | } 26 | } 27 | } 28 | 29 | func TestIncrement(t *testing.T) { 30 | m := Model(0) 31 | for i := 0; i < 10; i++ { 32 | m = Increment(m) 33 | } 34 | if got, want := int(m), 10; got != want { 35 | t.Errorf("Increment = %v, want %v", got, want) 36 | } 37 | } 38 | 39 | func TestDecrement(t *testing.T) { 40 | m := Model(0) 41 | for i := 0; i < 10; i++ { 42 | m = Decrement(m) 43 | } 44 | if got, want := int(m), -10; got != want { 45 | t.Errorf("Decrement = %v, want %v", got, want) 46 | } 47 | } 48 | 49 | func TestUpdate(t *testing.T) { 50 | m := Model(0) 51 | m = m.Update(Increment) 52 | if got, want := int(m), 1; got != want { 53 | t.Errorf("Increment = %v, want %v", got, want) 54 | } 55 | m = m.Update(Decrement) 56 | if got, want := int(m), 0; got != want { 57 | t.Errorf("Decrement = %v, want %v", got, want) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/inception/counterpair/counterpair.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package counterpair is an example using go-frp modeled after the Elm example found at: 6 | // https://github.com/evancz/elm-architecture-tutorial/blob/master/examples/inception/CounterPair.elm 7 | package counterpair 8 | 9 | import ( 10 | "github.com/gmlewis/go-frp/v2/examples/inception/counter" 11 | h "github.com/gmlewis/go-frp/v2/html" 12 | ) 13 | 14 | // MODEL 15 | 16 | type Model struct { 17 | top counter.Model 18 | bottom counter.Model 19 | } 20 | 21 | func Init(top, bottom int) Model { 22 | return Model{ 23 | top: counter.Init(top), 24 | bottom: counter.Init(bottom), 25 | } 26 | } 27 | 28 | func (m Model) Top() int { return int(m.top) } 29 | func (m Model) Bottom() int { return int(m.bottom) } 30 | 31 | // UPDATE 32 | 33 | type Action func(Model) Model 34 | 35 | func Updater(model Model) func(action Action) Model { 36 | return func(action Action) Model { return model.Update(action) } 37 | } 38 | func (m Model) Update(action Action) Model { return action(m) } 39 | 40 | func IncrementTop(model Model) Model { return Top(model)(counter.Increment) } 41 | func IncrementBottom(model Model) Model { return Bottom(model)(counter.Increment) } 42 | func DecrementTop(model Model) Model { return Top(model)(counter.Decrement) } 43 | func DecrementBottom(model Model) Model { return Bottom(model)(counter.Decrement) } 44 | func Reset(model Model) Model { return Init(0, 0) } 45 | 46 | type CounterAction func(counter.Action) Model 47 | 48 | func Top(model Model) CounterAction { 49 | return func(action counter.Action) Model { 50 | return Model{ 51 | top: model.top.Update(action), 52 | bottom: model.bottom, 53 | } 54 | } 55 | } 56 | 57 | func Bottom(model Model) CounterAction { 58 | return func(action counter.Action) Model { 59 | return Model{ 60 | top: model.top, 61 | bottom: model.bottom.Update(action), 62 | } 63 | } 64 | } 65 | 66 | func AdjustBy(top, bottom int) func(model Model) Model { 67 | return func(model Model) Model { 68 | return Model{ 69 | top: counter.Init(int(model.top) + top), 70 | bottom: counter.Init(int(model.bottom) + bottom), 71 | } 72 | } 73 | } 74 | 75 | type WrapFunc func(model Model) interface{} 76 | 77 | func wrapper(model Model, wrapFunc WrapFunc) func(action Action) interface{} { 78 | return func(action Action) interface{} { 79 | newModel := model.Update(action) 80 | return wrapFunc(newModel) 81 | } 82 | } 83 | 84 | func topWrapper(model Model, wrapFunc WrapFunc) counter.WrapFunc { 85 | return func(cm counter.Model) interface{} { 86 | newModel := Model{ 87 | top: cm, 88 | bottom: model.bottom, 89 | } 90 | return wrapFunc(newModel) 91 | } 92 | } 93 | 94 | func bottomWrapper(model Model, wrapFunc WrapFunc) counter.WrapFunc { 95 | return func(cm counter.Model) interface{} { 96 | newModel := Model{ 97 | top: model.top, 98 | bottom: cm, 99 | } 100 | return wrapFunc(newModel) 101 | } 102 | } 103 | 104 | // VIEW 105 | 106 | func (m Model) View(rootUpdateFunc interface{}, wrapFunc WrapFunc) h.HTML { 107 | return h.Div( 108 | m.top.View(rootUpdateFunc, topWrapper(m, wrapFunc)), 109 | m.bottom.View(rootUpdateFunc, bottomWrapper(m, wrapFunc)), 110 | h.Button(h.Text("Reset")).OnClick(rootUpdateFunc, wrapper(m, wrapFunc), Reset), 111 | ) 112 | } 113 | -------------------------------------------------------------------------------- /examples/inception/counterpair/counterpair_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package counterpair 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | 11 | "github.com/gmlewis/go-frp/v2/examples/inception/counter" 12 | ) 13 | 14 | func TestView(t *testing.T) { 15 | m := Init(1, 2) 16 | v := m.View(Updater(m), nil) 17 | want := []string{ 18 | "
", "
", 19 | `
1
`, 20 | `
2
`, 21 | "-", 22 | "+", 23 | "Reset", 24 | } 25 | got, _ := v.Render() 26 | for _, w := range want { 27 | if !strings.Contains(got, w) { 28 | t.Errorf("View = %q, want %q", got, w) 29 | } 30 | } 31 | } 32 | 33 | func TestCounterActions(t *testing.T) { 34 | const ( 35 | top = 10 36 | bottom = 100 37 | count = 5 38 | ) 39 | tests := []struct { 40 | actionTop, actionBottom counter.Action 41 | wantTop, wantBottom int 42 | }{ 43 | {counter.Increment, nil, 15, 100}, 44 | {nil, counter.Increment, 10, 105}, 45 | {counter.Increment, counter.Increment, 15, 105}, 46 | {counter.Decrement, nil, 5, 100}, 47 | {nil, counter.Decrement, 10, 95}, 48 | {counter.Decrement, counter.Decrement, 5, 95}, 49 | {counter.Increment, counter.Decrement, 15, 95}, 50 | {counter.Decrement, counter.Increment, 5, 105}, 51 | } 52 | for num, test := range tests { 53 | m := Init(top, bottom) 54 | for i := 0; i < count; i++ { 55 | if test.actionTop != nil { 56 | m = Top(m)(test.actionTop) 57 | } 58 | if test.actionBottom != nil { 59 | m = Bottom(m)(test.actionBottom) 60 | } 61 | } 62 | if gotTop := int(m.top); gotTop != test.wantTop { 63 | t.Errorf("test #%v: Top = %v, want %v", num, gotTop, test.wantTop) 64 | } 65 | if gotBottom := int(m.bottom); gotBottom != test.wantBottom { 66 | t.Errorf("test #%v: Bottom = %v, want %v", num, gotBottom, test.wantBottom) 67 | } 68 | } 69 | } 70 | 71 | func TestActions(t *testing.T) { 72 | const ( 73 | top = 10 74 | bottom = 100 75 | count = 5 76 | ) 77 | tests := []struct { 78 | action Action 79 | wantTop, wantBottom int 80 | }{ 81 | {IncrementTop, 15, 100}, 82 | {IncrementBottom, 10, 105}, 83 | {AdjustBy(1, 1), 15, 105}, 84 | {DecrementTop, 5, 100}, 85 | {DecrementBottom, 10, 95}, 86 | {AdjustBy(-1, -1), 5, 95}, 87 | {AdjustBy(1, -1), 15, 95}, 88 | {AdjustBy(-1, 1), 5, 105}, 89 | } 90 | for num, test := range tests { 91 | m := Init(top, bottom) 92 | for i := 0; i < count; i++ { 93 | m = Updater(m)(test.action) 94 | } 95 | if gotTop := int(m.top); gotTop != test.wantTop { 96 | t.Errorf("test #%v: Top = %v, want %v", num, gotTop, test.wantTop) 97 | } 98 | if gotBottom := int(m.bottom); gotBottom != test.wantBottom { 99 | t.Errorf("test #%v: Bottom = %v, want %v", num, gotBottom, test.wantBottom) 100 | } 101 | } 102 | } 103 | 104 | func TestReset(t *testing.T) { 105 | m := Init(10, 100) 106 | m = Updater(m)(Reset) 107 | if got, want := int(m.top), 0; got != want { 108 | t.Errorf("Top: Reset = %v, want %v", got, want) 109 | } 110 | if got, want := int(m.bottom), 0; got != want { 111 | t.Errorf("Bottom: Reset = %v, want %v", got, want) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /examples/inception/counterpairpair/counterpairpair.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package counterpairpair is an example using go-frp and two counterpairs. 6 | package counterpairpair 7 | 8 | import ( 9 | "math/rand" 10 | 11 | "github.com/gmlewis/go-frp/v2/examples/inception/counterpair" 12 | h "github.com/gmlewis/go-frp/v2/html" 13 | ) 14 | 15 | const max = 100 16 | 17 | // MODEL 18 | 19 | type Model struct { 20 | first counterpair.Model 21 | last counterpair.Model 22 | } 23 | 24 | func Init(firstTop, firstBottom, lastTop, lastBottom int) Model { 25 | return Model{ 26 | first: counterpair.Init(firstTop, firstBottom), 27 | last: counterpair.Init(lastTop, lastBottom), 28 | } 29 | } 30 | 31 | // UPDATE 32 | 33 | type Action func(Model) Model 34 | 35 | func Updater(model Model) func(action Action) Model { 36 | return func(action Action) Model { return model.Update(action) } 37 | } 38 | func (m Model) Update(action Action) Model { return action(m) } 39 | 40 | func ResetAll(model Model) Model { return Init(0, 0, 0, 0) } 41 | 42 | func RandomizeAll(model Model) Model { 43 | return Init(rand.Intn(max), rand.Intn(max), rand.Intn(max), rand.Intn(max)) 44 | } 45 | 46 | type CounterPairAction func(counterpair.Action) Model 47 | 48 | func First(model Model) CounterPairAction { 49 | return func(action counterpair.Action) Model { 50 | return Model{ 51 | first: model.first.Update(action), 52 | last: model.last, 53 | } 54 | } 55 | } 56 | 57 | func Last(model Model) CounterPairAction { 58 | return func(action counterpair.Action) Model { 59 | return Model{ 60 | first: model.first, 61 | last: model.last.Update(action), 62 | } 63 | } 64 | } 65 | 66 | type WrapFunc func(model Model) interface{} 67 | 68 | func identity(model Model) interface{} { 69 | return model 70 | } 71 | 72 | func wrapper(model Model, wrapFunc WrapFunc) func(action Action) interface{} { 73 | return func(action Action) interface{} { 74 | newModel := model.Update(action) 75 | return wrapFunc(newModel) 76 | } 77 | } 78 | 79 | func firstWrapper(model Model, wrapFunc WrapFunc) counterpair.WrapFunc { 80 | return func(cm counterpair.Model) interface{} { 81 | newModel := Model{ 82 | first: cm, 83 | last: model.last, 84 | } 85 | return wrapFunc(newModel) 86 | } 87 | } 88 | 89 | func lastWrapper(model Model, wrapFunc WrapFunc) counterpair.WrapFunc { 90 | return func(cm counterpair.Model) interface{} { 91 | newModel := Model{ 92 | first: model.first, 93 | last: cm, 94 | } 95 | return wrapFunc(newModel) 96 | } 97 | } 98 | 99 | // VIEW 100 | 101 | func (m Model) View(rootUpdateFunc, wrapFunc interface{}) h.HTML { 102 | var wf WrapFunc 103 | if wrapFunc == nil { 104 | wf = identity 105 | } 106 | return h.Div( 107 | m.first.View(rootUpdateFunc, firstWrapper(m, wf)), 108 | m.last.View(rootUpdateFunc, lastWrapper(m, wf)), 109 | h.Button(h.Text("Reset All")).OnClick(rootUpdateFunc, wrapper(m, wf), ResetAll), 110 | h.Button(h.Text("Randomize All")).OnClick(rootUpdateFunc, wrapper(m, wf), RandomizeAll), 111 | ) 112 | } 113 | -------------------------------------------------------------------------------- /examples/inception/counterpairpair/counterpairpair_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package counterpairpair 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | 11 | "github.com/gmlewis/go-frp/v2/examples/inception/counterpair" 12 | ) 13 | 14 | func TestView(t *testing.T) { 15 | m := Init(1, 2, 3, 4) 16 | v := m.View(Updater(m), nil) 17 | want := []string{ 18 | "
", "
", 19 | `
1
`, 20 | `
2
`, 21 | "-", 22 | "+", 23 | "Reset", 24 | } 25 | got, _ := v.Render() 26 | for _, w := range want { 27 | if !strings.Contains(got, w) { 28 | t.Errorf("View = %q, want %q", got, w) 29 | } 30 | } 31 | } 32 | 33 | func TestCounterPairActions(t *testing.T) { 34 | const ( 35 | firstTop = 10 36 | firstBottom = 100 37 | lastTop = 50 38 | lastBottom = 500 39 | count = 5 40 | ) 41 | tests := []struct { 42 | actionFirst, actionLast counterpair.Action 43 | wantFirstTop, wantFirstBottom int 44 | wantLastTop, wantLastBottom int 45 | }{ 46 | {counterpair.IncrementTop, nil, 15, 100, 50, 500}, 47 | {nil, counterpair.IncrementTop, 10, 100, 55, 500}, 48 | {counterpair.IncrementTop, counterpair.IncrementTop, 15, 100, 55, 500}, 49 | {counterpair.DecrementTop, nil, 5, 100, 50, 500}, 50 | {nil, counterpair.DecrementTop, 10, 100, 45, 500}, 51 | {counterpair.DecrementTop, counterpair.DecrementTop, 5, 100, 45, 500}, 52 | {counterpair.IncrementTop, counterpair.DecrementTop, 15, 100, 45, 500}, 53 | {counterpair.DecrementTop, counterpair.IncrementTop, 5, 100, 55, 500}, 54 | {counterpair.IncrementBottom, nil, 10, 105, 50, 500}, 55 | {nil, counterpair.IncrementBottom, 10, 100, 50, 505}, 56 | {counterpair.IncrementBottom, counterpair.IncrementBottom, 10, 105, 50, 505}, 57 | {counterpair.DecrementBottom, nil, 10, 95, 50, 500}, 58 | {nil, counterpair.DecrementBottom, 10, 100, 50, 495}, 59 | {counterpair.DecrementBottom, counterpair.DecrementBottom, 10, 95, 50, 495}, 60 | {counterpair.IncrementBottom, counterpair.DecrementBottom, 10, 105, 50, 495}, 61 | {counterpair.DecrementBottom, counterpair.IncrementBottom, 10, 95, 50, 505}, 62 | } 63 | for num, test := range tests { 64 | m := Init(firstTop, firstBottom, lastTop, lastBottom) 65 | for i := 0; i < count; i++ { 66 | if test.actionFirst != nil { 67 | m = First(m)(test.actionFirst) 68 | } 69 | if test.actionLast != nil { 70 | m = Last(m)(test.actionLast) 71 | } 72 | } 73 | if gotFirstTop := int(m.first.Top()); gotFirstTop != test.wantFirstTop { 74 | t.Errorf("test #%v: FirstTop = %v, want %v", num, gotFirstTop, test.wantFirstTop) 75 | } 76 | if gotFirstBottom := int(m.first.Bottom()); gotFirstBottom != test.wantFirstBottom { 77 | t.Errorf("test #%v: FirstBottom = %v, want %v", num, gotFirstBottom, test.wantFirstBottom) 78 | } 79 | if gotLastTop := int(m.last.Top()); gotLastTop != test.wantLastTop { 80 | t.Errorf("test #%v: LastTop = %v, want %v", num, gotLastTop, test.wantLastTop) 81 | } 82 | if gotLastBottom := int(m.last.Bottom()); gotLastBottom != test.wantLastBottom { 83 | t.Errorf("test #%v: LastBottom = %v, want %v", num, gotLastBottom, test.wantLastBottom) 84 | } 85 | } 86 | } 87 | 88 | func TestResetAll(t *testing.T) { 89 | m := Init(10, 100, 50, 500) 90 | m = Updater(m)(ResetAll) 91 | if got, want := int(m.first.Top()), 0; got != want { 92 | t.Errorf("FirstTop: ResetAll = %v, want %v", got, want) 93 | } 94 | if got, want := int(m.first.Bottom()), 0; got != want { 95 | t.Errorf("FirstBottom: ResetAll = %v, want %v", got, want) 96 | } 97 | if got, want := int(m.last.Top()), 0; got != want { 98 | t.Errorf("LastTop: ResetAll = %v, want %v", got, want) 99 | } 100 | if got, want := int(m.last.Bottom()), 0; got != want { 101 | t.Errorf("LastBottom: ResetAll = %v, want %v", got, want) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/inception/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Inception Counter Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/label/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/gmlewis/go-frp/v2/examples/label/label" 9 | "github.com/gmlewis/go-frp/v2/start" 10 | ) 11 | 12 | func main() { 13 | m := label.Model("Hello world!") 14 | start.Start(m, label.Updater(m)) 15 | } 16 | -------------------------------------------------------------------------------- /examples/label/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Functional Reactive Programming Book Label Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/label/label/label.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package label is an example using go-frp modeled after the example found in 6 | // the book "Functional Reactive Programming" by Stephen Blackheath and Anthony Jones: 7 | // http://www.manning.com/books/functional-reactive-programming 8 | package label 9 | 10 | import ( 11 | h "github.com/gmlewis/go-frp/v2/html" 12 | "honnef.co/go/js/dom" 13 | ) 14 | 15 | // MODEL 16 | 17 | type Model string 18 | 19 | func (m Model) String() string { return string(m) } 20 | 21 | // UPDATE 22 | 23 | type Action func(Model, dom.Event) Model 24 | 25 | func Updater(model Model) func(Action, dom.Event) Model { 26 | return func(action Action, event dom.Event) Model { return model.Update(action, event) } 27 | } 28 | func (m Model) Update(action Action, event dom.Event) Model { return action(m, event) } 29 | 30 | func Keypress(model Model, event dom.Event) Model { 31 | if t, ok := event.Target().(*dom.HTMLInputElement); ok { 32 | // TODO: save/restore focus and cursor position 33 | return Model(t.Value) 34 | } 35 | return model 36 | } 37 | 38 | // VIEW 39 | 40 | func (m Model) View(rootUpdateFunc, wrapFunc interface{}) h.HTML { 41 | return h.Div( 42 | h.Input(string(m)).OnKeypress(rootUpdateFunc, Updater(m), Keypress), 43 | h.Label(string(m)), 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /examples/label/label/label_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package label 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestView(t *testing.T) { 13 | m := Model("test string") 14 | v := m.View(Updater(m), nil) 15 | want := []string{ 16 | "
", "
", 17 | ``, 18 | "", 19 | } 20 | got, _ := v.Render() 21 | for _, w := range want { 22 | if !strings.Contains(got, w) { 23 | t.Errorf("View = %q, want %q", got, w) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/reverse/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "github.com/gmlewis/go-frp/v2/examples/reverse/reverse" 9 | "github.com/gmlewis/go-frp/v2/start" 10 | ) 11 | 12 | func main() { 13 | m := reverse.Model("Hello world!") 14 | start.Start(m, reverse.Updater(m)) 15 | } 16 | -------------------------------------------------------------------------------- /examples/reverse/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Functional Reactive Programming Book Reverse Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/reverse/reverse/reverse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package reverse is an example using go-frp modeled after the example found in 6 | // the book "Functional Reactive Programming" by Stephen Blackheath and Anthony Jones: 7 | // http://www.manning.com/books/functional-reactive-programming 8 | package reverse 9 | 10 | import ( 11 | h "github.com/gmlewis/go-frp/v2/html" 12 | "honnef.co/go/js/dom" 13 | ) 14 | 15 | // MODEL 16 | 17 | type Model string 18 | 19 | func (m Model) String() string { return string(m) } 20 | 21 | // UPDATE 22 | 23 | type Action func(Model, dom.Event) Model 24 | 25 | func Updater(model Model) func(Action, dom.Event) Model { 26 | return func(action Action, event dom.Event) Model { return model.Update(action, event) } 27 | } 28 | func (m Model) Update(action Action, event dom.Event) Model { return action(m, event) } 29 | 30 | func Keypress(model Model, event dom.Event) Model { 31 | if t, ok := event.Target().(*dom.HTMLInputElement); ok { 32 | // TODO: save/restore focus and cursor position 33 | return Model(t.Value) 34 | } 35 | return model 36 | } 37 | 38 | // VIEW 39 | 40 | func (m Model) View(rootUpdateFunc, wrapFunc interface{}) h.HTML { 41 | return h.Div( 42 | h.Input(string(m)).OnKeypress(rootUpdateFunc, Updater(m), Keypress), 43 | h.Label(reverse(string(m))), 44 | ) 45 | } 46 | 47 | // Reverse reverses a string. See: 48 | // http://stackoverflow.com/questions/1752414/how-to-reverse-a-string-in-go 49 | func reverse(input string) string { 50 | // Get Unicode code points. 51 | n := 0 52 | rune := make([]rune, len(input)) 53 | for _, r := range input { 54 | rune[n] = r 55 | n++ 56 | } 57 | rune = rune[0:n] 58 | // Reverse 59 | for i := 0; i < n/2; i++ { 60 | rune[i], rune[n-1-i] = rune[n-1-i], rune[i] 61 | } 62 | // Convert back to UTF-8. 63 | return string(rune) 64 | } 65 | -------------------------------------------------------------------------------- /examples/reverse/reverse/reverse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | package reverse 6 | 7 | import ( 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestView(t *testing.T) { 13 | m := Model("test string") 14 | v := m.View(Updater(m), nil) 15 | want := []string{ 16 | "
", "
", 17 | ``, 18 | "", 19 | } 20 | got, _ := v.Render() 21 | for _, w := range want { 22 | if !strings.Contains(got, w) { 23 | t.Errorf("View = %q, want %q", got, w) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gmlewis/go-frp/v2 2 | 3 | require ( 4 | github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f // indirect 5 | honnef.co/go/js/dom v0.0.0-20190403051653-d6d651dc5aea 6 | ) 7 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gmlewis/go-frp v1.0.0 h1:Xhjxj6gNV2vFmcaOvtgoFbSGl7XmUQtOtMntgYCJEj8= 2 | github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= 3 | github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 4 | github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f h1:4Gslotqbs16iAg+1KR/XdabIfq8TlAWHdwS5QJFksLc= 5 | github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 6 | honnef.co/go/js/dom v0.0.0-20180323154144-6da835bec70f h1:8wvPTUK0UjW0bwvb0Q2mdhzSf78P6dXQI6mE9v+jJvI= 7 | honnef.co/go/js/dom v0.0.0-20180323154144-6da835bec70f/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= 8 | honnef.co/go/js/dom v0.0.0-20190403051653-d6d651dc5aea h1:l6lyoxaYVdN7ef9QLiRuDMKFTX8a4QtrRg7rf8nmlb0= 9 | honnef.co/go/js/dom v0.0.0-20190403051653-d6d651dc5aea/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= 10 | -------------------------------------------------------------------------------- /html/html.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All rights reserved. 2 | // Use of this source code is governed by the Apache 2.0 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package html provides functions to build up the DOM of an app. 6 | package html 7 | 8 | import ( 9 | "fmt" 10 | "html/template" 11 | "log" 12 | "reflect" 13 | "strconv" 14 | 15 | "honnef.co/go/js/dom" 16 | ) 17 | 18 | // domNode is the ID of the root
to write the generated DOM. 19 | const domNode = "go-frp" 20 | 21 | var nextID int // Keep all HTML IDs unique. 22 | 23 | // A Viewer implements a View that renders to HTML. 24 | type Viewer interface { 25 | View(rootUpdateFunc, updateFunc interface{}) HTML 26 | } 27 | 28 | // Render renders a view and writes it to the DOM. 29 | func Render(view Viewer, rootUpdateFunc interface{}) { 30 | // log.Printf("GML: html.Render(view=%#v, rootUpdateFunc=%#v)", view, rootUpdateFunc) 31 | v := view.View(rootUpdateFunc, nil) 32 | str, initFuncs := v.Render() 33 | dom.GetWindow().Document().GetElementByID(domNode).SetInnerHTML(str) 34 | for _, initFunc := range initFuncs { 35 | initFunc() 36 | } 37 | } 38 | 39 | // HTML defines an HTML element. 40 | type HTML struct { 41 | tag string 42 | id string 43 | props [][]string 44 | styles [][]string 45 | body string 46 | elems []HTML 47 | initFuncs []func() 48 | } 49 | 50 | // Render renders the HTML element into its string representation. 51 | // It also surfaces all initFuncs to the top and returns them. 52 | func (s HTML) Render() (string, []func()) { 53 | var result string 54 | var initFuncs []func() 55 | if s.tag != "" { 56 | result = "<" + s.tag 57 | for _, v := range s.props { 58 | result += fmt.Sprintf(" %s=%q", v[0], template.HTMLEscapeString(v[1])) 59 | } 60 | var styles string 61 | for _, v := range s.styles { 62 | styles += fmt.Sprintf("%s:%s;", v[0], template.HTMLEscapeString(v[1])) 63 | } 64 | if styles != "" { 65 | result += fmt.Sprintf(" style=%q", styles) 66 | } 67 | if s.body == "" && len(s.elems) == 0 { 68 | result += "/" 69 | } 70 | result += ">" 71 | } 72 | for _, v := range s.elems { 73 | str, ifs := v.Render() 74 | result += str 75 | initFuncs = append(initFuncs, ifs...) 76 | } 77 | initFuncs = append(initFuncs, s.initFuncs...) 78 | result += template.HTMLEscapeString(s.body) 79 | if s.tag != "" && !(s.body == "" && len(s.elems) == 0) { 80 | result += "" 81 | } 82 | return result, initFuncs 83 | } 84 | 85 | // Props adds a slice of properties to an HTML element. 86 | func (s HTML) Props(props [][]string) HTML { 87 | s.props = props 88 | return s 89 | } 90 | 91 | // Style adds a slice of inline CSS styles to an HTML element. 92 | func (s HTML) Style(styles [][]string) HTML { 93 | s.styles = styles 94 | return s 95 | } 96 | 97 | // ID returns the current HTML ID of this element (assigning if necessary). 98 | func (s *HTML) ID() string { 99 | if s.id != "" { 100 | return s.id 101 | } 102 | // Check the properties to see if the user created an ID for the element. 103 | for _, p := range s.props { 104 | if p[0] == "id" || p[0] == "ID" { 105 | s.id = p[1] 106 | return s.id 107 | } 108 | } 109 | // Use the next-available numeric ID and bump it. 110 | s.id = strconv.Itoa(nextID) 111 | s.props = append(s.props, []string{"id", s.id}) 112 | nextID++ 113 | return s.id 114 | } 115 | 116 | // On adds an event handler to an HTML element. 117 | func (s HTML) On(event string, rootUpdateFunc, updateFunc, actionFunc interface{}) HTML { 118 | id := s.ID() 119 | s.initFuncs = append(s.initFuncs, func() { 120 | el := dom.GetWindow().Document().GetElementByID(id) 121 | if el == nil { 122 | log.Printf("unable to find DOM element id=%q", id) 123 | return 124 | } 125 | el.AddEventListener(event, false, func(de dom.Event) { 126 | go func() { 127 | u := reflect.ValueOf(updateFunc) 128 | // log.Printf("GML: updateFunc=%#v, u=%#v", updateFunc, u) 129 | a := reflect.ValueOf(actionFunc) 130 | // log.Printf("GML: actionFunc=%#v, a=%#v, a.Type=%q", actionFunc, a, a.Type()) 131 | args := []reflect.Value{a} 132 | if reflect.TypeOf(updateFunc).NumIn() == 2 { 133 | var e interface{} = de 134 | args = append(args, reflect.ValueOf(e)) 135 | } 136 | newModel := u.Call(args) 137 | // log.Printf("GML: in click handler! len(newModel)=%v", len(newModel)) 138 | // log.Printf("GML: in click handler! newModel[0]=%v", newModel[0]) 139 | // log.Printf("GML: in click handler! newModel[0].Type=%v", newModel[0].Type()) 140 | if view, ok := newModel[0].Interface().(Viewer); ok { 141 | Render(view, rootUpdateFunc) 142 | } 143 | }() 144 | }) 145 | }) 146 | return s 147 | } 148 | 149 | // OnClick adds an click handler to an HTML element. 150 | func (s HTML) OnClick(rootUpdateFunc, updateFunc, actionFunc interface{}) HTML { 151 | return s.On("click", rootUpdateFunc, updateFunc, actionFunc) 152 | } 153 | 154 | // OnKeypress adds a keypress handler to an HTML element. 155 | func (s HTML) OnKeypress(rootUpdateFunc, updateFunc, actionFunc interface{}) HTML { 156 | return s.On("keypress", rootUpdateFunc, updateFunc, actionFunc) 157 | } 158 | 159 | // Div creates an HTML
. 160 | func Div(s ...HTML) HTML { 161 | return HTML{tag: "div", elems: s} 162 | } 163 | 164 | // Button creates an HTML