3

I'm setting up like a framework who use python in backend and html/css/js for frontend. My problem arrived during the loading of a QWebEngineView.

I search on the web how to establish a communication between python and javascript with QWebEngineView and I finally tried to use QtWebChannel. So I setted up everything, and everything worked good with communication between python and javascript, but the next issue appeared:

  • First: i can't load javascript files directly in html with tags <script>
  • Second: javascript loaded randomly, i tried to load javascript from python with my_view.page().runJavascript(my_js) but it work one try on two. So sometimes jQuery load at the end, so an other part of the code doesn't work.

base.html:

<!DOCTYPE html>
<html lang="en">
    <p id="log"></p>
    <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
    <script>
        window.onerror = function (error, url, line) {
            console.error("ERROR: " + error.toString());
            console.error("LINE: " + line.toString());
         };
        function load_app(){
            new QWebChannel(qt.webChannelTransport, function (channel) {
                window.app = channel.objects.app;
                app.load_javascript(function(ret){
                    console.error("load javascript: " + ret)
                });
            });
         }
        load_app();
        console.error("app loaded")
    </script>
    {{ application_html_content | safe }}
</html>

Another part of HTML:

{% extends 'base.html' %}

{% block content %}
    <div class="row">
        {% for user_id, user in user_dict.items() %}
            <div id="{{ user_id }}" class="col s12 m6">
                <div class="card blue-grey darken-1">
                    <div class="card-content white-text">
                        <span class="card-title">Visit Card</span>
                        <p>{{ user.name }}</p>
                    </div>
                    <div class="card-action">
                        <button id="btn_del_{{ user_id }}" class="btn blue waves-effect waves-light" onclick="delete_user({{ user_id }})">Delete</button>
                        <button class="btn blue waves-effect waves-light" onclick="detail_user({{ user_id }})">Detail</button>
                    </div>
                </div>
            </div>
        {% endfor %}
    </div>
{% endblock %}

{% block javascript %}
    <script>
        $(document).ready(function() {
           app.request_result.connect(function (result) {
                if ("delete" in result) {
                    user_id = result.delete;
                    var element = document.getElementById(user_id);
                    element.parentNode.removeChild(element)
                }
            });
            console.error("ready");
        });

        function delete_user(user_id) {
            document.getElementById("btn_del_" + user_id).innerHTML = "Waiting ...";
            app.request('DemoHtml:Default:delete', user_id);
        }
        function detail_user(user_id) {
            app.path('detail_user', {"user_id": user_id});
        }
    </script>
{% endblock %}

load_javascript function:

JQUERY = "vendor/Resources/js/jquery.js"
MATERIALIZE = "vendor/Resources/css/materialize/js/materialize.js"

@pyqtSlot(result=str)
def load_javascript(self):
    with open(os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), self.MATERIALIZE), "r") as m_stream:
        materialize_content = m_stream.read()
    with open(os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), self.JQUERY), "r") as j_stream:
        jquery_content = j_stream.read()
    self.template_view.view.page().runJavaScript(jquery_content)
    self.template_view.view.page().runJavaScript(materialize_content)
    return "ok"

As you can see, normally I must see in log error:

  • First: "load javascript: ok"
  • Second: "app loaded"

but one time one two, this is reverse like:

  • js: app loaded
  • js: ERROR: Uncaught ReferenceError: $ is not defined
  • js: LINE: 67
  • js: Uncaught ReferenceError: $ is not defined
  • js: load javascript: ok

Any help for this?

Thank you in advance!

4
  • You might need to wait for the script been executed ? There is a callback function Commented Feb 5, 2019 at 18:02
  • The runJavaScript is asynchronous, so you must wait for the result before calling it again. Commented Feb 5, 2019 at 18:09
  • @MauriceMeyer so when my function load_javascript() is call, i can set a callback in runJavascript() to force the loading of the page waiting for the end ? @ekhumoro how can i wait for the result ? the runJavaScript() just load jQuery Commented Feb 6, 2019 at 10:40
  • @FlorianCouderc As explained in the linked docs, a calback passed to runJavascript will be invoked when it returns. So just chain the runJavascript calls together, rather than calling them one after the other. You could also create a wrapper function with a local event-loop that blocks until the callback is invoked. That would effectively make a synchronous version of runJavascript. Commented Feb 6, 2019 at 17:48

