Skip to content

Commit

Permalink
Fixed qmllint warnings for code samples of chapter 12, except from th…
Browse files Browse the repository at this point in the history
…ose triggered by QTBUG-97597.
  • Loading branch information
eunoia-cl committed Oct 18, 2021
1 parent 9bf4ea1 commit 97d428f
Show file tree
Hide file tree
Showing 37 changed files with 1,117 additions and 910 deletions.
139 changes: 12 additions & 127 deletions docs/ch12-networking/authentication.md
Expand Up @@ -10,20 +10,11 @@ OAuth is currently not part of a QML/JS API. So you would need to write some C++

Here are some links which we find useful:


* [http://oauth.net/](http://oauth.net/)


* [http://hueniverse.com/oauth/](http://hueniverse.com/oauth/)


* [https://github.com/pipacs/o2](https://github.com/pipacs/o2)


* [http://www.johanpaul.com/blog/2011/05/oauth2-explained-with-qt-quick/](http://www.johanpaul.com/blog/2011/05/oauth2-explained-with-qt-quick/)



## Integration example

In this section, we will go through an example of OAuth integration using the [Spotify API](https://developer.spotify.com/documentation/web-api/). This example uses a combination of C++ classes and QML/JS. To discover more on this integration, please refer to Chapter 16.
Expand All @@ -47,147 +38,41 @@ The process is divided in two phases:
1. The application connects to the Spotify API, which in turns requests the user to authorize it;
2. If authorized, the application displays the list of the top ten favourite artists of the user.

#### Authorizing the app

Let's start with the first step:

```qml
import QtQuick
import QtQuick.Window
import QtQuick.Controls
<<< @/docs/ch12-networking/src/oauth/main.qml#imports

import Spotify
When the application starts, we will first import a custom library, `Spotify`, that defines a `SpotifyAPI` component (we'll come to that later). This component will then be instanciated:

ApplicationWindow {
width: 320
height: 568
visible: true
title: qsTr("Spotify OAuth2")
<<< @/docs/ch12-networking/src/oauth/main.qml#setup

BusyIndicator {
visible: !spotifyApi.isAuthenticated
anchors.centerIn: parent
}
Once the application has been loaded, the `SpotifyAPI` component will request an authorization to Spotify:

SpotifyAPI {
id: spotifyApi
onIsAuthenticatedChanged: if(isAuthenticated) spotifyModel.update()
}
<<< @/docs/ch12-networking/src/oauth/main.qml#on-completed

Component.onCompleted: {
spotifyApi.setCredentials("CLIENT_ID", "CLIENT_SECRET")
spotifyApi.authorize()
}
}
```

When the application starts, we will first import a custom library, `Spotify`, that defines a `SpotifyAPI` component (we'll come to that later).

```qml
import Spotify
```

Once the application has been loaded, this `SpotifyAPI` component will request an authorization to Spotify:

```qml
Component.onCompleted: {
spotifyApi.setCredentials("CLIENT_ID", "CLIENT_SECRET")
spotifyApi.authorize()
}
```
Until the authorization is provided, a busy indicator will be displayed in the center of the app.

:::tip
Please note that for security reasons, the API credentials should never be put directly into a QML file!
:::

Until the authorization is provided, a busy indicator will be displayed in the center of the app:

```qml
BusyIndicator {
visible: !spotifyApi.isAuthenticated
anchors.centerIn: parent
}
```
#### Listing the user's favorite artists

The next step happens when the authorization has been granted. To display the list of artists, we will use the Model/View/Delegate pattern:

```qml
SpotifyModel {
id: spotifyModel
spotifyApi: spotifyApi
}
<<< @/docs/ch12-networking/src/oauth/main.qml#model-view{3,10,13,22,42,51,57,61}

ListView {
visible: spotifyApi.isAuthenticated
width: parent.width
height: parent.height
model: spotifyModel
delegate: Pane {
topPadding: 0
Column {
width: 300
spacing: 10
Rectangle {
height: 1
width: parent.width
color: model.index > 0 ? "#3d3d3d" : "transparent"
}
Row {
spacing: 10
Item {
width: 20
height: width
Rectangle {
width: 20
height: 20
anchors.top: parent.top
anchors.right: parent.right
color: "black"
Label {
anchors.centerIn: parent
font.pointSize: 16
text: model.index + 1
color: "white"
}
}
}
Image {
width: 80
height: width
source: model.imageURL
fillMode: Image.PreserveAspectFit
}
Column {
Label { text: model.name; font.pointSize: 16; font.bold: true }
Label { text: "Followers: " + model.followersCount }
}
}
}
}
}
}
```

The model `SpotifyModel` is defined in the `Spotify` library. To work properly, it needs a `SpotifyAPI`:

```qml
SpotifyModel {
id: spotifyModel
spotifyApi: spotifyApi
}
```
The model `SpotifyModel` is defined in the `Spotify` library. To work properly, it needs a `SpotifyAPI`.

The ListView displays a vertical list of artists. An artist is represented by a name, an image and the total count of followers.

### SpotifyAPI

Let's now get a bit deeper into the authentication flow. We'll focus on the `SpotifyAPI` class, a `QML_ELEMENT` defined on the C++ side.


```cpp
#ifndef SPOTIFYAPI_H
#define SPOTIFYAPI_H
Expand Down
58 changes: 3 additions & 55 deletions docs/ch12-networking/http-requests.md
Expand Up @@ -24,7 +24,7 @@ function request() {
For a response, you can get the XML format or just the raw text. It is possible to iterate over the resulting XML but more commonly used is the raw text nowadays for a JSON formatted response. The JSON document will be used to convert text to a JS object using `JSON.parse(text)`.

```js
...
/* ... */
} else if(xhr.readyState === XMLHttpRequest.DONE) {
var object = JSON.parse(xhr.responseText.toString());
print(JSON.stringify(object, null, 2));
Expand Down Expand Up @@ -87,64 +87,12 @@ for(var i=0; i<obj.items.length; i++) {

As a valid JS array, we can use the `obj.items` array also as a model for a list view. We will try to accomplish this now. First, we need to retrieve the response and convert it into a valid JS object. And then we can just set the `response.items` property as a model to a list view.

```js
function request() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(...) {
...
} else if(xhr.readyState === XMLHttpRequest.DONE) {
var response = JSON.parse(xhr.responseText.toString());
// set JS object as model for listview
view.model = response.items;
}
}
xhr.open("GET", "http://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1&tags=munich");
xhr.send();
}
```
<<< @/docs/ch12-networking/src/httprequest/httprequest.qml#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
Rectangle {
width: 320
height: 480
ListView {
id: view
anchors.fill: parent
delegate: Thumbnail {
width: view.width
text: modelData.title
iconSource: modelData.media.m
}
}
function request() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
print('HEADERS_RECEIVED')
} else if(xhr.readyState === XMLHttpRequest.DONE) {
print('DONE')
var json = JSON.parse(xhr.responseText.toString())
view.model = json.items
}
}
xhr.open("GET", "http://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1&tags=munich");
xhr.send();
}
Component.onCompleted: {
request()
}
}
```
<<< @/docs/ch12-networking/src/httprequest/httprequest.qml#global

When the document is fully loaded ( `Component.onCompleted` ) we request the latest feed content from Flickr. On arrival, we parse the JSON response and set the `items` array as the model for our view. The list view has a delegate, which displays the thumbnail icon and the title text in a row.

The other option would be to have a placeholder `ListModel` and append each item onto the list model. To support larger models it is required to support pagination (e.g page 1 of 10) and lazy content retrieval.

86 changes: 5 additions & 81 deletions docs/ch12-networking/local-files.md
Expand Up @@ -3,102 +3,26 @@
Is it also possible to load local (XML/JSON) files using the XMLHttpRequest. For example a local file named “colors.json” can be loaded using:

```js
xhr.open("GET", "colors.json");
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
Rectangle {
width: 360
height: 360
color: '#000'
GridView {
id: view
anchors.fill: parent
cellWidth: width/4
cellHeight: cellWidth
delegate: Rectangle {
width: view.cellWidth
height: view.cellHeight
color: modelData.value
}
}
function request() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
print('HEADERS_RECEIVED')
} else if(xhr.readyState === XMLHttpRequest.DONE) {
print('DONE');
var obj = JSON.parse(xhr.responseText.toString());
view.model = obj.colors
}
}
xhr.open("GET", "colors.json");
xhr.send();
}
Component.onCompleted: {
request()
}
}
```
<<< @/docs/ch12-networking/src/localfiles/localfiles.qml#global

:::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:
By default, using GET on a local file is disabled by the QML engine. To overcome this limitation, you can set the `QML_XHR_ALLOW_FILE_READ` environment variable to `1`:

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

The issue is when allowing a QML application to read local files through an `XMLHttpRequest`, hence `XHR`, this opens up the entire file system for reading, which is a potential security issue. Qt will allow you to read local files only if the environment variable is set, so that this is a concious decision.
:::




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

```qml
import QtQuick
import QtQml.XmlListModel
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' }
}
}
```
<<< @/docs/ch12-networking/src/localfiles/localfilesxmlmodel.qml#global

With the XmlListModel it is only possible to read XML files and not JSON files.

0 comments on commit 97d428f

Please sign in to comment.