Skip to content

Commit

Permalink
feat: add bind_opt and bind_opt_with_ctx (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
ttytm committed Oct 8, 2023
1 parent e823b38 commit bd5aeeb
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 27 deletions.
4 changes: 2 additions & 2 deletions examples/project-structure/src/api.v
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ fn (mut app App) bind(w &Webview) {
// The first string argument is the functions name in the JS frontend.
// Use JS's `camelCase` convention or distinct identifiers if you prefer it.
w.bind('get_settings', app.get_settings)
w.bind_ctx('toggle_setting', toggle, app) // Alternatively use the ctx ptr to pass a struct.
w.bind_with_ctx('toggle_setting', toggle, app) // Alternatively use the ctx ptr to pass a struct.
w.bind('login', login)
w.bind('fetch_news', fetch_news)
}
Expand All @@ -17,7 +17,7 @@ fn (app App) get_settings(_ &Event) Settings {
}

// Returns a value when it's called from JS.
// This examples uses bind_ctx, adding the App struct as context argument.
// This examples uses `bind_with_ctx` to add the App struct as context argument.
fn toggle(_ &Event, mut app App) bool {
app.settings.toggle = !app.settings.toggle
dump(app.settings.toggle)
Expand Down
4 changes: 2 additions & 2 deletions examples/v-js-interop-app/main.v
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn (app App) get_settings(_ &Event) Settings {
}

// Returns a value when it's called from JS.
// This examples uses bind_ctx, adding the App struct as context argument.
// This examples uses `bind_with_ctx` to add the App struct as context argument.
fn toggle(_ &Event, mut app App) bool {
app.settings.toggle = !app.settings.toggle
dump(app.settings.toggle)
Expand Down Expand Up @@ -78,7 +78,7 @@ fn main() {
// The first string argument is the functions name in the JS frontend.
// Use JS's `camelCase` convention or distinct identifiers if you prefer it.
w.bind('get_settings', app.get_settings)
w.bind_ctx('toggle_setting', toggle, &app) // Alternatively use the ctx ptr to pass a struct.
w.bind_with_ctx('toggle_setting', toggle, &app) // Alternatively use the ctx ptr to pass a struct.
w.bind('login', login)
w.bind('fetch_news', fetch_news)

Expand Down
71 changes: 52 additions & 19 deletions src/lib.v
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,6 @@ pub struct CreateOptions {
window voidptr
}

[params]
pub struct ReturnParams {
kind ReturnKind
}

pub enum ReturnKind {
value
error
}

// A Hint that is passed to the Webview 'set_size' method to determine the window sizing behavior.
pub enum Hint {
// Width and height are default size.
Expand Down Expand Up @@ -150,28 +140,71 @@ pub fn (w &Webview) eval(code string) {
C.webview_eval(w, &char(code.str))
}

// bind binds a callback so that it will appear under the given name as a
// global JavaScript function. Internally it uses webview_init().
// The callback receives an `&Event` pointer.
// bind binds a V callback to a global JavaScript function that will appear under the given name.
// The callback receives an `&Event` argument. Internally it uses webview_init().
pub fn (w &Webview) bind[T](name string, func fn (&Event) T) {
C.webview_bind(w, &char(name.str), fn [w, func] [T](event_id &char, args &char, ctx voidptr) {
e := unsafe { &Event{w, event_id, args} }
spawn fn [func] [T](e &Event) {
result := func(e)
e.@return(result)
e.@return(result, .value)
}(e.async())
}, 0)
}

// bind_opt binds a V callback with a result return type to a global JavaScript function that will
// appear under the given name. The callback receives an `&Event` argument. The callback can return an
// error to the calling JavaScript function. Internally it uses webview_init().
pub fn (w &Webview) bind_opt[T](name string, func fn (&Event) !T) {
C.webview_bind(w, &char(name.str), fn [w, func] [T](event_id &char, args &char, ctx voidptr) {
e := unsafe { &Event{w, event_id, args} }
spawn fn [func] [T](e &Event) {
if result := func(e) {
e.@return(result, .value)
} else {
e.@return(err.str(), .error)
}
}(e.async())
}, 0)
}

// bind_ctx binds a callback so that it will appear under the given name as a
// global JavaScript function. Internally it uses webview_init().
// The callback receives an `7Event` pointer and a user-provided ctx pointer.
// bind_ctx binds a V callback to a global JavaScript function that will appear under the given name.
// The callback receives an `&Event` and a user-provided ctx pointer argument.
[deprecated: 'will be removed with v0.7; use `bind_with_ctx` instead.']
pub fn (w &Webview) bind_ctx[T](name string, func fn (e &Event, ctx voidptr) T, ctx voidptr) {
C.webview_bind(w, &char(name.str), fn [w, func] [T](event_id &char, args &char, ctx voidptr) {
e := unsafe { &Event{w, event_id, args} }
spawn fn [func, ctx] [T](e &Event) {
result := func(e, ctx)
e.@return(result)
e.@return(result, .value)
}(e.async())
}, ctx)
}

// bind_with_ctx binds a V callback to a global JavaScript function that will appear under the given name.
// The callback receives an `&Event` and a user-provided ctx pointer argument.
pub fn (w &Webview) bind_with_ctx[T](name string, func fn (e &Event, ctx voidptr) T, ctx voidptr) {
C.webview_bind(w, &char(name.str), fn [w, func] [T](event_id &char, args &char, ctx voidptr) {
e := unsafe { &Event{w, event_id, args} }
spawn fn [func, ctx] [T](e &Event) {
result := func(e, ctx)
e.@return(result, .value)
}(e.async())
}, ctx)
}

// bind_opt_with_ctx binds a V callback with a result return type to a global JavaScript function that will
// appear under the given name. The callback receives an `&Event` and a user-provided ctx pointer argument.
// The callback can return an error to the calling JavaScript function. Internally it uses webview_init().
pub fn (w &Webview) bind_opt_with_ctx[T](name string, func fn (e &Event, ctx voidptr) T, ctx voidptr) {
C.webview_bind(w, &char(name.str), fn [w, func] [T](event_id &char, args &char, ctx voidptr) {
e := unsafe { &Event{w, event_id, args} }
spawn fn [func, ctx] [T](e &Event) {
if result := func(e, ctx) {
e.@return(result, .value)
} else {
e.@return(err, .error)
}
}(e.async())
}, ctx)
}
Expand Down Expand Up @@ -250,7 +283,7 @@ pub fn (e &Event) int_opt(idx usize) ?int {
return e.args_json[int]() or { return none }[int(idx)] or { return none }
}

// bool_opt decodes and return the argument with the given index as boolean option.
// bool_opt parses and return the argument with the given index as boolean option.
[deprecated: 'will be removed with v0.7; use `get_arg[T](idx int) !T` instead. E.g: `e.get_arg[bool](0)!`']
pub fn (e &Event) bool_opt(idx usize) ?bool {
return e.args_json[bool]() or { return none }[int(idx)] or { return none }
Expand Down
13 changes: 9 additions & 4 deletions src/utils.v
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ module webview

import json

enum ReturnKind {
value
error
}

// copy_char copies a C style string. The functions main use case is passing an `event_id &char`
// to another thread. It helps to keep the event id available when executing `@return`
// to another thread. It helps to keep the event id available when executing `@retrun`
// from the spawned thread. Without copying the `event_id` might get obscured during garbage
// collection and returning data to a calling JS function becomes error prone.
fn copy_char(s &char) &char {
Expand All @@ -14,11 +19,11 @@ fn copy_char(s &char) &char {
// be provided to allow the internal RPC engine to match the request and response.
// If the status is zero - the result is expected to be a valid JSON value.
// If the status is not zero - the result is an error JSON object.
fn (e &Event) @return[T](result T, return_params ReturnParams) {
fn (e &Event) @return[T](result T, kind ReturnKind) {
$if result is voidptr {
C.webview_return(e.instance, e.event_id, 0, &char(''.str))
C.webview_return(e.instance, e.event_id, int(kind), &char(''.str))
} $else {
C.webview_return(e.instance, e.event_id, 0, &char(json.encode(result).str))
C.webview_return(e.instance, e.event_id, int(kind), &char(json.encode(result).str))
}
}

Expand Down
39 changes: 39 additions & 0 deletions tests/v_fn_call_test.v
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import webview
import time
import os

struct Person {
name string
Expand Down Expand Up @@ -149,3 +150,41 @@ fn test_return_value_from_threaded_task_to_js() {
w.set_html(gen_html(@FN, script))
w.run()
}

fn test_fn_call_with_error() {
w := webview.create(debug: true)
w.set_size(600, 400, .@none)
w.bind_opt('v_fn_with_custom_error', fn (e &webview.Event) !int {
return error('my error')
})
w.bind[voidptr]('assert_custom_error', fn (e &webview.Event) {
assert e.get_arg[string](0) or { '' } == 'my error'
})
w.bind_opt('v_fn_with_error_propagation', fn (e &webview.Event) !string {
return os.existing_path('my_inexistent_path/file.v')!
})
w.bind[voidptr]('assert_error_propagation', fn (e &webview.Event) {
assert e.get_arg[string](0) or { '' } == 'path does not exist'
})
w.bind[voidptr]('exit', fn [w] (_ &webview.Event) {
w.terminate()
})
script := '
setTimeout(async () => {
try {
await window.v_fn_with_custom_error();
} catch (err) {
console.log(err);
await window.assert_custom_error(err);
}
try {
await window.v_fn_with_error_propagation();
} catch (err) {
console.log(err);
await window.assert_error_propagation(err);
await window.exit();
}
}, 500)'
w.set_html(gen_html(@FN, script))
w.run()
}

0 comments on commit bd5aeeb

Please sign in to comment.