diff --git a/core/modules/startup/rootwidget.js b/core/modules/startup/rootwidget.js
index a8ad5f8c6..4761fef85 100644
--- a/core/modules/startup/rootwidget.js
+++ b/core/modules/startup/rootwidget.js
@@ -20,6 +20,11 @@ exports.before = ["story"];
exports.synchronous = true;
exports.startup = function() {
+ // Install the HTTP client event handler
+ $tw.httpClient = new $tw.utils.HttpClient();
+ $tw.rootWidget.addEventListener("tm-http-request",function(event) {
+ $tw.httpClient.handleHttpRequest(event);
+ });
// Install the modal message mechanism
$tw.modal = new $tw.utils.Modal($tw.wiki);
$tw.rootWidget.addEventListener("tm-modal",function(event) {
diff --git a/core/modules/utils/dom/http.js b/core/modules/utils/dom/http.js
index 6e07b1040..797419b81 100644
--- a/core/modules/utils/dom/http.js
+++ b/core/modules/utils/dom/http.js
@@ -3,7 +3,7 @@ title: $:/core/modules/utils/dom/http.js
type: application/javascript
module-type: utils
-Browser HTTP support
+HTTP support
\*/
(function(){
@@ -13,11 +13,127 @@ Browser HTTP support
"use strict";
/*
-A quick and dirty HTTP function; to be refactored later. Options are:
+Manage tm-http-request events. Options are:
+wiki - the wiki object to use
+*/
+function HttpClient(options) {
+ options = options || {};
+}
+
+HttpClient.prototype.handleHttpRequest = function(event) {
+ console.log("Initiating an HTTP request",event)
+ var self = this,
+ wiki = event.widget.wiki,
+ paramObject = event.paramObject || {},
+ url = paramObject.url,
+ completionActions = paramObject.oncompletion || "",
+ progressActions = paramObject.onprogress || "",
+ bindStatus = paramObject["bind-status"],
+ bindProgress = paramObject["bind-progress"],
+ method = paramObject.method || "GET",
+ HEADER_PARAMETER_PREFIX = "header-",
+ QUERY_PARAMETER_PREFIX = "query-",
+ PASSWORD_HEADER_PARAMETER_PREFIX = "password-header-",
+ PASSWORD_QUERY_PARAMETER_PREFIX = "password-query-",
+ CONTEXT_VARIABLE_PARAMETER_PREFIX = "var-",
+ requestHeaders = {},
+ contextVariables = {},
+ setBinding = function(title,text) {
+ if(title) {
+ wiki.addTiddler(new $tw.Tiddler({title: title, text: text}));
+ }
+ };
+ if(url) {
+ setBinding(bindStatus,"pending");
+ setBinding(bindProgress,"0");
+ $tw.utils.each(paramObject,function(value,name) {
+ // Look for query- parameters
+ if(name.substr(0,QUERY_PARAMETER_PREFIX.length) === QUERY_PARAMETER_PREFIX) {
+ url = $tw.utils.setQueryStringParameter(url,name.substr(QUERY_PARAMETER_PREFIX.length),value);
+ }
+ // Look for header- parameters
+ if(name.substr(0,HEADER_PARAMETER_PREFIX.length) === HEADER_PARAMETER_PREFIX) {
+ requestHeaders[name.substr(HEADER_PARAMETER_PREFIX.length)] = value;
+ }
+ // Look for password-header- parameters
+ if(name.substr(0,PASSWORD_QUERY_PARAMETER_PREFIX.length) === PASSWORD_QUERY_PARAMETER_PREFIX) {
+ url = $tw.utils.setQueryStringParameter(url,name.substr(PASSWORD_QUERY_PARAMETER_PREFIX.length),$tw.utils.getPassword(value) || "");
+ }
+ // Look for password-query- parameters
+ if(name.substr(0,PASSWORD_HEADER_PARAMETER_PREFIX.length) === PASSWORD_HEADER_PARAMETER_PREFIX) {
+ requestHeaders[name.substr(PASSWORD_HEADER_PARAMETER_PREFIX.length)] = $tw.utils.getPassword(value) || "";
+ }
+ // Look for var- parameters
+ if(name.substr(0,CONTEXT_VARIABLE_PARAMETER_PREFIX.length) === CONTEXT_VARIABLE_PARAMETER_PREFIX) {
+ contextVariables[name.substr(CONTEXT_VARIABLE_PARAMETER_PREFIX.length)] = value;
+ }
+ });
+ // Set the request tracker tiddler
+ var requestTrackerTitle = wiki.generateNewTitle("$:/temp/HttpRequest");
+ wiki.addTiddler({
+ title: requestTrackerTitle,
+ tags: "$:/tags/HttpRequest",
+ text: JSON.stringify({
+ url: url,
+ type: method,
+ status: "inprogress",
+ headers: requestHeaders,
+ data: paramObject.body
+ })
+ });
+ $tw.utils.httpRequest({
+ url: url,
+ type: method,
+ headers: requestHeaders,
+ data: paramObject.body,
+ callback: function(err,data,xhr) {
+ var success = (xhr.status >= 200 && xhr.status < 300) ? "complete" : "error",
+ headers = {};
+ $tw.utils.each(xhr.getAllResponseHeaders().split("\r\n"),function(line) {
+ var pos = line.indexOf(":");
+ if(pos !== -1) {
+ headers[line.substr(0,pos)] = line.substr(pos + 1).trim();
+ }
+ });
+ setBinding(bindStatus,success);
+ setBinding(bindProgress,"100");
+ var results = {
+ status: xhr.status.toString(),
+ statusText: xhr.statusText,
+ error: (err || "").toString(),
+ data: (data || "").toString(),
+ headers: JSON.stringify(headers)
+ };
+ // Update the request tracker tiddler
+ wiki.addTiddler(new $tw.Tiddler(wiki.getTiddler(requestTrackerTitle),{
+ status: success,
+ }));
+ wiki.invokeActionString(completionActions,undefined,$tw.utils.extend({},contextVariables,results),{parentWidget: $tw.rootWidget});
+ // console.log("Back!",err,data,xhr);
+ },
+ progress: function(lengthComputable,loaded,total) {
+ if(lengthComputable) {
+ setBinding(bindProgress,"" + Math.floor((loaded/total) * 100))
+ }
+ wiki.invokeActionString(progressActions,undefined,{
+ lengthComputable: lengthComputable ? "yes" : "no",
+ loaded: loaded,
+ total: total
+ },{parentWidget: $tw.rootWidget});
+ }
+ });
+ }
+};
+
+exports.HttpClient = HttpClient;
+
+/*
+Make an HTTP request. Options are:
url: URL to retrieve
headers: hashmap of headers to send
type: GET, PUT, POST etc
callback: function invoked with (err,data,xhr)
+ progress: optional function invoked with (lengthComputable,loaded,total)
returnProp: string name of the property to return as first argument of callback
*/
exports.httpRequest = function(options) {
@@ -83,8 +199,16 @@ exports.httpRequest = function(options) {
options.callback($tw.language.getString("Error/XMLHttpRequest") + ": " + this.status,null,this);
}
};
+ // Handle progress
+ if(options.progress) {
+ request.onprogress = function(event) {
+ console.log("Progress event",event)
+ options.progress(event.lengthComputable,event.loaded,event.total);
+ };
+ }
// Make the request
request.open(type,url,true);
+ // Headers
if(headers) {
$tw.utils.each(headers,function(header,headerTitle,object) {
request.setRequestHeader(headerTitle,header);
@@ -96,6 +220,7 @@ exports.httpRequest = function(options) {
if(!hasHeader("X-Requested-With") && !isSimpleRequest(type,headers)) {
request.setRequestHeader("X-Requested-With","TiddlyWiki");
}
+ // Send data
try {
request.send(data);
} catch(e) {
@@ -104,4 +229,19 @@ exports.httpRequest = function(options) {
return request;
};
+exports.setQueryStringParameter = function(url,paramName,paramValue) {
+ var URL = $tw.browser ? window.URL : require("url").URL,
+ newUrl;
+ try {
+ newUrl = new URL(url);
+ } catch(e) {
+ }
+ if(newUrl && paramName) {
+ newUrl.searchParams.set(paramName,paramValue || "");
+ return newUrl.toString();
+ } else {
+ return url;
+ }
+};
+
})();
diff --git a/core/modules/wiki.js b/core/modules/wiki.js
index 6ae16a2b4..85cacaa32 100755
--- a/core/modules/wiki.js
+++ b/core/modules/wiki.js
@@ -1415,6 +1415,14 @@ exports.checkTiddlerText = function(title,targetText,options) {
return text === targetText;
}
+/*
+Execute an action string without an associated context widget
+*/
+exports.invokeActionString = function(actions,event,variables,options) {
+ var widget = this.makeWidget(null,{parentWidget: options.parentWidget});
+ widget.invokeActionString(actions,null,event,variables);
+};
+
/*
Read an array of browser File objects, invoking callback(tiddlerFieldsArray) once they're all read
*/
diff --git a/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request Example Zotero.tid b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request Example Zotero.tid
new file mode 100644
index 000000000..9419f526d
--- /dev/null
+++ b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request Example Zotero.tid
@@ -0,0 +1,112 @@
+title: WidgetMessage: tm-http-request Example - Zotero
+tags: $:/tags/Macro
+
+\procedure select-zotero-group()
+Specify the Zotero group ID to import
+<$edit-text tiddler="$:/config/zotero-group" tag="input"/> or
+<$select tiddler="$:/config/zotero-group">
+
+
+
+$select>
+\end
+
+\procedure zotero-save-item(item)
+<$action-createtiddler
+ $basetitle={{{ =[[_zotero_import ]] =[- jsonget[key]] =[[ ]] =[
- jsonget[title]] +[join[]] }}}
+ text={{{ [
- jsonget[title]] }}}
+ tags="$:/tags/ZoteroImport"
+>
+ <$action-setmultiplefields $tiddler=<> $fields="[
- jsonindexes[]addprefix[zotero-]]" $values="[
- jsonindexes[]] :map[
- jsongetelse[.XXXXX.]]"/>
+ <$list filter="[
- jsonindexes[creators]]" variable="creatorIndex">
+ <$action-setmultiplefields $tiddler=<> $fields="[
- jsonget[creators],,[creatorType]addprefix[zotero-]]" $values="[
- jsonget[creators],,[lastName]] [
- jsonget[creators],,[firstName]] +[join[, ]] :else[
- jsonget[creators],,[name]] "/>
+ $list>
+$action-createtiddler>
+\end zotero-save-item
+
+\procedure zotero-save-items(data)
+<$list filter="[jsonindexes[]] :map[jsonextract,[data]]" variable="item">
+ <$macrocall $name="zotero-save-item" item=<
- >/>
+$list>
+\end zotero-save-items
+
+\procedure zotero-get-items(start:"0",limit:"25")
+
+\procedure completion()
+\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]
+ <$action-log msg="In completion"/>
+ <$action-log/>
+
+ <$list filter="[compare:number:gteq[200]compare:number:lteq[299]]" variable="ignore">
+
+ <$macrocall $name="zotero-save-items" data=<>/>
+
+ <$list filter="[jsonget[total-results]subtractsubtractcompare:number:gt[0]]" variable="ignore">
+ <$macrocall $name="zotero-get-items" start={{{ [add] }}} limit=<>/>
+ $list>
+ $list>
+\end completion
+
+\procedure progress()
+\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]
+ <$action-log message="In progress-actions"/>
+\end progress
+
+\procedure request-url()
+\rules only transcludeinline transcludeblock filteredtranscludeinline filteredtranscludeblock
+https://api.zotero.org/groups/{{$:/config/zotero-group}}/items/
+\end request-url
+
+<$wikify name="url" text=<>>
+ <$action-sendmessage
+ $message="tm-http-request"
+ url=<>
+ method="GET"
+ query-format="json"
+ query-sort="title"
+ query-start=<>
+ query-limit=<>
+ header-accept="application/json"
+ bind-status="$:/temp/zotero/status"
+ bind-progress="$:/temp/zotero/progress"
+ oncompletion=<>
+ onprogress=<