Skip to content

lbwa/qwebchannel-bridge

Repository files navigation

QWebChannel bridge

This is an integration approach for QWebChannel with Vue.js v2, consisted of a message broker layer.

中文指南

Prerequisites

Qt side should provide one or more QObject which include all information shared with JS side.

  1. A Cpp function named emitEmbeddedPageLoad

    class WebBridge: public QObject {
      Q_OBJECT
      public slots:
        void emitEmbeddedPageLoad() {
            QMessageBox::information(NULL,"emitEmbeddedPageLoad", "I'm called by client JS!");
        }
    };
    
    WebBridge *webBridge = new WebBridge();
    QWebChannel *channel = new QWebChannel(this);
    channel->registerObject('keyNamedContext', webBridge);
    view->page()->setWebChannel(channel);
  2. In JS side, you should provide a init function when QWebChannel initialized.

    new QWebChannel(window.qt.webChannelTransport, function(channel) {
      const published = channel.objects.keyNamedContext
      Vue.prototype.$_bridge = published
    
      // This function calling will notify Qt server asynchronously
      published.emitEmbeddedPageLoad('', function(payload: string) {
        // This payload has included dispatcher name and its parameters.
        dispatch(payload)
        console.info(`
            Bridge load !
          `)
      })
    })

    Advance: You can also create a process like these implementation for function calling or properties reading with abstract namespace.

  3. dispatch function should include all navigation logic.

Once QWebChannel initialized, dispatch will be invoked when Cpp function named emitEmbeddedPageLoad return a value async notification. dispatch function would play a navigator role in JS side.

How to navigate

  1. In Qt side, all entry point should be based on root path - https://<YOUR_HOST>/. All navigation will be distributed by JS side (vue-router, a kind of front-end router) rather than Qt. Qt side would has more opportunities to focus on other business logic.

    • When Qt side receives a initial message from JS side, it should return a value which syntax should be like:

      interface InitialProps {
        type: string
        payload: any
      }
      // Actual value
      {
        type: [JS_SIDE_DISPATCHER_NAME],
        payload: [OPTIONAL_PAYLOAD]
      }

    type property will be used to invoke dispatcher in the dispatchersMap, then payload property including any messages from Qt side will passed dispatcher. dispatcher in the dispatchersMap plays a navigator role in front-end, and developer should add navigation logic into here. This is all secrets about front-end navigation without Qt routing.

    Above all process has described how to initialize Vue.js app in the QWebEngine, and how navigation works in the Vue.js with QWebEngine.

  2. Be careful any external link and redirect uri from any external web site like Alipay online payment links. If you want to respect any redirect uri and prevent navigation from above dispatch function, you MUST provide non-root redirect uri (eg. https://<YOUR_HOST>/#/NOT_EMPTY_PATH). You can find more details from dispatch function here.

How to push message from JS side

If you want to push messages from JS side to Qt side, you can invoke the mapping of Qt methods in JS side directly:

channel.object[QObjectJSMappingKey].methodNameMappingFromQtSide(
  payload,
  callback
)

Enhance: the following logic is based on these implementation:

// A QObject named `QObjectJSMappingKey` (as an abstract namespace) in Qt/JS side
// in the vue instance
this.$$pusher.QObjectJSMappingKey({
  action: 'QT_QOBJECT_KEY',
  payload: 'CALLING_PAYLOAD'
})

How to push messages from Qt side

Qt signal listener mechanism is a kind of good solution for communicate from Qt side to JS side. You may be wondering why we don't use signal mechanism directly to handle first frontend navigation? Because Qt side never known when frontend router is available until JS side push loaded message to Qt side positively.

class WebBridge: public QObject {
  Q_OBJECT
  public slots:
    void emitEmbeddedPageLoad();

  // define your own signal
  signals:
    void signalMessageFromQt(const QString &str);
};

Always define all available signal listeners in config/bridge.ts:

interface SignalCallbacks {
  [targetSignal: string]: Function
}

All signal would be handled automatically by these codes.