qonsole.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. /* Copyright (c) 2012-2013 Epimorphics Ltd. Released under Apache License 2.0 http://www.apache.org/licenses/ */
  2. var qonsole = function() {
  3. "use strict";
  4. var YASQE = require('yasqe'),
  5. YASR = require('yasr');
  6. /**
  7. * Escape html function, inspired by http://stackoverflow.com/questions/5499078/fastest-method-to-escape-html-tags-as-html-entities
  8. */
  9. var escapeString = function(unescaped) {
  10. if (!unescaped) return '';
  11. return unescaped.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  12. }
  13. /**
  14. * Some custom requirements for Jena, on how to present the bindings. I.e., bnodes prefixed with _:, literals with surrounding quotes, and URIs with brackets
  15. */
  16. YASR.plugins.table.defaults.getCellContent = function (yasr, plugin, bindings, variable, context) {
  17. var binding = bindings[variable];
  18. var value = null;
  19. if (binding.type == "uri") {
  20. var title = null;
  21. var href = binding.value;
  22. var visibleString = escapeString(href);
  23. var prefixed = false;
  24. if (context.usedPrefixes) {
  25. for (var prefix in context.usedPrefixes) {
  26. if (visibleString.indexOf(context.usedPrefixes[prefix]) == 0) {
  27. visibleString = prefix + ':' + href.substring(context.usedPrefixes[prefix].length);
  28. prefixed = true;
  29. break;
  30. }
  31. }
  32. }
  33. if (!prefixed) visibleString = "&lt;" + visibleString + "&gt;";
  34. value = "<a " + (title? "title='" + href + "' ": "") + "class='uri' target='_blank' href='" + href + "'>" + visibleString + "</a>";
  35. } else if (binding.type == "bnode"){
  36. value = "<span class='nonUri'>_:" + escapeString(binding.value) + "</span>";
  37. } else if (binding.type == "literal") {
  38. var stringRepresentation = escapeString(binding.value);
  39. if (binding["xml:lang"]) {
  40. stringRepresentation = '"' + stringRepresentation + '"@' + binding["xml:lang"];
  41. } else if (binding.datatype) {
  42. var xmlSchemaNs = "http://www.w3.org/2001/XMLSchema#";
  43. var dataType = binding.datatype;
  44. if (dataType.indexOf(xmlSchemaNs) == 0) {
  45. dataType = "xsd:" + dataType.substring(xmlSchemaNs.length);
  46. } else {
  47. dataType = "<" + dataType + ">";
  48. }
  49. stringRepresentation = '"' + stringRepresentation + '"^^' + dataType;
  50. } else {
  51. //just put quotes around it
  52. stringRepresentation = '"' + stringRepresentation + '"';
  53. }
  54. value = "<span class='nonUri'>" + stringRepresentation + "</span>";
  55. } else {
  56. //this is a catch-all: when using e.g. a csv content type, the bindings are not typed
  57. value = escapeString(binding.value);
  58. }
  59. return "<div>" + value + "</div>";
  60. };
  61. /* JsLint */
  62. /*
  63. * global sprintf, testCSS, loadConfig, bindEvents, $, onConfigLoaded,
  64. * updatePrefixDeclaration, _, showCurrentQuery, setCurrentEndpoint,
  65. * setCurrentFormat, elementVisible, runQuery, onLookupPrefix,
  66. * startTimingResults, onAddPrefix, initQuery, CodeMirror, onQuerySuccess,
  67. * onQueryFail, ajaxDataType, resetResults, XMLSerializer, showTableResult,
  68. * showCodeMirrorResult
  69. */
  70. /* --- module vars --- */
  71. /** The loaded configuration */
  72. var _config = {};
  73. var yasqe = null;
  74. var yasr = null;
  75. var _startTime = 0;
  76. var _outstandingQueries = 0;
  77. /* --- utils --- */
  78. /**
  79. * Return the string representation of the given XML value, which may be a
  80. * string or a DOM object
  81. */
  82. var xmlToString = function(xmlData) {
  83. var xs = _.isString(xmlData) ? xmlData : null;
  84. if (!xs && window.ActiveXObject && xmlData.xml) {
  85. xs = xmlData.xml;
  86. }
  87. if (!xs) {
  88. xs = new XMLSerializer().serializeToString(xmlData);
  89. }
  90. return xs;
  91. };
  92. /** Browser sniffing */
  93. var isOpera = function() {
  94. return !!(window.opera && window.opera.version);
  95. }; // Opera 8.0+
  96. var isFirefox = function() {
  97. return testCSS('MozBoxSizing');
  98. }; // FF 0.8+
  99. var isSafari = function() {
  100. return Object.prototype.toString.call(window.HTMLElement).indexOf(
  101. 'Constructor') > 0;
  102. }; // At least Safari 3+: "[object HTMLElementConstructor]"
  103. var isChrome = function() {
  104. return !isSafari() && testCSS('WebkitTransform');
  105. }; // Chrome 1+
  106. var isIE = function() {
  107. return /* @cc_on!@ */false || testCSS('msTransform');
  108. }; // At least IE6
  109. var testCSS = function(prop) {
  110. return document.documentElement.style.hasOwnProperty(prop);
  111. };
  112. /* --- application code --- */
  113. /** Initialisation - only called once */
  114. var init = function(config) {
  115. initYasqe();
  116. loadConfig(config);
  117. bindEvents();
  118. };
  119. var initYasqe = function() {
  120. yasqe = YASQE(document.getElementById("query-edit-cm"), {
  121. sparql: {
  122. showQueryButton: true,
  123. callbacks: {
  124. beforeSend: startTimingResults,
  125. complete: showTime,
  126. }
  127. }
  128. });
  129. yasr = YASR(document.getElementById("results"), {
  130. useGoogleCharts: false,
  131. //this way, the URLs in the results are prettified using the defined prefixes in the query
  132. getUsedPrefixes: yasqe.getPrefixesFromQuery
  133. });
  134. /**
  135. * Set some of the hooks to link YASR and YASQE
  136. */
  137. yasqe.options.sparql.callbacks.complete = yasr.setResponse;
  138. };
  139. /** Load the configuration definition */
  140. var loadConfig = function(config) {
  141. if (config.configURL) {
  142. $.getJSON(config.configURL, onConfigLoaded);
  143. } else {
  144. onConfigLoaded(config);
  145. }
  146. };
  147. /** Return the current config object */
  148. var config = function() {
  149. return _config;
  150. };
  151. /** Bind events that we want to manage */
  152. var bindEvents = function() {
  153. $("ul.prefixes").on(
  154. "click",
  155. "a.btn",
  156. function(e) {
  157. var elem = $(e.currentTarget);
  158. updatePrefixDeclaration($.trim(elem.text()), elem
  159. .data("uri"), !elem.is(".active"));
  160. });
  161. $("ul.examples").on("click", "a", function(e) {
  162. var elem = $(e.currentTarget);
  163. $("ul.examples a").removeClass("active");
  164. _.defer(function() {
  165. showCurrentQuery();
  166. });
  167. });
  168. $(".endpoints").on("click", "a", function(e) {
  169. var elem = $(e.currentTarget);
  170. setCurrentEndpoint($.trim(elem.text()));
  171. });
  172. $("#sparqlEndpoint").change(function() {
  173. yasqe.options.sparql.endpoint = $(this).val();
  174. });
  175. // dialogue events
  176. $("#prefixEditor").on("click", "#lookupPrefix", onLookupPrefix).on(
  177. "keyup", "#inputPrefix", function(e) {
  178. var elem = $(e.currentTarget);
  179. $("#lookupPrefix span").text(sprintf("'%s'", elem.val()));
  180. });
  181. $("#addPrefix").on("click", onAddPrefix);
  182. /**
  183. * register content type changes.
  184. * Do not need to set them on load, as their default values are already the default vals of YASQE as well
  185. */
  186. $("#graphContentType").change(function(){yasqe.options.sparql.acceptHeaderGraph = $(this).val()});
  187. $("#selectContentType").change(function(){yasqe.options.sparql.acceptHeaderSelect = $(this).val()});
  188. };
  189. /** List the current defined prefixes from the config */
  190. var initPrefixes = function(config) {
  191. var prefixAdd = $("ul.prefixes li:last");
  192. $
  193. .each(
  194. config.prefixes,
  195. function(key, value) {
  196. var html = sprintf(
  197. "<li><a class='btn btn-custom2 btn-sm' data-toggle='button' data-uri='%s'>%s</a></li>",
  198. value, key);
  199. $(html).insertBefore(prefixAdd);
  200. });
  201. };
  202. /** List the example queries from the config */
  203. var initExamples = function(config) {
  204. var examples = $("ul.examples");
  205. examples.empty();
  206. $
  207. .each(
  208. config.queries,
  209. function(i, queryDesc) {
  210. var html = sprintf(
  211. "<li><a class='btn btn-custom2 btn-sm' data-toggle='button'>%s</a></li>",
  212. queryDesc.name);
  213. examples.append(html);
  214. if (queryDesc.queryURL) {
  215. loadRemoteQuery(queryDesc.name,
  216. queryDesc.queryURL);
  217. }
  218. });
  219. setFirstQueryActive();
  220. };
  221. /** Set the default active query */
  222. var setFirstQueryActive = function() {
  223. if (_outstandingQueries === 0 && yasqe.getValue() == YASQE.defaults.value) {
  224. //only load the example query, when YASQE has not retrieved a previous query executed by the client
  225. $("ul.examples").find("a").first().addClass("active");
  226. showCurrentQuery();
  227. }
  228. };
  229. /** Load a remote query */
  230. var loadRemoteQuery = function(name, url) {
  231. _outstandingQueries++;
  232. var options = {
  233. success : function(data, xhr) {
  234. namedExample(name).query = data;
  235. _outstandingQueries--;
  236. setFirstQueryActive();
  237. },
  238. failure : function() {
  239. namedExample(name).query = "Not found: " + url;
  240. _outstandingQueries--;
  241. setFirstQueryActive();
  242. },
  243. dataType : "text"
  244. };
  245. $.ajax(url, options);
  246. };
  247. /** Set up the drop-down list of end-points */
  248. var initEndpoints = function(config) {
  249. var endpoints = $("ul.endpoints");
  250. endpoints.empty();
  251. if (config.endpoints) {
  252. $
  253. .each(
  254. config.endpoints,
  255. function(key, url) {
  256. var html = sprintf(
  257. "<li role='presentation'><a role='menuitem' tabindex='-1' href='#'>%s</a></li>",
  258. url);
  259. endpoints.append(html);
  260. });
  261. setCurrentEndpoint(config.endpoints["default"]);
  262. }
  263. };
  264. /** Successfully loaded the configuration */
  265. var onConfigLoaded = function(config, status, jqXHR) {
  266. _config = config;
  267. initPrefixes(config);
  268. initExamples(config);
  269. initEndpoints(config);
  270. };
  271. /** Set the current endpoint text */
  272. var setCurrentEndpoint = function(url) {
  273. yasqe.options.sparql.endpoint = url;
  274. $("[id=sparqlEndpoint]").val(url);
  275. };
  276. /** Return the current endpoint text */
  277. var currentEndpoint = function(url) {
  278. return $("[id=sparqlEndpoint]").val();
  279. };
  280. /** Return the query definition with the given name */
  281. var namedExample = function(name) {
  282. return _.find(config().queries, function(ex) {
  283. return ex.name === name;
  284. });
  285. };
  286. /** Return the currently active named example */
  287. var currentNamedExample = function() {
  288. return namedExample($.trim($("ul.examples a.active").first().text()));
  289. };
  290. /** Display the given query, with the currently defined prefixes */
  291. var showCurrentQuery = function() {
  292. var query = currentNamedExample();
  293. displayQuery(query);
  294. };
  295. /** Display the given query */
  296. var displayQuery = function(query) {
  297. if (query) {
  298. var queryBody = query.query ? query.query : query;
  299. var prefixes = assemblePrefixes(queryBody, query.prefixes)
  300. var q = sprintf("%s\n\n%s", renderPrefixes(prefixes),
  301. stripLeader(queryBody));
  302. yasqe.setValue(q);
  303. syncPrefixButtonState(prefixes);
  304. }
  305. };
  306. /** Return the currently selected output format */
  307. var selectedFormat = function() {
  308. return $("a.display-format").data("value");
  309. };
  310. /** Update the user's format selection */
  311. var setCurrentFormat = function(val, label) {
  312. $("a.display-format").data("value", val).find("span").text(label);
  313. };
  314. /** Assemble the set of prefixes to use when initially rendering the query */
  315. var assemblePrefixes = function(queryBody, queryDefinitionPrefixes) {
  316. if (queryBody.match(/^prefix/)) {
  317. // strategy 1: there are prefixes encoded in the query body
  318. return assemblePrefixesFromQuery(queryBody);
  319. } else if (queryDefinitionPrefixes) {
  320. // strategy 2: prefixes given in query def
  321. return _.map(queryDefinitionPrefixes, function(prefixName) {
  322. return {
  323. name : prefixName,
  324. uri : config().prefixes[prefixName]
  325. };
  326. });
  327. } else {
  328. return assembleCurrentPrefixes();
  329. }
  330. };
  331. /** Return an array comprising the currently selected prefixes */
  332. var assembleCurrentPrefixes = function() {
  333. var l = $("ul.prefixes a.active").map(function(i, elt) {
  334. return {
  335. name : $.trim($(elt).text()),
  336. uri : $(elt).data("uri")
  337. };
  338. });
  339. return $.makeArray(l);
  340. };
  341. // /** Return an array of the prefixes parsed from the given query body */
  342. // var assemblePrefixesFromQuery = function(queryBody) {
  343. // var leader = queryLeader(queryBody)[0].trim();
  344. // var pairs = _.compact(leader.split("prefix"));
  345. // var prefixes = [];
  346. //
  347. // _.each(pairs, function(pair) {
  348. // var m = pair.match("^\\s*(\\w+)\\s*:\\s*<([^>]*)>\\s*$");
  349. // prefixes.push({
  350. // name : m[1],
  351. // uri : m[2]
  352. // });
  353. // });
  354. //
  355. // return prefixes;
  356. // };
  357. /**
  358. * Ensure that the prefix buttons are in sync with the prefixes used in a
  359. * new query
  360. */
  361. var syncPrefixButtonState = function(prefixes) {
  362. $("ul.prefixes a").each(function(i, elt) {
  363. var name = $.trim($(elt).text());
  364. if (_.find(prefixes, function(p) {
  365. return p.name === name;
  366. })) {
  367. $(elt).addClass("active");
  368. } else {
  369. $(elt).removeClass("active");
  370. }
  371. });
  372. };
  373. /** Split a query into leader (prefixes and leading blank lines) and body */
  374. var queryLeader = function(query) {
  375. var pattern = /(prefix [^>]+>[\s\n]*)/;
  376. var queryBody = query;
  377. var i = 0;
  378. var m = queryBody.match(pattern);
  379. while (m) {
  380. i += m[1].length;
  381. queryBody = queryBody.substring(i);
  382. m = queryBody.match(pattern);
  383. }
  384. return [ query.substring(0, query.length - queryBody.length), queryBody ];
  385. };
  386. /** Remove the query leader */
  387. var stripLeader = function(query) {
  388. return queryLeader(query)[1];
  389. };
  390. /** Return a string comprising the given prefixes */
  391. var renderPrefixes = function(prefixes) {
  392. return _.map(prefixes, function(p) {
  393. return sprintf("prefix %s: <%s>", p.name, p.uri);
  394. }).join("\n");
  395. };
  396. /** Add or remove the given prefix declaration from the current query */
  397. var updatePrefixDeclaration = function(prefix, uri, added) {
  398. var prefixObj = {};
  399. prefixObj[prefix] = uri;
  400. if (added) {
  401. yasqe.addPrefixes(prefixObj);
  402. } else {
  403. yasqe.removePrefixes(prefixObj);
  404. }
  405. };
  406. /** Return the sparql service we're querying against */
  407. var sparqlService = function() {
  408. var service = config().service;
  409. if (!service) {
  410. // default is the remote service
  411. config().service = new RemoteSparqlService();
  412. service = config().service;
  413. }
  414. return service;
  415. };
  416. /** Hide or reveal an element using Bootstrap .hidden class */
  417. var elementVisible = function(elem, visible) {
  418. if (visible) {
  419. $(elem).removeClass("hidden");
  420. } else {
  421. $(elem).addClass("hidden");
  422. }
  423. };
  424. /** Prepare to show query time taken */
  425. var startTimingResults = function() {
  426. _startTime = new Date().getTime();
  427. elementVisible(".timeTaken");
  428. };
  429. /** Show results count and time */
  430. var showTime = function() {
  431. var duration = new Date().getTime() - _startTime;
  432. var ms = duration % 1000;
  433. duration = Math.floor(duration / 1000);
  434. var s = duration % 60;
  435. var m = Math.floor(duration / 60);
  436. var html = sprintf("time taken: %d min %d.%03d s", m, s, ms);
  437. $(".timeTaken").html(html);
  438. elementVisible(".timeTaken", true);
  439. };
  440. /** Lookup a prefix on prefix.cc */
  441. var onLookupPrefix = function(e) {
  442. e.preventDefault();
  443. var prefix = $.trim($("#inputPrefix").val());
  444. $("#inputURI").val("");
  445. if (prefix) {
  446. $.getJSON(sprintf("http://prefix.cc/%s.file.json", prefix),
  447. function(data) {
  448. $("#inputURI").val(data[prefix]);
  449. });
  450. }
  451. };
  452. /** User wishes to add the prefix */
  453. var onAddPrefix = function(e) {
  454. var prefix = $.trim($("#inputPrefix").val());
  455. var uri = $.trim($("#inputURI").val());
  456. if (uri) {
  457. _config.prefixes[prefix] = uri;
  458. } else {
  459. delete _config.prefixes[prefix];
  460. }
  461. // remember the state of current user selections, then re-create the
  462. // list
  463. var selections = {};
  464. $("ul.prefixes a.btn").each(function(i, a) {
  465. selections[$(a).text()] = $(a).hasClass("active");
  466. });
  467. $("ul.prefixes li[class!=keep]").remove();
  468. initPrefixes(_config);
  469. // restore selections state
  470. $.each(selections, function(k, v) {
  471. if (!v) {
  472. $(sprintf("ul.prefixes a.btn:contains('%s')", k)).removeClass(
  473. "active");
  474. }
  475. });
  476. var lines = yasqe.getValue().split("\n");
  477. lines = _.reject(lines, function(line) {
  478. return line.match(/^prefix/);
  479. });
  480. var q = sprintf("%s\n%s", renderPrefixes(assembleCurrentPrefixes()),
  481. lines.join("\n"));
  482. yasqe.setValue(q);
  483. };
  484. /** Disable or enable the button to submit a query */
  485. var disableSubmit = function(disable) {
  486. var elem = $("a.run-query");
  487. elem.prop('disabled', disable);
  488. if (disable) {
  489. elem.addClass("disabled");
  490. } else {
  491. elem.removeClass("disabled");
  492. }
  493. };
  494. return {
  495. init : init,
  496. setCurrentEndpoint: setCurrentEndpoint
  497. };
  498. }();