diff --git a/examples/project-structure/src/api.v b/examples/project-structure/src/api.v index 48fbfa9..79a4f8f 100644 --- a/examples/project-structure/src/api.v +++ b/examples/project-structure/src/api.v @@ -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) } @@ -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) diff --git a/examples/v-js-interop-app/main.v b/examples/v-js-interop-app/main.v index 555342c..70a6f80 100644 --- a/examples/v-js-interop-app/main.v +++ b/examples/v-js-interop-app/main.v @@ -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) @@ -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) diff --git a/src/lib.v b/src/lib.v index 0bcbd89..6b38d77 100644 --- a/src/lib.v +++ b/src/lib.v @@ -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. @@ -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) } @@ -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 } diff --git a/src/utils.v b/src/utils.v index 96d31e7..4263e31 100644 --- a/src/utils.v +++ b/src/utils.v @@ -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 { @@ -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)) } } diff --git a/tests/v_fn_call_test.v b/tests/v_fn_call_test.v index 60d76ec..a15480a 100644 --- a/tests/v_fn_call_test.v +++ b/tests/v_fn_call_test.v @@ -1,5 +1,6 @@ import webview import time +import os struct Person { name string @@ -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() +}