diff --git a/Makefile b/Makefile index ffcfa94..8e42650 100644 --- a/Makefile +++ b/Makefile @@ -98,5 +98,5 @@ clean: all: \ $(JS_FILES) \ $(JS_FILES:.js=.min.js) \ - docs \ - example.html + example.html \ + #docs diff --git a/sgvizler.js b/sgvizler.js new file mode 100644 index 0000000..00099ff --- /dev/null +++ b/sgvizler.js @@ -0,0 +1,5218 @@ +// Sgvizler 0.7 - https://dev.data2000.no/sgvizler/ +// (c) 2011--2013 - Martin G. Skjæveland - MIT license + +(function (window, undefined) { // trick: safekeep 'undefined', and minify. + "use strict"; + + /*global window, jQuery */ + /*jslint todo: true */ + + var document = window.document, // trick: minify. + S = {}, // local scope sgvizler variable. + + // Used in end. (Odd names such that these variables are not + // accidentally accessed by child modules.) + globalGetSet, + globalDefaultsQuery, + globalDefaultsChart; + + + //// OTHER SOURCE FILES ARE CONCATENATED IN BELOW + //// ENDING WITH end.js.part + + /** + * Holds central constants. + * + * @class sgvizler.core + */ + + S.core = (function () { + + // global public constants + var + + /** + * The version number of this sgvizler. + * @property {string} VERSION + * @final + * @public + * @for sgvizler + * @since 0.6.0 + **/ + VERSION = "0.6.0", + + /** + * sgvizler's homepage. + * @property {string} HOMEPAGE + * @final + * @public + * @for sgvizler + * @since 0.6.0 + **/ + HOMEPAGE = "http://dev.data2000.no/sgvizler/", + + // global private constants + LOGOIMAGE = "http://beta.data2000.no/sgvizler/misc/image/mr.sgvizler.png", + CHARTSCSS = "http://beta.data2000.no/sgvizler/release/0.6/sgvizler.charts.css"; + + return { + VERSION: VERSION, + HOMEPAGE: HOMEPAGE, + LOGOIMAGE: LOGOIMAGE, + CHARTSCSS: CHARTSCSS + }; + }()); + /** + * A helpful set of static utility functions: type checking + * variables, generic get-setter, get-setting values in + * hierarchial objects, array functions, DOM manipulation, and + * inheritance. + * + * Dependencies: + * + * - jQuery + * + * @class sgvizler.util + * @static + */ + S.util = (function () { + + /*global $ */ + + var + + /** + * Checks if `input` is a string. + * @method isString + * @protected + * @param input + * @return {boolean} True iff `input` is a string. + * @since 0.6.0 + **/ + isString = function (input) { + return typeof input === 'string'; + }, + + /** + * Checks if `input` is a number. + * @method isNumber + * @protected + * @param input + * @return {boolean} True iff `input` is a number. + * @since 0.6.0 + **/ + isNumber = function (input) { + return typeof input === 'number'; + }, + + /** + * Checks if `input` is a boolean. + * @method isBoolean + * @protected + * @param input + * @return {boolean} True iff `input` is a boolean. + * @since 0.6.0 + **/ + isBoolean = function (input) { + return typeof input === 'boolean'; + }, + + /** + * Checks if `input` is a primitive, i.e., either a string, + * a number or a boolean. + * @method isPrimitive + * @protected + * @param input + * @return {boolean} True iff `input` is a string, a number or a boolean. + * @since 0.6.0 + **/ + isPrimitive = function (input) { + return isString(input) || isNumber(input) || isBoolean(input); + }, + + /** + * Checks if `input` is a function. + * @method isFunction + * @protected + * @param input + * @requires jQuery + * @return {boolean} True iff `input` is a function. + * @since 0.6.0 + **/ + isFunction = $.isFunction, + + /** + * Checks if `input` is an array. + * @method isArray + * @protected + * @param input + * @requires jQuery + * @return {boolean} True iff `input` is an array. + * @since 0.6.0 + **/ + isArray = $.isArray, + + /** + * Checks if `input` is a URL. + * @method isURL + * @protected + * @param input + * @return {boolean} True iff `input` is a URL. + * @since 0.6.0 + **/ + URLpattern = new RegExp( + '^(https?:\\/\\/)?' // protocol + + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' // domain name + + '((\\d{1,3}\\.){3}\\d{1,3}))' // OR ip (v4) address + + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' // port and path + + '(\\?[;&a-z\\d%_.~+=-]*)?' // query string + + '(\\#[-a-z\\d_]*)?$', // fragment locator + 'i' + ), + + isURL = function (input) { + return URLpattern.test(input); + }, + + /** + * Establish "classical inheritance" from Parent to + * Child. Child is linked to the Parent's prototype + * through a new proxy object. This means the Child has a + * prototype object of its own, and access to the Parent's + * prototype. + * + * Taken from book "JavaScript Patterns". + * @method inherit + * @protected + * @param {Object} Child + * @param {Object} Parent + * @since 0.6.0 + **/ + inherit = (function () { + var Proxy = function () { return; }; + return function (Child, Parent) { + Proxy.prototype = Parent.prototype; + Child.prototype = new Proxy(); + //Child.superobject = Parent.prototype; + Child.prototype.constructor = Child; + }; + }()), + + + /** + * Generic set/get method. If `value` is defined, then the + * attribute/property `attr` of `setObject` is set to + * `value` and `returnObject` is returned. Otherwise, the + * (value of) `attr` attribute/property is + * returned. Useful for a casading pattern. + * @method getset + * @protected + * @param {string} attr The name of the property to get/set. + * @param {Object} [value] The value to set. + * @param {Object} setObject The object for which the property shall be set/get. + * @param {Object} returnObject The object to return if value is undefined. + * @return {any} Either `returnObject` or `setObject[attr]` + * @example + * getset('age', 55, person.myArray, person) + * sets `person.myArray.age = 55` and returns `person`. + * + * getset('age', undefined, person.myArray, person) + * returns `person.myArray.age`. + * @since 0.6.0 + **/ + getset = function (attr, value, setObject, returnObject) { + if (value !== undefined) { + setObject[attr] = value; + } + return (value !== undefined) ? returnObject : setObject[attr]; + }, + + /** + * Checks if a string starts with (is the prefix of) an other string. + * @method startsWith + * @protected + * @param {string} string + * @param {string} prefix + * @return {boolean} True iff `prefix` is the prefix of `string`. + * @example + * startsWith("Hal", "Hallo!"); // returns true + * startsWith("hal", "Hallo!"); // returns false + * @since 0.6.0 + **/ + startsWith = function (string, prefix) { + return string.lastIndexOf(prefix, 0) === 0; + }, + + /** + * Gets the object located at `path` from `object`. `path` + * is given in dot notation. + * + * @method getObjectByPath + * @protected + * @param {string} path + * @param {Object} [object=window] + * @param {boolean} [create=false] + * @return {Object} Returns the object/value located at + * the `path` of `object`; otherwise, if `create` is true, + * it is created. + * @example + * getObjectByPath('sgvizler.visualization.Table', registry, true) + * returns the object located at + * `registry['sgvizler']['visualization']['Table']` if it + * exists; otherwise, since `'create' === true`, the path + * and (empty) object is created and returned. + * @since 0.6.0 + **/ + getObjectByPath = function (path, object, create) { + var i, len, + segments = path.split('.'), + cursor = object || window; // window is the global scope. + + for (i = 0, len = segments.length; i < len; i += 1) { + if (cursor !== undefined && // cursor must be defined + cursor[segments[i]] === undefined && + create) { // create new child element. + cursor[segments[i]] = {}; + } + cursor = cursor && cursor[segments[i]]; // if cursor is undefined, it remains undefined. + } + return cursor; + }, + + /** + * Checks if a an array contains a given element. + * @method isInArray + * @protected + * @param {any} item + * @param {Array} array + * @return {boolean} True iff `array` contains an element `item`. + * @since 0.6.0 + **/ + isInArray = function (item, array) { + return ($.inArray(item, array) !== -1); + }, + + /** + * Removes duplicates from an array. + * @method removeDuplicates + * @protected + * @param {Array} array + * @return {Array} The input array with duplicates removed. + * @example + * removeDuplicates([1, 1, 1, 2, 4, 3, 2]); // returns [1, 2, 4, 3] + * @since 0.6.0 + **/ + removeDuplicates = function (array) { + var i, len, + unique = []; + for (i = 0, len = array.length; i < len; i += 1) { + if (!isInArray(array[i], unique)) { + unique.push(array[i]); + } + } + return unique; + }, + + /** + * Converts `input` to an array. If `input` is undefined, + * then an empty array is returned. If `input` is + * primitive, then it is put in an (empty) array. If `input` + * /is/ an array, then the `input` is simply returned. + * + * Useful for converting input to other methods to arrays. + * @method toArray + * @protected + * @param {undefined|primitive|Array} input + * @return {Array} An array representation of `input`. + * @example + * toArray(undefined); // returns [] + * toArray('myString'); // returns ['myString'] + * toArray([1, 2, 3]); // returns [1, 2, 3] + * toArray(function () {}); // throws TypeError + * @since 0.6.0 + **/ + toArray = function (input) { + var output; + if (input === undefined) { + output = []; + } else if (isPrimitive(input)) { + output = [input]; + } else if (isArray(input)) { + output = input; + } else { + throw new TypeError(); + } + return output; + }, + + + /** + * Return the attribute values of a object as a (unsorted) list. + * @protected + * @param {Object} object + * @return {Array} + * @example + * getObjectValues({a: 'apple', b: 'orange', c: 'horse'}) = ['apple', 'orange', 'horse'] + */ + getObjectValues = function (object) { + var key, list = []; + for (key in object) { + if (object.hasOwnProperty(key)) { + list.push(object[key]); + } + } + return list; + }, + + /** + * Creates an HTML element according to a custom made + * "array syntax". Used to make HTML DOM manipulation more + * code compact. + * @method createHTMLElement + * @protected + * @param {string} elementType The type of element to + * create, e.g., "div" or "h1". + * @param {Object} [attributes] Object of + * attribute--value's to be added to the element. + * @param {Array|primitive} [children] An array of + * children to be added to the element; each element in + * the `children` array is an array of three elements, one + * for each parameter of this method. If this argument is + * a primitive, then it is inserted as a text node. + * @return {Object} Element (ready for insertion into DOM.) + * @example + * createHTMLElement('ul', { 'class': "myClass", 'id': "myID" }, [ ['li', null, "One" ], + * ['li', { 'id': "ABC" } , 2 ], + * ['li', null, true] ] ); + * + * will create the HTML element: + * + * + * @since 0.6.0 + **/ + createHTMLElement = function createHTMLElement(elementType, attributes, children) { + var i, len, + element = $(document.createElement(elementType)), + attr, + childs = toArray(children), // [sic] + child; + + // Add attributes to element. + for (attr in attributes) { + if (attributes.hasOwnProperty(attr)) { + element.attr(attr, attributes[attr]); + } + } + + // Add children to element. String are "simply" added, else it + // should be an array of arguments to (recursive) create() call. + for (i = 0, len = childs.length; i < len; i += 1) { + child = childs[i]; + if (isPrimitive(child)) { + element.append(child); + } else if (isArray(child)) { + element.append(createHTMLElement.apply(undefined, child)); + } else { + throw new TypeError(); + } + } + return element; + }; + + return { + isString: isString, + isNumber: isNumber, + isBoolean: isBoolean, + isPrimitive: isPrimitive, + isFunction: isFunction, + isArray: isArray, + isURL: isURL, + + startsWith: startsWith, + + isInArray: isInArray, + toArray: toArray, + removeDuplicates: removeDuplicates, + + getObjectValues: getObjectValues, + getObjectByPath: getObjectByPath, + + getset: getset, + inherit: inherit, + + createHTMLElement: createHTMLElement + }; + }()); + + /** + * Static class for handling prefixes and namespaces. Use for + * storing prefixes used in SPARQL queries and for formatting + * result sets, i.e., replacing namespaces with prefixes, which + * many chart functions automatically do. + * + * Already defined prefixes are `rdf`, `rdfs`, `owl` and `xsd`. + * + * Dependencies: + * + * - sgvizler.util + * + * @class sgvizler.namespace + * @static + */ + S.namespace = (function () { + + // Module dependencies: + var startsWith = S.util.startsWith, + isString = S.util.isString, + + /** + * Stores prefix--namespace pairs. + * @property nss + * @type Object + * @private + * @since 0.1 + **/ + nss = { + 'rdf' : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + 'rdfs': "http://www.w3.org/2000/01/rdf-schema#", + 'owl' : "http://www.w3.org/2002/07/owl#", + 'xsd' : "http://www.w3.org/2001/XMLSchema#" + }, + + /** + * @property baseURL + * @type String + * @private + * @since 0.6.0 + **/ + baseURL = null; + + ///////////////////////////////////////////////////////// + // PUBLICs + + return { + + /** + * Get a namespace. + * + * See also set. + * + * @method get + * @protected + * @param {string} prefix The prefix to get the namespace for. + * @return {string} The namespace set for 'prefix'; + * undefined if 'prefix' does not exist. + * @example + * get('xsd'); // returns "http://www.w3.org/2001/XMLSchema#" + * @since 0.6.0 + **/ + get: function (prefix) { + return nss[prefix]; + }, + + /** + * Set a namespace. + * + * See also get. + * + * @method set + * @protected + * @param {string} prefix The prefix to set. + * @param {string} namespace The namespace to set. + * @example + * set('foaf', "http://xmlns.com/foaf/0.1/"); + * sets `'foaf'` as prefix for the FOAF namespace. + * @since 0.6.0 + **/ + set: function (prefix, namespace) { + nss[prefix] = namespace; + }, + + /** + * Get Base URL value. + * + * See also setBaseURL. + * + * @method getBaseURL + * @return {string} The base URL. + * @protected + * @since 0.6.0 + **/ + getBaseURL: function () { + return baseURL; + }, + /** + * Set Base URL value. + * + * See also setBaseURL. + * + * @method getBaseURL + * @param {string} url The base URL. + * @protected + * @since 0.6.0 + **/ + setBaseURL: function (url) { + baseURL = url; + }, + + /** + * Get all prefixes in SPARQL format. + * @method prefixesSPARQL + * @protected + * @return {string} An SPARQL formatted prefix declaration + * text block containing all set prefixes. + * @since 0.1 + **/ + prefixesSPARQL: function () { + var prefix, + prefixes = ""; + for (prefix in nss) { + if (nss.hasOwnProperty(prefix)) { + prefixes += "PREFIX " + prefix + ": <" + nss[prefix] + ">\n"; + } + } + return prefixes; + }, + + /** + * Replace a namespace with its prefix, for string which + * starts with a namespace. Typically used for URLs of + * resources. + * + * Leaves other strings untouched. + * + * See also unprefixify. + * + * @method prefixify + * @protected + * @param {string} url + * @return {string} + * @example + * prefixify("http://www.w3.org/2002/07/owl#Class"); // returns "owl:Class" + * prefixify("Hello World!"); // returns "Hello World!" + * @since 0.3.3 + **/ + prefixify: function (url) { + var prefix; + if (isString(url)) { + for (prefix in nss) { + if (nss.hasOwnProperty(prefix) && + startsWith(url, nss[prefix])) { + return url.replace(nss[prefix], prefix + ":"); + } + } + } + return url; + }, + + /** + * Replace a prefix with its namespace, for string which + * starts with a prefix: Typically used for prefixed URLs + * (QNames) of resources. + * + * Leaves other strings untouched. + * + * See also prefixify. + * + * @method unprefixify + * @protected + * @param {string} qname + * @return {string} + * @example + * unprefixify("owl:Class"); // returns "http://www.w3.org/2002/07/owl#Class" + * unprefixify("Hello World!"); // returns "Hello World!" + * @since 0.3.3 + **/ + unprefixify: function (qname) { + var prefix; + if (isString(qname)) { + for (prefix in nss) { + if (nss.hasOwnProperty(prefix) && + startsWith(qname, prefix + ":")) { + return qname.replace(prefix + ":", nss[prefix]); + } + } + } + return qname; + } + }; + + }()); + + //TODO publish libs at libFolder + + /** + * Static class for handling functions used for drawing charts, + * mainpulating datacharts, and what their dependencies are. + * + * Dependencies: + * + * - sgvizler.util + * + * See also: + * + * - sgvizler.charts, sgvizler.datatables (classes for creating new such functions) + * - sgvizler.loader (class for loading dependencies) + * + * @class sgvizler.registry + * @static + */ + S.registry = (function () { + + // Module dependencies: + var util = S.util, + + /** + * The Google Visualization package name. + * @property GVIZ + * @type String + * @private + * @final + * @since 0.6.0 + **/ + GVIZ = 'google.visualization', + + /** + * The Google Visualization DataTable class name. + * @property DATATABLE + * @type String + * @private + * @final + * @since 0.6.0 + **/ + DATATABLE = GVIZ + '.DataTable', + + /** + * The Google Maps package name. + * @property GVIZ + * @type String + * @private + * @final + * @since 0.6.0 + **/ + GMAP = 'google.maps', + + /** + * Stores the modules of the registered functions + * according to the type of function, i.e., `chart` or + * `datatable`. + * @property modules + * @type Object + * @private + * @since 0.6.0 + **/ + modules = { + chart: [GVIZ],//, GMAP], + datatable: [] + }, + + /** + * Stores registered function names and their + * dependencies, e.g., specifies which google + * visualization packages to load for the different + * charts. + * + * Property legend: + * + * - `t`: type. Values: `core`, `datatable`, `chart` (default) + * - `d`: dependences. Object containing functions--gviz package/js file + * - `i`: the function itself, as in I. + * + * @property registry + * @type Object + * @private + * @since 0.6.0 + **/ + registry = { + google: { + ////////////////////////////////////////////////////// + // google.visualization + visualization: { + DataTable: { + t: 'core', + d: { i: GVIZ } + }, + LineChart: { + d: { i: 'corechart' } + }, + AreaChart: { + d: { i: 'corechart' } + }, + SteppedAreaChart: { + d: { i: 'corechart' } + }, + PieChart: { + d: { i: 'corechart' } + }, + BubbleChart: { + d: { i: 'corechart' } + }, + ColumnChart: { + d: { i: 'corechart' } + }, + BarChart: { + d: { i: 'corechart' } + }, + ImageSparkLine: { + d: { i: 'imagesparkline' } + }, + ScatterChart: { + d: { i: 'corechart' } + }, + CandlestickChart: { + d: { i: 'corechart' } + }, + Gauge: { + d: { i: 'gauge' } + }, + OrgChart: { + d: { i: 'orgchart' } + }, + TreeMap: { + d: { i: 'treemap' } + }, + AnnotatedTimeLine: { + d: { i: 'annotatedtimeline' } + }, + MotionChart: { + d: { i: 'motionchart' } + }, + GeoChart: { + d: { i: 'geochart' } + }, + GeoMap: { + d: { i: 'geomap' } + }, + Map: { + d: { i: 'map' } + }, + Table: { + d: { i: 'table' } + } + }, + ////////////////////////////////////////////////////// + // google.maps + maps: { + Map: { + d: { i: 'map' } + } + } + } + }; + + //////////////////////////////////////////// + // PUBLICs + + return { + + // Constants + GVIZ: GVIZ, + GMAP: GMAP, + DATATABLE: DATATABLE, + + /** + * Get list of registered chart module (names), i.e., modules for + * which there are registered functions for drawing + * charts. + * @method chartModules + * @protected + * @return {Array} (an array of strings) + * @since 0.6.0 + **/ + chartModules: function () { + return modules.chart; + }, + + /** + * Get list of registered chart functions names (not the + * functions themselves). + * @method chartsFunctions + * @protected + * @return {Array} (an array of strings) + * @since 0.6.0 + **/ + chartFunctions: function () { + var i, len, + libs = modules.chart,//TODO: should be chartModules() but gives "is not defined"-error. + lib, + func, + charts = []; + + for (i = 0, len = libs.length; i < len; i += 1) { + lib = util.getObjectByPath(libs[i], registry); + for (func in lib) { + if (lib.hasOwnProperty(func) && + (lib[func].t === undefined || + lib[func].t === 'chart')) { + charts.push(libs[i] + "." + func); + } + } + } + return charts; + }, + + /** + * Get list of dependencies, either google visualization + * packages or javascripts (URLs), for given function + * name. + * @method getDependencies + * @protected + * @param {String} functionName + * @return {Array} (an array of strings) + * @since 0.6.0 + **/ + getDependencies: function (functionName) { + var regFunc = util.getObjectByPath(functionName, registry), + deps = (regFunc && regFunc.d) || {}; + + // rename i to functionName: + if (deps.i) { + deps[functionName] = deps.i; + delete deps.i; + } + return deps; + }, + + /** + * Add function to registry. + * @method addFunction + * @protected + * @param {String} module name of module to which function belongs. + * @param {String} name name of function. + * @param {String} type of function, usually either `'chart'`, `'datatable'`. + * @param {Object} dependencies list of function + * name--dependency pairs. Example: `{ 'XYZ': + * 'http://example.org/XYZ.js' }` if the function requires + * the XYX function to draw and this function is located + * at `http://example.org/XYZ.js`. + * @since 0.6.0 + **/ + addFunction: function (module, name, type, dependencies) { + var regFunc; // The function's place in registry. + + // Add module if it is new. + if (!util.isInArray(module, modules[type])) { + modules[type].push(module); + } + + // Add function to registry. + regFunc = util.getObjectByPath(module + "." + name, registry, true); + + if (type) { + regFunc.t = type; + } + if (dependencies) { + regFunc.d = dependencies; + } + } + + }; + }()); + /** + * Handles all logging, either to console or designated HTML + * container. + * + * Needs more work. + * + * @class sgvizler.logger + * @static + */ + S.logger = (function () { + + /*global $, console*/ + + // Module dependencies: + //var util = S.util, + var + /** + * The timestamp for the load start of the current running + * version of sgvizler. Used to calculate time elapse of + * future events. + * @property start + * @type number + * @private + * @since 0.6.0 + **/ + startTime = Date.now(), + + /** + * @method timeElapsed + * @private + * @return {number} The number of seconds elapsed since + * this sgvizler got loaded. + * @since 0.6.0 + **/ + elapsedTime = function () { + return (Date.now() - startTime) / 1000; + }, + + /** + * @property waitingCharts + * @type number + * @private + * @beta + **/ + waitingCharts = 0; + + return { + + /** + * Logs a message. + * @method log + * @protected + * @param {string} message The message to log. + * @beta + */ + log: function (message) { + console.log(elapsedTime() + "s: " + message); + }, + + // TODO + loadingChart: function () { + waitingCharts += 1; + if (!$('body,html').css('cursor', 'progress')) { + $('body,html').css('cursor', 'progress'); + } + }, + doneLoadingChart: function () { + waitingCharts -= 1; + if (waitingCharts === 0 && $('body,html').css('cursor', 'progress')) { + $('body,html').css('cursor', 'default'); + } + } + + // TODO + // displayFeedback: function (query, messageName) { + // var message, + // container = query.logContainer(); + + // if (query.loglevel() === 0) { + // message = ""; + // } else if (query.loglevel() === 1) { + // if (messageName === "LOADING") { + // message = "Loading..."; + // } else if (messageName === "ERROR_ENDPOINT" || messageName === "ERROR_UNKNOWN") { + // message = "Error."; + // } + // } else { + // if (messageName === "LOADING") { + // message = "Sending query ..."; + // } else if (messageName === "ERROR_ENDPOINT") { + // message = "Error querying endpoint. Possible errors:" + + // util.html.ul( + // util.html.a(query.endpoint(), "SPARQL endpoint") + " down? " + + // util.html.a(query.endpoint() + query.endpointQueryURL + query.encodedQuery(), + // "Check if query runs at the endpoint") + ".", + // "Malformed SPARQL query? " + + // util.html.a(query.validatorQueryURL() + query.encodedQuery(), "Check if it validates") + ".", + // "CORS supported and enabled? Read more about " + + // util.html.a(S.homepage + "/wiki/Compatibility", "CORS and compatibility") + ".", + // "Is your " + util.html.a(S.homepage + "/wiki/Compatibility", "browser support") + "ed?", + // "Hmm.. it might be a bug! Please file a report to " + + // util.html.a(S.homepage + "/issues/", "the issues") + "." + // ); + // } else if (messageName === "ERROR_UNKNOWN") { + // message = "Unknown error."; + // } else if (messageName === "NO_RESULTS") { + // message = "Query returned no results."; + // } else if (messageName === "DRAWING") { + // message = "Received " + query.noRows + " rows. Drawing chart...
" + + // util.html.a(query.endpoint + query.endpoint_query_url + query.encodedQuery, + // "View query results", "target='_blank'") + " (in new window)."; + // } + // } + // if (message) { + // $('#' + container).append(util.html.tag("p", message)); + // } + // } + }; + }()); + + /** + * Factory for creating new chart types. Ensures that chart types + * correctly inherit methods from the inner class Chart. + * + * Dependencies: + * + * - sgvizler.util + * - sgvizler.registry + * + * + * + * @class sgvizler.charts + * @static + */ + S.charts = (function () { + + var + // Module dependencies: + inherit = S.util.inherit, + addFunction = S.registry.addFunction, + + Chart, // parent chart class. Created below so that + // documentation of methods falls into the right + // class. + + /** + * Create new Chart type. + * @method chartsAdd + * @public + * @for sgvizler + * @param {String} module The module/namespace name to + * which the function belongs. + * @param {String} name The name of the function. + * @param {Function} draw The function which will be the + * `draw()` function of the new chart type. + * @param {Object} dependencies The list of dependencies + * for the chart type: function name -- google + * package/javascript URL pairs. + * @return {Object} The chart type. + * @since 0.6.0 + **/ + add = function (module, name, draw, dependencies) { + // This is the object from which new are created. + var NewChart = function (container) { + Chart.call(this, container); + }; + + // Set inheritance. + inherit(NewChart, Chart); + + // Copy draw() into new object. + NewChart.prototype.draw = draw; + + addFunction(module, name, 'chart', dependencies); + return NewChart; + }; + + + ///////////////////////////////////////////////////////// + // Inner class Chart + + /** + * Inner class which all chart types created by + * sgvizler.charts inherit from, i.e., don't create new charts + * from this class, but use sgvizler.charts.create() instead. + * @class sgvizler.charts.Chart + * @constructor + * @param {Object} container The container element where the + * chart will be drawn. + */ + // This function builds the class Chart. + Chart = (function () { + var C = function (container) { + this.container = container; + this.listeners = {}; + }; + C.prototype = { + + /** + * Add a function which is to be fired when the + * listener named `name` is fired. + * + * See `fireListener` + * + * @method addListener + * @public + * @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. + this.listeners[name] = this.listeners[name] || []; + this.listeners[name].push(func); + } else { + throw new TypeError(); + } + }, + + /** + * Fires (runs, executes) all functions registered + * on the listener `name`. + * + * See `addListener`. + * + * @method fireListener + * @public + * @param {String} name The name of the listener. + * @since 0.6.0 + **/ + fireListener: function (name) { + if (this.listeners[name]) { + while (this.listeners[name].length) { + (this.listeners[name].pop())(); // run function. + } + } + } + }; + return C; + }()); + + ///////////////////////////////////////////////////////// + // PUBLICs for sgvizler.charts + return { + add: add + }; + }()); + + /** + * Factory for creating new datatypes functions. + * + * Dependencies: + * + * - sgvizler.registry + * + * @class sgvizler.datatables + * @static + */ + S.datatables = (function () { + + // Module dependencies: + var addFunction = S.registry.addFunction, + + /** + * Create new Chart type. + * @method datatablesAdd + * @public + * @for sgvizler + * @param {String} module The module/namespace name to + * which the function belongs. + * @param {String} name The name of the function. + * @param {Function} func The datatable processing function. + * @param {Object} dependencies The list of dependencies + * for the chart type: function name -- google + * package/javascript URL pairs. + * @return {Function} The datatable processing function. + * @since 0.6.0 + */ + add = function (module, name, func, dependencies) { + addFunction(module, name, 'datatable', dependencies); + return func; + }; + + ///////////////////////////////////////////////////////// + // PUBLICs chartFactory + + return { + add: add + }; + }()); + + /** + * Parses a SPARQL result set, assumed to be in either W3C's + * [XML](http://www.w3.org/TR/rdf-sparql-XMLres/) or + * [JSON](http://www.w3.org/TR/rdf-sparql-json-res/) format, into + * [Google + * JSON](https://developers.google.com/chart/interactive/docs/reference#DataTable) + * which is the JSON format that the + * `google.visualization.DataTable` class accepts. + * + * Variable notation: xtable, xcol(s), xrow(s) -- x is 's'(parql) + * or 'g'(oogle). + * + * Dependencies: + * - `sgvizler.namespace` + * - jQuery - for xml "browsing". + * + * @class sgvizler.parser + * @static + */ + + S.parser = (function () { + + /*global $ */ + + // Module dependencies: + var namespace = S.namespace, + + /** + * Convertion table for turning XSD datatypes into the + * "javascript" datatypes which the + * `google.visualization.DataTable` accepts, which is: `string`, + * `number`, `boolean`, `date`, `datetime`, `timeofday`. + * @property datatypeXSD2JS + * @type Object + * @private + * @since 0.1 + **/ + + datatypeXSD2JS = (function () { + var xsdns = namespace.get('xsd'), + table = []; + table[xsdns + "float"] = 'number'; + table[xsdns + "decimal"] = 'number'; + table[xsdns + "int"] = 'number'; + table[xsdns + "integer"] = 'number'; + table[xsdns + "long"] = 'number'; + table[xsdns + "boolean"] = 'boolean'; + table[xsdns + "date"] = 'date'; + table[xsdns + "dateTime"] = 'datetime'; + table[xsdns + "time"] = 'timeofday'; + return table; + }()), + + + /** + * Converts XSD datatypes into Google JSON datatypes. + * + * See also property `datatypeXSD2JS`. + * @method getGoogleJsonDatatype + * @private + * @param {string} sdatatype An XSD datatype, full URL. + * @return {string} gdatatype, defaults to `string` + * @since 0.1 + **/ + getGoogleJsonDatatype = function (sdatatype) { + return datatypeXSD2JS[sdatatype] || 'string'; + }, + + /** Converts results values into Google JSON values + * according to the Google JSON datatype, i.e., values + * other than strings and booleans need special + * treatment. If the value is an URL, we "prefixify" it. + * + * See also `sgvizler.namespace.prefixify` + * + * @method getGoogleJsonValue + * @private + * @param {string|number|boolean} value The value from the SPARQL result set. + * @param {string} gdatatype The Google JSON datatype. + * @param {string} stype The `type` of the value in the + * SPARQL endpoint, e.g. `uri` or `literal`. + * @return {Date|number|string} The converted value. + * @since 0.1 + **/ + getGoogleJsonValue = function (value, gdatatype, stype) { + var newvalue; + if (gdatatype === 'number') { + newvalue = Number(value); + } else if (gdatatype === 'date') { + //assume format yyyy-MM-dd + newvalue = new Date(value.substr(0, 4), + parseInt(value.substr(5, 2), 10) - 1, + value.substr(8, 2)); + } else if (gdatatype === 'datetime') { + //assume format yyyy-MM-ddZHH:mm:ss + newvalue = new Date(value.substr(0, 4), + parseInt(value.substr(5, 2), 10) - 1, + value.substr(8, 2), + value.substr(11, 2), + value.substr(14, 2), + value.substr(17, 2)); + } else if (gdatatype === 'timeofday') { + //assume format HH:mm:ss + newvalue = [value.substr(0, 2), + value.substr(3, 2), + value.substr(6, 2)]; + } else { // datatype === 'string' || datatype === 'boolean' + if (stype === 'uri') { // replace namespace with prefix + newvalue = namespace.prefixify(value); + } + newvalue = value; + } + return newvalue; + }, + + /** + * Converts a SPARQL XML result set into "Google JSON", + * see + * https://developers.google.com/chart/interactive/docs/reference#DataTable. + * @method convertXML + * @private + * @param {Object} sxml The SPARQL XML result set. + * @return {Object} Object literal ready for + * `google.visualization.DataTable` consumption. + * @since 0.2.2 + **/ + convertXML = function (sxml) { + var c, clen, // column index. + r, // row index. + gcols = [], + grows = [], + gdatatype = [], // for easy reference of datatypes. + sresults = $(sxml).find('sparql').find('results').find('result'); + + // Build gcols: find column names and datatypes. + c = 0; + $(sxml).find('sparql').find('head').find('variable').each(function () { + var sdatatype = null, + name = $(this).attr('name'), + scell = null, + scells = $(sresults).find('binding[name="' + name + '"]'); + if (scells.length) { + scell = $(scells).first().children().first()[0]; // uri, literal element + sdatatype = $(scell).attr('datatype'); + } + gdatatype[c] = getGoogleJsonDatatype(sdatatype); + gcols[c] = { id: name, label: name, type: gdatatype[c] }; + c += 1; + }); + + // Build grows: get results. + r = 0; + $(sresults).each(function () { + var gvalue, + scells, + scell, + stype, + svalue, + grow = []; + for (c = 0, clen = gcols.length; c < clen; c += 1) { + gvalue = null; + scells = $(this).find('binding[name="' + gcols[c].id + '"]'); + if (scells.length && + $(scells).first().children().first() && + $(scells).first().children().first()[0]) { + scell = $(scells).first().children().first()[0]; // uri, literal element + stype = scell.nodeName; + svalue = $(scell).first().text(); + gvalue = getGoogleJsonValue(svalue, gdatatype[c], stype); + } + grow[c] = { v: gvalue }; + } + grows[r] = { c: grow }; + r += 1; + }); + + return { cols: gcols, rows: grows }; + }, + + /** + * Converts a SPARQL JSON result set into "Google JSON", + * see + * https://developers.google.com/chart/interactive/docs/reference#DataTable. + * @method convertJSON + * @private + * @param {Object} stable The SPARQL JSON result set. + * @return {Object} Object literal ready for + * `google.visualization.DataTable` consumption. + * @since 0.1 + **/ + convertJSON = function (stable) { + var c, clen, // column index. + r, rlen, // row index. + srow, + grow, + gvalue, + sdatatype, + gcols = [], + grows = [], + gdatatype = [], // for easy reference of datatypes + scols = stable.head.vars, + srows = stable.results.bindings; + + // Build gcols: find column names and datatypes. + for (c = 0, clen = scols.length; c < clen; c += 1) { + // Find a row where there is a value for this column + // in order to determine correct datatype. + r = 0; + while (r + 1 < srows.length && srows[r][scols[c]] === undefined) { + r += 1; + } + sdatatype = (srows[r] && (srows[r][scols[c]] && srows[r][scols[c]].datatype)) || null; + gdatatype[c] = getGoogleJsonDatatype(sdatatype); + gcols[c] = { id: scols[c], label: scols[c], type: gdatatype[c] }; + } + + // Build grows. + // loop rows + for (r = 0, rlen = srows.length; r < rlen; r += 1) { + srow = srows[r]; + grow = []; + // loop cells + for (c = 0, clen = scols.length; c < clen; c += 1) { + gvalue = null; + if (srow[scols[c]] && srow[scols[c]].value) { + gvalue = getGoogleJsonValue(srow[scols[c]].value, gdatatype[c], srow[scols[c]].type); + } + grow[c] = { v: gvalue }; + } + grows[r] = { c: grow }; + } + + return { cols: gcols, rows: grows }; + }, + + /** + * Returns number of results, SPARQL XML. + * @method countXML + * @private + * @param {Object} sxml The SPARQL XML result set. + * @return {number} The number of result set rows. + * @since 0.2.2 + */ + countXML = function (sxml) { + return $(sxml).find('sparql').find('results').find('result').length; + }, + + /** + * Returns number of results, SPARQL JSON. + * @method countJSON + * @private + * @param {Object} stable The SPARQL JSON result set. + * @return {number} The number of result set rows. + * @since 0.1 + */ + countJSON = function (stable) { + return stable.results.bindings && stable.results.bindings.length; + }, + + /** + * Converts SPARQL XML to Google JSON + * @method getGoogleJsonFromSparqlXml + * @protected + * @param {Object} sxml The SPARQL XML result set. + * @return {Object} Object literal ready for + * `google.visualization.DataTable` consumption. + * @since 0.2.2 + */ + getGoogleJsonFromSparqlXml = function (sxml) { + if (countXML(sxml)) { + return convertXML(sxml); + } + }, + + /** + * Converts SPARQL JSON to Google JSON + * @method getGoogleJsonFromSparqlJson + * @protected + * @param {Object} sxml The SPARQL XML result set. + * @return {Object} Object literal ready for + * `google.visualization.DataTable` consumption. + * @since 0.2.2 + */ + getGoogleJsonFromSparqlJson = function (sjson) { + if (countJSON(sjson)) { + return convertJSON(sjson); + } + }; + + + /////////////////////////////////////////////////// + // PUBLICs + + return { + getGoogleJsonValue: getGoogleJsonValue, // revealed for testing + getGoogleJsonFromSparqlJson: getGoogleJsonFromSparqlJson, + getGoogleJsonFromSparqlXml: getGoogleJsonFromSparqlXml + }; + + }()); + + /** + * Loads dependencies for external functions. + * + * Dependencies: + * + * - sgvizler.util + * - sgvizler.logger + * - sgvizler.registry + * - jQuery + * - google.load + * + * @class sgvizler.loader + * @static + */ + S.loader = (function () { + + /*global $ */ + + // Module dependencies: + var util = S.util, + logger = S.logger, + registry = S.registry, + moduleGooVis = registry.GVIZ, + moduleGooMap = registry.GMAP, + + /** + * Contains a list of dependency loaders: function + * name--deferred pairs. Keeps track of dependencies which + * have already been asked for (but possibly not been + * loaded yet). + * + * @property loaders + * @type Object + * @private + * @since 0.6.0 + **/ + loaders = {}, + + /** + * @method loadGVizScript + * @private + * @param {Array} [packages] List of + * `google.visualization` packages to load. + * @param {boolean} [loadLib] True if + * `google.visualization` should be loaded even if + * `packages` array is empty. This is needed in order to + * load the `DataTable` class, which belongs to no + * package. + * @return {jQuery.Promise} Promise object which resolves + * the loading of the given packages/library. + * @since 0.6.0 + **/ + loadGVizScript = function (packages, loadLib) { + /*global google */ + var loader, + promise = {}, + packs = util.removeDuplicates(packages).sort(), + options; + + if (packs.length || loadLib) { + loader = $.Deferred(); + options = { + callback: function () { + loader.resolve(); + loaders[moduleGooVis].resolve(); // Always resolve google visualization loader. + logger.log("loadGVizScript: packages LOADED: " + packs); + } + }; + + if (packs.length) { + options.packages = packs; + } + google.load('visualization', '1', options); + logger.log("loadGVizScript: loading packages: " + packs); + + promise = loader.promise(); + } + + return promise; + }, + + /** + * @method loadGMapScript + * @private + * @return {jQuery.Promise} Promise object which resolves + * the loading of google.maps. + * @since 0.6.0 + **/ + loadGMapScript = function () { + /*global google */ + var loader = $.Deferred(), + options = { + other_params: "sensor=false", + callback: function () { + loader.resolve(); + logger.log("loadGVizScript: google.maps. LOADED: "); + } + }; + google.load('maps', '3', options); + logger.log("loadGMapScript: loading google.maps."); + + return loader.promise(); + }, + + /** + * Load the dependencies of all the given function + * names---as specified in `sgvizler.registry`. + * @method loadDependencies + * @protected + * @param {Array|string} functions A list of function + * names (or just a single function name) to load + * dependencies for. + * @return {jQuery.Promise} A promise object which + * resolves the loading of all function dependencies. + * @example loadDependencies('google.visualization.Map'); + * returns deferred which will load the dependencies + * for the `google.visualization.Map` function as + * specified by `sgvizler.registry`. + * @since 0.6.0 + **/ + loadDependencies = function (functions) { + var i, ilen, + func, + deferreds = [], // Collect an array of deferreds. + gVizPacks = [], // List of google visualization packages to collect. + gLoader, + deps, + dep, + loadGLib; + + functions = util.removeDuplicates(util.toArray(functions)); + + while (functions.length) { + + func = functions.pop(); + deps = registry.getDependencies(func); + + for (dep in deps) { + if (deps.hasOwnProperty(dep)) { + // Dependency is already loaded/loading. + if (loaders[deps[dep]]) { + deferreds.push(loaders[deps[dep]]); + } else if (util.getObjectByPath(dep) === undefined) { + // If it is a googleViz function, then collect package in an array. + if (util.startsWith(dep, moduleGooVis)) { + // Special handling of DataTable. + if (dep === registry.DATATABLE) { + loadGLib = true; + loaders[deps[dep]] = $.Deferred(); + } else { + gVizPacks.push(deps[dep]); + } + } else if (util.startsWith(dep, moduleGooMap)) { + loaders[deps[dep]] = loadGMapScript(); + deferreds.push(loaders[deps[dep]]); + logger.log("loadDependencies: loading script: " + deps[dep]); + } else { + // Assume dependency is a link to a javascript. + loaders[deps[dep]] = $.getScript(deps[dep]) + .done(function () { logger.log("loadDependencies: loaded: " + deps[dep]); }); + deferreds.push(loaders[deps[dep]]); + logger.log("loadDependencies: loading script: " + deps[dep]); + } + } + } + } + } + // If there are GViz packages, collect them to one deferred. + if (gVizPacks.length || loadGLib) { + gLoader = loadGVizScript(gVizPacks, loadGLib); + deferreds.push(gLoader); + + // Register this gLoader with all input function + // dependencies solved by this loader. + for (i = 0, ilen = gVizPacks.length; i < ilen; i += 1) { + loaders[gVizPacks[i]] = gLoader; + } + } + // Sending array of arguments to when(). See http://bugs.jquery.com/ticket/8256. + return $.when.apply($, deferreds); + }; + + ///////////////////////////////////////////////// + // PUBLICs + + return { + loadDependencies: loadDependencies + }; + }()); + + /** + * A set of default values used mostly, if not only, by the + * sgvizler.Query class. These values may be get and set by the + * get-setters of the sgvizler class. + * + * Dependencies: + * + * - sgvizler.registry + * + * @class sgvizler.defaults + * @static + */ + S.defaults = (function () { + + // Module dependencies + var moduleGooVis = S.registry.GVIZ, + + // Note: all property names must be lowercase. + queryDefaults = { + query: "SELECT ?class (count(?instance) AS ?noOfInstances)\n" + + "WHERE{ ?instance a ?class }\n" + + "GROUP BY ?class\n" + + "ORDER BY ?class", + froms: [], + endpoint: "http://sws.ifi.uio.no/sparql/world", + endpoint_output_format: 'json', // xml, json, jsonp + endpoint_results_urlpart: "?output=text&query=", + validator_url: "http://sparql.org/validate/query" + + "?languageSyntax=SPARQL" + + "&outputFormat=sparql" + + "&linenumbers=true" + + "&query=", + chart: moduleGooVis + '.Table', + loglevel: 2, + params: { + query: 'query', + output: 'output' + } + }, + + chartDefaults = { + width: 800, + height: 400, + chartArea: { + left: '5%', + top: '5%', + width: '75%', + height: '80%' + } + }, + chartSpecificDefaults = (function () { + var options = {}; + options[moduleGooVis + '.GeoMap'] = { + dataMode: 'markers' + }; + options[moduleGooVis + '.Map'] = { + dataMode: 'markers' + }; + options[moduleGooVis + '.Sparkline'] = { + showAxisLines: false + }; + return options; + }()); + return { + /** + * Collects query option default values. Should only be + * used if you want to edit this values persistently + * (passed by reference). If you want a copy of these + * values, use method `getQueryOptions`. + * + * @property query + * @type Object + * @protected + * @since 0.6.0 + **/ + query: queryDefaults, + + /** + * Collects chart option default values. Should only be + * used if you want to edit this values persistently + * (passed by reference). If you want a copy of these + * values, use method `getChartOptions`. + * + * @property chart + * @type Object + * @protected + * @since 0.6.0 + **/ + chart: chartDefaults, + + /** + * @method getQueryOptions + * @protected + * @return {Object} A copy of the query default options. + * @since 0.6.0 + **/ + getQueryOptions: function () { + return $.extend(true, {}, queryDefaults); + }, + + /** + * @method getChartOptions + * @protected + * @return {Object} A copy of the chart default options. + * @since 0.6.0 + **/ + getChartOptions: function () { + return $.extend(true, {}, chartDefaults); + }, + + /** + * @method getChartSpecificOptions + * @protected + * @param {String} chart The function name to retrieve options for, e.g., `'google.visualization.Map'`. + * @return {Object} A copy of the chart specific default options. + * @since 0.6.0 + **/ + getChartSpecificOptions: function (chart) { + return $.extend({}, chartSpecificDefaults[chart]); + }, + + /** + * @method setChartSpecificOption + * @protected + * @param {String} chart The function name to set the option for, e.g., `'google.visualization.Map'`. + * @param {String} option The name of the option to set. + * @param {String} value The value to set. + * @example + * setChartSpecificOption('google.visualization.Map', 'dataMode', 'markers'); + * sets the `'dataMode'` option for the + * `'google.visualization.Map'` function to the value + * `'markers'`. + * @since 0.6.0 + **/ + setChartSpecificOption: function (chart, option, value) { + chartSpecificDefaults[chart] = chartSpecificDefaults[chart] || {}; + chartSpecificDefaults[chart][option] = value; + } + }; + }()); + + /** + * 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 + **/ + + S.Query = function (queryOptions, chartOptions) { + + /*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 queryOpt + * @type Object + * @private + * @since 0.5 + **/ + queryOpt, + + /** + * 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 chartOpt + * @type Object + * @private + * @since 0.5 + **/ + chartOpt, + + //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) { + queryOpt.froms.push(url); + }, + + /** + * Remove all registered FROMs. + * + * See also `addFrom`. + * @method clearFroms + * @public + * @since 0.5 + **/ + clearFroms = function () { + queryOpt.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, queryOpt, 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, queryOpt, 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, queryOpt, this); + }, + + // TODO + endpointResultsURLPart = function (value) { + return getset('endpoint_results_urlpart', value, queryOpt, 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, queryOpt, this); + }, + + // TODO + loglevel = function (value) { + return getset('loglevel', value, queryOpt, this); + }, + + // TODO + logContainer = function (value) { + return getset('logContainer', value, queryOpt, 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, queryOpt, 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, queryOpt, 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, chartOpt, 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, chartOpt, this); + }, + + /** + * Get a current query parameter by its default name. + * @public + * @return {string} + * @since 0.6.1 + */ + /** + * Set a query parameter. + * @public + * @param {string} defaultname The default name of the query parameter, e.g,. `query`. + * @param {string} newname The new name of the query parameter. + * @chainable + * @since 0.6.1 + */ + queryParameter = function (defaultname, newname) { + return getset(defaultname, newname, queryOpt.params, 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 = queryOpt.froms.length, + from; + if (len) { + from = ""; + for (i = 0; i < len; i += 1) { + from += 'FROM <' + queryOpt.froms[i] + '>\n'; + } + query(query().replace(/((WHERE)?(\s)*\{)/i, '\n' + from + '$1')); + } + }, + + /** + * 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 (endpointOutputFormat() === qfXML) { + gJSON = parser.getGoogleJsonFromSparqlXml(data); + } else { + gJSON = parser.getGoogleJsonFromSparqlJson(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() + + "?" + queryOpt.params.query + "=" + getEncodedQuery() + + "&" + queryOpt.params.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: ( + function () { + var data = {}; + data[queryOpt.params.query] = prefixesSPARQL() + query(); + data[queryOpt.params.output] = (myEndpointOutput === qfJSONP) ? qfJSON : myEndpointOutput; + return data; + }() + ), + 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)); + noOfResults = dataTable.getNumberOfRows(); + 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, chartOpt); + } 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. + queryOpt = $.extend(defaults.getQueryOptions(), + queryOptions); + chartOpt = $.extend(defaults.getChartOptions(), + defaults.getChartSpecificOptions(chartFunction()), + chartOptions); + + // 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, + queryParameter: queryParameter + }; + }; + + /** + * Draws charts specified in HTML containers, here we call them + * "sgvizler-containers". + * + * Example of use: The following sgvizler-container will be + * selected by sgvizler due to the use of designated + * attributes. The result is a pie chart (draw with + * `google.visualization.PieChart`) showing the number of instance + * per class in the endpoint at + * `http://sws.ifi.uio.no/sparql/ndp`. + * + *
+ * + * These container must have an id attribute (or else sgvizler + * will not know where to put the chart) and a query attribute (or + * else the container will be ignored by sgvizler). + * + * Dependencies: + * + * - sgvizler.util + * - sgvizler.loader + * - sgvizler.logger + * - sgvizler.Query + * - jQuery + * + * @class sgvizler.container + * @static + */ + + S.container = (function () { + + /*global $ */ + + // Module dependencies: + var util = S.util, + loadDependencies = S.loader.loadDependencies, + logger = S.logger, + Query = S.Query, + + // CONSTANTS + /** + * The prefix of attributes which values designated for + * sgvizler should be given. Currently `data-sgvizler-`. + * + * Note that `data-` prefixed attribute names is valid + * HTML5. + * @property PREFIX + * @type string + * @final + * @private + * @since 0.2 + **/ + PREFIX = 'data-sgvizler-', + + /** + * The prefix of attributes which values designated for + * sgvizler and which are sent to the chart function + * should be given. Currently + * `data-sgvizler-chart-options`. + * + * @property PREFIXCHART + * @type string + * @final + * @private + * @since 0.2 + **/ + PREFIXCHART = PREFIX + 'chart-options', + + /** + * In attributes where multiple values may be given the + * properties VALUEASSIGN and VALUESPILT decides how to + * parse the attribute value into multiple name--value + * pairs. + * @property VALUEASSIGN + * @type string + * @final + * @private + * @since 0.2 + **/ + VALUEASSIGN = '=', + + /** + * In attributes where multiple values may be given the + * properties VALUEASSIGN and VALUESPILT decides how to + * parse the attribute value into multiple name--value + * pairs. + * + * @property VALUESPILT + * @type string + * @final + * @private + * @since 0.2 + **/ + VALUESPLIT = '|', + + /** + * Collects values designated for sgvizler in the given + * element---by element id. + * + * See also property PREFIX. + * + * @method getQueryAttributes + * @private + * @param {string} elementID The ID for which the attributes should be collected. + * @return {Object} List of name--value pairs. + * @since 0.2 + **/ + getQueryAttributes = function (elementID) { + var i, len, + queryOpt = {}, + elmAttrs = document.getElementById(elementID).attributes; + for (i = 0, len = elmAttrs.length; i < len; i += 1) { + if (util.startsWith(elmAttrs[i].name, PREFIX)) { + queryOpt[elmAttrs[i].name.substring(PREFIX.length)] = elmAttrs[i].value; + } + } + return queryOpt; + }, + + /** + * Collects values designated for sgvizler, and which are + * options to the chart function, in the given + * element---by element id. + * + * See also property CHARTPREFIX. + * + * @method getChartAttributes + * @private + * @param {string} elementID The ID for which the attributes should be collected. + * @return {Object} List of name--value pairs. + * @since 0.2 + **/ + getChartAttributes = function (elementID) { + var i, j, ilen, jlen, + options, + assignment, + path, + o, + chartOpt = {}, + attrValue = $("#" + elementID).attr(PREFIXCHART); + if (attrValue !== undefined) { + options = attrValue.split(VALUESPLIT); + for (i = 0, ilen = options.length; i < ilen; i += 1) { + assignment = options[i].split(VALUEASSIGN); + path = assignment[0].split("."); + o = chartOpt; + for (j = 0, jlen = path.length - 1; j < jlen; j += 1) { + if (o[path[j]] === undefined) { + o[path[j]] = {}; + } + o = o[path[j]]; + } + o[path[j]] = assignment[1]; + } + } + // get width and heigth from css. take only numbers. + chartOpt.width = /(\d+)/.exec($("#" + elementID).css('width'))[1]; + chartOpt.height = /(\d+)/.exec($("#" + elementID).css('height'))[1]; + return chartOpt; + }, + + /** + * Finds all sgvizler-containers on the page and loads + * their dependencies in one go. + * @method loadDependenciesOnPage + * @private + * @param {Array|string} [strFunctions] Array (or just a + * single string) of function names to get in addition to + * the functions specifies in sgvizler-containers. + * @since 0.6.0 + **/ + loadDependenciesOnPage = function (strFunctions) { + strFunctions = util.toArray(strFunctions); + + // Collect functions specified in html tags. + $('[' + PREFIX + 'chart]').each(function () { + strFunctions.push($(this).attr(PREFIX + "chart")); + }); + logger.log("initializer: loading functions: " + strFunctions); + loadDependencies(strFunctions); + }, + + /** + * Draws the sgvizler-containers with the given element id. + * @method containerDraw + * @public + * @for sgvizler + * @param {String} elementID + * @since 0.6.0 + **/ + draw = function (elementID) { + var queryOptions = getQueryAttributes(elementID), + chartOptions = getChartAttributes(elementID), + froms = (queryOptions.rdf && queryOptions.rdf.split(VALUESPLIT)) || [], + query = new Query(queryOptions, chartOptions); + + while (froms.length) { + query.addFrom(froms.pop()); + } + + logger.log("drawing id: " + elementID); + query.draw(elementID); + }, + + /** + * Draws all sgvizler-containers on page. + * @method containerDrawAll + * @public + * @for sgvizler + * @since 0.3 + **/ + drawAll = function () { + // Load dependencies for charts found on page. + loadDependenciesOnPage(); + + // Draw all containers with sgvizler query string attribute. + $('[' + PREFIX + 'query]').each(function () { + draw($(this).attr("id")); + }); + }; + + + ///////////////////////////////////////// + // PUBLICs + + return { + draw: draw, + drawAll: drawAll + }; + + }()); + /** + * Handles all UI business for the HTML form for writing, issuing + * and drawing sgvizler queries. + * + * Dependencies: + * + * - sgvizler.util + * - sgvizler.namespace + * - sgvizler.registry + * - sgvizler.Query + * + * @class sgvizler.form + * @static + **/ + S.form = (function () { + + /*global $ */ + + // Module dependencies: + var util = S.util, + prefixesSPARQL = S.namespace.prefixesSPARQL, + registry = S.registry, + Query = S.Query, + + /** + * Approx. 15 properties giving name to HTML elements + * which appear in the form. + * @property idXs + * @type String + * @private + * @since 0.2 + **/ + idprefix = 'sgvzlr_', + idMainCon = idprefix + 'mainCon', + idFormCon = idprefix + 'formCon', + idChartCon = idprefix + 'gchart', // #id to the container to hold the chart + idQueryForm = idprefix + 'formQuery', // + idQueryTxt = idprefix + 'cQuery', // query text area. + idFormQuery = idprefix + 'strQuery', // hidden query string. "trick" taken from snorql. + idFormEndpointGrp = idprefix + 'strEndpointGrp', // + idFormEndpoint = idprefix + 'strEndpoint', // + idFormFormatGrp = idprefix + 'btnFormatGrp', // + idFormFormat = idprefix + 'btnFormat', // + idFormSizeGrp = idprefix + 'strSizeGrp', // + idFormWidth = idprefix + 'strWidth', // + idFormHeight = idprefix + 'strHeight', // + idFormChartGrp = idprefix + 'optChartGrp', // + idFormChart = idprefix + 'optChart', // + idFormButtonGrp = idprefix + 'btnSendGrp', // + idPrefixCon = idprefix + 'cPrefix', // print prefixes + idMessageCon = idprefix + 'cMessage', // print messages + idLogo = idprefix + 'logo', + idFooter = idprefix + 'footer', + + /** + * Contains groups of elements which make out the + * form. Described using the array syntax edible by + * sgvizler.util.createHTMLElement. + * @property html + * @type Object + * @private + * @since 0.6.0 + **/ + html = (function () { + // Difficult to get whitespace correct while keeping it readable: + /*jslint white: true */ + return { + + /** + * The heading for the form: "Sgvizler". + * @property html.heading + * @type Array + * @private + * @since 0.6.0 + **/ + heading: ['h1', null, ['Sgvizler']], + + /** + * Logo pointing to homepage. + * @property html.logo + * @type Array + * @private + * @since 0.6.0 + **/ + logo: ['div', { id: idLogo }, + [ + ['a', { href: S.core.HOMEPAGE }, + [ + ['img', + { + src: S.core.LOGOIMAGE, + alt: "Mr. Sgvizler" + } + ] + ] + ], + 'Mr. Sgvizler' + ] + ], + + /** + * The form. + * @property html.main + * @type Array + * @private + * @since 0.6.0 + **/ + main: + ['div', { id: idFormCon }, + [ + // prefixes and namespaces. + ['pre', { id: idPrefixCon } ], + // textarea for writing query. + ['textarea', { id: idQueryTxt, rows: '10', cols: '80' } ], + ['form', { id: idQueryForm, method: 'get' }, + [ + ['p', null, + [ + // hidden query string + ['input', + { + id: idFormQuery, + type: 'hidden', + name: 'query', + value: '' + } + ], + ['span', { 'id': idFormEndpointGrp }, [ + ['label', { 'for': idFormEndpoint }, ['Endpoint: '] ], + ['input', + { + id: idFormEndpoint, + type: 'text', + name: 'endpoint', + size: '30' + } + ] + ] + ], + // format radio buttons + ['span', { 'id': idFormFormatGrp }, [ + ['label', { 'for': idFormFormat }, ['Output format: '] ], + ['input', + { + id: idFormFormat, + type: 'radio', + name: 'endpoint_output', + value: 'xml' + } + ], + "xml ", + ['input', + { + id: idFormFormat, + type: 'radio', + name: 'endpoint_output', + value: 'json' + } + ], + "json ", + ['input', + { + id: idFormFormat, + type: 'radio', + name: 'endpoint_output', + value: 'jsonp' + } + ], + "jsonp" + ] + ], + + // Chart type, dropdown. + ['span', { 'id': idFormChartGrp }, [ + ['label', { 'for': idFormChart }, ['Chart type: '] ], + ['select', + { + id: idFormChart, + name: 'chart' + } + ] + ] + ], + + // Width, Height + ['span', { 'id': idFormSizeGrp }, [ + ['label', { 'for': idFormWidth }, ['Width: '] ], + ['input', + { + id: idFormWidth, + name: 'width', + type: 'text', + size: '3' + } + ], + ['label', { 'for': idFormHeight }, ['Height: '] ], + ['input', + { + id: idFormHeight, + name: 'height', + type: 'text', + size: '3' + } + ] + ] + ], + + // Buttons + ['span', { 'id': idFormButtonGrp }, [ + ['input', + { + type: 'button', + value: 'Reset', + onclick: 'sgvizler.formReset()' // NB! must be the global function. + } + ], + ['input', + { + type: 'button', + value: 'Go', + onclick: 'sgvizler.formSubmit()' // NB! must be the global function. + } + ] + ] + ] + ] + ] + ] + ], + // Logging container. + ['div', { id: idMessageCon } ] + ] + ], + + /** + * Container for holding the chart. + * @property html.chart + * @type Array + * @private + * @since 0.6.0 + **/ + chart: + ['div', + { + id: idChartCon, + style: "width:800px; height:400px;" + } + ], + + /** + * The footer + * @property html.footer + * @type Array + * @private + * @since 0.6.0 + **/ + footer: + ['div', + { + id: idFooter + }, + [ + ['p', null, + [ + ['a', { href: S.core.HOMEPAGE }, ['Sgvizler'] ], + ' version ' + S.core.VERSION + + ' (c) 2011–' + new Date().getFullYear() + ' Martin G. Skjæveland.' + ] + ] + ] + ] + }; + }()), + + /** + * A list of permissible URL parameters. The parameter + * name must be in this list to be read by the form. + * @property urlparamnames + * @type Array + * @private + * @since 0.3.1 + **/ + urlparamnames = { + query: 'query', + endpoint: 'endpoint', + endpoint_output: 'endpoint_output', + chart: 'chart', + width: 'width', + height: 'height', + ui: 'ui' + }, + + /** + * Get-setter function for the URL parameter names. Useful + * for changing what parameters Sgvizler is listening + * to. The list of default names are: + * - query + * - endpoint + * - endpoint_output + * - chart + * - width + * - height + * - ui + * @method formParameterName + * @for sgvizler + * @public + * @param {String} defaultname The parameter name to change. + * @param {String} newname The new value of the parameter + * name. If this value is undefined, then the function + * acts as a get function, returning the value of the + * value set for the parameter with the given default + * name. + * @return {String} + * @since 0.6.1 + */ + parameterName = function (defaultname, newname) { + return util.getset(defaultname, newname, urlparamnames); + }, + + /** + * Tests if there really is an element with the give + * element id. + * @method isElement + * @private + * @param {String} elementID The element Id + * @return {boolean} Returns true iff the element with + * this element id exists. + * @since 0.5 + **/ + isElement = function (elementID) { + return $('#' + elementID).length > 0; + }, + + /** + * Set a value for a given element. Is used to set the + * value of form input fields. Uses `jQuery.val`. + * @method setElementValue + * @private + * @param {String} elementID The element id of the element to set value for. + * @param {Primitive} value The value to set. + * @since 0.5 + **/ + setElementValue = function (elementID, value) { + if (isElement(elementID)) { + $('#' + elementID).val(value); + } + }, + + /** + * Set the text for a given element. Is used to set the + * text contents of containers. Uses `jQuery.text`. + * @method setElementText + * @private + * @param {String} elementID The element id of the element to set value for. + * @param {String} text The value to set. + * @since 0.5 + **/ + setElementText = function (elementID, text) { + if (isElement(elementID)) { + $('#' + elementID).text(text); + } + }, + /* UNUSED + * Set the html content for a given element. Uses `jQuery.html`. + * @method setElementHTML + * @private + * @param {String} elementID The element id of the element to set value for. + * @param {String} html The value to set. + * @since 0.6.0 + * setElementHTML = function (elementID, html) { + if (isElement(elementID)) { + $('#' + elementID).html(html); + } + }, + */ + + /** + * Displays the prefixes set in `sgvizler.namespace` as + * SPARQL prefix declarations in the designated container. + * @method displayPrefixes + * @private + * @since 0.1 + **/ + displayPrefixes = function () { + setElementText(idPrefixCon, prefixesSPARQL()); + }, + + /** + * Displays query information in the form input fields, + * e.g., the query string, query format, chart dimensions, + * set in the input parameter. + * @method displayUserInput + * @param {sgvizler.Query} query + * @private + * @since 0.1 + **/ + displayUserInput = function (query) { + setElementValue(idQueryTxt, query.query()); + setElementValue(idFormEndpoint, query.endpointURL()); + $('input:radio[id=' + idFormFormat + '][value=' + query.endpointOutputFormat() + ']').attr('checked', true); + setElementValue(idFormChart, query.chartFunction()); + setElementValue(idFormWidth, query.chartWidth()); + setElementValue(idFormHeight, query.chartHeight()); + }, + + /** + * Populates the drop-down menu of available chart types + * with the registered chart types found in the + * `sgvizler.registry`, grouped by modules. + * @method displayChartTypesMenu + * @private + * @since 0.2 + **/ + displayChartTypesMenu = function () { + var i, j, ilen, jlen, + charts = registry.chartFunctions().sort(), + createOptGrp = function (name) { + return util.createHTMLElement('optgroup', { label: name + '.*' }); + }, + libs = registry.chartModules(), + group = {}, + added = false; + + if (isElement(idFormChart)) { + // Create option groups for chart function modules. + for (j = 0, jlen = libs.length; j < jlen; j += 1) { + group[libs[j]] = createOptGrp(libs[j]); + $('#' + idFormChart).append(group[libs[j]]); + } + + for (i = 0, ilen = charts.length; i < ilen; i += 1) { + added = false; + for (j = 0, jlen = libs.length; j < jlen; j += 1) { + if (util.startsWith(charts[i], libs[j])) { + $(group[libs[j]]) + .append($('