2

I'm just starting out with javascript and Qt so bear with me

My issue is with the runJavaScript method. I can't seem to get the callback function to run with a returned value that's delayed. For example, the following prints None.

js = '''
function returnHello(){
    var i = 0;
    var wait = setInterval(function() { //arbitrary delay
        i++
        if(i>2){
            return('hello')
        }
    }, 10);
}
returnHello();
'''

def test(a):
    print(a)

mw.reviewer.web.page().runJavaScript(js, test)

I suspect it has something to do with how javascript runs asynchronously, and I tried playing around with javascript callback methods, but if there's any delay in returning values, the Qt python callback method always seems to accept the undefined default javascript return value.

I've been scouring the internet for answers, so any help would be great!

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Quip13
  • 33
  • 7
  • The return value of an asynchronous function is not used. – Barmar Mar 24 '20 at 05:48
  • I want to print 'hello' in python, not javascript - how would I go about doing that – Quip13 Mar 24 '20 at 05:51
  • Maybe this question is related: https://stackoverflow.com/questions/45230931/qwebengineview-javascript-callback – Barmar Mar 24 '20 at 05:58
  • That's for the suggestion, but that seems to be a different issue - all their javascript returns values without delay – Quip13 Mar 24 '20 at 06:08
  • Unless there's a way to pass a Python callback to `runJavaScript`, and have the JS code call it, I think you're out of luck. Nothing waits for an asynchronous function to finish. – Barmar Mar 24 '20 at 06:10

1 Answers1

1

Explanation:

It seems that you do not know how an asynchronous task works, an asynchronous function does not return internal information since at the time of evaluating the assignment, the task has not yet been executed nor is it blocked until it is executed. For example, the variable "wait" is a timer ID that is used to cancel the execution of the timer. Therefore runJavaScript will not work in this case.

Solution:

One possible solution is to use Qt WebChannel, based on my previous answer I have implemented the following solution:

import os
import sys
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
from jinja2 import Template


class Element(QtCore.QObject):
    def __init__(self, name, parent=None):
        super(Element, self).__init__(parent)
        self._name = name

    @property
    def name(self):
        return self._name

    def script(self):
        raise NotImplementedError


class TestObject(Element):
    def script(self):
        _script = r"""
        function returnHello(){
            var i = 0;
            var id_timer = setInterval(function() { //arbitrary delay
                i++
                if(i>2){
                    {{name}}.test('hello')
                }
            }, 10);
        }
        returnHello();
        """
        return Template(_script).render(name=self.name)

    @QtCore.pyqtSlot(str)
    def test(self, a):
        print(a)


class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self, *args, **kwargs):
        super(WebEnginePage, self).__init__(*args, **kwargs)
        self.loadFinished.connect(self.onLoadFinished)
        self._objects = []

    def add_object(self, obj):
        self._objects.append(obj)

    @QtCore.pyqtSlot(bool)
    def onLoadFinished(self, ok):
        if ok:
            self.load_qwebchannel()
            self.load_objects()

    def load_qwebchannel(self):
        file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
        if file.open(QtCore.QIODevice.ReadOnly):
            content = file.readAll()
            file.close()
            self.runJavaScript(content.data().decode())
        if self.webChannel() is None:
            channel = QtWebChannel.QWebChannel(self)
            self.setWebChannel(channel)

    def load_objects(self):
        if self.webChannel() is not None:
            objects = {obj.name: obj for obj in self._objects}
            self.webChannel().registerObjects(objects)
            _script = r"""
            {% for obj in objects %}
            var {{obj}};
            {% endfor %}
            new QWebChannel(qt.webChannelTransport, function (channel) {
            {% for obj in objects %}
                {{obj}} = channel.objects.{{obj}};
            {% endfor %}
            }); 
            """
            self.runJavaScript(Template(_script).render(objects=objects.keys()))
            for obj in self._objects:
                if isinstance(obj, Element):
                    self.runJavaScript(obj.script())


class WebPage(QtWebEngineWidgets.QWebEngineView):
    def __init__(self, parent=None):
        super().__init__(parent)

        page = WebEnginePage(self)
        self.setPage(page)

        test_object = TestObject("test_object", self)
        page.add_object(test_object)

        self.load(QtCore.QUrl("https://stackoverflow.com/"))


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    web = WebPage()
    web.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241