From 4ec4ce407cf2c55811ff3876b49254785761e2dd Mon Sep 17 00:00:00 2001 From: Jerome Pasion Date: Tue, 23 Jul 2013 14:34:35 +0200 Subject: Doc: Modified the term for the Qt global object. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QtQml::Qt is a "QML global object" to differentiate it from the Qt namespace. "QML Qt object" "QML Qt global object" "Qt global object" (in the context of QML-only pages) Change-Id: I1d2896ad48e8432c5eb8b18f05af247986bff336 Reviewed-by: Topi Reiniƶ Reviewed-by: Laszlo Papp Reviewed-by: Alan Alpert --- examples/quick/text/doc/src/text.qdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/quick/text/doc/src/text.qdoc b/examples/quick/text/doc/src/text.qdoc index c93d2db8aa..3a300fbcf3 100644 --- a/examples/quick/text/doc/src/text.qdoc +++ b/examples/quick/text/doc/src/text.qdoc @@ -50,7 +50,7 @@ or finally using a FontLoader and specifying a remote font file: \snippet text/fonts/fonts.qml fontloaderremote - 'Available Fonts' shows how to use the QML global Qt object and a list view + 'Available Fonts' shows how to use the QML \l{QtQml::Qt}{Qt} global object and a list view to display all the fonts available on the system. The ListView type uses the list of fonts available as its model: \snippet text/fonts/availableFonts.qml model -- cgit v1.2.3 From c9ec0c206544b37139057932a6dceed88565a75f Mon Sep 17 00:00:00 2001 From: Topi Reinio Date: Fri, 21 Jun 2013 10:50:48 +0200 Subject: 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 --- .../demos/tweetsearch/content/TweetDelegate.qml | 6 +- .../demos/tweetsearch/content/TweetsModel.qml | 96 +++++++++++++++------- .../quick/demos/tweetsearch/content/tweetsearch.js | 61 ++++++++++++-- .../demos/tweetsearch/doc/src/tweetsearch.qdoc | 45 +++++++++- examples/quick/demos/tweetsearch/tweetsearch.pro | 5 ++ examples/quick/demos/tweetsearch/tweetsearch.qml | 20 +++-- 6 files changed, 180 insertions(+), 53 deletions(-) (limited to 'examples') 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) + + '' + + textForEntity(links[i]) + '' + + text.substring(links[i].indices[1]) + } + return text.replace(/\n/g, '
'); } 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 -- cgit v1.2.3