5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 17:52:29 +08:00

docs: update binding sections

This commit is contained in:
Lea Anthony 2022-04-05 21:30:46 +10:00
parent 55855ccc4d
commit f21c9ba880

View File

@ -123,9 +123,10 @@ option [OnBeforeClose](reference/options.mdx#onbeforeclose).
#### Method Binding
The `Bind` option is one of the most important options in a Wails application. It specifies which struct methods
to expose to the frontend. When the application starts, it examines the struct instances listed in the `Bind` field in
the options, determines which methods are public (starts with an uppercase letter) and will generate Javascript versions
of those methods that can be called by the frontend code.
to expose to the frontend. Think of structs like "controllers" in a traditional web application. When the application
starts, it examines the struct instances listed in the `Bind` field in the options, determines which methods are
public (starts with an uppercase letter) and will generate Javascript versions of those methods that can be called
by the frontend code.
:::info Note
@ -158,8 +159,6 @@ func main() {
Width: 1024,
Height: 768,
Assets: &assets,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
@ -174,67 +173,41 @@ type App struct {
ctx context.Context
}
func (b *App) startup(ctx context.Context) {
b.ctx = ctx
}
func (b *App) shutdown(ctx context.Context) {}
func (b *App) Greet(name string) string {
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
}
```
You may bind as many structs as you like. Just make sure you create an instance of it and pass it in `Bind`:
```go {10-12}
...
```go {8-10}
//...
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
Assets: &assets,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
&mystruct1{},
&mystruct2{},
},
})
...
```
The bound methods are located in the frontend at `window.go.<packagename>.<struct>.<method>`.
In the example above, we bind `app`, which has one public method `Greet`.
This can be called in Javascript by calling `window.go.main.App.Greet`.
These methods return a Promise. A successful call will result in the first return value from the Go call to be passed
to the `resolve` handler. An unsuccessful call is when a Go method that has an error type as it's second return value,
passes an error instance back to the caller. This is passed back via the `reject` handler.
In the example above, `Greet` only returns a `string` so the Javascript call will never reject - unless invalid data
is passed to it.
When you run `wails dev` (or `wails generate module`), a frontend module will be generated containing the following:
- Javascript bindings for all bound methods
- Typescript declarations for all bound methods
- Typescript definitions for all Go structs used as inputs or outputs by the bound methods
All data types are correctly translated between Go and Javascript. Even structs. If you return a struct from a Go call,
it will be returned to your frontend as a Javascript map. Note: If you wish to use structs, you **must** define `json` struct
tags for your fields!
:::info Note
Anonymous nested structs are not supported at this time.
:::
It is also possible to send structs back to Go. Any Javascript map passed as an argument that
is expecting a struct, will be converted to that struct type. To make this process a lot easier, in `dev` mode,
a TypeScript module is generated, defining all the struct types used in bound methods. Using this module, it's possible
to construct and send native Javascript objects to the Go code.
More information on Binding can be found in the [Binding Methods](guides/application-development.mdx#binding-methods)
section of the [Application Development Guide](guides/application-development.mdx).
This makes it incredibly simple to call Go code from the frontend, using the same strongly typed datastructures.
## The Frontend
### Overview
The frontend is a collection of files rendered by webkit. It's like a browser and webserver in one.
The frontend is a collection of files rendered by webkit. It''s like a browser and webserver in one.
There is virtually[^1] no limit to which frameworks or libraries you can use. The main points of interaction between
the frontend and your Go code are:
@ -247,48 +220,61 @@ the frontend and your Go code are:
### Calling bound Go methods
All bound Go methods are available at `window.go.<package>.<struct>.<method>`. As stated in
the previous section, these return a Promise where a successful call returns a value to the
resolve handler and an error returns a value to the reject handler.
When you run your application with `wails dev`, it will automatically generate Javascript bindings for your structs in a
directory called `wailsjs/go` (You can also do this by running `wails generate module`). The generated files mirror the
package names in your application. In the example above, we bind `app`, which has one public method `Greet`. This will
lead to the generation of the following files:
```go title="mycode.js"
window.go.main.App.Greet("Bill").then((result) => {
console.log("The greeting is: " + result);
})
```bash
wailsjs
└─go
└─main
├─App.d.ts
└─App.js
```
Here we can see that there is a `main` package that contains the Javascript bindings for the bound `App` struct, as well
as the Typescript declaration file for those methods. To call `Greet` from our frontend, we simply import the method and
call it like a regular Javascript function:
```javascript
// ...
import {Greet} from '../wailsjs/go/main/App'
function doGreeting(name) {
Greet(name).then((result) => {
// Do something with result
})
}
```
The Typescript declaration file gives you the correct types for the bound methods:
```ts
export function Greet(arg1:string):Promise<string>;
```
When running the application in `dev` mode, a javascript module is generated that wraps these
methods with JSDoc annotations. This really help with development, especially as most
IDEs will process JSDoc to provide code completion and type hinting. This module is called `go`
and is generated in the directory specified by the `wailsjsdir` flag. In this module is a file
called `bindings.js` containing these wrappers. For the above example, the file contains the
following code:
The generated methods return a Promise. A successful call will result in the first return value from the Go call to be passed
to the `resolve` handler. An unsuccessful call is when a Go method that has an error type as it''s second return value,
passes an error instance back to the caller. This is passed back via the `reject` handler.
In the example above, `Greet` only returns a `string` so the Javascript call will never reject - unless invalid data
is passed to it.
```js title="bindings.js"
const go = {
main: {
App: {
/**
* Greet
* @param {Person} arg1 - Go Type: string
* @returns {Promise<string>} - Go Type: string
*/
Greet: (arg1) => {
return window.go.main.App.Greet(arg1);
},
},
},
};
export default go;
```
All data types are correctly translated between Go and Javascript. Even structs. If you return a struct from a Go call,
it will be returned to your frontend as a Javascript class. Note: If you wish to use structs, you **must** define
`json` struct tags for your fields!
#### Support for structs
:::info Note
Anonymous nested structs are not supported at this time.
:::
There is also additional support for Go methods that use structs in their signature. All Go structs
specified by bound method (either as parameters or return types) will have Typescript versions auto
generated as part of the Go code wrapper module. Using these, it's possible to share the same data
model between Go and Javascript. These models align with the JSDoc annotations, empowering IDE code
completion.
It is possible to send structs back to Go. Any Javascript map/class passed as an argument that
is expecting a struct, will be converted to that struct type. To make this process a lot easier, in `dev` mode,
a TypeScript module is generated, defining all the struct types used in bound methods. Using this module, it''s possible
to construct and send native Javascript objects to the Go code.
There is also support for Go methods that use structs in their signature. All Go structs
specified by a bound method (either as parameters or return types) will have Typescript versions auto
generated as part of the Go code wrapper module. Using these, it''s possible to share the same data
model between Go and Javascript.
Example: We update our `Greet` method to accept a `Person` instead of a string:
@ -309,76 +295,77 @@ func (a *App) Greet(p Person) string {
}
```
Our `bindings.js` file has now been updated to reflect the change:
The `wailsjs/go/main/App.js` file will still have the following code:
```js title="bindings.js"
const go = {
main: {
App: {
/**
* Greet
* @param {Person} arg1 - Go Type: main.Person
* @returns {Promise<string>} - Go Type: string
*/
Greet: (arg1) => {
return window.go.main.App.Greet(arg1);
},
},
},
};
export default go;
```js title="App.js"
export function Greet(arg1) {
return window['go']['main']['App']['Greet'](arg1);
}
```
Alongside `bindings.js`, there is a file called `models.ts`. This contains our Go structs in TypeScript form:
But the `wailsjs/go/main/App.d.ts` file will be updated with the following code:
```ts title="App.d.ts"
import {main} from '../models';
export function Greet(arg1:main.Person):Promise<string>;
```
As we can see, the "main" namespace is imported from a new "models.ts" file. This file contains all the struct definitions
used by our bound methods. In this example, this is a `Person` struct. If we look at `models.ts`, we can see how the models
are defined:
```ts title="models.ts"
export class Address {
street: string;
postcode: string;
export namespace main {
static createFrom(source: any = {}) {
return new Address(source);
}
export class Address {
street: string;
postcode: string;
constructor(source: any = {}) {
if ("string" === typeof source) source = JSON.parse(source);
this.street = source["street"];
this.postcode = source["postcode"];
}
}
export class Person {
name: string;
age: number;
address?: Address;
static createFrom(source: any = {}) {
return new Address(source);
}
static createFrom(source: any = {}) {
return new Person(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.street = source["street"];
this.postcode = source["postcode"];
}
}
export class Person {
name: string;
age: number;
address?: Address;
constructor(source: any = {}) {
if ("string" === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.age = source["age"];
this.address = this.convertValues(source["address"], Address);
}
static createFrom(source: any = {}) {
return new Person(source);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map((elem) => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.age = source["age"];
this.address = this.convertValues(source["address"], Address);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
```
@ -386,22 +373,23 @@ So long as you have TypeScript as part of your frontend build configuration, you
the following way:
```js title="mycode.js"
import go from "./wailsjs/go/bindings";
import { Person } from "./wailsjs/go/models";
import {Greet} from '../wailsjs/go/main/App'
import {main} from '../wailsjs/go/models'
let name = "";
function greet(name) {
let p = new Person();
p.name = name;
p.age = 42;
go.main.App.Greet(p).then((result) => {
console.log(result);
});
}
function generate() {
let person = new main.Person()
person.name = "Peter"
person.age = 27
Greet(person).then((result) => {
console.log(result)
})
}
```
The combination of JSDoc and TypeScript generated models makes for a powerful development environment.
The combination of generated bindings and TypeScript models makes for a powerful development environment.
More information on Binding can be found in the [Binding Methods](guides/application-development.mdx#binding-methods)
section of the [Application Development Guide](guides/application-development.mdx).
### Calling runtime methods