Skip to content

Commit

Permalink
Updated sections networking, serve-qml, http-requests, local-files. U…
Browse files Browse the repository at this point in the history
…pdated related examples.
  • Loading branch information
eunoia-cl committed Sep 2, 2021
1 parent 80e8147 commit 1235796
Show file tree
Hide file tree
Showing 16 changed files with 251 additions and 79 deletions.
2 changes: 1 addition & 1 deletion docs/ch12-networking/http-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function request() {
Here is the full source code, where we create the request when the component is loaded. The request response is then used as the model for our simple list view.

```qml
import QtQuick 2.5
import QtQuick
Rectangle {
width: 320
Expand Down
51 changes: 43 additions & 8 deletions docs/ch12-networking/local-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ xhr.open("GET", "colors.json");
We use this to read a color table and display it as a grid. It is not possible to modify the file from the Qt Quick side. To store data back to the source we would need a small REST based HTTP server or a native Qt Quick extension for file access.

```qml
import QtQuick 2.5
import QtQuick
Rectangle {
width: 360
Expand Down Expand Up @@ -47,19 +47,54 @@ Rectangle {
request()
}
}
```

:::tip
For this to work, the `QML_XHR_ALLOW_FILE_READ` must be set and enabled (set to `1`). You can do so by running:

```sh
QML_XHR_ALLOW_FILE_READ=1 qml -f localfiles.qml
```
:::



```qml

Instead of using the `XMLHttpRequest` it is also possible to use the XmlListModel to access local files.

```qml
import QtQuick.XmlListModel 2.0
import QtQuick
import QtQml.XmlListModel
XmlListModel {
source: "http://localhost:8080/colors.xml"
query: "/colors"
XmlRole { name: 'color'; query: 'name/string()' }
XmlRole { name: 'value'; query: 'value/string()' }
Rectangle {
width: 360
height: 360
color: '#000'
GridView {
id: view
anchors.fill: parent
cellWidth: width/4
cellHeight: cellWidth
model: xmlModel
delegate: Rectangle {
width: view.cellWidth
height: view.cellHeight
color: model.value
Text {
anchors.centerIn: parent
text: model.name
}
}
}
XmlListModel {
id: xmlModel
source: "colors.xml"
query: "/colors/color"
XmlListModelRole { name: 'name'; elementName: 'name' }
XmlListModelRole { name: 'value'; elementName: 'value' }
}
}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/ch12-networking/networking.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Networking

Qt 5 comes with a rich set of networking classes on the C++ side. There are for example high-level classes on the HTTP protocol layer in a request-reply fashion such as `QNetworkRequest`, `QNetworkReply` and `QNetworkAccessManager`. But also lower levels classes on the TCP/IP or UDP protocol layer such as `QTcpSocket`, `QTcpServer` and `QUdpSocket`. Additional classes exist to manage proxies, network cache and also the systems network configuration.
Qt 6 comes with a rich set of networking classes on the C++ side. There are for example high-level classes on the HTTP protocol layer in a request-reply fashion such as `QNetworkRequest`, `QNetworkReply` and `QNetworkAccessManager`. But also lower levels classes on the TCP/IP or UDP protocol layer such as `QTcpSocket`, `QTcpServer` and `QUdpSocket`. Additional classes exist to manage proxies, network cache and also the systems network configuration.

This chapter will not be about C++ networking, this chapter is about Qt Quick and networking. So how can I connect my QML/JS user interface directly with a network service or how can I serve my user interface via a network service? There are good books and references out there to cover network programming with Qt/C++. Then it is just a manner to read the chapter about C++ integration to come up with an integration layer to feed your data into the Qt Quick world.

142 changes: 76 additions & 66 deletions docs/ch12-networking/serve-qml.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Serving UI via HTTP

To load a simple user interface via HTTP we need to have a web-server, which serves the UI documents. We start off with our own simple web-server using a python one-liner. But first, we need to have our demo user interface. For this, we create a small `main.qml` file in our project folder and create a red rectangle inside.
To load a simple user interface via HTTP we need to have a web-server, which serves the UI documents. We start off with our own simple web-server using a python one-liner. But first, we need to have our demo user interface. For this, we create a small `Remote.qml` file in our project folder and create a red rectangle inside.

```qml
// main.qml
import QtQuick 2.5
// remote.qml
import QtQuick
Rectangle {
width: 320
Expand All @@ -13,49 +13,62 @@ Rectangle {
}
```

To serve this file we launch a small python script:
To serve this file we can start a small python script:

```sh
cd <PROJECT>
python -m SimpleHTTPServer 8080
python -m http.server 8080
```

Now our file should be reachable via `http://localhost:8080/Remote.qml`. You can test it with:

```sh
curl http://localhost:8080/Remote.qml
```

Now our file should be reachable via `http://localhost:8080/main.qml`. You can test it with:
Or just point your browser to the location. Your browser does not understand QML and will not be able to render the document through.

Hopefully, Qt6 provides such a browser in the form of the `qml` binary. You can directly load a remote QML document by using the following command:

```sh
curl http://localhost:8080/main.qml
qml -f http://localhost:8080/Remote.qml
```

Or just point your browser to the location. Your browser does not understand QML and will not be able to render the document through. We need to create now such a browser for QML documents. To render the document we need to point our `qmlscene` to the location. Unfortunately the `qmlscene` is limited to local files only. We could overcome this limitation by writing our own `qmlscene` replacement or simple dynamically load it using QML. We choose the dynamic loading as it works just fine. For this, we use a loader element to retrieve for us the remote document.
Sweet and simple.

::: tip
If the `qml` program is not in your path, you can find it in the Qt binaries: `<qt-install-path>/<qt-version>/<your-os>/bin/qml`.
:::

Another way of importing a remote QML document is to dynamically load it using QML ! For this, we use a `Loader` element to retrieve for us the remote document.

```qml
// remote.qml
import QtQuick 2.5
// main.qml
import QtQuick
Loader {
id: root
source: 'http://localhost:8080/main2.qml'
source: 'http://localhost:8080/Remote.qml'
onLoaded: {
root.width = item.width
root.height = item.height
}
}
```

Now we can ask the `qmlscene` to load the local `remote.qml` loader document. There is one glitch still. The loader will resize to the size of the loaded item. And our `qmlscene` needs also to adapt to that size. This can be accomplished using the `--resize-to-root` option to the `qmlscene`,
Now we can ask the `qml` executable to load the local `main.qml` loader document.

```sh
qmlscene --resize-to-root remote.qml
qml -f main.qml
```

Resize to root tells the qml scene to resize its window to the size of the root element. The remote is now loading the `main.qml` from our local server and resizes itself to the loaded user interface. Sweet and simple.

::: tip
If you do not want to run a local server you can also use the gist service from GitHub. The gist is a clipboard like online services like Pastebin and others. It is available under [https://gist.github.com](https://gist.github.com). I created for this example a small gist under the URL [https://gist.github.com/jryannel/7983492](https://gist.github.com/jryannel/7983492). This will reveal a green rectangle. As the gist URL will provide the website as HTML code we need to attach a `/raw` to the URL to retrieve the raw file and not the HTML code.
:::

```qml
// remote.qml
import QtQuick 2.5
import QtQuick
Loader {
id: root
Expand All @@ -66,23 +79,42 @@ Loader {
}
}
```
:::

To load another file over the network you just need to reference the component name. For example a `Button.qml` can be accessed as normal, as long it is in the same remote folder.
To load another file over the network from `Remote.qml`, you will need to create a dedicated `qmldir` file in the same directory on the server. Once done, you will be able to reference the component by its name.

## Networked Components

Let us create a small experiment. We add to our remote side a small button as a reusable component.
Let us create a small experiment. We add to our remote side a small button as a reusable component.

Here's the directory structure that we will use:

```sh
./src/main.qml
./src/Button.qml
./src/remote/qmldir
./src/remote/Button.qml
./src/remote/Remote.qml
```

We modify our `main.qml` to use the button and save it as `main2.qml`:
Our `main.qml` is the same as in our previous example:

```qml
import QtQuick 2.5
import QtQuick
Loader {
id: root
anchors.fill: parent
source: 'http://localhost:8080/Remote.qml'
onLoaded: {
root.width = item.width
root.height = item.height
}
}
```

In the `remote` directory, we will update the `Remote.qml` file so that it uses a custom `Button` component coming from our own remote `Button.qml` file:

```qml
import QtQuick
Rectangle {
width: 320
Expand All @@ -97,78 +129,56 @@ Rectangle {
}
```

And launch our web-server again
Using a `qmldir`, we will define the content of our (remote) QML directory:

```sh
cd src
python -m SimpleHTTPServer 8080
```qmldir
Button 1.0 Button.qml
```

And our remote loader loads the main QML via HTTP again

```sh
qmlscene --resize-to-root remote.qml
```
And finally we will create our dummy `Button.qml` file:

What we see is an error
```qml
import QtQuick.Controls
```
http://localhost:8080/main2.qml:11:5: Button is not a type
Button {
}
```

So QML cannot resolve the button component when it is loaded remotely. If the code would be local `qmlscene src/main.qml` this would be no issue. Locally Qt can parse the directory and detect which components are available but remotely there is no “list-dir” function for HTTP. We can force QML to load the element using the import statement inside `main.qml`:
We can now launch our web-server (keep in mind that we now have a `remote` subdirectory):

```js
import "http://localhost:8080" as Remote

...

Remote.Button { ... }
```sh
cd src
python -m http.server --directory ./remote 8080
```

This will work then when the `qmlscene` is run again:
And remote QML loader:

```sh
qmlscene --resize-to-root remote.qml
qml -f main.qml
```

Here the full code:
## Importing a QML components directory

By defining a `qmldir` file, it's also possible to directly import a library of components from a remote repository. To do so, a classical import works:

```js
// main2.qml
import QtQuick 2.5
import "http://localhost:8080" 1.0 as Remote
import QtQuick
import "http://localhost:8080" as Remote

Rectangle {
width: 320
height: 320
color: '#ff0000'
color: 'blue'

Remote.Button {
anchors.centerIn: parent
text: 'Click Me'
text: 'Quit'
onClicked: Qt.quit()
}
}
```

A better option is to use the `qmldir` file on the server side to control the export.

```qml
// qmldir
Button 1.0 Button.qml
```

And then updating the `main.qml`:

```js
import "http://localhost:8080" 1.0 as Remote

...

Remote.Button { ... }
```

::: tip
When using components from a local file system, they are created immediately without a latency. When components are loaded via the network they are created asynchronously. This has the effect that the time of creation is unknown and an element may not yet be fully loaded when others are already completed. Take this into account when working with components loaded over the network.
:::
Expand Down
2 changes: 1 addition & 1 deletion docs/ch12-networking/src/httprequest/Thumbnail.qml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import QtQuick 2.5
import QtQuick

Rectangle {
id: root
Expand Down
2 changes: 1 addition & 1 deletion docs/ch12-networking/src/httprequest/httprequest.qml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import QtQuick 2.5
import QtQuick

Rectangle {
width: 320
Expand Down
32 changes: 32 additions & 0 deletions docs/ch12-networking/src/localfiles/colors.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<colors>
<color>
<name>red</name>
<value>#f00</value>
</color>
<color>
<name>green</name>
<value>#0f0</value>
</color>
<color>
<name>blue</name>
<value>#00f</value>
</color>
<color>
<name>cyan</name>
<value>#0ff</value>
</color>
<color>
<name>magenta</name>
<value>#f0f</value>
</color>
</color>
<color>
<name>yellow</name>
<value>#ff0</value>
</color>
<color>
<name>black</name>
<value>#000</value>
</color>
</colors>
2 changes: 1 addition & 1 deletion docs/ch12-networking/src/localfiles/localfiles.qml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import QtQuick 2.5
import QtQuick

Rectangle {
width: 360
Expand Down
Loading

0 comments on commit 1235796

Please sign in to comment.