Commit 4aa961f0 authored by malzer's avatar malzer
Browse files

Fix issue #72 by providing selection and sorting options

parent 897c1b6f
#dataTable {
height: 30em;
overflow: scroll;
.dataTable {
height: 30em;
overflow: hidden;
}
#firstStartWizard {
......@@ -38,3 +38,7 @@
height: 20em;
}
.ol-attribution:not(.ol-collapsed) {
background: none;
}
......@@ -123,12 +123,13 @@
<div id="sheetArea">
<div id="howtoArea"></div>
<button id="howtoButton" class="btn btn-small pull-right" onclick="toggleHowto();" title="Read a small howto to get help filling in the table data.">How to fill the table</button>
<button id="clearSelectionButton" class="btn btn-small pull-right" style="margin-right: 5px" onclick="clearTableSelection();" title="Clear selection of table cells.">Clear selection</button>
<br/>
<p>
<img id="spinningConsoleCompletionFlower" class="hide" src="https://res.de.dariah.eu/dhrep/img/spinning-flower_slow.gif" alt="Data table loading..." width="16" />
<span id="console" class="console">&nbsp;</span>
</p>
<div id="dataTable" class="dataTable"></div>
<div id="dataTable" class="dataTable handsontable"></div>
</div>
</div>
</div>
......@@ -152,9 +153,10 @@
</div>
<div class="input-append">
<input id="osmPlaceName" style="width:77%;" type="text" class="input-large" placeholder="Enter exact place name as in “Address“ field..."/>
<button id="setAllPlacesOSMButton" class="btn" style="width:18%;" onClick="setAllPlacesOSM()" title="Sets all places with this name to the chosen coordinates."><i class="icon-pencil"></i>&nbsp;Set</button>
<button id="setAllPlacesOSMButton" class="btn" style="width:18%;" onClick="setAllPlacesOSM()" title="Sets all places with this name to the chosen coordinates."><i id='setAllPlacesIcon' class="icon-pencil"></i>&nbsp;Set</button>
</div>
<br/>
<i>Note: 'Set' will set coordinates for <b>all</b> table rows with this address unless you only select specific rows or cells first.</i>
<br/><br/>
<div id="editMap" class="osmMap"></div>
<br/>
<h3>Map search</h3>
......
......@@ -90,7 +90,7 @@ function loadFromDariahStorage(id) {
}
else if (!checkIfDatasheetEditorFile(firstLineOfData)) {
var title = 'File has not got the correct Datasheet Editor format!';
var message = '<p>The dataset with ID <i>' + id + ' (mimetype ' + responseMimetype + ') can not be loaded into the Datasheet Editor: The first line must at least contain one of the following titles: <strong>' + e4dHeader + '</strong>.</p><p>First line seems to be instead: <strong>' + firstLineOfData + '</strong></p>';
var message = '<p>The dataset with ID <i>' + id + ' (mimetype ' + responseMimetype + ') can not be loaded into the Datasheet Editor: The first line must at least contain one of the following titles: <strong>' + defaultColumnHeaders + '</strong>.</p><p>First line seems to be instead: <strong>' + firstLineOfData + '</strong></p>';
newAlert('error', title, message);
// Re-set storage ID.
dsid = "";
......
......@@ -119,13 +119,11 @@ function getEmbeddedDariahStatus() {
*/
function checkIfDatasheetEditorFile(firstLineOfData) {
var headerCount = 0;
e4dHeader.forEach(function (val, index) {
defaultColumnHeaders.forEach(function (val, index) {
if (firstLineOfData.includes(`${val}`)) {
headerCount++;
}
});
if (headerCount > 0) {
return true;
}
return false;
return headerCount > 0;
}
......@@ -10,16 +10,15 @@ var logIDPrefix = 'DATASHEET_';
var bearerPrefix = 'bearer ';
// All the possible headers (for new sheets).
var e4dHeader = ["Name", "Address", "Description", "Longitude", "Latitude", "TimeStamp", "TimeSpan:begin", "TimeSpan:end", "GettyID"];
const defaultColumnHeaders = ["Name", "Address", "Description", "Longitude", "Latitude", "TimeStamp", "TimeSpan:begin", "TimeSpan:end", "GettyID"];
// Column headers which need to be there for geolocation completion.
var requiredColumnsGeolocation = ["Address"];
// Column headers which needs to be added for geolocation completion table filling.
var addableColumns = ["GettyID", "Longitude", "Latitude"];
const requiredColumnsGeolocation = ["Address", "Longitude", "Latitude", "GettyID"];
// Column headers which need to be there for opening in Geo-Browser.
var requiredColumnsGeobrowser = ["Address", "TimeStamp", "TimeSpan:begin", "TimeSpan:end", "Longitude", "Latitude"];
const requiredColumnsGeobrowser = ["Address", "TimeStamp", "TimeSpan:begin", "TimeSpan:end", "Longitude", "Latitude"];
// columns with date format
const dateColumns = ["TimeStamp", "TimeSpan:begin", "TimeSpan:end"];
// Mimetypes which pass check to get parsed into table.
var allowedMimeTypes = ["text/csv", "text/comma-separated-values", "application/vnd.dariahde.geobrowser.csv", "application/vnd.ms-excel"]
// CSV mimetype used for storing datasheets.
var csvStorageMimetype = 'application/vnd.dariahde.geobrowser.csv';
......
......@@ -17,10 +17,27 @@ function initMap() {
});
}
/*
*
*/
function getUniqueColumnValues(columnName){
// somehow sort() & $.unique() failed on chromium, this solution using underscore.js works:
//http://stackoverflow.com/questions/4833651/javascript-array-sort-and-unique
var sortUnique = _.compose(_.uniq, function(array) {
return _.sortBy(array, _.identity);
});
var dataSet = tableInstance.getData();
var column = findColumn(dataSet, columnName);
var selection = tableInstance.getSelectedLast();
var columnValues = tableInstance.getDataAtCol(column);
var selectedValues = [];
$.each(columnValues, function(index, value){
if(!isEmptyString(value) && value !== columnName) {
if (isSelected(index, column, selection)){
selectedValues.push(value.trim());
}
}
})
return sortUnique(selectedValues);
}
function startTGN() {
$('#tgnArea').show();
......@@ -33,33 +50,19 @@ function startTGN() {
$('#tgnList').empty();
var addressColumn = findColumn(arr, 'Address');
// get list of places from multidimensional array,
// do not return the "Address" header or emtpy strings
var places = $.map(arr, function(x) {
var ret = x[addressColumn];
if(!isEmptyString(ret) && ret !== 'Address') {
// remove whitespaces
return $.trim(ret);
}
});
// somehow sort() & $.unique() failed on chromium, this solution using underscore.js works:
//http://stackoverflow.com/questions/4833651/javascript-array-sort-and-unique
var sortUnique = _.compose(_.uniq, function(array) {
return _.sortBy(array, _.identity);
});
places = sortUnique(places);
var places = getUniqueColumnValues('Address');
// count number of TGN calls to do
var numberOfCallsToDo = places.length;
if (numberOfCallsToDo === 0){
var selection = tableInstance.getSelectedLast();
if (selection){
newAlert('error', 'No address values in current selection!', '<p>No address column values found in selected cells. If you want to process the whole data set, click on <button class="btn btn-small btn-primary" onclick="clearTableSelection();$(\'.geocodeAlert\').alert(\'close\');" title="Clear selection">Clear selection</button> first. <br>' +
'Otherwise, select table rows with non-empty values in the <i>Address</i> column.</p>', 'geocodeAlert');
} else newAlert('error', 'No data available for Geolocation!', '<p>No address values found in column <i>Address</i>.' + '</p>', 'geocodeAlert');
} else $(".geocodeAlert").alert('close');
var numberOfCallsCompleted = 0;
$.each(places, function(index, place) {
// TODO Remove all "'s from place name for not destroying HTML code. Where exactly is this needed?
// TODO Just removing is not quite correct, because we had to remove it from the table, too.
// TODO Leave it as it is right now...
// place = place.replaceAll('"', '');
// disable Geolocation completion button before first Getty GET!
if (index == 0 ) {
geolocationCompletionStart();
......@@ -127,7 +130,10 @@ function startTGN() {
}
})
.fail(function() {
// TODO How do we use this? Do we need it at all? Maybe increase numberOfCallsCompleted?
numberOfCallsCompleted++;
if (numberOfCallsToDo === numberOfCallsCompleted) {
geolocationCompletionComplete();
}
});
});
}
......@@ -442,16 +448,15 @@ function checkCoordinates() {
var result = true;
$(arr).each(function(rowIndex, rowData) {
// Do not check header and empty rows.
// NOTE Row's index must be +1 because of the defined title row with index 0.
if (rowIndex !== 0 && !rowIsEmpty(rowData)) {
var rvla = isValidLat(rowIndex, rowData, latCol);
if (!rvla) {
latErrorInRow.push(errLink(rowIndex + 1, latCol));
latErrorInRow.push(errLink(rowIndex, latCol));
result = rvla;
}
var rvlo = isValidLong(rowIndex, rowData, longCol);
if (!rvlo) {
longErrorInRow.push(errLink(rowIndex + 1, longCol));
longErrorInRow.push(errLink(rowIndex, longCol));
result = rvlo;
}
}
......@@ -469,7 +474,8 @@ function checkCoordinates() {
*/
function errLink(row, col) {
//var link = $('<a>', {text: row + 1});
var link = '<a href="#" onclick="jumpToCell('+row+','+col+'); return false;">'+row+'</a>';
var displayRow = row+1;
var link = '<a href="#" onclick="jumpToCell('+row+','+col+'); return false;">'+displayRow+'</a>';
return link;
}
......
......@@ -7,7 +7,7 @@
function createInitialTableContent(){
// column titles are placed in the first row, i.e. are no actual column headers
var initialData = [["Name", "Address", "Description", "Longitude", "Latitude", "TimeStamp", "TimeSpan:begin", "TimeSpan:end", "GettyID"]];
var initialData = [defaultColumnHeaders];
for (var i = 0; i < 30; i++) initialData.push(['', '', '', '', '', '', '', '', '']);
return initialData;
}
......@@ -24,7 +24,7 @@ function handleCells(row, col, readOnly) {
cellProperties.renderer = headerRenderer;
cellProperties.type = 'autocomplete';
cellProperties.trimDropdown = false;
cellProperties.source = e4dHeader;
cellProperties.source = defaultColumnHeaders;
cellProperties.strict = false;
} else {
var dateColumns = getDateColumns();
......@@ -36,7 +36,7 @@ function handleCells(row, col, readOnly) {
cellProperties.datePickerConfig = {
yearRange: [1000, 2050]
}
}
} else cellProperties.type = 'text';
}
return cellProperties;
}
......@@ -44,7 +44,7 @@ function handleCells(row, col, readOnly) {
function initTable() {
var container = document.getElementById('dataTable');
if (tableInstance) {console.log('Destroy'); tableInstance.destroy();}
if (tableInstance) tableInstance.destroy();
tableInstance = new Handsontable(container, {
licenseKey: 'non-commercial-and-evaluation',
manualColumnResize: true,
......@@ -53,19 +53,59 @@ function initTable() {
contextMenu: true,
rowHeaders: true, // 123
colHeaders: true, // ABC
stretchH: 'all',
columnSorting: {
headerAction: true,
indicator: true,
sortEmptyCells: false,
compareFunctionFactory: function (sortOrder) {
return function (value, nextValue) {
if (!value) return 0;
if (getColumnNames().includes(value)) return -1; //always stays on top
if (value.toLowerCase() === nextValue.toLowerCase()) return 0;
if (value.toLowerCase() > nextValue.toLowerCase()) return sortOrder === 'asc' ? 1 : -1;
else return sortOrder === 'asc' ? -1 : 1;
}
}
},
outsideClickDeselects: function (event) {
//clicking buttons does not clear selection
return !(event.nodeName === 'BUTTON' || event.parentElement && event.parentElement.nodeName === 'BUTTON');
},
minSpareCols: 1, // always keep at least 1 spare row at the right
minSpareRows: 1, // always keep at least 1 spare row at the bottom
cells: handleCells,
afterChange: function (change, source) {
if (source === 'loadData') {
return; // don't save this change
afterChange: function (changes, source) {
if (!changes) return;
if (source !== 'loadData') { //don't save on load
autoSave(changes);
}
autoSave(change);
changes.forEach(function([row, prop, oldValue, newValue]) {
if (row === 0){
var readOnly = tableInstance.getCellMeta(0, 0).readOnly;
if (dateColumns.includes(newValue) || (dateColumns.includes(oldValue) && !dateColumns.includes(newValue))){
tableInstance.updateSettings({ //re-render cells
cells: function (row, col) {
return handleCells(row, col, readOnly);
}
});
}
}
});
}
});
}
/**
* Retrieves values in the first table row, which we use as column names
*/
function getColumnNames(){
var columnNames = defaultColumnHeaders;
if (tableInstance) columnNames = tableInstance.getData()[0];
return columnNames;
}
/**
* Identifies indices of columns that should receive "date" type
*/
......@@ -74,8 +114,7 @@ function getDateColumns() {
if (!tableInstance) return null;
var data = tableInstance.getData();
var indices = [];
var dateColumnNames = ['TimeStamp', 'TimeSpan:begin', 'TimeSpan:end'];
dateColumnNames.forEach(function(name){
dateColumns.forEach(function(name){
if (data.length < 1) return;
var dateColumn = data[0].findIndex(x => x === name);
if (dateColumn !== -1) indices.push(dateColumn);
......@@ -83,12 +122,22 @@ function getDateColumns() {
return indices;
}
/**
* Highlights column names in first row
*/
function headerRenderer(instance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
td.style.fontWeight = 'bold';
td.style.color = 'green';
}
/**
* Clears selection of cells
*/
function clearTableSelection(){
if (tableInstance) tableInstance.deselectCell();
}
/**
* This function checks if every needed column is there for envoking Geolocation completion
* (means Address column only!).
......@@ -103,11 +152,7 @@ function checkColumnHeadersGeolocation() {
$('#missingCols').empty();
$(requiredColumnsGeolocation).each(function(index, val) {
ready = findOrAlertCreatableColumn(arr, val);
});
$(addableColumns).each(function(index, val) {
ready = findOrAlertCreatableColumn(arr, val);
ready = findOrAlertCreatableColumn(arr, val) && ready;
});
return ready;
......@@ -126,7 +171,7 @@ function checkColumnHeadersGeobrowser() {
$('#missingCols').empty();
$(requiredColumnsGeobrowser).each(function(index, val) {
ready = findOrAlertCreatableColumn(arr, val);
ready = findOrAlertCreatableColumn(arr, val) && ready;
});
return ready;
......@@ -168,11 +213,10 @@ function createColumn(columnName) {
break;
}
}
//tableInstance.alter('insert_col', colId, columnName);
tableInstance.selectCell(0, colId);
if (["TimeStamp", "TimeSpan:begin", "TimeSpan:end"].includes(columnName)){
tableInstance.setCellMeta(0, colId, 'type', 'time');
}
tableInstance.setDataAtCell(0, colId, columnName);
clearTableSelection();
// Delete column alert.
$("#" + getBase64Without(columnName)).alert('close');
}
......@@ -180,6 +224,15 @@ function createColumn(columnName) {
return id;
}
function isSelected (row, col){
var selected = tableInstance.getSelectedLast();
if (!selected) return true; //no selection at all --> treat as selected
var startRow = selected[0];
var startCol = selected[1];
var endRow = selected[2];
var endCol = selected[3];
return row >= startRow && row <= endRow && col >= startCol && col <= endCol;
}
/**
* Table is filled with coords and Getty IDs only if address is contained in table's address field
* or address field is empty AND (if given) address field content is contained in variant string.
......@@ -193,22 +246,25 @@ function updateCoordinates(address, lat, long, gettyId, forceOverwrite, variant)
}
var arr = tableInstance.getData();
var addressColumn = findColumn(arr, 'Address');
var longColumn = findColumn(arr, 'Longitude');
var latColumn = findColumn(arr, 'Latitude');
var gettyColumn = findColumn(arr, 'GettyID');
var save = false;
$(arr).each(function(index, val) {
if ($.trim(val[addressColumn]) === $.trim(address) || ($.trim(val[addressColumn]) !== "" && variant.includes($.trim(val[addressColumn])))) {
if (forceOverwrite || (isEmptyString(val[longColumn]) && isEmptyString(val[latColumn]) && isEmptyString(val[gettyColumn]))) {
tableInstance.setDataAtCell(index, longColumn, long);
tableInstance.setDataAtCell(index, latColumn, lat);
tableInstance.setDataAtCell(index, gettyColumn, gettyId);
save = true;
if ((forceOverwrite || isEmptyString(val[gettyColumn])) && isSelected(index, gettyColumn)){
if (!isEmptyString(gettyId)) tableInstance.setDataAtCell(index, gettyColumn, gettyId);
}
if ((forceOverwrite || isEmptyString(val[longColumn])) && isSelected(index, longColumn)){
if (!isEmptyString(long)) tableInstance.setDataAtCell(index, longColumn, long);
}
if ((forceOverwrite || isEmptyString(val[latColumn])) && isSelected(index, latColumn)){
if (!isEmptyString(lat)) tableInstance.setDataAtCell(index, latColumn, lat);
}
save = true;
}
});
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment