-
- /**
- * Important class. Runs SPARQL query against SPARQL
- * endpoints.
- *
- * Dependencies:
- *
- * - sgvizler.util
- * - sgvizler.namespace
- * - sgvizler.registry
- * - sgvizler.parser
- * - sgvizler.loader
- * - sgvizler.logger
- * - sgvizler.defaults
- * - jQuery
- * - google.visualization
- *
- *
- * Example of how to use the Query class:
- *
- * var sparqlQueryString = "SELECT * {?s ?p ?o} LIMIT 10",
- * containerID = "myElementID",
- * Q = new sgvizler.Query();
- *
- * // Note that default values may be set in the sgvizler object.
- * Q.query(sparqlQueryString)
- * .endpointURL("http://dbpedia.org/sparql")
- * .endpointOutputFormat("json") // Possible values 'xml', 'json', 'jsonp'.
- * .chartFunction("google.visualization.BarChart") // The name of the function to draw the chart.
- * .draw(containerID); // Draw the chart in the designated HTML element.
- *
- * @class sgvizler.Query
- * @constructor
- * @param {Object} queryOptions
- * @param {Object} chartOptions
- * @since 0.5
- **/
-
- // Note: the parameter names in the documention are different just
- // for better readability.
- S.Query = function (queryOpt, chartOpt) {
-
- /*global $ */
-
- // Module dependencies:
- var util = S.util,
- getset = util.getset,
- prefixesSPARQL = S.namespace.prefixesSPARQL,
- registry = S.registry,
- moduleGooVis = registry.GVIZ,
- fDataTable = registry.DATATABLE,
- parser = S.parser,
- loadDependencies = S.loader.loadDependencies,
- logger = S.logger,
- defaults = S.defaults,
-
- /* Constants for query formats (qf) */
- qfXML = 'xml',
- qfJSON = 'json',
- qfJSONP = 'jsonp',
-
- /**
- * Contains properties relevant for query business. Get
- * and set values using get/setter functions.
- *
- * Default values are found in sgvizler.defaults (these
- * may be get/set with the get/setter function on the
- * sgvizler class) and are loaded on construction---and
- * get overwritten by properties in the constructed
- * parameter.
- * @property queryOptions
- * @type Object
- * @private
- * @since 0.5
- **/
- queryOptions,
-
- /**
- * Contains properties relevant for chart drawing
- * business. Get and set values using get/setter
- * functions.
- *
- * Default values are found in sgvizler.defaults (these
- * may be get/set with the get/setter function on the
- * sgvizler class) and are loaded on construction---and
- * get overwritten by properties in the constructed
- * parameter.
- * @property chartOptions
- * @type Object
- * @private
- * @since 0.5
- **/
- chartOptions,
-
- //TODO
- listeners = {},
-
- /**
- * DataTable query results.
- * @property dataTable
- * @type google.visualization.DataTable
- * @private
- * @since 0.5
- **/
- dataTable,
-
- /**
- * The raw results as retuned by the endpoint.
- * @property queryResult
- * @type Object either XML or JSON
- * @private
- * @since 0.5
- **/
- queryResult,
-
- /**
- * The number of rows in the query results.
- * @property noOfResults
- * @type number
- * @public
- * @since 0.5
- **/
- noOfResults,
-
- // TODO: better logging.
- // processQueryResults = function (data) {
- // var noRows = getResultRowCount(data);
- // if (noRows === null) {
- // S.logger.displayFeedback(this, "ERROR_UNKNOWN");
- // } else if (noRows === 0) {
- // S.logger.displayFeedback(this, "NO_RESULTS");
- // } else {
- // S.logger.displayFeedback(this, "DRAWING");
- // return getGoogleJSON(data);
- // }
- // },
-
- /**
- * Add a url as an RDF source to be included in the SPARQL
- * query in the `FROM` block.
- * @method addFrom
- * @public
- * @param {String} url
- * @since 0.5
- **/
- addFrom = function (url) {
- queryOptions.froms.push(url);
- },
-
- /**
- * Remove all registered FROMs.
- *
- * See also `addFrom`.
- * @method clearFroms
- * @public
- * @since 0.5
- **/
- clearFroms = function () {
- queryOptions.froms = [];
- },
-
- //// Getters/Setters
- // TODO redefine query function setters when query is
- // issued: only one query per Query.
-
- /**
- * Get query string.
- * @method query
- * @public
- * @return {string}
- */
- /**
- * Set query string.
- * @method query
- * @public
- * @param {string} queryString
- * @chainable
- */
- query = function (queryString) {
- if (queryString !== undefined) {
- clearFroms();
- }
- return getset('query', queryString, queryOptions, this);
- },
- /**
- * Get endpoint URL.
- * @method endpointURL
- * @public
- * @return {string}
- * @since 0.5
- **/
- /**
- * Set endpoint URL.
- * @method endpointURL
- * @public
- * @param {string} url
- * @chainable
- * @example
- * sgvizler.endpointURL('http://sparql.dbpedia.org');
- * sets this Query object's endpoint to DBpedia.
- * @since 0.5
- **/
- endpointURL = function (url) {
- return getset('endpoint', url, queryOptions, this);
- },
-
- /**
- * Get endpoint output format.
- * @method endpointOutputFormat
- * @public
- * @return {string}
- * @since 0.5
- **/
- /**
- * Set endpoint output format. Legal values are `'xml'`,
- * `'json'`, `'jsonp'`.
- * @method endpointOutputFormat
- * @public
- * @param {string} format
- * @chainable
- * @since 0.5
- **/
- endpointOutputFormat = function (format) {
- return getset('endpoint_output_format', format, queryOptions, this);
- },
-
- // TODO
- endpointResultsURLPart = function (value) {
- return getset('endpoint_results_urlpart', value, queryOptions, this);
- },
-
- /**
- * Get URL to online SPARQL query validator.
- * @method validatorURL
- * @public
- * @return {string}
- * @since 0.5
- **/
- /**
- * Set URL to online SPARQL query validator. Appending a
- * SPARQL query to the end of this URL should give a page
- * which validates the given query.
- * @method validatorURL
- * @public
- * @param {string} url
- * @chainable
- * @since 0.5
- * @example
- * TODO
- **/
- validatorURL = function (url) {
- return getset('validator_url', url, queryOptions, this);
- },
-
- // TODO
- loglevel = function (value) {
- return getset('loglevel', value, queryOptions, this);
- },
-
- // TODO
- logContainer = function (value) {
- return getset('logContainer', value, queryOptions, this);
- },
-
- /**
- * Get the name of datatable preprocessing function.
- * @method datatableFunction
- * @public
- * @return {string}
- * @since 0.5
- **/
- /**
- * Set the name of datatable preprocessing function. The
- * function should be availble in the global object, or
- * registered with dependencies in Sgvizler's registry;
- * see TODO
- * @method datatableFunction
- * @public
- * @param {string} functionName
- * @chainable
- * @since 0.5
- **/
- datatableFunction = function (functionName) {
- return getset('datatable', functionName, queryOptions, this);
- },
-
- /**
- * Get the name of chart function.
- * @method chartFunction
- * @public
- * @return {string}
- * @since 0.5
- **/
- /**
- * Set the name of chart function. The function should be
- * availble in the global object, or registered with
- * dependencies in Sgvizler's registry; see TODO
- * @method chartFunction
- * @public
- * @param {string} functionName
- * @chainable
- * @since 0.5
- **/
- chartFunction = function (functionName) {
- return getset('chart', functionName, queryOptions, this);
- },
-
- /**
- * Get the height of the chart container.
- * @method chartHeight
- * @public
- * @return {string}
- * @since 0.5
- **/
- /**
- * Set the height of the chart container.
- * @method chartHeight
- * @public
- * @param {number} height
- * @chainable
- * @since 0.5
- **/
- chartHeight = function (height) {
- return getset('height', height, chartOptions, this);
- },
-
- /**
- * Get the width of the chart container.
- * @method chartWidth
- * @public
- * @return {string}
- * @since 0.5
- **/
- /**
- * Set the width of the chart container.
- * @method chartWidth
- * @public
- * @param {number} width
- * @chainable
- * @since 0.5
- **/
- chartWidth = function (width) {
- return getset('width', width, chartOptions, this);
- },
-
- /**
- * Get the query string with prefixes added and encoded
- * for URL insertion.
- * @method getEncodedQuery
- * @public
- * @return {String}
- * @since 0.5
- **/
- getEncodedQuery = function () {
- return encodeURIComponent(prefixesSPARQL() + query());
- },
-
- /**
- * Extends the query string by including the urls in
- * `from` as `FROM` statements in the (SPARQL) `query`.
- * @method insertFrom
- * @private
- * @since 0.5
- **/
- insertFrom = function () {
- var i, len = queryOptions.froms.length,
- from;
- if (len) {
- from = "";
- for (i = 0; i < len; i += 1) {
- from += 'FROM <' + queryOptions.froms[i] + '>\n';
- }
- query(query().replace(/((WHERE)?(\s)*\{)/i, '\n' + from + '$1'));
- }
- },
-
- /**
- * Sets and returns `noOfResults`, i.e., the number of
- * rows in the query result.
- * @method getResultRowCount
- * @private
- * @param {google.visualization.DataTable} dataTable
- * @return {Number}
- * @since 0.5
- **/
- getResultRowCount = function (dataTable) {
- if (noOfResults === undefined) {
- if (endpointOutputFormat() === qfXML) {
- noOfResults = parser.countXML(dataTable);
- } else {
- noOfResults = parser.countJSON(dataTable);
- }
- }
- return noOfResults;
- },
-
- /**
- * Converts "raw" query results into Google JSON, using
- * sgvizler.parser.
- * @method getGoogleJSON
- * @private
- * @param {Object} data Query result set
- * @return {JSON} JSON edable by google.visualization.DataTable
- * @since 0.5
- **/
- getGoogleJSON = function (data) {
- var gJSON = {};
- if (getResultRowCount(data)) {
- if (endpointOutputFormat() === qfXML) {
- gJSON = parser.convertXML(data);
- } else {
- gJSON = parser.convertJSON(data);
- }
- }
- return gJSON;
- },
-
- // TODO: add different listeners. onQuery, onResults, onDraw?
- /**
- * Add a function which is to be fired when the
- * listener named `name` is fired.
- *
- * See `fireListener`
- *
- * @method addListener
- * @private
- * @param {String} name The name of the listener.
- * @param {Function} func The function to fire.
- * @example
- * addListener("ready", function () { console.log("Ready!") });
- * @since 0.6.0
- **/
- addListener = function (name, func) {
- if (typeof func === 'function') { // accept only functions.
- listeners[name] = listeners[name] || [];
- listeners[name].push(func);
- } else {
- throw new TypeError();
- }
- },
-
- /**
- * Fires (runs, executes) all functions registered
- * on the listener `name`.
- *
- * See `addListener`.
- *
- * @method fireListener
- * @private
- * @param {String} name The name of the listener.
- * @since 0.6.0
- **/
- fireListener = function (name) {
- if (listeners[name]) {
- while (listeners[name].length) {
- (listeners[name].pop())(); // run function.
- }
- }
- },
-
- /**
- * Sends query to endpoint using AJAX. "Low level" method,
- * consider using `saveQueryResults()`.
- * @method sendQuery
- * @private
- * @async
- * @return {jQuery.Promise} A Promise containing the query results.
- * @since 0.5
- **/
- // TODO .fail, .progress: logging.
- sendQuery = function () {
- var promise, // query promise.
- myEndpointOutput = endpointOutputFormat();
-
- insertFrom();
-
- if (myEndpointOutput !== qfJSONP &&
- window.XDomainRequest) {
-
- // special query function for IE. Hiding variables in inner function.
- // TODO See: https://gist.github.com/1114981 for inspiration.
- promise = (
- function () {
- /*global XDomainRequest */
- var qx = $.Deferred(),
- xdr = new XDomainRequest(),
- url = endpointURL() +
- "?query=" + getEncodedQuery() +
- "&output=" + myEndpointOutput;
- xdr.open("GET", url);
- xdr.onload = function () {
- var data;
- if (myEndpointOutput === qfXML) {
- data = $.parseXML(xdr.responseText);
- } else {
- data = $.parseJSON(xdr.responseText);
- }
- qx.resolve(data);
- };
- xdr.send();
- return qx.promise();
- }()
- );
- } else {
- promise = $.ajax({
- url: endpointURL(),
- data: {
- query: prefixesSPARQL() + query(),
- output: (myEndpointOutput === qfJSONP) ? qfJSON : myEndpointOutput
- },
- dataType: myEndpointOutput
- });
- }
- return promise;
- },
-
- /**
- * Saves the query result set in the private property
- * `results`. Works like a wrapper for sendQuery().
- *
- * See also saveDataTable().
- *
- * @TODO: also put the results in the promise object---and
- * how to get them out?
- *
- * @method saveQueryResults.
- * @private
- * @async
- * @return {jQuery.Promise} A Promise which resolves when
- * the results are saved.
- * @since 0.5
- **/
- saveQueryResults = function () {
- var qr;
-
- if (queryResult !== undefined) {
- qr = queryResult;
- } else {
- qr = sendQuery();
- qr.fail(
- function (xhr, textStatus, thrownError) {
- logger.log("Error: A '" + textStatus + "' occurred in Query.saveQueryResults()");
- fireListener('onFail');
- }
- );
- // add callback to save query results in object.
- qr.done(
- function (data) {
- queryResult = data;
- fireListener('onDone');
- }
- );
- }
- return qr;
- },
-
- /**
- * Converts the the query result set into a
- * google.visualization.DataTable, and if specified,
- * applies datatable preprocessing function, and saves
- * the datatable in the private property
- * `dataTable`.
- *
- * @TODO: also put the results in the promise object---and
- * how to get them out?
- *
- * @method saveDataTable
- * @private
- * @async
- * @return {jQuery.Promise} A Promise which resolves when
- * the datatable is saved.
- * @since 0.5
- **/
- saveDataTable = function () {
- /*global google */
- var qdt, // query data table.
- myDatatableFunction = datatableFunction();
-
- if (dataTable) { // dataTable already computed.
- qdt = dataTable;
- } else {
- qdt =
- $.when(
- saveQueryResults(),
- loadDependencies(fDataTable),
- // Get possible preprocess function.
- (function () {
- var loader = {};
- if (myDatatableFunction) {
- loader = loadDependencies(myDatatableFunction);
- }
- return loader;
- }())
- )
- //TODO .fail(function () {})
- .done(
- function () {
- dataTable = new google.visualization.DataTable(getGoogleJSON(queryResult));
- if (myDatatableFunction) {
- var func = util.getObjectByPath(myDatatableFunction);
- dataTable = func(dataTable);
- }
- }
- );
- // TODO .fail, .progress: logging.
- }
- return qdt;
- },
-
- /**
- * Draws the result of the query in a given container.
- * @method draw
- * @public
- * @param {String} containerId The elementId of the
- * container to draw the result of the query.
- * @since 0.5
- **/
- draw = function (containerId) {
- /*global google */
- // Get query results and necessary charting functions in parallel,
- // then draw chart in container.
-
- var myChart = chartFunction();
-
- $.when(saveDataTable(),
- loadDependencies(myChart))
- .then(
- function () {
- try {
- // chart is loaded by loadDependencies.
- var Func = util.getObjectByPath(myChart),
- chartFunc = new Func(document.getElementById(containerId)),
- ready = function () {
- logger.log(myChart + " for " + containerId + " is ready.");
- fireListener('onDraw');
- };
-
- // log when chart is loaded.
- if (util.startsWith(myChart, moduleGooVis)) {
- google.visualization.events.addListener(chartFunc, 'ready', ready);
- } else if (chartFunc.addListener) {
- chartFunc.addListener('ready', ready);
- }
-
- // dataTable is set by saveDataTable.
- chartFunc.draw(dataTable, chartOptions);
- } catch (x) {
- // TODO: better error reporting, what went wrong?
- logger.log(myChart + " -- " + x);
- }
- }
- );
- };
-
- /////////////////////////////////////////////////////////
- // Initialize things.
-
- // Load default values, and overwrite them with values given
- // in constructer parameters.
- queryOptions = $.extend(defaults.getQueryOptions(),
- queryOpt);
- chartOptions = $.extend(defaults.getChartOptions(),
- defaults.getChartSpecificOptions(chartFunction()),
- chartOpt);
-
- // Safeguard constructor.
- if (!(this instanceof S.Query)) {
- throw new Error("Constructor 'Query' called as a function. Use 'new'.");
- }
-
- ////////////////////////////////////////////////////////
- // PUBLICs
-
- return {
-
- //// attributes
- noOfResults: noOfResults,
-
- //// functions
- draw: draw,
- getEncodedQuery: getEncodedQuery,
-
- // listeners
- onFail: function (func) {
- addListener('onFail', func);
- },
- onDone: function (func) {
- addListener('onDone', func);
- },
- onDraw: function (func) {
- addListener('onDraw', func);
- },
-
- /**
- * @method getDataTable
- * @public
- * @param {Function} success
- * @param {Function} fail
- * @async
- * @beta
- */
- getDataTable: function (success, fail) {
- $.when(saveDataTable())
- .then(
- function () {
- var data = dataTable.clone();
- success(data);
- },
- function () {
- var data = dataTable.clone();
- fail(data);
- }
- );
- },
- // TODO
- /*getQueryResults : function (success, fail) {
- $.when(saveQueryResults()).then(success(queryResult), fail);
- },
- getGoogleJSON: function (success, fail) {
- getQueryResults(
- function (queryResult) {
- success(getGoogleJSON(queryResult));
- },
- fail
- );
- },*/
-
- //// FROM
- addFrom: addFrom,
- clearFroms: clearFroms,
-
- //// Getters/setters. Cascade pattern.
- query: query,
- endpointURL: endpointURL,
- endpointOutputFormat: endpointOutputFormat,
- endpointResultsURLPart: endpointResultsURLPart,
- validatorURL: validatorURL,
- loglevel: loglevel,
- logContainer: logContainer,
- datatableFunction: datatableFunction,
- chartFunction: chartFunction,
- chartHeight: chartHeight,
- chartWidth: chartWidth
- };
- };
-
-