This post participates in the competition "
Smart phones for smart posts "
Introduction
It is no secret that with the advent of HTML5, the development focus gradually began to shift towards the Web. These include simple Web sites and dynamic applications, and even mobile applications written entirely using HTML5. Independent from the platform and execution environment and requiring only unquestioning implementation of standards. But nevertheless, no matter how good the whole stack of technologies introduced by the new HTML standards, there are still some tasks that require the use of native development tools.
Such problems, for example, is to gain access to system information, control and change anything in the system. Access from HTML5 to contacts, calendar, organizer on a mobile device and others. Again, if our application performs some serious calculations, they can be transferred from slow JS to fast C ++. In this article I want to consider several techniques of interaction between a Web application and native code on the example of the QtWebKit module that may be useful.
Table of contents
- Calling C ++ Methods from JavaScript
- Calling JavaScript code from C ++ methods
- Integration of QWidget into a web page
Calling C ++ Methods from JavaScript
Theoretically, in this case, we need to add a new JS object to the HTML page, through which we could have access to one of our classes. QtWebKit provides the ability to embed such objects in a Web page, but with one condition - adding a new object must be done every time we reload the page in our browser component. That is, each time you clean up both the DOM tree and the JS object tree.
')
In practice, we need to create a new Qt Mobile Application, a Qt Simulator profile, the rest to our taste in Qt Creator. After that, drop the QWebView component onto the form, add a line in the .pro file
QT += webkit
in the header of mainwindow.h declare one private and one public slots:
private slots: void addJSObject(); public slots: void webViewQuitClicked ();
And in the implementation file mainwindow.cpp include the QWebFrame header, change the constructor a bit and describe the implementations for the slots added to mainwindow.h:
... #include <QWebFrame> #include <QMessageBox> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->webView->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(addJSObject())); } void MainWindow::addJSObject() { ui->webView->page()->mainFrame()->addToJavaScriptWindowObject(QString("mainWindow"), this); } void MainWindow::webViewQuitClicked () { qApp->quit(); } ...
Now let's deal with this code:
- Private slot addJSObject () - is responsible for adding to each new page a JS object pointing to the current form. At the same time, it will be called every time the web page is reloaded. The javaScriptWindowObjectCleared () signal is responsible for this. As can be seen from the code, on the Web page our object will have the name “mainWindow”.
- The webViewQuitClicked () public slot is the slot that will be accessible to us from our JavaScript code located on the Web page. Naturally, by analogy, you can add other methods, which are then called from JS.
We go further. Now we need to try to call this code from our Web page. To do this, create an HTML file of the following content:
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Application Example</title> <script> function quitApplication() { mainWindow.webViewQuitClicked (); } </script> </head> <body> <a href="#" onclick='quitApplication();'>Quit Application</a> </body> </html>
And a bit the rule is the constructor of the application, namely, we add one line to it:
ui->webView->load("file:///"+QApplication::applicationDirPath() + "/../../html5caller/example.htm")
Of course, you can download a page from somewhere on the Internet.
Yeah, not bad. And what to do if we want to transfer some parameter to the C ++ function? Nothing is easier! Simply we describe in C ++ code a new public slot with the desired parameters. For example - display a message to the user:
mainwindow.h:
public slots: void webViewQuitClicked (); void webViewShowMessageClicked (QString message, int number);
mainwindow.cpp:
... void MainWindow::webViewShowMessageClicked (QString message, int number) { QMessageBox::information(this, "From Web", message+QString::number(number)); } ...
And change example.htm:
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Application Example</title> <script> function quitApplication() { mainWindow.webViewQuitClicked (); } function calculate(val1, val2) { result = eval(parseFloat(document.calc.val1.value)+parseFloat(document.calc.val2.value)) mainWindow.webViewShowMessageClicked ("Result: ", result); } </script> </head> <body> <a href="#" onclick='quitApplication();'>Quit Application</a> <FORM name="calc"> <input name="val1" type="text" value="3" size="4">+ <input name="val2" type="text" value="4" size="4"> <input type="button" value=" + " onclick="calculate(val1, val2)"> </FORM> </body> </html>
Now, we can not only close the application, but also pass some parameters to it, and with normal data type control. And that's what happened:

