Today we’ll show you how to create a WebView-based QML component for BlackBerry 10. If you haven’t already, take a look at the introduction article in this series on Hybrid apps that take advantage of both HTML5 and native Cascades.
Cascades and Qt allows developers to abstract their QML code into separate files as custom components, which encourages code reuse and organization. In this blog post we will create a WebView based QML component that can be reused across your projects.
Purpose
The sample code for the result can be found on GitHub.
The QML Component we are going to build can be used to display an image from the Web inside of your Cascades application. This consists of a small Web App with an image tag (<img>), and a WebView based QML Component that will take care of passing the URL.
Let’s begin
- First, create a new Cascades project (File > New > BlackBerry Project > Cascades). A standard empty project will do for this example.
- Inside of main.qml, define a WebView that points at a bundled html file and waits for a “ready” notification from the web side
- For now, let’s pass a hardcoded URL to an image by using webView.postMessage(URL)
//file: main.qml WebView { url: "local:///assets/web/index.html" id: webView onMessageReceived: function(){ if(message === "ready") webView.postMessage("http://www.w3.org/html/logo/downloads/HTML5_Logo_256.png"); } }
- The HTML side will contain an image tag, and code that sets its source based on the message received from Cascades.
<!-- file: index.html --> <html> <head> <title>Sample<title> </head> <body> <img src="#" id="image"/> <script> window.onload = function(){ var imageEl = document.getElementById("image"); navigator.cascades.onmessage = function(message){ imageEl.src = message; } navigator.cascades.postMessage("ready"); } </script> </body>
Now that a hard-coded image URL shows up in the WebView, let’s make it more dynamic.
Custom QML Component using WebView
To create a custom QML component using Cascades, add a QML file into the assets folder. The first letter needs to be capitalized.
assets/ ImageWebView.qml main.qml web/ index.html
Moving the WebView code into ImageWebView.qml turns it into a component that displays the HTML5 logo where desired:
//file: ImageWebView.qml WebView { url: "local:///assets/web/index.html" id: webView onMessageReceived: function(){ if(message === "ready") webView.postMessage("http://www.w3.org/html/logo/downloads/HTML5_Logo_256.png"); } } //file: main.qml ImageWebView { }
Next, let’s make the component more dynamic so a URL can be passed in to display an image from the Internet.
//file: main.qml ImageWebView { imageURL: "http://www.w3.org/html/logo/downloads/HTML5_Logo_256.png" }
Building and deploying will result in the following error:
Cannot assign to non-existent property "imageURL"
The imageURL property needs to be defined on the WebView inside ImageWebView.qml:
//file: ImageWebView.qml WebView { property variant imageURL: "" ...
Instead of the hard-coded URL, let’s post the value of imageURL to the WebView:
//file: ImageWebView.qml WebView { url: "local:///assets/web/index.html" id: webView property variant imageURL: "" onMessageReceived: function(){ if(message === "ready") webView.postMessage(imageURL); } }
The ImageWebView component is complete, and uses the following workflow:
- WebView begins to load index.html
- index.html is loaded and Cascades is notified
- Cascades posts a message with WebView imageURL (which was set in main.qml)
- index.html displays image
Next Steps
So what about loading images located on the device using this component (for example, content located in the shared space, or content created with the application, residing in the data folder)?
Let’s say we have an image stored inside of the pictures folder in the shared space. Setting the image source to an image located on the device will be done as follows:
//file: main.qml ImageWebView { imageURL: "file:////accounts/1000/shared/pictures/PIC1.JPG" }
If you have Web Inspector enabled, this will yield the following error:
>Not allowed to load local resource: file:////accounts/1000/shared/pictures/PIC1.JPG
If you recall, the ImageWebView component loaded the index.html page using the “local:///” protocol scheme. Due to security restrictions, files loaded using the “local:///” scheme cannot access files referenced by the “file:///” scheme.
To work around this, we need to load the index.html file using the “file:///” scheme:
//file: ImageWebView.qml WebView { url: "file:////accounts/1000/appdata/<app id here>/assets/web/index.html" }
When using “file:///”, developers need to provide an absolute path from root, where <app id here> needs to be replaced with the application id that is generated at install time.
This can be quite difficult, and one approach is to expose the app’s home directory to QML so that the path can be written as follows:
//file: main.qml WebView { url: homeDir + "/assets/web/index.html" }
Exposing the app’s home directory to QML
First, include <QDir> into src/applicationui.cpp (QDir documentation here).
//file: src/applicationui.cpp #include <QDir> using namespace bb::cascades; ApplicationUI::ApplicationUI(bb::cascades::Application *app) : …
Next, declare a QString, assign the value of the app’s home path plus the path to the “native” folder and expose the property to the root QML document by using setContextProperty():
//file: applicationui.cpp ... QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this); //create QString and assign value QString m_homeDir = QDir::homePath() + "/../app/native"; //expose as property to QML QmlDocument::defaultDeclarativeEngine()->rootContext()->setContextProperty("homeDir",m_homeDir); ....
That’s it, the
homeDir
property is now available to QML, so you can define a WebView, and use it to resolve the path to index.html:
//file: ImageWebView.qml WebView { url: “file:///” + homeDir + "/assets/web/index.html" }
Attempts to load local resources using the “file:///” schema will now be successful. Remote resources will work as well (“http://”):
//file: main.qml ImageWebView { imageURL: "file:////accounts/1000/shared/pictures/PIC1.JPG" }
Note: don’t forget to add the “access_shared” permission to allow this application access into the shared space.
The WebView-based Image Viewer sample is hosted on GitHub. Check it out and let us know if you have any questions in the comments below!