API Docs for: 0.6.0
Show:

File: src/Query.js

  1.  
  2. /**
  3. * Important class. Runs SPARQL query against SPARQL
  4. * endpoints.
  5. *
  6. * Dependencies:
  7. *
  8. * - sgvizler.util
  9. * - sgvizler.namespace
  10. * - sgvizler.registry
  11. * - sgvizler.parser
  12. * - sgvizler.loader
  13. * - sgvizler.logger
  14. * - sgvizler.defaults
  15. * - jQuery
  16. * - google.visualization
  17. *
  18. *
  19. * Example of how to use the Query class:
  20. *
  21. * var sparqlQueryString = "SELECT * {?s ?p ?o} LIMIT 10",
  22. * containerID = "myElementID",
  23. * Q = new sgvizler.Query();
  24. *
  25. * // Note that default values may be set in the sgvizler object.
  26. * Q.query(sparqlQueryString)
  27. * .endpointURL("http://dbpedia.org/sparql")
  28. * .endpointOutputFormat("json") // Possible values 'xml', 'json', 'jsonp'.
  29. * .chartFunction("google.visualization.BarChart") // The name of the function to draw the chart.
  30. * .draw(containerID); // Draw the chart in the designated HTML element.
  31. *
  32. * @class sgvizler.Query
  33. * @constructor
  34. * @param {Object} queryOptions
  35. * @param {Object} chartOptions
  36. * @since 0.5
  37. **/
  38.  
  39. // Note: the parameter names in the documention are different just
  40. // for better readability.
  41. S.Query = function (queryOpt, chartOpt) {
  42.  
  43. /*global $ */
  44.  
  45. // Module dependencies:
  46. var util = S.util,
  47. getset = util.getset,
  48. prefixesSPARQL = S.namespace.prefixesSPARQL,
  49. registry = S.registry,
  50. moduleGooVis = registry.GVIZ,
  51. fDataTable = registry.DATATABLE,
  52. parser = S.parser,
  53. loadDependencies = S.loader.loadDependencies,
  54. logger = S.logger,
  55. defaults = S.defaults,
  56.  
  57. /* Constants for query formats (qf) */
  58. qfXML = 'xml',
  59. qfJSON = 'json',
  60. qfJSONP = 'jsonp',
  61.  
  62. /**
  63. * Contains properties relevant for query business. Get
  64. * and set values using get/setter functions.
  65. *
  66. * Default values are found in sgvizler.defaults (these
  67. * may be get/set with the get/setter function on the
  68. * sgvizler class) and are loaded on construction---and
  69. * get overwritten by properties in the constructed
  70. * parameter.
  71. * @property queryOptions
  72. * @type Object
  73. * @private
  74. * @since 0.5
  75. **/
  76. queryOptions,
  77.  
  78. /**
  79. * Contains properties relevant for chart drawing
  80. * business. Get and set values using get/setter
  81. * functions.
  82. *
  83. * Default values are found in sgvizler.defaults (these
  84. * may be get/set with the get/setter function on the
  85. * sgvizler class) and are loaded on construction---and
  86. * get overwritten by properties in the constructed
  87. * parameter.
  88. * @property chartOptions
  89. * @type Object
  90. * @private
  91. * @since 0.5
  92. **/
  93. chartOptions,
  94.  
  95. //TODO
  96. listeners = {},
  97.  
  98. /**
  99. * DataTable query results.
  100. * @property dataTable
  101. * @type google.visualization.DataTable
  102. * @private
  103. * @since 0.5
  104. **/
  105. dataTable,
  106.  
  107. /**
  108. * The raw results as retuned by the endpoint.
  109. * @property queryResult
  110. * @type Object either XML or JSON
  111. * @private
  112. * @since 0.5
  113. **/
  114. queryResult,
  115.  
  116. /**
  117. * The number of rows in the query results.
  118. * @property noOfResults
  119. * @type number
  120. * @public
  121. * @since 0.5
  122. **/
  123. noOfResults,
  124.  
  125. // TODO: better logging.
  126. // processQueryResults = function (data) {
  127. // var noRows = getResultRowCount(data);
  128. // if (noRows === null) {
  129. // S.logger.displayFeedback(this, "ERROR_UNKNOWN");
  130. // } else if (noRows === 0) {
  131. // S.logger.displayFeedback(this, "NO_RESULTS");
  132. // } else {
  133. // S.logger.displayFeedback(this, "DRAWING");
  134. // return getGoogleJSON(data);
  135. // }
  136. // },
  137.  
  138. /**
  139. * Add a url as an RDF source to be included in the SPARQL
  140. * query in the `FROM` block.
  141. * @method addFrom
  142. * @public
  143. * @param {String} url
  144. * @since 0.5
  145. **/
  146. addFrom = function (url) {
  147. queryOptions.froms.push(url);
  148. },
  149.  
  150. /**
  151. * Remove all registered FROMs.
  152. *
  153. * See also `addFrom`.
  154. * @method clearFroms
  155. * @public
  156. * @since 0.5
  157. **/
  158. clearFroms = function () {
  159. queryOptions.froms = [];
  160. },
  161.  
  162. //// Getters/Setters
  163. // TODO redefine query function setters when query is
  164. // issued: only one query per Query.
  165.  
  166. /**
  167. * Get query string.
  168. * @method query
  169. * @public
  170. * @return {string}
  171. */
  172. /**
  173. * Set query string.
  174. * @method query
  175. * @public
  176. * @param {string} queryString
  177. * @chainable
  178. */
  179. query = function (queryString) {
  180. if (queryString !== undefined) {
  181. clearFroms();
  182. }
  183. return getset('query', queryString, queryOptions, this);
  184. },
  185. /**
  186. * Get endpoint URL.
  187. * @method endpointURL
  188. * @public
  189. * @return {string}
  190. * @since 0.5
  191. **/
  192. /**
  193. * Set endpoint URL.
  194. * @method endpointURL
  195. * @public
  196. * @param {string} url
  197. * @chainable
  198. * @example
  199. * sgvizler.endpointURL('http://sparql.dbpedia.org');
  200. * sets this Query object's endpoint to DBpedia.
  201. * @since 0.5
  202. **/
  203. endpointURL = function (url) {
  204. return getset('endpoint', url, queryOptions, this);
  205. },
  206.  
  207. /**
  208. * Get endpoint output format.
  209. * @method endpointOutputFormat
  210. * @public
  211. * @return {string}
  212. * @since 0.5
  213. **/
  214. /**
  215. * Set endpoint output format. Legal values are `'xml'`,
  216. * `'json'`, `'jsonp'`.
  217. * @method endpointOutputFormat
  218. * @public
  219. * @param {string} format
  220. * @chainable
  221. * @since 0.5
  222. **/
  223. endpointOutputFormat = function (format) {
  224. return getset('endpoint_output_format', format, queryOptions, this);
  225. },
  226.  
  227. // TODO
  228. endpointResultsURLPart = function (value) {
  229. return getset('endpoint_results_urlpart', value, queryOptions, this);
  230. },
  231.  
  232. /**
  233. * Get URL to online SPARQL query validator.
  234. * @method validatorURL
  235. * @public
  236. * @return {string}
  237. * @since 0.5
  238. **/
  239. /**
  240. * Set URL to online SPARQL query validator. Appending a
  241. * SPARQL query to the end of this URL should give a page
  242. * which validates the given query.
  243. * @method validatorURL
  244. * @public
  245. * @param {string} url
  246. * @chainable
  247. * @since 0.5
  248. * @example
  249. * TODO
  250. **/
  251. validatorURL = function (url) {
  252. return getset('validator_url', url, queryOptions, this);
  253. },
  254.  
  255. // TODO
  256. loglevel = function (value) {
  257. return getset('loglevel', value, queryOptions, this);
  258. },
  259.  
  260. // TODO
  261. logContainer = function (value) {
  262. return getset('logContainer', value, queryOptions, this);
  263. },
  264.  
  265. /**
  266. * Get the name of datatable preprocessing function.
  267. * @method datatableFunction
  268. * @public
  269. * @return {string}
  270. * @since 0.5
  271. **/
  272. /**
  273. * Set the name of datatable preprocessing function. The
  274. * function should be availble in the global object, or
  275. * registered with dependencies in Sgvizler's registry;
  276. * see TODO
  277. * @method datatableFunction
  278. * @public
  279. * @param {string} functionName
  280. * @chainable
  281. * @since 0.5
  282. **/
  283. datatableFunction = function (functionName) {
  284. return getset('datatable', functionName, queryOptions, this);
  285. },
  286.  
  287. /**
  288. * Get the name of chart function.
  289. * @method chartFunction
  290. * @public
  291. * @return {string}
  292. * @since 0.5
  293. **/
  294. /**
  295. * Set the name of chart function. The function should be
  296. * availble in the global object, or registered with
  297. * dependencies in Sgvizler's registry; see TODO
  298. * @method chartFunction
  299. * @public
  300. * @param {string} functionName
  301. * @chainable
  302. * @since 0.5
  303. **/
  304. chartFunction = function (functionName) {
  305. return getset('chart', functionName, queryOptions, this);
  306. },
  307.  
  308. /**
  309. * Get the height of the chart container.
  310. * @method chartHeight
  311. * @public
  312. * @return {string}
  313. * @since 0.5
  314. **/
  315. /**
  316. * Set the height of the chart container.
  317. * @method chartHeight
  318. * @public
  319. * @param {number} height
  320. * @chainable
  321. * @since 0.5
  322. **/
  323. chartHeight = function (height) {
  324. return getset('height', height, chartOptions, this);
  325. },
  326.  
  327. /**
  328. * Get the width of the chart container.
  329. * @method chartWidth
  330. * @public
  331. * @return {string}
  332. * @since 0.5
  333. **/
  334. /**
  335. * Set the width of the chart container.
  336. * @method chartWidth
  337. * @public
  338. * @param {number} width
  339. * @chainable
  340. * @since 0.5
  341. **/
  342. chartWidth = function (width) {
  343. return getset('width', width, chartOptions, this);
  344. },
  345.  
  346. /**
  347. * Get the query string with prefixes added and encoded
  348. * for URL insertion.
  349. * @method getEncodedQuery
  350. * @public
  351. * @return {String}
  352. * @since 0.5
  353. **/
  354. getEncodedQuery = function () {
  355. return encodeURIComponent(prefixesSPARQL() + query());
  356. },
  357.  
  358. /**
  359. * Extends the query string by including the urls in
  360. * `from` as `FROM` statements in the (SPARQL) `query`.
  361. * @method insertFrom
  362. * @private
  363. * @since 0.5
  364. **/
  365. insertFrom = function () {
  366. var i, len = queryOptions.froms.length,
  367. from;
  368. if (len) {
  369. from = "";
  370. for (i = 0; i < len; i += 1) {
  371. from += 'FROM <' + queryOptions.froms[i] + '>\n';
  372. }
  373. query(query().replace(/((WHERE)?(\s)*\{)/i, '\n' + from + '$1'));
  374. }
  375. },
  376.  
  377. /**
  378. * Sets and returns `noOfResults`, i.e., the number of
  379. * rows in the query result.
  380. * @method getResultRowCount
  381. * @private
  382. * @param {google.visualization.DataTable} dataTable
  383. * @return {Number}
  384. * @since 0.5
  385. **/
  386. getResultRowCount = function (dataTable) {
  387. if (noOfResults === undefined) {
  388. if (endpointOutputFormat() === qfXML) {
  389. noOfResults = parser.countXML(dataTable);
  390. } else {
  391. noOfResults = parser.countJSON(dataTable);
  392. }
  393. }
  394. return noOfResults;
  395. },
  396.  
  397. /**
  398. * Converts "raw" query results into Google JSON, using
  399. * sgvizler.parser.
  400. * @method getGoogleJSON
  401. * @private
  402. * @param {Object} data Query result set
  403. * @return {JSON} JSON edable by google.visualization.DataTable
  404. * @since 0.5
  405. **/
  406. getGoogleJSON = function (data) {
  407. var gJSON = {};
  408. if (getResultRowCount(data)) {
  409. if (endpointOutputFormat() === qfXML) {
  410. gJSON = parser.convertXML(data);
  411. } else {
  412. gJSON = parser.convertJSON(data);
  413. }
  414. }
  415. return gJSON;
  416. },
  417.  
  418. // TODO: add different listeners. onQuery, onResults, onDraw?
  419. /**
  420. * Add a function which is to be fired when the
  421. * listener named `name` is fired.
  422. *
  423. * See `fireListener`
  424. *
  425. * @method addListener
  426. * @private
  427. * @param {String} name The name of the listener.
  428. * @param {Function} func The function to fire.
  429. * @example
  430. * addListener("ready", function () { console.log("Ready!") });
  431. * @since 0.6.0
  432. **/
  433. addListener = function (name, func) {
  434. if (typeof func === 'function') { // accept only functions.
  435. listeners[name] = listeners[name] || [];
  436. listeners[name].push(func);
  437. } else {
  438. throw new TypeError();
  439. }
  440. },
  441.  
  442. /**
  443. * Fires (runs, executes) all functions registered
  444. * on the listener `name`.
  445. *
  446. * See `addListener`.
  447. *
  448. * @method fireListener
  449. * @private
  450. * @param {String} name The name of the listener.
  451. * @since 0.6.0
  452. **/
  453. fireListener = function (name) {
  454. if (listeners[name]) {
  455. while (listeners[name].length) {
  456. (listeners[name].pop())(); // run function.
  457. }
  458. }
  459. },
  460.  
  461. /**
  462. * Sends query to endpoint using AJAX. "Low level" method,
  463. * consider using `saveQueryResults()`.
  464. * @method sendQuery
  465. * @private
  466. * @async
  467. * @return {jQuery.Promise} A Promise containing the query results.
  468. * @since 0.5
  469. **/
  470. // TODO .fail, .progress: logging.
  471. sendQuery = function () {
  472. var promise, // query promise.
  473. myEndpointOutput = endpointOutputFormat();
  474.  
  475. insertFrom();
  476.  
  477. if (myEndpointOutput !== qfJSONP &&
  478. window.XDomainRequest) {
  479.  
  480. // special query function for IE. Hiding variables in inner function.
  481. // TODO See: https://gist.github.com/1114981 for inspiration.
  482. promise = (
  483. function () {
  484. /*global XDomainRequest */
  485. var qx = $.Deferred(),
  486. xdr = new XDomainRequest(),
  487. url = endpointURL() +
  488. "?query=" + getEncodedQuery() +
  489. "&output=" + myEndpointOutput;
  490. xdr.open("GET", url);
  491. xdr.onload = function () {
  492. var data;
  493. if (myEndpointOutput === qfXML) {
  494. data = $.parseXML(xdr.responseText);
  495. } else {
  496. data = $.parseJSON(xdr.responseText);
  497. }
  498. qx.resolve(data);
  499. };
  500. xdr.send();
  501. return qx.promise();
  502. }()
  503. );
  504. } else {
  505. promise = $.ajax({
  506. url: endpointURL(),
  507. data: {
  508. query: prefixesSPARQL() + query(),
  509. output: (myEndpointOutput === qfJSONP) ? qfJSON : myEndpointOutput
  510. },
  511. dataType: myEndpointOutput
  512. });
  513. }
  514. return promise;
  515. },
  516.  
  517. /**
  518. * Saves the query result set in the private property
  519. * `results`. Works like a wrapper for sendQuery().
  520. *
  521. * See also saveDataTable().
  522. *
  523. * @TODO: also put the results in the promise object---and
  524. * how to get them out?
  525. *
  526. * @method saveQueryResults.
  527. * @private
  528. * @async
  529. * @return {jQuery.Promise} A Promise which resolves when
  530. * the results are saved.
  531. * @since 0.5
  532. **/
  533. saveQueryResults = function () {
  534. var qr;
  535.  
  536. if (queryResult !== undefined) {
  537. qr = queryResult;
  538. } else {
  539. qr = sendQuery();
  540. qr.fail(
  541. function (xhr, textStatus, thrownError) {
  542. logger.log("Error: A '" + textStatus + "' occurred in Query.saveQueryResults()");
  543. fireListener('onFail');
  544. }
  545. );
  546. // add callback to save query results in object.
  547. qr.done(
  548. function (data) {
  549. queryResult = data;
  550. fireListener('onDone');
  551. }
  552. );
  553. }
  554. return qr;
  555. },
  556.  
  557. /**
  558. * Converts the the query result set into a
  559. * google.visualization.DataTable, and if specified,
  560. * applies datatable preprocessing function, and saves
  561. * the datatable in the private property
  562. * `dataTable`.
  563. *
  564. * @TODO: also put the results in the promise object---and
  565. * how to get them out?
  566. *
  567. * @method saveDataTable
  568. * @private
  569. * @async
  570. * @return {jQuery.Promise} A Promise which resolves when
  571. * the datatable is saved.
  572. * @since 0.5
  573. **/
  574. saveDataTable = function () {
  575. /*global google */
  576. var qdt, // query data table.
  577. myDatatableFunction = datatableFunction();
  578.  
  579. if (dataTable) { // dataTable already computed.
  580. qdt = dataTable;
  581. } else {
  582. qdt =
  583. $.when(
  584. saveQueryResults(),
  585. loadDependencies(fDataTable),
  586. // Get possible preprocess function.
  587. (function () {
  588. var loader = {};
  589. if (myDatatableFunction) {
  590. loader = loadDependencies(myDatatableFunction);
  591. }
  592. return loader;
  593. }())
  594. )
  595. //TODO .fail(function () {})
  596. .done(
  597. function () {
  598. dataTable = new google.visualization.DataTable(getGoogleJSON(queryResult));
  599. if (myDatatableFunction) {
  600. var func = util.getObjectByPath(myDatatableFunction);
  601. dataTable = func(dataTable);
  602. }
  603. }
  604. );
  605. // TODO .fail, .progress: logging.
  606. }
  607. return qdt;
  608. },
  609.  
  610. /**
  611. * Draws the result of the query in a given container.
  612. * @method draw
  613. * @public
  614. * @param {String} containerId The elementId of the
  615. * container to draw the result of the query.
  616. * @since 0.5
  617. **/
  618. draw = function (containerId) {
  619. /*global google */
  620. // Get query results and necessary charting functions in parallel,
  621. // then draw chart in container.
  622.  
  623. var myChart = chartFunction();
  624.  
  625. $.when(saveDataTable(),
  626. loadDependencies(myChart))
  627. .then(
  628. function () {
  629. try {
  630. // chart is loaded by loadDependencies.
  631. var Func = util.getObjectByPath(myChart),
  632. chartFunc = new Func(document.getElementById(containerId)),
  633. ready = function () {
  634. logger.log(myChart + " for " + containerId + " is ready.");
  635. fireListener('onDraw');
  636. };
  637.  
  638. // log when chart is loaded.
  639. if (util.startsWith(myChart, moduleGooVis)) {
  640. google.visualization.events.addListener(chartFunc, 'ready', ready);
  641. } else if (chartFunc.addListener) {
  642. chartFunc.addListener('ready', ready);
  643. }
  644.  
  645. // dataTable is set by saveDataTable.
  646. chartFunc.draw(dataTable, chartOptions);
  647. } catch (x) {
  648. // TODO: better error reporting, what went wrong?
  649. logger.log(myChart + " -- " + x);
  650. }
  651. }
  652. );
  653. };
  654.  
  655. /////////////////////////////////////////////////////////
  656. // Initialize things.
  657.  
  658. // Load default values, and overwrite them with values given
  659. // in constructer parameters.
  660. queryOptions = $.extend(defaults.getQueryOptions(),
  661. queryOpt);
  662. chartOptions = $.extend(defaults.getChartOptions(),
  663. defaults.getChartSpecificOptions(chartFunction()),
  664. chartOpt);
  665.  
  666. // Safeguard constructor.
  667. if (!(this instanceof S.Query)) {
  668. throw new Error("Constructor 'Query' called as a function. Use 'new'.");
  669. }
  670.  
  671. ////////////////////////////////////////////////////////
  672. // PUBLICs
  673.  
  674. return {
  675.  
  676. //// attributes
  677. noOfResults: noOfResults,
  678.  
  679. //// functions
  680. draw: draw,
  681. getEncodedQuery: getEncodedQuery,
  682.  
  683. // listeners
  684. onFail: function (func) {
  685. addListener('onFail', func);
  686. },
  687. onDone: function (func) {
  688. addListener('onDone', func);
  689. },
  690. onDraw: function (func) {
  691. addListener('onDraw', func);
  692. },
  693.  
  694. /**
  695. * @method getDataTable
  696. * @public
  697. * @param {Function} success
  698. * @param {Function} fail
  699. * @async
  700. * @beta
  701. */
  702. getDataTable: function (success, fail) {
  703. $.when(saveDataTable())
  704. .then(
  705. function () {
  706. var data = dataTable.clone();
  707. success(data);
  708. },
  709. function () {
  710. var data = dataTable.clone();
  711. fail(data);
  712. }
  713. );
  714. },
  715. // TODO
  716. /*getQueryResults : function (success, fail) {
  717. $.when(saveQueryResults()).then(success(queryResult), fail);
  718. },
  719. getGoogleJSON: function (success, fail) {
  720. getQueryResults(
  721. function (queryResult) {
  722. success(getGoogleJSON(queryResult));
  723. },
  724. fail
  725. );
  726. },*/
  727.  
  728. //// FROM
  729. addFrom: addFrom,
  730. clearFroms: clearFroms,
  731.  
  732. //// Getters/setters. Cascade pattern.
  733. query: query,
  734. endpointURL: endpointURL,
  735. endpointOutputFormat: endpointOutputFormat,
  736. endpointResultsURLPart: endpointResultsURLPart,
  737. validatorURL: validatorURL,
  738. loglevel: loglevel,
  739. logContainer: logContainer,
  740. datatableFunction: datatableFunction,
  741. chartFunction: chartFunction,
  742. chartHeight: chartHeight,
  743. chartWidth: chartWidth
  744. };
  745. };
  746.