aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorTopi Reinio <topi.reinio@digia.com>2013-06-21 10:50:48 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-07-26 10:16:27 +0200
commitc9ec0c206544b37139057932a6dceed88565a75f (patch)
tree90ef92716ec84e074497018e7046ae0b7521bc4d /examples
parent1099b26535fedbaaa134ccb63310362951fce847 (diff)
Doc: Update Tweet Search Demo to use Twitter Search API v1.1
Twitter REST API v1 is no longer supported. This change updates the Tweet Search Demo to use the new version (v1.1). Specifically, - Use of OAuth tokens (authentication required in v1.1) - JSON parsing for results instead of XML - Use of url/hashtag/username entities returned in search results Also, update the documentation to discuss authentication and registering the application to dev.twitter.com. Task-number: QTBUG-31745 Change-Id: I00cd7b07f065babb03483daabe8df22f22995c29 Reviewed-by: Alan Alpert <aalpert@blackberry.com>
Diffstat (limited to 'examples')
-rw-r--r--examples/quick/demos/tweetsearch/content/TweetDelegate.qml6
-rw-r--r--examples/quick/demos/tweetsearch/content/TweetsModel.qml96
-rw-r--r--examples/quick/demos/tweetsearch/content/tweetsearch.js61
-rw-r--r--examples/quick/demos/tweetsearch/doc/src/tweetsearch.qdoc45
-rw-r--r--examples/quick/demos/tweetsearch/tweetsearch.pro5
-rw-r--r--examples/quick/demos/tweetsearch/tweetsearch.qml20
6 files changed, 180 insertions, 53 deletions
diff --git a/examples/quick/demos/tweetsearch/content/TweetDelegate.qml b/examples/quick/demos/tweetsearch/content/TweetDelegate.qml
index 8cd22110d4..e8bfff437b 100644
--- a/examples/quick/demos/tweetsearch/content/TweetDelegate.qml
+++ b/examples/quick/demos/tweetsearch/content/TweetDelegate.qml
@@ -103,7 +103,7 @@ Item {
Text {
id: name
- text: Helper.realName(model.name)
+ text: model.name
anchors { left: avatar.right; leftMargin: 10; top: avatar.top; topMargin: -3 }
font.pixelSize: 12
font.bold: true
@@ -121,7 +121,7 @@ Item {
color: "#adebff"
linkColor: "white"
onLinkActivated: {
- var tag = link.split("http://search.twitter.com/search?q=%23")
+ var tag = link.split("https://twitter.com/search?q=%23")
var user = link.split("https://twitter.com/")
if (tag[1] != undefined) {
mainListView.positionViewAtBeginning()
@@ -166,7 +166,7 @@ Item {
Text {
id: username
- text: Helper.twitterName(model.name)
+ text: model.twitterName
x: 10; anchors { top: avatar2.top; topMargin: -3 }
font.pixelSize: 12
font.bold: true
diff --git a/examples/quick/demos/tweetsearch/content/TweetsModel.qml b/examples/quick/demos/tweetsearch/content/TweetsModel.qml
index cd91a787b7..7d813d18c8 100644
--- a/examples/quick/demos/tweetsearch/content/TweetsModel.qml
+++ b/examples/quick/demos/tweetsearch/content/TweetsModel.qml
@@ -39,53 +39,87 @@
****************************************************************************/
import QtQuick 2.0
-import QtQuick.XmlListModel 2.0
+import "tweetsearch.js" as Helper
Item {
id: wrapper
- property variant model: xmlModel
+ // Insert valid consumer key and secret tokens below
+ // See https://dev.twitter.com/apps
+//! [auth tokens]
+ property string consumerKey : ""
+ property string consumerSecret : ""
+//! [auth tokens]
+ property string bearerToken : ""
+
+ property variant model: tweets
property string from : ""
property string phrase : ""
- property string mode : "everyone"
- property int status: xmlModel.status
-
- function reload() { xmlModel.reload(); }
-
- property bool isLoading: status == XmlListModel.Loading
+ property int status: XMLHttpRequest.UNSENT
+ property bool isLoading: status === XMLHttpRequest.LOADING
property bool wasLoading: false
signal isLoaded
- XmlListModel {
- id: xmlModel
+ ListModel { id: tweets }
- onStatusChanged: {
- if (status == XmlListModel.Ready && wasLoading == true)
- wrapper.isLoaded()
- if (status == XmlListModel.Loading)
- wasLoading = true;
- else
- wasLoading = false;
- }
+ function encodePhrase(x) { return encodeURIComponent(x); }
- function encodePhrase(x) { return encodeURIComponent(x); }
+ function reload() {
+ tweets.clear()
- source: (from == "" && phrase == "") ? "" :
- 'http://search.twitter.com/search.atom?from='+from+"&rpp=10&phrase="+encodePhrase(phrase)
+ if (from == "" && phrase == "")
+ return;
- namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom'; " +
- "declare namespace twitter=\"http://api.twitter.com/\";";
+//! [requesting]
+ var req = new XMLHttpRequest;
+ req.open("GET", "https://api.twitter.com/1.1/search/tweets.json?from=" + from +
+ "&count=10&q=" + encodePhrase(phrase));
+ req.setRequestHeader("Authorization", "Bearer " + bearerToken);
+ req.onreadystatechange = function() {
+ status = req.readyState;
+ if (status === XMLHttpRequest.DONE) {
+ var objectArray = JSON.parse(req.responseText);
+ if (objectArray.errors !== undefined)
+ console.log("Error fetching tweets: " + objectArray.errors[0].message)
+ else {
+ for (var key in objectArray.statuses) {
+ var jsonObject = objectArray.statuses[key];
+ tweets.append(jsonObject);
+ }
+ }
+ if (wasLoading == true)
+ wrapper.isLoaded()
+ }
+ wasLoading = (status === XMLHttpRequest.LOADING);
+ }
+ req.send();
+//! [requesting]
+ }
- query: "/feed/entry"
+ onPhraseChanged: reload();
+ onFromChanged: reload();
- XmlRole { name: "id"; query: "id/string()" }
- XmlRole { name: "content"; query: "content/string()" }
- XmlRole { name: "published"; query: "published/string()" }
- XmlRole { name: "source"; query: "twitter:source/string()" }
- XmlRole { name: "name"; query: "author/name/string()" }
- XmlRole { name: "uri"; query: "author/uri/string()" }
- XmlRole { name: "image"; query: "link[@rel = 'image']/@href/string()" }
+ Component.onCompleted: {
+ if (consumerKey === "" || consumerSecret == "") {
+ bearerToken = encodeURIComponent(Helper.demoToken())
+ return;
+ }
+ var authReq = new XMLHttpRequest;
+ authReq.open("POST", "https://api.twitter.com/oauth2/token");
+ authReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
+ authReq.setRequestHeader("Authorization", "Basic " + Qt.btoa(consumerKey + ":" + consumerSecret));
+ authReq.onreadystatechange = function() {
+ if (authReq.readyState === XMLHttpRequest.DONE) {
+ var jsonResponse = JSON.parse(authReq.responseText);
+ if (jsonResponse.errors !== undefined)
+ console.log("Authentication error: " + jsonResponse.errors[0].message)
+ else
+ bearerToken = jsonResponse.access_token;
+ }
+ }
+ authReq.send("grant_type=client_credentials");
}
+
}
diff --git a/examples/quick/demos/tweetsearch/content/tweetsearch.js b/examples/quick/demos/tweetsearch/content/tweetsearch.js
index 9b8638f69e..42a76c99fc 100644
--- a/examples/quick/demos/tweetsearch/content/tweetsearch.js
+++ b/examples/quick/demos/tweetsearch/content/tweetsearch.js
@@ -1,19 +1,62 @@
.pragma library
-function twitterName(str)
+function formatDate(date)
{
- var s = str.split("(")
- return s[0]
+ var da = new Date(date)
+ return da.toDateString()
}
-function realName(str)
+function demoToken()
{
- var s = str.split("(")
- return s[1].substring(0, s[1].length-1)
+ var a = new Array(22).join('A')
+ return a + String.fromCharCode(0x44, 0x69, 0x4a, 0x52, 0x51, 0x41, 0x41, 0x41, 0x41,
+ 0x41, 0x41, 0x74, 0x2b, 0x72, 0x6a, 0x6c, 0x2b, 0x71,
+ 0x6d, 0x7a, 0x30, 0x72, 0x63, 0x79, 0x2b, 0x42, 0x62,
+ 0x75, 0x58, 0x42, 0x42, 0x73, 0x72, 0x55, 0x48, 0x47,
+ 0x45, 0x67, 0x3d, 0x71, 0x30, 0x45, 0x4b, 0x32, 0x61,
+ 0x57, 0x71, 0x51, 0x4d, 0x62, 0x31, 0x35, 0x67, 0x43,
+ 0x5a, 0x4e, 0x77, 0x5a, 0x6f, 0x39, 0x79, 0x71, 0x61,
+ 0x65, 0x30, 0x68, 0x70, 0x65, 0x32, 0x46, 0x44, 0x73,
+ 0x53, 0x39, 0x32, 0x57, 0x41, 0x75, 0x30, 0x67)
}
-function formatDate(date)
+function linkForEntity(entity)
{
- var da = new Date(date)
- return da.toDateString()
+ return (entity.url ? entity.url :
+ (entity.screen_name ? 'https://twitter.com/' + entity.screen_name :
+ 'https://twitter.com/search?q=%23' + entity.text))
+}
+
+function textForEntity(entity)
+{
+ return (entity.display_url ? entity.display_url :
+ (entity.screen_name ? entity.screen_name : entity.text))
+}
+
+function insertLinks(text, entities)
+{
+ if (typeof text !== 'string')
+ return "";
+
+ if (!entities)
+ return text;
+
+ // Add all links (urls, usernames and hashtags) to an array and sort them in
+ // descending order of appearance in text
+ var links = []
+ if (entities.urls)
+ links = entities.urls.concat(entities.hashtags, entities.user_mentions)
+ else if (entities.url)
+ links = entities.url.urls
+
+ links.sort(function(a, b) { return b.indices[0] - a.indices[0] })
+
+ for (var i = 0; i < links.length; i++) {
+ var offset = links[i].url ? 0 : 1
+ text = text.substring(0, links[i].indices[0] + offset) +
+ '<a href=\"' + linkForEntity(links[i]) + '\">' +
+ textForEntity(links[i]) + '</a>' +
+ text.substring(links[i].indices[1])
+ }
+ return text.replace(/\n/g, '<br>');
}
diff --git a/examples/quick/demos/tweetsearch/doc/src/tweetsearch.qdoc b/examples/quick/demos/tweetsearch/doc/src/tweetsearch.qdoc
index 9ba252fcb5..a56ed0d7e9 100644
--- a/examples/quick/demos/tweetsearch/doc/src/tweetsearch.qdoc
+++ b/examples/quick/demos/tweetsearch/doc/src/tweetsearch.qdoc
@@ -32,5 +32,48 @@
\brief A Twitter search client with 3D effects.
\image qtquick-demo-tweetsearch-med-1.png
\image qtquick-demo-tweetsearch-med-2.png
-*/
+ \section1 Demo Introduction
+
+ The Tweet Search demo searches items posted to Twitter service
+ using a number of query parameters. Search can be done for tweets
+ from a specified user, a hashtag or a search phrase.
+
+ The search result is a list of items showing the contents of the
+ tweet as well as the name and image of the user who posted it.
+ Hashtags, names and links in the content are clickable. Clicking
+ on the image will flip the item to reveal more information.
+
+ \section1 Running the Demo
+
+ Tweet Search uses Twitter API v1.1 for running seaches.
+
+ \section2 Authentication
+
+ Each request must be authenticated on behalf of the application.
+ For demonstration purposes, the application uses a hard-coded
+ token for identifying itself to the Twitter service. However, this
+ token is subject to rate limits for the number of requests as well
+ as possible expiration.
+
+ If you are having authentication or rate limit problems running the
+ demo, obtain a set of application-specific tokens (consumer
+ key and consumer secret) by registering a new application on
+ \l{https://dev.twitter.com/apps}.
+
+ Type in the two token values in \e {TweetsModel.qml}:
+
+ \snippet demos/tweetsearch/content/TweetsModel.qml auth tokens
+
+ Rebuild and run the demo.
+
+ \section2 JSON Parsing
+
+ Search results are returned in JSON (JavaScript Object Notation)
+ format. \c TweetsModel uses an \l XMLHTTPRequest object to send
+ an HTTP GET request, and calls JSON.parse() on the returned text
+ string to convert it to a JavaScript object. Each object
+ representing a tweet is then added to a \l ListModel:
+
+ \snippet demos/tweetsearch/content/TweetsModel.qml requesting
+*/
diff --git a/examples/quick/demos/tweetsearch/tweetsearch.pro b/examples/quick/demos/tweetsearch/tweetsearch.pro
index b063cc4106..27c34bac5d 100644
--- a/examples/quick/demos/tweetsearch/tweetsearch.pro
+++ b/examples/quick/demos/tweetsearch/tweetsearch.pro
@@ -4,5 +4,10 @@ QT += quick qml
SOURCES += main.cpp
RESOURCES += tweetsearch.qrc
+OTHER_FILES = tweetsearch.qml \
+ content/*.qml \
+ content/*.js \
+ content/resources/*
+
target.path = $$[QT_INSTALL_EXAMPLES]/quick/demos/tweetsearch
INSTALLS += target
diff --git a/examples/quick/demos/tweetsearch/tweetsearch.qml b/examples/quick/demos/tweetsearch/tweetsearch.qml
index d7e77ceb4b..19d3b5e708 100644
--- a/examples/quick/demos/tweetsearch/tweetsearch.qml
+++ b/examples/quick/demos/tweetsearch/tweetsearch.qml
@@ -40,6 +40,7 @@
import QtQuick 2.0
import "content"
+import "content/tweetsearch.js" as Helper
Rectangle {
id: main
@@ -47,7 +48,6 @@ Rectangle {
height: 480
color: "#d6d6d6"
- property string searchTerms: ""
property int inAnimDur: 250
property int counter: 0
property alias isLoading: tweetsModel.isLoading
@@ -85,13 +85,15 @@ Rectangle {
onTriggered: {
main.counter--;
var id = tweetsModel.model.get(idx[main.counter]).id
- mainListView.add( { "statusText": tweetsModel.model.get(main.counter).content,
- "name": tweetsModel.model.get(main.counter).name,
- "userImage": tweetsModel.model.get(main.counter).image,
- "source": tweetsModel.model.get(main.counter).source,
- "id": id,
- "uri": tweetsModel.model.get(main.counter).uri,
- "published": tweetsModel.model.get(main.counter).published } );
+ var item = tweetsModel.model.get(main.counter)
+ mainListView.add( { "statusText": Helper.insertLinks(item.text, item.entities),
+ "twitterName": item.user.screen_name,
+ "name" : item.user.name,
+ "userImage": item.user.profile_image_url,
+ "source": item.source,
+ "id": id,
+ "uri": Helper.insertLinks(item.user.url, item.user.entities),
+ "published": item.created_at } );
ids.push(id)
}
}
@@ -107,7 +109,7 @@ Rectangle {
PropertyAction { property: "appear"; value: 250 }
}
- onDragEnded: if (header.refresh) { tweetsModel.model.reload() }
+ onDragEnded: if (header.refresh) { tweetsModel.reload() }
ListHeader {
id: header