Well, the last. what can be added on this topic. Let's say. we send some data to C ++, they are processed there and we want to get it back. To do this, we simply describe the returned data type in our slot, for example:
... QString readResult(int val1, int val2) { ... return QString(); } ...
And from the JS-code we call this method like the others, with the only difference that we set the variable where we want to add the return value:
var lolo = mainWindow.readResult();
That's it ... Go ahead ...
Calling JavaScript code from C ++ methods
We will not dwell on this for long. To call the JS code, we must use the methods of the QWebFrame class. Here is an example: we need to get the url of all the pictures on the page:
QVariant listOfImages = webView.page()->mainFrame()->evaluateJavaScript("document.getElementsByTagName(\"img\").length;"); double numberOfImages = listOfImages.toDouble(); for (double i = 0; i < numberOfImages; ++i) { srcOfImages = webView.page()->mainFrame()->evaluateJavaScript(QString("document.getElementsByTagName(\"img\")[%1].src;").arg(i)); imageUrl = srcOfImages.toString(); }
First we get the number of tags like img, and then we go through this list and pull out the necessary URLs. As you can see from the example, we can easily refer to any objects on the page, and accordingly execute any methods on it.
Integration of QWidget into a web page
This is the second big topic I would like to talk about. It will be a matter of not quite standard procedure ... We really will include in our web page some widget from our application.
This can be done using the QWebPluginFactory class. Let's get started First of all, in Qt Creator, in the project tree on the left, right-click on our project. From the contact menu, select the item “Add new” -> C ++ class. The class name is MyWidgetFactory, the base class QWebPluginFactory, type QObject, next, complete. In order for our plugin to become complete, we need to override two methods of the base class:
QObject *create(const QString &mimeType, const QUrl &url, const QStringList &argumentNames, const QStringList &argumentValues) const; QList<QWebPluginFactory::Plugin> plugins() const;
The first is creating widgets on demand from Web pages. The second one describes the Mime types to which the plugin should respond and for which it is necessary to create new widgets. In general, initially, I used a similar approach a couple of years ago, when I wrote an instant messenger for Vkontakte.ru. Then I needed to embed a music player widget into the page in order to make it easier for users to share music. You can see its full implementation here
code.google.com/p/vimka/source/browse/#svn%2Ftrunk%2Fchats in the vkmediafactory.h and vkmediafactory.cpp files
We will do something simpler now. So let's first change the resulting class template.
mywidgetfactory.h:
#ifndef MYWIDGETFACTORY_H #define MYWIDGETFACTORY_H #include <QWebPluginFactory> class MyWidgetFactory : public QWebPluginFactory { Q_OBJECT public: explicit MyWidgetFactory(QObject *parent = 0); QObject *create(const QString &mimeType, const QUrl &url, const QStringList &argumentNames, const QStringList &argumentValues) const; QList<QWebPluginFactory::Plugin> plugins() const; signals: public slots: }; #endif
mywidgetfactory.cpp:
#include "mywidgetfactory.h" #include <QTabWidget> #include <QPushButton> #include <QProgressBar> #include <QDebug> #include <QApplication> #include <QUrl> MyWidgetFactory::MyWidgetFactory(QObject *parent) : QWebPluginFactory(parent) { } QList<QWebPluginFactory::Plugin> MyWidgetFactory::plugins() const { QWebPluginFactory::MimeType mimeType; mimeType.name = "text/qtexample"; mimeType.description = "Comma-separated values"; mimeType.fileExtensions = QStringList() << "txt"; QWebPluginFactory::Plugin plugin; plugin.name = "PluginFactory Example"; plugin.description = "PluginFactory Example"; plugin.mimeTypes = QList<MimeType>() << mimeType; return QList<QWebPluginFactory::Plugin>() << plugin; } QObject *MyWidgetFactory::create(const QString &mimeType, const QUrl &url, const QStringList &argumentNames, const QStringList &argumentValues) const { qDebug() << mimeType << url << argumentNames << argumentValues; if (mimeType != "text/qtexample") return 0; QTabWidget *tab = new QTabWidget(); int max = argumentValues.last().split(",").first().toInt(); int val = argumentValues.last().split(",").last().toInt(); QProgressBar *progressBar = new QProgressBar(); tab->addTab(progressBar, "Progress"); progressBar->setMaximum(max); progressBar->setValue(val); QPushButton *pb = new QPushButton("Click me!"); tab->addTab(pb, "Button"); connect(pb, SIGNAL(clicked()),QApplication::instance(), SLOT(quit())); return tab; }
What have we done? In the plugins () function, we described a Mime type that we would like to react to. And then they described the plugin itself, which will monitor all of the loaded pages for the presence of this Mime type and, if it is detected, call the create () method and then turn the resulting widget into the page itself.
Next, in the create () method we accept and output data to the console, check the mime type matches the text / qtexample we need and return a non-empty widget if the mime types match. To make it a little more visual, here is the console output of data received by this function:
"text/qtexample" QUrl( "file:///D:/devel/html5caller/100,28" ) ("type", "data", "width", "height", "src") ("text/qtexample", "100,28", "300", "300", "100,28")
As for the factory ...
The next step is to tell our application about the presence of our own plugin factory. To do this, let's add two new headings to the mainwondow.cpp file:
#include <QWebSettings> #include "mywidgetfactory.h"
And change the constructor of this class:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { QWebSettings::globalSettings()->setAttribute( QWebSettings::PluginsEnabled, true); ui->setupUi(this); connect(ui->webView->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(addJSObject())); MyWidgetFactory *factory = new MyWidgetFactory(ui->webView); ui->webView->page()->setPluginFactory(factory); ui->webView->load("file:///"+QApplication::applicationDirPath() + "/../../html5caller/example.htm"); }
Here, first of all, in QtWebKit's global settings, we have included plugin support. After that, we created an object of our Factory class and set it as a Plugin Factory for our QWebPage object related to our webView.
There remains the last, final touch - you need to somehow embed our plugin into web-cnhfybxre / To do this, open example.htm and add a line like this after describing our form:
<object type="text/qtexample" data="100,28" width="300" height="300"></object>
As it is easy to guess, here we specify the mime-type in order for the created plugin to react to us, set the dimensions of the future widget and transfer some data for initialization.
The result of our work will be the following window:

According to the widget, you can poke, switch between tabs, and if you press the button on the second tab, the application will process the click and close.
In general, that's all that I would like to tell about this time. The source code of the application can be taken here and used as an example:
code.google.com/p/html5cppmixer/downloads/listI hope someone my experience will be useful and will be useful.