solid-ui
Version:
UI library for writing Solid read-write-web applications
1,594 lines (1,228 loc) • 48.9 kB
JavaScript
"use strict";
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.renderTableViewPane = renderTableViewPane;
var _index2 = require("./authn/index");
var debug = _interopRequireWildcard(require("./debug"));
var _iconBase = require("./iconBase");
var _logic = require("./logic");
var log = _interopRequireWildcard(require("./log"));
var ns = _interopRequireWildcard(require("./ns"));
var rdf = _interopRequireWildcard(require("rdflib"));
var style = _interopRequireWildcard(require("./style"));
var utils = _interopRequireWildcard(require("./utils"));
var widgets = _interopRequireWildcard(require("./widgets"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
// Table Widget: Format an array of RDF statements as an HTML table.
//
// This can operate in one of three modes: when the class of object is given
// or when the source document from whuch data is taken is given,
// or if a prepared query object is given.
// (In principle it could operate with neither class nor document
// given but typically
// there would be too much data.)
// When the tableClass is not given, it looks for common classes in the data,
// and gives the user the option.
//
// 2008 Written, Ilaria Liccardi as the tableViewPane.js
// 2014 Core table widget moved into common/table.js - timbl
//
// pull in first avoid cross-refs
var UI = {
icons: _iconBase.icons,
log: log,
ns: ns,
store: _logic.store,
utils: utils,
widgets: widgets
}; // UI.widgets.renderTableViewPane
function renderTableViewPane(doc, options) {
var sourceDocument = options.sourceDocument;
var tableClass = options.tableClass;
var givenQuery = options.query;
var RDFS_LITERAL = 'http://www.w3.org/2000/01/rdf-schema#Literal';
var ns = UI.ns;
var kb = UI.store;
var rowsLookup = {}; // Persistent mapping of subject URI to dom TR
// Predicates that are never made into columns:
var FORBIDDEN_COLUMNS = {
'http://www.w3.org/2002/07/owl#sameAs': true,
'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': true
}; // Number types defined in the XML schema:
var XSD_NUMBER_TYPES = {
'http://www.w3.org/2001/XMLSchema#decimal': true,
'http://www.w3.org/2001/XMLSchema#float': true,
'http://www.w3.org/2001/XMLSchema#double': true,
'http://www.w3.org/2001/XMLSchema#integer': true,
'http://www.w3.org/2001/XMLSchema#nonNegativeInteger': true,
'http://www.w3.org/2001/XMLSchema#positiveInteger': true,
'http://www.w3.org/2001/XMLSchema#nonPositiveInteger': true,
'http://www.w3.org/2001/XMLSchema#negativeInteger': true,
'http://www.w3.org/2001/XMLSchema#long': true,
'http://www.w3.org/2001/XMLSchema#int': true,
'http://www.w3.org/2001/XMLSchema#short': true,
'http://www.w3.org/2001/XMLSchema#byte': true,
'http://www.w3.org/2001/XMLSchema#unsignedLong': true,
'http://www.w3.org/2001/XMLSchema#unsignedInt': true,
'http://www.w3.org/2001/XMLSchema#unsignedShort': true,
'http://www.w3.org/2001/XMLSchema#unsignedByte': true
};
var XSD_DATE_TYPES = {
'http://www.w3.org/2001/XMLSchema#dateTime': true,
'http://www.w3.org/2001/XMLSchema#date': true
}; // Classes that indicate an image:
var IMAGE_TYPES = {
'http://xmlns.com/foaf/0.1/Image': true,
'http://purl.org/dc/terms/Image': true
}; // Name of the column used as a "key" value to look up the row.
// This is necessary because in the normal view, the columns are
// all "optional" values, meaning that we will get a result set
// for every individual value that is found. The row key acts
// as an anchor that can be used to combine this information
// back into the same row.
var keyVariable = options.keyVariable || '?_row';
var subjectIdCounter = 0;
var allType, types;
var typeSelectorDiv, addColumnDiv; // The last SPARQL query used:
var lastQuery = null;
var mostCommonType = null;
var resultDiv = doc.createElement('div');
resultDiv.className = 'tableViewPane';
resultDiv.appendChild(generateControlBar()); // sets typeSelectorDiv
var tableDiv = doc.createElement('div');
resultDiv.appendChild(tableDiv); // Save a refresh function for use by caller
resultDiv.refresh = function () {
runQuery(table.query, table.logicalRows, table.columns, table); // updateTable(givenQuery, mostCommonType) // This could be a lot more incremental and efficient
}; // A specifically asked-for query
if (givenQuery) {
var table = renderTableForQuery(givenQuery); // lastQuery = givenQuery
tableDiv.appendChild(table);
} else {
// Find the most common type and select it by default
var s = calculateTable();
allType = s[0];
types = s[1];
if (!tableClass) {
typeSelectorDiv.appendChild(generateTypeSelector(allType, types));
}
mostCommonType = getMostCommonType(types);
if (mostCommonType) {
buildFilteredTable(mostCommonType);
} else {
buildFilteredTable(allType);
}
}
return resultDiv; // /////////////////////////////////////////////////////////////////
/*
function closeDialog (dialog) {
dialog.parentNode.removeChild(dialog)
}
function createActionButton (label, callback) {
var button = doc.createElement('input')
button.setAttribute('type', 'submit')
button.setAttribute('value', label)
button.addEventListener('click', callback, false)
return button
}
// @@ Tdo: put these buttonsback in,
// to allow user to see and edit and save the sparql query for the table they are looking at
//
function createSparqlWindow () {
var dialog = doc.createElement('div')
dialog.setAttribute('class', 'sparqlDialog')
var title = doc.createElement('h3')
title.appendChild(doc.createTextNode('Edit SPARQL query'))
var inputbox = doc.createElement('textarea')
inputbox.value = rdf.queryToSPARQL(lastQuery)
dialog.appendChild(title)
dialog.appendChild(inputbox)
dialog.appendChild(createActionButton('Query', function () {
var query = rdf.SPARQLToQuery(inputbox.value)
updateTable(query)
closeDialog(dialog)
}))
dialog.appendChild(createActionButton('Close', function () {
closeDialog(dialog)
}))
return dialog
}
function sparqlButtonPressed () {
var dialog = createSparqlWindow()
resultDiv.appendChild(dialog)
}
function generateSparqlButton () {
var image = doc.createElement('img')
image.setAttribute('class', 'sparqlButton')
image.setAttribute('src', UI.iconBase + 'icons/1pt5a.gif')
image.setAttribute('alt', 'Edit SPARQL query')
image.addEventListener('click', sparqlButtonPressed, false)
return image
}
*/
// Generate the control bar displayed at the top of the screen.
function generateControlBar() {
var result = doc.createElement('table');
result.setAttribute('class', 'toolbar');
var tr = doc.createElement('tr');
/* @@ Add in later -- not debugged yet
var sparqlButtonDiv = doc.createElement("td")
sparqlButtonDiv.appendChild(generateSparqlButton())
tr.appendChild(sparqlButtonDiv)
*/
typeSelectorDiv = doc.createElement('td');
tr.appendChild(typeSelectorDiv);
addColumnDiv = doc.createElement('td');
tr.appendChild(addColumnDiv);
result.appendChild(tr);
return result;
} // Add the SELECT details to the query being built.
function addSelectToQuery(query, type) {
var selectedColumns = type.getColumns();
for (var i = 0; i < selectedColumns.length; ++i) {
// TODO: autogenerate nicer names for variables
// variables have to be unambiguous
var variable = kb.variable('_col' + i);
query.vars.push(variable);
selectedColumns[i].setVariable(variable);
}
} // Add WHERE details to the query being built.
function addWhereToQuery(query, rowVar, type) {
var queryType = type.type;
if (!queryType) {
queryType = kb.variable('_any');
} // _row a type
query.pat.add(rowVar, UI.ns.rdf('type'), queryType);
} // Generate OPTIONAL column selectors.
function addColumnsToQuery(query, rowVar, type) {
var selectedColumns = type.getColumns();
for (var i = 0; i < selectedColumns.length; ++i) {
var column = selectedColumns[i];
var formula = kb.formula();
formula.add(rowVar, column.predicate, column.getVariable());
query.pat.optional.push(formula);
}
} // Generate a query object from the currently-selected type
// object.
function generateQuery(type) {
var query = new rdf.Query();
var rowVar = kb.variable(keyVariable.slice(1)); // don't pass '?'
addSelectToQuery(query, type);
addWhereToQuery(query, rowVar, type);
addColumnsToQuery(query, rowVar, type);
return query;
} // Build the contents of the tableDiv element, filtered according
// to the specified type.
function buildFilteredTable(type) {
// Generate "add column" cell.
clearElement(addColumnDiv);
addColumnDiv.appendChild(generateColumnAddDropdown(type));
var query = generateQuery(type);
updateTable(query, type);
}
function updateTable(query, type) {
// Stop the previous query from doing any updates.
if (lastQuery) {
lastQuery.running = false;
} // Render the HTML table.
var htmlTable = renderTableForQuery(query, type); // Clear the tableDiv element, and replace with the new table.
clearElement(tableDiv);
tableDiv.appendChild(htmlTable); // Save the query for the edit dialog.
lastQuery = query;
} // Remove all subelements of the specified element.
function clearElement(element) {
while (element.childNodes.length > 0) {
element.removeChild(element.childNodes[0]);
}
} // A SubjectType is created for each rdf:type discovered.
function SubjectType(type) {
this.type = type;
this.columns = null;
this.allColumns = [];
this.useCount = 0; // Get a list of all columns used by this type.
this.getAllColumns = function () {
return this.allColumns;
}; // Get a list of the current columns used by this type
// (subset of allColumns)
this.getColumns = function () {
// The first time through, get a list of all the columns
// and select only the six most popular columns.
if (!this.columns) {
var allColumns = this.getAllColumns();
this.columns = allColumns.slice(0, 7);
}
return this.columns;
}; // Get a list of unused columns
this.getUnusedColumns = function () {
var allColumns = this.getAllColumns();
var columns = this.getColumns();
var result = [];
for (var i = 0; i < allColumns.length; ++i) {
if (columns.indexOf(allColumns[i]) === -1) {
result.push(allColumns[i]);
}
}
return result;
};
this.addColumn = function (column) {
this.columns.push(column);
};
this.removeColumn = function (column) {
this.columns = this.columns.filter(function (x) {
return x !== column;
});
};
this.getLabel = function () {
return utils.label(this.type);
};
this.addUse = function () {
this.useCount += 1;
};
} // Class representing a column in the table.
function Column() {
this.useCount = 0; // Have we checked any values for this column yet?
this.checkedAnyValues = false; // If the range is unknown, but we just get literals in this
// column, then we can generate a literal selector.
this.possiblyLiteral = true; // If the range is unknown, but we just get literals and they
// match the regular expression for numbers, we can generate
// a number selector.
this.possiblyNumber = true; // We accumulate classes which things in the column must be a member of
this.constraints = []; // Check values as they are read. If we don't know what the
// range is, we might be able to infer that it is a literal
// if all of the values are literals. Similarly, we might
// be able to determine if the literal values are actually
// numbers (using regexps).
this.checkValue = function (term) {
var termType = term.termType;
if (this.possiblyLiteral && termType !== 'Literal' && termType !== 'NamedNode') {
this.possiblyNumber = false;
this.possiblyLiteral = false;
} else if (this.possiblyNumber) {
if (termType !== 'Literal') {
this.possiblyNumber = false;
} else {
var literalValue = term.value;
if (!literalValue.match(/^-?\d+(\.\d*)?$/)) {
this.possiblyNumber = false;
}
}
}
this.checkedAnyValues = true;
};
this.getVariable = function () {
return this.variable;
};
this.setVariable = function (variable) {
this.variable = variable;
};
this.getKey = function () {
return this.variable.toString();
};
this.addUse = function () {
this.useCount += 1;
};
this.getHints = function () {
if (options && options.hints && this.variable && options.hints[this.variable.toNT()]) {
return options.hints[this.variable.toNT()];
}
return {};
};
this.getLabel = function () {
if (this.getHints().label) {
return this.getHints().label;
}
if (this.predicate) {
if (this.predicate.sameTerm(ns.rdf('type')) && this.superClass) {
return utils.label(this.superClass, true); // do initial cap
}
return utils.label(this.predicate);
} else if (this.variable) {
return this.variable.toString();
} else {
return 'unlabeled column?';
}
};
this.setPredicate = function (predicate, inverse, other) {
if (inverse) {
// variable is in the subject pos
this.inverse = predicate;
this.constraints = this.constraints.concat(kb.each(predicate, UI.ns.rdfs('domain')));
if (predicate.sameTerm(ns.rdfs('subClassOf')) && other.termType === 'NamedNode') {
this.superClass = other;
this.alternatives = kb.each(undefined, ns.rdfs('subClassOf'), other);
}
} else {
// variable is the object
this.predicate = predicate;
this.constraints = this.constraints.concat(kb.each(predicate, UI.ns.rdfs('range')));
}
};
this.getConstraints = function () {
return this.constraints;
};
this.filterFunction = function () {
return true;
};
this.sortKey = function () {
return this.getLabel().toLowerCase();
};
this.isImageColumn = function () {
for (var i = 0; i < this.constraints.length; i++) {
if (this.constraints[i].uri in IMAGE_TYPES) return true;
}
return false;
};
} // Convert an object to an array.
function objectToArray(obj, filter) {
var result = [];
for (var property in obj) {
// @@@ have to guard against methods
var value = obj[property];
if (!filter || filter(property, value)) {
result.push(value);
}
}
return result;
} // Generate an <option> in a drop-down list.
function optionElement(label, value) {
var result = doc.createElement('option');
result.setAttribute('value', value);
result.appendChild(doc.createTextNode(label));
return result;
} // Generate drop-down list box for choosing type of data displayed
function generateTypeSelector(allType, types) {
var resultDiv = doc.createElement('div');
resultDiv.appendChild(doc.createTextNode('Select type: '));
var dropdown = doc.createElement('select');
dropdown.appendChild(optionElement('All types', 'null'));
for (var uri in types) {
dropdown.appendChild(optionElement(types[uri].getLabel(), uri));
}
dropdown.addEventListener('click', function () {
var type;
if (dropdown.value === 'null') {
type = allType;
} else {
type = types[dropdown.value];
}
typeSelectorChanged(type);
}, false);
resultDiv.appendChild(dropdown);
return resultDiv;
} // Callback invoked when the type selector drop-down list is changed.
function typeSelectorChanged(selectedType) {
buildFilteredTable(selectedType);
} // Build drop-down list to add a new column
function generateColumnAddDropdown(type) {
var resultDiv = doc.createElement('div');
var unusedColumns = type.getUnusedColumns();
unusedColumns.sort(function (a, b) {
var aLabel = a.sortKey();
var bLabel = b.sortKey();
return (aLabel > bLabel) - (aLabel < bLabel);
}); // If there are no unused columns, the div is empty.
if (unusedColumns.length > 0) {
resultDiv.appendChild(doc.createTextNode('Add column: ')); // Build dropdown list of unused columns.
var dropdown = doc.createElement('select');
dropdown.appendChild(optionElement('', '-1'));
for (var i = 0; i < unusedColumns.length; ++i) {
var column = unusedColumns[i];
dropdown.appendChild(optionElement(column.getLabel(), '' + i));
}
resultDiv.appendChild(dropdown); // Invoke callback when the dropdown is changed, to add
// the column and reload the table.
dropdown.addEventListener('click', function () {
var columnIndex = Number(dropdown.value);
if (columnIndex >= 0) {
type.addColumn(unusedColumns[columnIndex]);
buildFilteredTable(type);
}
}, false);
}
return resultDiv;
} // Find the column for a given variable
function getColumnForVariable(columns, variableNT) {
for (var predicateUri in columns) {
var column = columns[predicateUri];
if (column.variable.toNT() === variableNT) {
return column;
}
}
throw new Error("getColumnForVariable: no column for variable ".concat(variableNT)); // return null
} // Find the column for a given predicate, creating a new column object
// if necessary.
function getColumnForPredicate(columns, predicate) {
var column;
if (predicate.uri in columns) {
column = columns[predicate.uri];
} else {
column = new Column();
column.setPredicate(predicate);
columns[predicate.uri] = column;
}
return column;
} // Find a type by its URI, creating a new SubjectType object if
// necessary.
function getTypeForObject(types, type) {
var subjectType;
if (type.uri in types) {
subjectType = types[type.uri];
} else {
subjectType = new SubjectType(type);
types[type.uri] = subjectType;
}
return subjectType;
} // Discover types and subjects for search.
function discoverTypes() {
// rdf:type properties of subjects, indexed by URI for the type.
var types = {}; // Get a list of statements that match: ? rdfs:type ?
// From this we can get a list of subjects and types.
var subjectList = kb.statementsMatching(undefined, UI.ns.rdf('type'), tableClass, // can be undefined OR
sourceDocument); // can be undefined
// Subjects for later lookup. This is a mapping of type URIs to
// lists of subjects (it is necessary to record the type of
// a subject).
var subjects = {};
for (var i = 0; i < subjectList.length; ++i) {
var type = subjectList[i].object;
if (type.termType !== 'NamedNode') {
// @@ no bnodes?
continue;
}
var typeObj = getTypeForObject(types, type);
if (!(type.uri in subjects)) {
subjects[type.uri] = [];
}
subjects[type.uri].push(subjectList[i].subject);
typeObj.addUse();
}
return [subjects, types];
} // Get columns for the given subject.
function getSubjectProperties(subject, columns) {
// Get a list of properties of this subject.
var properties = kb.statementsMatching(subject, undefined, undefined, sourceDocument);
var result = {};
for (var j = 0; j < properties.length; ++j) {
var predicate = properties[j].predicate;
if (predicate.uri in FORBIDDEN_COLUMNS) {
continue;
} // Find/create a column for this predicate.
var column = getColumnForPredicate(columns, predicate);
column.checkValue(properties[j].object);
result[predicate.uri] = column;
}
return result;
} // Identify the columns associated with a type.
function identifyColumnsForType(type, subjects) {
var allColumns = {}; // Process each subject of this type to build up the
// column list.
for (var i = 0; i < subjects.length; ++i) {
var columns = getSubjectProperties(subjects[i], allColumns);
for (var predicateUri in columns) {
var column = columns[predicateUri];
column.addUse();
}
} // Generate the columns list
var allColumnsList = objectToArray(allColumns);
sortColumns(allColumnsList);
type.allColumns = allColumnsList;
} // Build table information from parsing RDF statements.
function calculateTable() {
// Find the types that we will display in the dropdown
// list box, and associated objects of those types.
var subjects, types;
var s = discoverTypes(); // eslint-disable-next-line prefer-const
subjects = s[0]; // eslint-disable-next-line prefer-const
types = s[1]; // no [ ] on LHS
for (var typeUrl in subjects) {
var subjectList = subjects[typeUrl];
var type = types[typeUrl];
identifyColumnsForType(type, subjectList);
} // TODO: Special type that captures all rows.
// Combine columns from all types
var allType = new SubjectType(null);
return [allType, objectToArray(types)];
} // Sort the list of columns by the most common columns.
function sortColumns(columns) {
function sortFunction(a, b) {
return (a.useCount < b.useCount) - (a.useCount > b.useCount);
}
columns.sort(sortFunction);
} // Create the delete button for a column.
function renderColumnDeleteButton(type, column) {
var button = doc.createElement('a');
button.appendChild(doc.createTextNode('[x]'));
button.addEventListener('click', function () {
type.removeColumn(column);
buildFilteredTable(type);
}, false);
return button;
} // Render the table header for the HTML table.
function renderTableHeader(columns, type) {
var tr = doc.createElement('tr');
/* Empty header for link column */
var linkTd = doc.createElement('th');
tr.appendChild(linkTd);
for (var i = 0; i < columns.length; ++i) {
var th = doc.createElement('th');
var column = columns[i];
th.appendChild(doc.createTextNode(column.getLabel())); // We can only add a delete button if we are using the
// proper interface and have a type to delete from:
if (type) {
th.appendChild(renderColumnDeleteButton(type, column));
}
tr.appendChild(th);
}
return tr;
} // Sort the rows in the rendered table by data from a specific
// column, using the provided sort function to compare values.
function applyColumnSort(rows, column, sortFunction, reverse) {
var columnKey = column.getKey(); // Sort the rows array.
rows.sort(function (row1, row2) {
var row1Value = null;
var row2Value = null;
if (columnKey in row1.values) {
row1Value = row1.values[columnKey][0];
}
if (columnKey in row2.values) {
row2Value = row2.values[columnKey][0];
}
var result = sortFunction(row1Value, row2Value);
if (reverse) {
return -result;
} else {
return result;
}
}); // Remove all rows from the table:
if (rows.length) {
var parentTable = rows[0]._htmlRow.parentNode;
for (var i = 0; i < rows.length; ++i) {
parentTable.removeChild(rows[i]._htmlRow);
} // Add back the rows in the new sorted order:
for (var _i = 0; _i < rows.length; ++_i) {
parentTable.appendChild(rows[_i]._htmlRow);
}
}
} // Filter the list of rows based on the selectors for the
// columns.
function applyColumnFiltersToRow(row, columns) {
var rowDisplayed = true; // Check the filter functions for every column.
// The row should only be displayed if the filter functions
// for all of the columns return true.
for (var c = 0; c < columns.length; ++c) {
var column = columns[c];
var columnKey = column.getKey();
var columnValue = null;
if (columnKey in row.values) {
columnValue = row.values[columnKey][0];
}
if (!column.filterFunction(columnValue)) {
rowDisplayed = false;
break;
}
} // Show or hide the HTML row according to the result
// from the filter function.
var htmlRow = row._htmlRow;
if (rowDisplayed) {
htmlRow.style.display = '';
} else {
htmlRow.style.display = 'none';
}
} // Filter the list of rows based on the selectors for the
// columns.
function applyColumnFilters(rows, columns) {
// Apply filterFunction to each row.
for (var r = 0; r < rows.length; ++r) {
var row = rows[r];
applyColumnFiltersToRow(row, columns);
}
} // /////////////////////////////////// Literal column handling
// Sort by literal value
function literalSort(rows, column, reverse) {
function literalToString(colValue) {
if (colValue) {
if (colValue.termType === 'Literal') {
return colValue.value.toLowerCase();
} else if (colValue.termType === 'NamedNode') {
return utils.label(colValue).toLowerCase();
}
return colValue.value.toLowerCase();
} else {
return '';
}
}
function literalCompare(value1, value2) {
var strValue1 = literalToString(value1);
var strValue2 = literalToString(value2);
if (strValue1 < strValue2) {
return -1;
} else if (strValue1 > strValue2) {
return 1;
} else {
return 0;
}
}
applyColumnSort(rows, column, literalCompare, reverse);
} // Generates a selector for an RDF literal column.
function renderLiteralSelector(rows, columns, column) {
var result = doc.createElement('div');
var textBox = doc.createElement('input');
textBox.setAttribute('type', 'text');
textBox.style.width = '70%';
result.appendChild(textBox);
var sort1 = doc.createElement('span');
sort1.appendChild(doc.createTextNode("\u25BC"));
sort1.addEventListener('click', function () {
literalSort(rows, column, false);
}, false);
result.appendChild(sort1);
var sort2 = doc.createElement('span');
sort2.appendChild(doc.createTextNode("\u25B2"));
sort2.addEventListener('click', function () {
literalSort(rows, column, true);
}, false);
result.appendChild(sort2);
var substring = null; // Filter the table to show only rows that have a particular
// substring in the specified column.
column.filterFunction = function (colValue) {
if (!substring) {
return true;
} else if (!colValue) {
return false;
} else {
var literalValue;
if (colValue.termType === 'Literal') {
literalValue = colValue.value;
} else if (colValue.termType === 'NamedNode') {
literalValue = utils.label(colValue);
} else {
literalValue = '';
}
return literalValue.toLowerCase().indexOf(substring) >= 0;
}
};
textBox.addEventListener('keyup', function () {
if (textBox.value !== '') {
substring = textBox.value.toLowerCase();
} else {
substring = null;
}
applyColumnFilters(rows, columns);
}, false);
return result;
} // /////////////////////////////////// Enumeration
// Generates a dropdown selector for enumeration types include
//
// @param rows,
// @param columns, the mapping of predictae URIs to columns
// @param column,
// @param list, List of alternative terms
//
function renderEnumSelector(rows, columns, column, list) {
var doMultiple = true;
var result = doc.createElement('div');
var dropdown = doc.createElement('select');
var searchValue = {}; // Defualt to all enabled
for (var i = 0; i < list.length; ++i) {
var value = list[i];
searchValue[value.uri] = true;
}
var initialSelection = getHints(column).initialSelection;
if (initialSelection) searchValue = initialSelection;
if (doMultiple) dropdown.setAttribute('multiple', 'true');else dropdown.appendChild(optionElement('(All)', '-1'));
for (var _i2 = 0; _i2 < list.length; ++_i2) {
var _value = list[_i2];
var ele = optionElement(utils.label(_value), _i2);
if (searchValue[_value.uri]) ele.selected = true;
dropdown.appendChild(ele);
}
result.appendChild(dropdown); // Select based on an enum value.
column.filterFunction = function (colValue) {
return !searchValue || colValue && searchValue[colValue.uri];
};
dropdown.addEventListener('click', function () {
if (doMultiple) {
searchValue = {};
var opt = dropdown.options;
for (var _i3 = 0; _i3 < opt.length; _i3++) {
var option = opt[_i3];
var index = Number(option.value);
if (opt[_i3].selected) searchValue[list[index].uri] = true;
}
} else {
var _index = Number(dropdown.value); // adjusted in Standard tweaks 2018-01
if (_index < 0) {
searchValue = null;
} else {
searchValue = {};
searchValue[list[_index].uri] = true;
}
}
applyColumnFilters(rows, columns);
}, true);
return result;
} // //////////////////////////////////// Numeric
//
// Selector for XSD number types.
function renderNumberSelector(rows, columns, column) {
var result = doc.createElement('div');
var minSelector = doc.createElement('input');
minSelector.setAttribute('type', 'text');
minSelector.style.width = '40px';
result.appendChild(minSelector);
var maxSelector = doc.createElement('input');
maxSelector.setAttribute('type', 'text');
maxSelector.style.width = '40px';
result.appendChild(maxSelector); // Select based on minimum/maximum limits.
var min = null;
var max = null;
column.filterFunction = function (colValue) {
if (colValue) {
colValue = Number(colValue);
}
if (min && (!colValue || colValue < min)) {
return false;
}
if (max && (!colValue || colValue > max)) {
return false;
}
return true;
}; // When the values in the boxes are changed, update the
// displayed columns.
function eventListener() {
if (minSelector.value === '') {
min = null;
} else {
min = Number(minSelector.value);
}
if (maxSelector.value === '') {
max = null;
} else {
max = Number(maxSelector.value);
}
applyColumnFilters(rows, columns);
}
minSelector.addEventListener('keyup', eventListener, false);
maxSelector.addEventListener('keyup', eventListener, false);
return result;
} // /////////////////////////////////////////////////////////////////
// Fallback attempts at generating a selector if other attempts fail.
function fallbackRenderTableSelector(rows, columns, column) {
// Have all values matched as numbers?
if (column.checkedAnyValues && column.possiblyNumber) {
return renderNumberSelector(rows, columns, column);
} // Have all values been literals?
if (column.possiblyLiteral) {
return renderLiteralSelector(rows, columns, column);
}
return null;
} // Render a selector for a given row.
function renderTableSelector(rows, columns, column) {
// What type of data is in this column? Check the constraints for
// this predicate.
// If this is a class which can be one of various sibling classes?
if (column.superClass && column.alternatives.length > 0) {
return renderEnumSelector(rows, columns, column, column.alternatives);
}
var cs = column.getConstraints();
var range;
for (var i = 0; i < cs.length; i++) {
range = cs[i]; // Is this a number type?
// Alternatively, is this an rdf:Literal type where all of
// the values match as numbers?
if (column.checkedAnyValues && column.possiblyNumber || range.uri in XSD_NUMBER_TYPES) {
return renderNumberSelector(rows, columns, column);
} // rdf:Literal? Assume a string at this point
if (range.uri === RDFS_LITERAL) {
return renderLiteralSelector(rows, columns, column);
} // Is this an enumeration type?
// Also ToDo: @@@ Handle membership of classes whcih are disjointUnions
var choices = kb.each(range, UI.ns.owl('oneOf'));
if (choices.length > 0) {
return renderEnumSelector(rows, columns, column, choices.elements);
}
}
return fallbackRenderTableSelector(rows, columns, column);
} // Generate the search selectors for the table columns.
function renderTableSelectors(rows, columns) {
var tr = doc.createElement('tr');
tr.className = 'selectors'; // Empty link column
tr.appendChild(doc.createElement('td')); // Generate selectors.
for (var i = 0; i < columns.length; ++i) {
var td = doc.createElement('td');
var selector = renderTableSelector(rows, columns, columns[i]);
if (selector) {
td.appendChild(selector);
}
/*
// Useful debug: display URI of predicate in column header
if (columns[i].predicate.uri) {
td.appendChild(document.createTextNode(columns[i].predicate.uri))
}
*/
tr.appendChild(td);
}
return tr;
}
function linkTo(uri, linkText, hints) {
hints = hints || {};
var result = doc.createElement('a');
var linkFunction = hints.linkFunction;
result.setAttribute('href', uri);
result.appendChild(doc.createTextNode(linkText));
if (!linkFunction) {
result.addEventListener('click', UI.widgets.openHrefInOutlineMode, true);
} else {
result.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
var target = utils.getTarget(e);
var uri = target.getAttribute('href');
if (!uri) debug.log('No href found \n');
linkFunction(uri);
}, true);
}
return result;
}
function linkToObject(obj, hints) {
var match = false;
if (obj.uri) {
match = obj.uri.match(/^mailto:(.*)/);
}
if (match) {
return linkTo(obj.uri, match[1], hints);
} else {
return linkTo(obj.uri, utils.label(obj), hints);
}
} // Render an image
function renderImage(obj) {
var result = doc.createElement('img');
result.setAttribute('src', obj.uri); // Set the height, so it appears as a thumbnail.
result.style.height = '40px';
return result;
} // Render an individual RDF object to an HTML object displayed
// in a table cell.
function getHints(column) {
if (options && options.hints && column.variable && options.hints[column.variable.toNT()]) {
return options.hints[column.variable.toNT()];
}
return {};
}
function renderValue(obj, column) {
// hint
var hints = getHints(column);
var cellFormat = hints.cellFormat;
if (cellFormat) {
switch (cellFormat) {
case 'shortDate':
return doc.createTextNode(UI.widgets.shortDate(obj.value));
// break
default: // drop through
}
} else {
if (obj.termType === 'Literal') {
if (obj.datatype) {
if (XSD_DATE_TYPES[obj.datatype.uri]) {
return doc.createTextNode(UI.widgets.shortDate(obj.value));
} else if (XSD_NUMBER_TYPES[obj.datatype.uri]) {
var span = doc.createElement('span');
span.textContent = obj.value;
span.setAttribute('style', 'text-align: right');
return span;
}
}
return doc.createTextNode(obj.value);
} else if (obj.termType === 'NamedNode' && column.isImageColumn()) {
return renderImage(obj);
} else if (obj.termType === 'NamedNode' || obj.termType === 'BlankNode') {
return linkToObject(obj, hints);
} else if (obj.termType === 'Collection') {
var _span = doc.createElement('span');
_span.appendChild(doc.createTextNode('['));
obj.elements.forEach(function (x) {
_span.appendChild(renderValue(x, column));
_span.appendChild(doc.createTextNode(', '));
});
_span.removeChild(_span.lastChild);
_span.appendChild(doc.createTextNode(']'));
return _span;
} else {
return doc.createTextNode("unknown termtype '" + obj.termType + "'!");
}
}
} // Render a row of the HTML table, from the given row structure.
// Note that unlike other functions, this renders into a provided
// row (<tr>) element.
function renderTableRowInto(tr, row, columns, _downstream) {
/* Link column, for linking to this subject. */
var linkTd = doc.createElement('td');
if (row._subject && 'uri' in row._subject) {
linkTd.appendChild(linkTo(row._subject.uri, "\u2192"));
}
tr.appendChild(linkTd); // Create a <td> for each column (whether the row has data for that
// column or not).
for (var i = 0; i < columns.length; ++i) {
var column = columns[i];
var td = doc.createElement('td');
var orig;
var columnKey = column.getKey();
if (columnKey in row.values) {
var objects = row.values[columnKey];
var different = false;
if (row.originalValues && row.originalValues[columnKey]) {
if (objects.length !== row.originalValues[columnKey].length) {
different = true;
}
}
for (var j = 0; j < objects.length; ++j) {
var obj = objects[j];
if (row.originalValues && row.originalValues[columnKey] && row.originalValues[columnKey].length > j) {
orig = row.originalValues[columnKey][j];
if (obj.toString() !== orig.toString()) {
different = true;
}
}
td.appendChild(renderValue(obj, column));
if (j !== objects.length - 1) {
td.appendChild(doc.createTextNode(',\n'));
}
if (different) {
td.style.background = '#efe'; // green = new changed
}
}
}
tr.appendChild(td);
} // Save a reference to the HTML row in the row object.
row._htmlRow = tr;
return tr;
} // Check if a value is already stored in the list of values for
// a cell (the query can sometimes find it multiple times)
function valueInList(value, list) {
var key = null;
if (value.termType === 'Literal') {
key = 'value';
} else if (value.termType === 'NamedNode') {
key = 'uri';
} else {
return list.indexOf(value) >= 0;
} // Check the list and compare keys:
var i;
for (i = 0; i < list.length; ++i) {
if (list[i].termType === value.termType && list[i][key] === value[key]) {
return true;
}
} // Not found?
return false;
} // Update a row, add new values, and regenerate the HTML element
// containing the values.
function updateRow(row, columns, values) {
var key;
var needUpdate = false;
for (key in values) {
var value = values[key]; // If this key is not already in the row, create a new entry
// for it:
if (!(key in row.values)) {
row.values[key] = [];
} // Possibly add this new value to the list, but don't
// add it if we have already added it:
if (!valueInList(value, row.values[key])) {
row.values[key].push(value);
needUpdate = true;
}
} // Regenerate the HTML row?
if (needUpdate) {
clearElement(row._htmlRow);
renderTableRowInto(row._htmlRow, row, columns);
}
applyColumnFiltersToRow(row, columns); // Hide immediately if nec
} // Get a unique ID for the given subject. This is normally the
// URI; if the subject has no URI, a unique ID is assigned.
function getSubjectId(subject) {
if ('uri' in subject) {
return subject.uri;
} else if ('_subject_id' in subject) {
return subject._subject_id;
} else {
var result = '' + subjectIdCounter;
subject._subject_id = result;
++subjectIdCounter;
return result;
}
} // Run a query and populate the table.
// Populates also an array of logical rows. This will be empty when the function
// first returns (as the query is performed in the background)
function runQuery(query, rows, columns, table) {
query.running = true;
var startTime = Date.now();
var progressMessage = doc.createElement('tr');
table.appendChild(progressMessage);
progressMessage.textContent = 'Loading ...';
for (var i = 0; i < rows.length; i++) {
rows[i].original = true;
if (!rows[i].originalValues) {
// remember first set
rows[i].originalValues = rows[i].values;
}
rows[i].values = {}; // oldStyle = rows[i]._htmlRow.getAttribute('style') || ''
// rows[i]._htmlRow.style.background = '#ffe'; //setAttribute('style', ' background-color: #ffe;')// yellow
}
var onResult = function onResult(values) {
if (!query.running) {
return;
}
progressMessage.textContent += '.'; // give a progress bar
var row = null;
var rowKey = null;
var rowKeyId; // If the query has a row key, use it to look up the row.
if (keyVariable in values) {
rowKey = values[keyVariable];
rowKeyId = getSubjectId(rowKey); // Do we have a row for this already?
// If so, reuse it; otherwise, we must create a new row.
if (rowKeyId in rowsLookup) {
row = rowsLookup[rowKeyId];
}
} // Create a new row?
if (!row) {
var tr = doc.createElement('tr');
table.appendChild(tr);
row = {
_htmlRow: tr,
_subject: rowKey,
values: {}
};
rows.push(row);
if (rowKey) {
rowsLookup[rowKeyId] = row;
}
} // Add the new values to this row.
delete row.original; // This is included in the new data
updateRow(row, columns, values);
};
var onDone = function onDone() {
if (progressMessage && progressMessage.parentNode && progressMessage.parentNode.removeChild) {
progressMessage.parentNode.removeChild(progressMessage);
progressMessage = null;
}
var elapsedTimeMS = Date.now() - startTime;
debug.log('Query done: ' + rows.length + ' rows, ' + elapsedTimeMS + 'ms'); // Delete rows which were from old values not new
for (var _i4 = rows.length - 1; _i4 >= 0; _i4--) {
// backwards
if (rows[_i4].original) {
debug.log(' deleting row ' + rows[_i4]._subject);
var tr = rows[_i4]._htmlRow;
tr.parentNode.removeChild(tr);
delete rowsLookup[getSubjectId(rows[_i4]._subject)];
rows.splice(_i4, 1);
}
}
if (options.sortBy) {
// @@ for each column check needs sorting
var column = getColumnForVariable(columns, options.sortBy);
literalSort(rows, column, options.sortReverse);
}
if (options.onDone) options.onDone(resultDiv); // return div makes testing easier
};
kb.query(query, onResult, undefined, onDone);
} // Given the formula object which is the query pattern,
// deduce from where the variable occurs constraints on
// what values it can take.
function inferColumnsFromFormula(columns, formula) {
UI.log.debug('>> processing formula');
for (var i = 0; i < formula.statements.length; ++i) {
var statement = formula.statements[i]; // UI.log.debug("processing statement " + i)
// Does it match this?:
// <something> <predicate> ?var
// If so, we can use the predicate as the predicate for the
// column used for the specified variable.
if (statement.predicate.termType === 'NamedNode' && statement.object.termType === 'Variable') {
var variable = statement.object.toString();
if (variable in columns) {
var column = columns[variable];
column.setPredicate(statement.predicate, false, statement.subject);
}
}
if (statement.predicate.termType === 'NamedNode' && statement.subject.termType === 'Variable') {
var _variable = statement.subject.toString();
if (_variable in columns) {
var _column = columns[_variable];
_column.setPredicate(statement.predicate, true, statement.object);
}
}
} // Apply to OPTIONAL formulas:
for (var _i5 = 0; _i5 < formula.optional.length; ++_i5) {
UI.log.debug('recurse to optional subformula ' + _i5);
inferColumnsFromFormula(columns, formula.optional[_i5]);
}
UI.log.debug('<< finished processing formula');
} // Generate a list of column structures and infer details about the
// predicates based on the contents of the query
function inferColumns(query) {
// Generate the columns list:
var result = [];
var columns = {};
for (var i = 0; i < query.vars.length; ++i) {
var column = new Column();
var queryVar = query.vars[i];
UI.log.debug('column ' + i + ' : ' + queryVar);
column.setVariable(queryVar);
columns[queryVar] = column;
result.push(column);
}
inferColumnsFromFormula(columns, query.pat);
return result;
} // Generate a table from a query.
function renderTableForQuery(query, type) {
// infer columns from query, to allow generic queries
var columns;
if (!givenQuery) {
columns = type.getColumns();
} else {
columns = inferColumns(query);
} // Start with an empty list of rows; this will be populated
// by the query.
var rows = []; // Create table element and header.
var table = doc.createElement('table');
table.appendChild(renderTableHeader(columns, type));
table.appendChild(renderTableSelectors(rows, columns)); // Run query. Note that this is perform asynchronously; the
// query runs in the background and this call does not block.
table.logicalRows = rows; // Save for refresh
table.columns = columns;
table.query = query;
runQuery(query, rows, columns, table);
return table;
} // Find the most common type of row
function getMostCommonType(types) {
var bestCount = -1;
var best = null;
var typeUri;
for (typeUri in types) {
var type = types[typeUri];
if (type.useCount > bestCount) {
best = type;
bestCount = type.useCount;
}
}
return best;
} // Filter list of columns to only those columns used in the
// specified rows.
/*
function filterColumns (columns, rows) {
var filteredColumns = {}
// Copy columns from "columns" -> "filteredColumns", but only
// those columns that are used in the list of rows specified.
for (let columnUri in columns) {
for (let i = 0; i < rows.length; ++i) {
if (columnUri in rows[i]) {
filteredColumns[columnUri] = columns[columnUri]
break
}
}
}
return filteredColumns
}
*/
} // ///////////////////////////////////////////////////////////////////
// ENDS
//# sourceMappingURL=table.js.map