DEVELOPERS BLOG

Hybrid Apps for BlackBerry 10: Creating a WebView-based QML Component

HTML5_Logo_512

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!

About anzorb