1 Answer 1

1

I resolved my problem, thanks to @ekhumoro for trying to help me, i found an answer on this thread:

How to wait for another JS to load to proceed operation ?: https://stackoverflow.com/a/8618519/8293533

So to make it work, i change my javascript to this: I named this file app.js

function set_app() {
    try{
        new QWebChannel(qt.webChannelTransport, function (channel) {
            window.app_channel = channel.objects.app;
        });
    } catch (e) {
        console.error("setting_app error: " + e)
    }
}

set_app();

function request(route, args) {
    let interval = 10;
    window.setTimeout(function () {
        if (window["app_channel"]) {
            app_channel.request(route, args)
        } else {
            try {
                set_app();
            }
            catch(error) {
                console.error("app load error: " + error)
            }
            window.setTimeout(arguments.callee, interval);
        }
    }, interval)
}

function path(route, args) {
    let interval = 10;
    window.setTimeout(function () {
        if (window["app_channel"]) {
            app_channel.path(route, args)
        } else {
            try {
                set_app();
            }
            catch(error) {
                console.error("app load error: " + error)
            }
            window.setTimeout(arguments.callee, interval);
        }
    }, interval)
}

function request_result(callback) {
    let interval = 10;
    window.setTimeout(function () {
        if (window["app_channel"]) {
            app_channel.request_result.connect(callback)
        } else {
            try {
                set_app();
            }
            catch(error) {
                console.error("app load error: " + error)
            }
            window.setTimeout(arguments.callee, interval);
        }
    }, interval)
}

I erase my code load_javascript in python because i found the way to call js with <script> tags and qrc:/// path.

Now my html head look like this:

<!DOCTYPE html>
<html lang="en">
    <p id="log"></p>
    <script src="qrc:///qwebchannel.js"></script>
    <script src="qrc:///app.js"></script>
    <script src="qrc:///jquery.js"></script>
    {{ application_html_content | safe }}
    <script src="qrc:///materialize.min.js"></script>
</html>

To use qrc:///xxx.js i used QResource and .qrc, .rcc files. This is an example of my code for those who want:

class ApplicationContainer:

    SRC_QRC_PATH = "src/*Bundle/Resources/qrc/*.qrc"
    SRC_RCC_PATH = "src/*Bundle/Resources/qrc/*.rcc"
    VENDOR_QRC_PATH = "vendor/*Bundle/Resources/qrc/*.qrc"
    VENDOR_RCC_PATH = "vendor/*Bundle/Resources/qrc/*.rcc"

    def __init__(self):
        self.__pyqt_application = QApplication(sys.argv)
        self.__pyqt_resources = QResource()
        self.set_rcc_files()

    @property
    def application(self):
        return self.__pyqt_application

    @application.setter
    def application(self, new_app: QApplication):
        self.__pyqt_application = new_app

    def set_rcc_files(self):
        qrc_files = glob.glob(os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), self.SRC_QRC_PATH))
        qrc_files += glob.glob(os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), self.VENDOR_QRC_PATH))
        for qrc in qrc_files:
            subprocess.call(["rcc", "-binary", qrc, "-o", qrc[:-3] + "rcc"])

        rcc_files = glob.glob(os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), self.SRC_RCC_PATH))
        rcc_files += glob.glob(os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), self.VENDOR_RCC_PATH))

        for rcc in rcc_files:
            self.__pyqt_resources.registerResource(rcc)

As you can see i use rcccommand, not pyrcc5

To finish, this is my .qrc file:

<!DOCTYPE RCC>
<RCC version="1.0">
    <qresource>
        <file alias="jquery.js">../js/jquery.js</file>
        <file alias="app.js">../js/app.js</file>
        <file alias="qwebchannel.js">../js/qwebchannel.js</file>
        <file alias="materialize.js">../css/materialize/js/materialize.js</file>
        <file alias="materialize.css">../css/materialize/css/materialize.css</file>
    </qresource>
</RCC>

I know there can be a lot of improvment and optimisation in javascript code and python code. But it works like this !

Thank's and hope i help someone too.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.