├── .gitignore ├── LICENSE ├── README.md ├── pet ├── js │ ├── index.js │ └── main.go └── pet.go └── user ├── go ├── db │ └── db.go └── main.go ├── js ├── db │ └── db.go ├── index.js └── main.go └── user.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | main.js 3 | main.js.map 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jason Stone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gopherjs-demo 2 | A demo project showing how to use GopherJS to share Go code between Go and JS applications 3 | 4 | # Setup 5 | 6 | Install `gopherjs` as a command line tool 7 | 8 | `go get -u github.com/gopherjs/gopherjs` 9 | 10 | # pet 11 | A simple example of a Go struct being exported for use in JS code. 12 | 13 | ``` 14 | cd pet/js 15 | gopherjs build main.go 16 | node index.js 17 | ``` 18 | 19 | # user 20 | A more complex example of a Go library that could be reused between Go and JS applications. 21 | 22 | Note that, for brevity's sake, the `user` example does not include as much type checking as it should. 23 | For example, `RegisterDBJS(jsdb)` should check that `jsdb` really does include a `Query()` function. 24 | 25 | To test in Go: 26 | 27 | ``` 28 | cd user/go 29 | go run main.go 30 | ``` 31 | 32 | To test in NodeJS: 33 | 34 | ``` 35 | cd user/js 36 | gopherjs build main.go 37 | node index.js 38 | ``` -------------------------------------------------------------------------------- /pet/js/index.js: -------------------------------------------------------------------------------- 1 | // Imports and uses the 'main.js' generated through gopherjs 2 | 'use strict'; 3 | console.log('Starting'); 4 | 5 | console.log('Loading main.js'); 6 | require('./main.js'); 7 | var main = global.pet; 8 | 9 | console.log('Creating a new Pet'); 10 | var pet = main.New('brian'); 11 | console.log(pet.Name()); 12 | 13 | console.log('Changing the pet\'s name'); 14 | pet.SetName('steve'); 15 | console.log(pet.Name()); 16 | 17 | console.log('Done!'); -------------------------------------------------------------------------------- /pet/js/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gopherjs/gopherjs/js" 5 | "github.com/rolaveric/gopherjs-demo/pet" 6 | ) 7 | 8 | func main() { 9 | js.Global.Set("pet", map[string]interface{}{ 10 | "New": pet.New, 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /pet/pet.go: -------------------------------------------------------------------------------- 1 | package pet 2 | 3 | import "github.com/gopherjs/gopherjs/js" 4 | 5 | type Pet struct { 6 | name string 7 | } 8 | 9 | func (p *Pet) Name() string { 10 | return p.name 11 | } 12 | 13 | func (p *Pet) SetName(newName string) { 14 | p.name = newName 15 | } 16 | 17 | func New(name string) *js.Object { 18 | return js.MakeWrapper(&Pet{name}) 19 | } 20 | -------------------------------------------------------------------------------- /user/go/db/db.go: -------------------------------------------------------------------------------- 1 | // Go implementation of user.DB interfaces 2 | package db 3 | 4 | import ( 5 | "github.com/rolaveric/gopherjs-demo/user" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // Hard coded rows 11 | var rows []*DBRow = []*DBRow{ 12 | &DBRow{[]string{"Jane", "1"}}, 13 | &DBRow{[]string{"John", "2"}}, 14 | &DBRow{[]string{"Sarah", "3"}}, 15 | &DBRow{[]string{"Steve", "4"}}, 16 | &DBRow{[]string{"Jess", "5"}}, 17 | } 18 | 19 | // Implements the user.DBRow interface 20 | type DBRow struct { 21 | Columns []string 22 | } 23 | 24 | func (row *DBRow) GetInt(colnum int) int { 25 | i, err := strconv.Atoi(row.Columns[colnum]) 26 | if err != nil { 27 | panic(err) 28 | } 29 | return i 30 | } 31 | 32 | func (row *DBRow) GetString(colnum int) string { 33 | return row.Columns[colnum] 34 | } 35 | 36 | // Implements the user.DBResult interface 37 | type DBResult struct { 38 | Rows []*DBRow 39 | Index int 40 | } 41 | 42 | func (result *DBResult) NextRow() user.DBRow { 43 | v := result.Rows[result.Index] 44 | result.Index++ 45 | return v 46 | } 47 | 48 | func (result *DBResult) RowCount() int { 49 | return len(result.Rows) 50 | } 51 | 52 | // A new type that implements user.DB by using a Javascript object 53 | type DB struct {} 54 | 55 | // Uses the 'Query()' function from the JS object and turns the result into a DBResult 56 | func (db DB) Query(query string, params ...interface{}) user.DBResult { 57 | switch { 58 | case strings.HasPrefix(query, "UPDATE"): 59 | rows[params[1].(int) - 1].Columns[0] = params[0].(string) 60 | case strings.HasPrefix(query, "INSERT"): 61 | rows = append(rows, &DBRow{[]string{params[0].(string), strconv.Itoa(len(rows) + 1)}}) 62 | case query == "SELECT @@IDENTITY": 63 | row := &DBRow{[]string{strconv.Itoa(len(rows))}} 64 | return &DBResult{[]*DBRow{row}, 0} 65 | case strings.Contains(query, "WHERE"): 66 | return &DBResult{[]*DBRow{rows[params[0].(int) - 1]}, 0} 67 | case strings.HasPrefix(query, "SELECT"): 68 | return &DBResult{rows, 0} 69 | } 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /user/go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/rolaveric/gopherjs-demo/user" 5 | "github.com/rolaveric/gopherjs-demo/user/go/db" 6 | "fmt" 7 | ) 8 | 9 | func main() { 10 | fmt.Println("Starting") 11 | 12 | fmt.Println("Registering the DB adapter"); 13 | user.RegisterDB(db.DB{}); 14 | 15 | fmt.Println("Getting all users"); 16 | for _, v := range user.All() { 17 | fmt.Println(v) 18 | } 19 | 20 | fmt.Println("Adding a new user"); 21 | user.New("Richard"); 22 | fmt.Println(user.Get(6)); 23 | 24 | fmt.Println("Update the name for a user"); 25 | u := user.Get(2); 26 | u.Name = "Jason"; 27 | user.Save(u); 28 | for _, v := range user.All() { 29 | fmt.Println(v) 30 | } 31 | 32 | fmt.Println("Done!") 33 | } 34 | -------------------------------------------------------------------------------- /user/js/db/db.go: -------------------------------------------------------------------------------- 1 | // Contains types for implementing the DB, DBResult and DBRow interfaces using JS objects 2 | // Assumes that the JS version of 'Query()' returns a matrix of rows and columns. 3 | package db 4 | 5 | import ( 6 | "github.com/gopherjs/gopherjs/js" 7 | "github.com/rolaveric/gopherjs-demo/user" 8 | ) 9 | 10 | // Implements the user.DBRow interface 11 | type JSDBRow struct { 12 | O *js.Object // An array of column values 13 | } 14 | 15 | func (jsrow *JSDBRow) GetInt(colnum int) int { 16 | return jsrow.O.Index(colnum).Int() 17 | } 18 | 19 | func (jsrow *JSDBRow) GetString(colnum int) string { 20 | return jsrow.O.Index(colnum).String() 21 | } 22 | 23 | // Implements the user.DBResult interface 24 | type JSDBResult struct { 25 | O *js.Object // An array of rows 26 | Index int // The current row index 27 | } 28 | 29 | func (jsresult *JSDBResult) NextRow() user.DBRow { 30 | v := &JSDBRow{jsresult.O.Index(jsresult.Index)} 31 | jsresult.Index++ 32 | return v 33 | } 34 | 35 | func (jsresult *JSDBResult) RowCount() int { 36 | return jsresult.O.Length() 37 | } 38 | 39 | // A new type that implements user.DB by using a Javascript object 40 | type JSDB struct { 41 | O *js.Object 42 | } 43 | 44 | // Uses the 'Query()' function from the JS object and turns the result into a DBResult 45 | func (jsdb JSDB) Query(query string, params ...interface{}) user.DBResult { 46 | return &JSDBResult{jsdb.O.Call("Query", query, params), 0} 47 | } 48 | 49 | -------------------------------------------------------------------------------- /user/js/index.js: -------------------------------------------------------------------------------- 1 | // Imports and uses the 'main.js' generated through gopherjs 2 | 'use strict'; 3 | console.log('Starting'); 4 | 5 | console.log('Loading main.js'); 6 | require('./main.js'); // Attaches methods to a 'user' property on the 'global' object 7 | var main = global.user; 8 | 9 | console.log('Creating a DB adapter'); 10 | var data = [ 11 | ['Jane', 1], 12 | ['John', 2], 13 | ['Sarah', 3], 14 | ['Steve', 4], 15 | ['Jess', 5] 16 | ]; 17 | var db = { 18 | Query: function(query, params) { 19 | // Cheating a bit here... 20 | switch (true) { 21 | case query.indexOf('UPDATE') === 0: 22 | data[params[1] - 1][0] = params[0]; 23 | break; 24 | case query.indexOf('INSERT') === 0: 25 | data.push([params[0], data.length + 1]); 26 | break; 27 | case query === 'SELECT @@IDENTITY': 28 | return [[data.length]]; 29 | break; 30 | case query.indexOf('WHERE') !== -1: 31 | return [data[params[0] - 1]]; 32 | break; 33 | case query.indexOf('SELECT') === 0: 34 | return data; 35 | break; 36 | } 37 | return []; 38 | } 39 | }; 40 | 41 | console.log('Registering the DB adapter'); 42 | main.registerDB(db); 43 | 44 | console.log('Getting all users'); 45 | console.log(main.all()); 46 | 47 | console.log('Adding a new user'); 48 | main.new('Richard'); 49 | console.log(main.get(6)); 50 | 51 | console.log('Update the name for a user'); 52 | var user = main.get(2); 53 | user.Name = 'Jason'; 54 | main.save(user); 55 | console.log(main.all()); 56 | 57 | console.log('Done!'); -------------------------------------------------------------------------------- /user/js/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gopherjs/gopherjs/js" 5 | "github.com/rolaveric/gopherjs-demo/user" 6 | "github.com/rolaveric/gopherjs-demo/user/js/db" 7 | ) 8 | 9 | // Starting point for compiling JS code 10 | func main() { 11 | js.Global.Set("user", map[string]interface{}{ 12 | "registerDB": RegisterDBJS, 13 | "new": user.New, 14 | "get": user.Get, 15 | "all": user.All, 16 | "save": SaveJS, 17 | }) 18 | } 19 | 20 | // Takes a DB adapter written in Javascript and wraps it as a DB interface 21 | func RegisterDBJS(o *js.Object) { 22 | user.RegisterDB(db.JSDB{o}) 23 | } 24 | 25 | // Takes a JS object and wraps it as a User struct 26 | func SaveJS(o *js.Object) { 27 | user.Save(&user.User{o.Get("Name").String(), o.Get("ID").Int()}) 28 | } 29 | -------------------------------------------------------------------------------- /user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | // Interface for a database result row 4 | type DBRow interface { 5 | GetInt(colnum int) int 6 | GetString(colnum int) string 7 | } 8 | 9 | // Interface for a database result 10 | type DBResult interface { 11 | NextRow() DBRow 12 | RowCount() int 13 | } 14 | 15 | // Interface for an object which can be used to make database queries 16 | type DB interface { 17 | Query(query string, params ...interface{}) DBResult 18 | } 19 | 20 | // Private package variable for the registered DB interface 21 | var db DB 22 | 23 | // Method for registering a DB interface 24 | func RegisterDB(newDb DB) { 25 | db = newDb 26 | } 27 | 28 | // User type 29 | type User struct { 30 | Name string 31 | ID int 32 | } 33 | 34 | // Save method for the User type 35 | func Save(u *User) { 36 | db.Query("UPDATE User SET name = ? WHERE id = ?", u.Name, u.ID) 37 | } 38 | 39 | // Function for creating a new User 40 | func New(name string) *User { 41 | db.Query("INSERT INTO User (name) VALUES (?)", name) 42 | id := db.Query("SELECT @@IDENTITY").NextRow().GetInt(0) 43 | return &User{name, id} 44 | } 45 | 46 | // Function for getting a single User 47 | func Get(id int) *User { 48 | result := db.Query("SELECT name FROM User WHERE id = ?", id) 49 | if result.RowCount() == 0 { 50 | return nil 51 | } 52 | name := result.NextRow().GetString(0) 53 | return &User{name, id} 54 | } 55 | 56 | // Function for getting all users 57 | func All() []*User { 58 | result := db.Query("SELECT name, id FROM User") 59 | users := make([]*User, result.RowCount()) 60 | for x, c := 0, result.RowCount(); x < c; x++ { 61 | row := result.NextRow() 62 | users[x] = &User{row.GetString(0), row.GetInt(1)} 63 | } 64 | return users 65 | } 66 | --------------------------------------------------------------------------------