justpy-org / justpy

An object oriented high-level Python Web Framework that requires no frontend programming
https://justpy.io
Apache License 2.0
1.22k stars 96 forks source link

Obtaining results from javascript #613

Closed aalexei closed 2 years ago

aalexei commented 2 years ago

The example in the docs at https://justpy.io/tutorial/page_events/#run_javascript-example no longer works. Neither does the code technique in #240. Seems wp.on("result_ready", result_ready) never triggers. Using Justpy 0.10.2.

WolfgangFahl commented 2 years ago

@aalexei thx for the bug report. https://jsfiddle.net/6ravphnc/ has a javascript fiddle for the code to be executed

a = 3;
b = 5;
c = a * b;
d = {r: c, appName: navigator.appName, appVersion: navigator.appVersion}
alert(JSON.stringify(d))
WolfgangFahl commented 2 years ago

Debugging the javscript code in justpy_core.js shows that

case 'run_javascript':
                this.handleRunJavascriptEvent(msg);
                break;

is triggered correctly.

WolfgangFahl commented 2 years ago

which then calls the evaluation code:

/**
     * Handles the run_javascript event
     * @param msg
     */
    handleRunJavascriptEvent(msg){
        function eval_success(js_result) {
            e = {
                'event_type': 'result_ready',
                'visibility': document.visibilityState,
                'page_id': page_id,
                'websocket_id': websocket_id,
                'request_id': msg.request_id,
                'result': js_result //JSON.stringify(js_result)
            };
            if (this.result_ready) {
                if (msg.send) send_to_server(e, 'page_event', false);
            }
        }

        const jsPromise = (new Promise(function () {
            return eval(msg.data);
        })).then(
            (value) => {eval_success(value);},
            (reason) => {
                if (this.debug){
                    console.log(reason);
                }
            }
        );
    }

which shows an error in the console:

Uncaught (in promise) ReferenceError: assignment to undeclared variable a

So the javascript is not o.k. in the first place for more current javascript environments.

WolfgangFahl commented 2 years ago

https://jsfiddle.net/wpdb801x/ has an improved version

var a = 3;
var b = 5;
var c = a * b;
var d = {r: c, appName: navigator.appName, appVersion: navigator.appVersion}
aalexei commented 2 years ago

Oh, I don't think it's the js code. That was just an example of the problem. I've stopped being able to get javascript return values in other code that used to work. Seems wp.on("result_ready", result_ready) doesn't trigger any more.

aalexei commented 2 years ago

e.g shouldn't this trigger result_ready() callback?

async def page_ready(self, msg):
    jp.run_task(msg.page.run_javascript("send_to_server('hello', 'result_ready');"))

async def result_ready(self, msg):
    print('Triggered:', msg)

def result_test():
    wp = jp.WebPage()
    wp.on("page_ready", page_ready)
    wp.on("result_ready", result_ready)
    return wp

jp.justpy(result_test)

On debug setting I get: DEBUG justpy: Socket 0 data received: {"type":"result_ready","event_data":"hello"} but the result_ready method doesn't get called.

Alternatively, if there is a better way to get return values from javascript I can recode my modules to use that.

WolfgangFahl commented 2 years ago

The html page below uses a slight modified version of the javascript code for better debugging. See also https://jsfiddle.net/wf_bitplan_com/eaxzt5hq/19/

When entering

a = 3;
b = 5;
c = a * b;
d = {r: c, appName: navigator.appName, appVersion: navigator.appVersion};

it will try to send

{"type":"page_event","event_data":{"event_type":"result_ready","visibility":"visible","page_id":"1","websocket_id":"","result":"Error in javascript"}}

and with

var a = 3;
var b = 5;
var c = a * b;
var d = {r: c, appName: navigator.appName, appVersion: navigator.appVersion};
(d)

it will try to send

{"type":"page_event","event_data":{"event_type":"result_ready","visibility":"visible","page_id":"1","websocket_id":"","result":{"r":15,"appName":"Netscape","appVersion":"5.0 (Macintosh)"}}}

Testpage with javascript code source

<html>

<head>
  <title>Javascript eval</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script type='text/javascript'>
    // legacy global variable
    var websocket_id = '';

    function evaluate_javascript(code) {
      msg = {
        'data': code,
        'send': true
      }
      let justpy_core = new JustpyCore(
        this,
        page_id="1"
      );
      justpy_core.handleRunJavascriptEvent(msg)
    }

    // send to server surrogate for debugging
    function send_to_server(e,event_type,debug_flag) {
      const json_msg = JSON.stringify({'type': event_type, 'event_data': e});
      document.getElementById("#result").innerText = json_msg;
    }

    class JustpyCore {
      constructor(window,page_id) {
        this.window = window;
        this.page_id= page_id;
        this.page_ready=true;
        this.result_ready=true;
        this.debug = true;
      }

      /**
       * handle Error
       */
      handleError(error) {
        if (this.debug) {
          console.log(error);
        }
        this.send_result("Error in javascript")
      }

      /**
       * send javascript eval result back to server
       * @param js_result - the javascript result to send
       */
      send_result(js_result) {
        let e = {
          'event_type': 'result_ready',
          'visibility': document.visibilityState,
          'page_id': this.page_id,
          'websocket_id': websocket_id,
          'request_id': msg.request_id,
          'result': js_result //JSON.stringify(js_result)
        };
        if (this.result_ready) {
          if (msg.send) {
            send_to_server(e, 'page_event', false);
          }
        }
      }

      /**
       * Handles the run_javascript event
       * @param msg
       */
      handleRunJavascriptEvent(msg) {
        /**
         * callback to send javascript result back to server
         */
         const jsPromise = new Promise((resolve, reject) => {
           try{
             let eval_result=eval(msg.data)
             resolve(eval_result)
           } catch(error){
             reject(error)
           }
         });
         jsPromise.then((value) => {
           this.send_result(value);
         }).catch((error) => {
           this.handleError(error);
         });
      }
    }
  </script>
</head>

<body>
  <textarea id="#editor" oninput="evaluate_javascript(event.currentTarget.value)" rows="4" cols="80">
a = 3;
b = 5;
c = a * b;
d = {r: c, appName: navigator.appName, appVersion: navigator.appVersion};
  </textarea>
  <textarea id="#result" rows="4" cols="80"></textarea>
</body>

</html>
WolfgangFahl commented 2 years ago

To try the fix i am releasing a 0.11 version

WolfgangFahl commented 2 years ago

and

seem to be ok after 0.11 update

aalexei commented 2 years ago

Then fixed it. Thanks for your work!