view_SparqlQueryWindow.js
import { SparqlEndpointResponseProvider } from '../service/SparqlEndpointResponseProvider';
import { D3GraphCanvas } from './D3GraphCanvas';
import { Table } from './Table';
import { JsonRenderer } from './JsonRenderer';
import { loadTextFile } from '@ud-viz/utils_browser';
import { SparqlQuery } from './SparqlQuery';
/**
* The SPARQL query window class which provides the user interface for querying
* a SPARQL endpoint and displaying the endpoint response.
*/
export class SparqlQueryWindow {
/**
* Creates a SPARQL query window.
*
* @param {SparqlEndpointResponseProvider} sparqlProvider The SPARQL Endpoint Response Provider
* @param {object} configSparqlWidget The sparqlModule view configuration.
* @param {object} configSparqlWidget.queries Query configurations
* @param {object} configSparqlWidget.queries.title The query title
* @param {object} configSparqlWidget.queries.filepath The path to the file which contains the query text
* @param {object} configSparqlWidget.queries.formats Configuration for which visualizations are allowed
* with this query. Should be an object of key, value
* pairs. The keys of these pairs should correspond
* with the cases in the updateDataView() function.
* @param {Function} handleZoom Function to handle the zoom.
*/
constructor(sparqlProvider, configSparqlWidget, handleZoom = undefined) {
/** @type {HTMLElement} */
this.domElement = null;
/** @type {HTMLElement} */
this.dataView = null;
/** @type {HTMLElement} */
this.form = null;
/** @type {HTMLElement} */
this.querySelect = null;
/** @type {HTMLElement} */
this.resultSelect = null;
/** @type {HTMLElement} */
this.resetButton = null;
/** @type {HTMLElement} */
this.queryTextArea = null;
/** @type {HTMLElement} */
this.toggleQueryTextAreaButton = null;
/** @type {HTMLElement} */
this.menu = null;
/** @type {HTMLElement} */
this.menuList = null;
/**
* The SPARQL Endpoint Response Provider
*
* @type {SparqlEndpointResponseProvider}
*/
this.sparqlProvider = sparqlProvider;
this.explorationQuery = undefined;
this.jsonRenderer = new JsonRenderer();
/**
* Contains the D3 graph view to display RDF data.
*
* @type {D3GraphCanvas}
*/
this.d3Graph = new D3GraphCanvas(configSparqlWidget, handleZoom, undefined);
/**
* The event listeners for the graphs.
*
* @type {object}
*/
this.eventListeners = {};
/**
* The sparqlModule view configuration.
*
* @type {object}
*/
this.configSparqlWidget = configSparqlWidget;
/**
* Contains the D3 table to display RDF data.
*
* @type {Table}
*/
this.table = new Table(this);
/**
* Store the queries of the SparqlQueryWindow from the config.
*
* @type {object}
*/
this.queries = configSparqlWidget['queries'];
/**
* The Sparql exploration query
*
* @type {string}
*/
this.explorationQuery = undefined;
this.initHtml();
this.toggleQueryTextAreaButton.onclick = () => this.toggleQueryTextArea();
/**
* Sets the SparqlEndpointResponseProvider
* and graph view. Also updates this.queries with the queries declared in the configuration file
* Should be called by a `SparqlWidgetView`. Once this is done,
* the window is actually usable ; service event listerers are set here.
*/
// Get queries from text files and update the this.queries
const promises = [];
this.queries.forEach((query) => {
if (query.filepath) {
promises.push(
loadTextFile(query.filepath).then((result) => {
query.text = result;
})
);
} else {
query.text = '';
}
});
Promise.all(promises).then(() => {
// Once query text is updated, update the query select dropdown
// and query text area
this.updateQueryDropdown(this.queries);
if (this.queries.length) {
this.updateQueryTextArea(0);
this.updateResultDropdown(0);
}
});
if (this.queries.length > 1) {
this.querySelect.onchange = () => {
this.updateQueryTextArea(this.querySelect.value);
this.updateResultDropdown(this.querySelect.value);
};
}
this.form.onsubmit = () => {
console.debug('submit');
this.sparqlProvider
.querySparqlEndpointService(this.queryTextArea.value)
.then((response) =>
this.updateDataView(response, this.resultSelect.value)
);
return false;
};
this.resetButton.onclick = () => {
this.d3Graph.clearCanvas();
this.d3Graph.data.clear();
};
}
/**
* Update query to add node's children
*
* @param {string} node_id a node ID
*/
updateExplorationQuery(node_id) {
this.explorationQuery.where_conditions.push([
'?subject ?predicate ?object ;a ?subjectType .',
'OPTIONAL { ?object a ?objectType }',
'FILTER (?subject = data:' + node_id + ')',
]);
this.queryTextArea.value = this.explorationQuery.generateQuery();
this.d3Graph.clearCanvas();
this.d3Graph.data.clear();
this.sparqlProvider
.querySparqlEndpointService(this.queryTextArea.value)
.then((response) =>
this.updateDataView(response, this.resultSelect.value)
);
}
/**
* Update the DataView
*
* @param {object} response a JSON object returned by a SparqlEndpointResponseProvider.EVENT_ENDPOINT_RESPONSE_UPDATED event
* @param {string} view_type the selected semantic data view type
*/
updateDataView(response, view_type) {
this.clearDataView();
switch (view_type) {
case 'graph':
this.d3Graph.init(response);
Object.entries(this.eventListeners).forEach(([event, listener]) => {
this.d3Graph.addEventListener(event, listener);
});
this.d3Graph.update(response);
this.dataView.append(this.d3Graph.canvas);
this.dataView.style['height'] = this.d3Graph.height + 'px';
this.dataView.style['width'] = this.d3Graph.width + 'px';
this.resetButton.style.display = 'block';
break;
case 'json':
this.jsonRenderer.renderjson.set_icons('▶', '▼');
this.jsonRenderer.renderjson.set_max_string_length(40);
this.dataView.style['height'] = '100%';
this.dataView.append(this.jsonRenderer.renderjson(response));
break;
case 'table':
this.dataView.append(this.table.domElement);
this.dataView.style['height'] = '100%';
this.table.dataAsTable(response.results.bindings, response.head.vars);
this.table.filterInput.addEventListener('change', (e) =>
Table.update(this.table, e)
);
break;
default:
console.error('This result format is not supported: ' + view_type);
}
}
/**
* Add event listeners to the graphs
*
* @param {object} eventListeners An object containing event listeners to be added to the graph
*/
addEventListeners(eventListeners) {
this.eventListeners = eventListeners;
}
/**
* Clear the DataView of content
*/
clearDataView() {
this.dataView.innerText = '';
}
toggleQueryTextArea() {
if (
!this.queryTextArea.style.display ||
this.queryTextArea.style.display == 'none'
) {
this.queryTextArea.style.display = 'inherit';
this.toggleQueryTextAreaButton.textContent = 'Hide the query ▼';
} else {
this.queryTextArea.style.display = 'none';
this.toggleQueryTextAreaButton.textContent = 'Show the query ▶';
}
}
/**
* Update the this.queryTextArea with the text of the query that was selected in the dropdown, or the exploration query
*
* @param {number} index the index of the query in the this.queries array
*/
updateQueryTextArea(index) {
const query = this.queries[Number(index)];
if (query.exploration != undefined) {
this.explorationQuery = new SparqlQuery();
this.explorationQuery.prefix = query.exploration.prefix;
this.explorationQuery.select_variable = query.exploration.select_variable;
this.explorationQuery.options = query.exploration.options;
this.queryTextArea.value = this.explorationQuery.generateQuery();
} else {
this.explorationQuery = undefined;
this.queryTextArea.value = query.text;
}
}
/**
* Update this.querySelect options using an array of queries. For each element in the array,
* create an option element, set the innerText of the option to the query's title,
* set the value of the option to the index of the query in the array, then append
* the option to this.querySelect
*
* @param {Array<object>} queries - An array of objects that contain a query title and the query text itself
*/
updateQueryDropdown(queries) {
if (this.queries.length > 1) {
for (let index = 0; index < queries.length; index++) {
const option = document.createElement('option');
option.innerText = queries[index].title;
option.value = index;
this.querySelect.appendChild(option);
}
}
}
/**
* Remove all the children of this.resultSelect, then adds new children options based
* on the formats declared in each query configuration from from this.queries
*
* @param {number} index - the index of the query in the queries array
*/
updateResultDropdown(index) {
// this is a weird work around to do this.resultSelect.children.forEach(...)
while (this.resultSelect.children.length > 0) {
this.resultSelect.removeChild(this.resultSelect.children.item(0));
}
const formats = Object.entries(this.queries[Number(index)].formats);
formats.forEach(([k, v]) => {
const option = document.createElement('option');
option.value = k;
option.innerText = v;
this.resultSelect.appendChild(option);
});
}
/**
* Initialize the query text area of the view
*/
initQueryTextAreaForm() {
if (this.queries.length > 1) {
const selectLabel = document.createElement('label');
selectLabel.innerText = 'Select Query: ';
this.interfaceElement.appendChild(selectLabel);
this.querySelect = document.createElement('select');
this.interfaceElement.appendChild(this.querySelect);
}
this.toggleQueryTextAreaButton = document.createElement('button');
this.toggleQueryTextAreaButton.innerText = 'Show the query ▶';
this.interfaceElement.appendChild(this.toggleQueryTextAreaButton);
this.form = document.createElement('form');
this.interfaceElement.appendChild(this.form);
this.queryTextArea = document.createElement('textarea');
this.form.appendChild(this.queryTextArea);
const submitButton = document.createElement('input');
submitButton.setAttribute('type', 'submit');
submitButton.setAttribute('value', 'Send');
this.form.appendChild(submitButton);
}
/**
* Initialize the displaying of the result
*/
initResultDisplay() {
const formatLabel = document.createElement('label');
formatLabel.innerText = 'Results Format: ';
this.interfaceElement.appendChild(formatLabel);
this.resultSelect = document.createElement('select');
this.interfaceElement.appendChild(this.resultSelect);
this.resetButton = document.createElement('button');
this.resetButton.innerText = 'Clear Graph';
this.resetButton.style['width'] = this.d3Graph.width + 'px';
this.resetButton.style.display = 'none';
this.dataView = document.createElement('div');
this.dataView.className = 'box-selection';
this.dataView.setAttribute('style', 'display:flex');
this.interfaceElement.appendChild(this.dataView);
this.interfaceElement.appendChild(this.resetButton);
}
/**
* Initialize the html of the view
*/
initHtml() {
this.domElement = document.createElement('div');
this.interfaceElement = document.createElement('div');
this.interfaceElement.className = 'box-section';
this.domElement.appendChild(this.interfaceElement);
this.initQueryTextAreaForm();
this.toggleQueryTextArea();
this.initResultDisplay();
}
}