Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make all properties available in Tabulator records list, show record type with icon #233

Merged
merged 11 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ services:
volumes:
- pgdata:/var/lib/postgresql/data
- ./backend/create_db.sql:/docker-entrypoint-initdb.d/edpop.sql
ports:
- 127.0.0.1:5432:5432
blazegraph:
image: islandora/blazegraph:main
healthcheck:
Expand Down
20 changes: 20 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"sinon": "^14.0.1"
},
"dependencies": {
"@uu-cdh/backbone-collection-transformers": "github:CentreForDigitalHumanities/backbone-collection-transformers#release/0.1.0",
"@uu-cdh/backbone-util": "^0.1.1",
"backbone": "^1.3.3",
"backbone-fractal": "^1.1.1",
Expand Down
2 changes: 1 addition & 1 deletion frontend/vre/edpop-record-ontology.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"@graph": [
{
"@id": "edpoprec:bibliographicalRecord",
"@id": "edpoprec:BibliographicalRecord",
"rdfs:subClassOf": {
"@id": "_:na743fb6ca02e49568bc2e2bb36208e43b2"
}
Expand Down
48 changes: 3 additions & 45 deletions frontend/vre/record/record.list.view.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,7 @@
import Backbone from "backbone";
import {properties} from "../utils/record-ontology";
import {getStringLiteral} from "../utils/jsonld.model";
import {vreChannel} from "../radio";
import Tabulator from "tabulator";
import {columnChooseMenu} from "../utils/tabulator-utils";

const columnProperties = {
'edpoprec:title': {
visible: true,
widthGrow: 5,
formatter: 'textarea',
},
'edpoprec:placeOfPublication': {
visible: true,
},
'edpoprec:dating': {
visible: true,
widthGrow: 0.5,
},
'edpoprec:publisherOrPrinter': {
visible: true,
},
'edpoprec:contributor': {
visible: true,
},
'edpoprec:activity': {
visible: true,
},
};
import {adjustDefinitions} from "../utils/tabulator-utils";

export var RecordListView = Backbone.View.extend({
id: "record-list",
Expand All @@ -48,23 +22,7 @@ export var RecordListView = Backbone.View.extend({
height: 650, // set height of table (in CSS or here), this enables the Virtual DOM and improves render speed dramatically (can be any valid css height value)
data: initialData,
autoColumns: true,
autoColumnsDefinitions: (definitions) => {
for (let definition of definitions) {
// All columns invisible by default
definition.visible = false;
const property = properties.get(definition.field);
if (property) {
definition.title = getStringLiteral(property.get("skos:prefLabel"));
}
definition.headerFilter = true;
definition.headerContextMenu = columnChooseMenu;
const hardcodedProperties = columnProperties[definition.field];
if (hardcodedProperties) {
Object.assign(definition, hardcodedProperties);
}
}
return definitions;
},
autoColumnsDefinitions: adjustDefinitions,
layout: "fitColumns",
rowHeader: {
width: 50,
Expand Down Expand Up @@ -103,4 +61,4 @@ export var RecordListView = Backbone.View.extend({
downloadCSV: function() {
this.table.download("csv", "edpop.csv");
},
});
});
3 changes: 2 additions & 1 deletion frontend/vre/record/record.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export var Record = JsonLdModel.extend({
const fields = new FlatFields(undefined, {record: this});
const data = {
model: this,
type: this.get('@type'),
};
fields.forEach((field) => {
data[field.id] = field.getMainDisplay();
Expand All @@ -48,4 +49,4 @@ export var Record = JsonLdModel.extend({

export var Records = JsonLdWithOCCollection.extend({
model: Record,
});
});
8 changes: 8 additions & 0 deletions frontend/vre/record/record.type.icon.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{#isBibliographical}}
<span class="glyphicon glyphicon-book" aria-hidden=true></span>
<span class="sr-only">Bibliographical</span>
{{/isBibliographical}}
{{#isBiographical}}
<span class="glyphicon glyphicon-user" aria-hidden=true></span>
<span class="sr-only">Biographical</span>
{{/isBiographical}}
25 changes: 25 additions & 0 deletions frontend/vre/utils/mapped.collection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Collection } from 'backbone';
import { deriveMapped } from '@uu-cdh/backbone-collection-transformers';

/**
* @class
* @extends Collection
*/
export var MappedCollection = deriveMapped();

function clone(model) {
return model.clone();
}

// MappedCollection is not clonable by default. In our case, it is useful to
// have a deep copy feature available.
/**
* Create a deep copy of the models in the collection.
* @returns {Collection} A plain (i.e., non-mapped, non-proxy) collection that
* has no ties to the original mapped or collection or its underlying
* collection.
*/
MappedCollection.prototype.clone = function() {
var clonedModels = this.map(clone);
return new Collection(clonedModels);
}
130 changes: 129 additions & 1 deletion frontend/vre/utils/tabulator-utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import _ from 'lodash';
import {Model, Collection} from 'backbone';
import {MappedCollection} from './mapped.collection.js';
import {properties} from './record-ontology';
import {getStringLiteral} from './jsonld.model';
import recordTypeIcon from '../record/record.type.icon.mustache';

/**
* A Tabulator menu to hide and show the available columns.
* Adapted from: https://tabulator.info/examples/6.2#menu
Expand Down Expand Up @@ -56,4 +63,125 @@ export const columnChooseMenu = function(){
}

return menu;
};
};

/**
* Translation table from compacted JSON-LD `@type` strings to payload objects
* suitable for decision making in a Mustache template.
*/
const typeTranslation = {
'edpoprec:BibliographicalRecord': {isBibliographical: true},
'edpoprec:BiographicalRecord': {isBiographical: true},
};

const defaultColumnFeatures = {
visible: false,
headerFilter: true,
headerContextMenu: columnChooseMenu,
};

/**
* Preferred columns, which are prioritized in the given order over other
* columns. Keys are field names, values are objects with any column definition
* overrides.
*/
const columnProperties = {
type: {},
'edpoprec:title': {
widthGrow: 5,
},
'edpoprec:placeOfPublication': {},
'edpoprec:dating': {
widthGrow: 0.5,
},
'edpoprec:publisherOrPrinter': {},
'edpoprec:contributor': {},
'edpoprec:activity': {},
};

/**
* Table of the form `{type: 0, 'edpoprec:title': 1, ...}`, derived from
* {@link columnProperties}.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I had no idea that this was possible!

const columnOrder = _.invert(_.keys(columnProperties));

/**
* Model wrapper for Tabulator's column definition schema.
* @class
*/
const ColumnDefinition = Model.extend({
idAttribute: 'field',
});

/**
* Comparator function for {@link Collection#sort}. Columns that appear in
* {@link columnOrder} are sorted by their value in that table, all other
* columns after that.
* @param {ColumnDefinition} columnDef - Column definition model.
* @returns {number} Order of preference, with lower numbers indicating greater
* preference.
*/
function byPreference(columnDef) {
const definedOrder = columnOrder[columnDef.id];
return definedOrder != null ? definedOrder : columnOrder.length;
}

/**
* Given a property in the EDPOP Record Ontology, return the corresponding
* Tabulator column definition, taking special cases into account.
* @param {JsonLdModel} property - JSON-LD model of the ontology property.
* @returns {object} Tabulator column definition (suitable as payload for a
* {@link ColumnDefinition}).
*/
function property2definition(property) {
return _.assign({
title: getStringLiteral(property.get('skos:prefLabel')),
field: property.id,
}, defaultColumnFeatures, columnProperties[property.id])
}

/**
* Set of all available columns in the Tabulator results table. Contained as a
* Backbone.Collection for easy referencing and computation. Call the `.toJSON`
* method in order to extract the column definitions in the format that
* Tabulator understands.
*/
const standardColumns = new MappedCollection(
properties,
property2definition,
{model: ColumnDefinition, comparator: byPreference},
);

// `@type` is not a property, so we add it as a special case. This enables the
// person/book icons at the left end of every row.
standardColumns.unshift({
field: 'type',
title: 'Type',
visible: true,
headerContextMenu: columnChooseMenu,
formatter: cell => recordTypeIcon(typeTranslation[cell.getValue()]),
hozAlign: 'right',
tooltip: (e, cell) => cell.getValue().slice(9, -6),
width: 48,
// The `_ucid` is an implementation detail of `MappedCollection`. It appears
// here because mapped collections are not really intended for non-mapped
// additions or other modifications.
_ucid: {},
}, {convert: false});

/**
* Callback for Tabulator's `autoColumnsDefinitions`. It always returns all
* columns defined in {@link standardColumns}, but leverages the autodetected
* columns to determine which columns should be visible. Columns that are both
* preferred and present in the data are visible, all other columns are
* invisible.
*/
export function adjustDefinitions(autodetected) {
const customizedColumns = standardColumns.clone();
_.each(autodetected, autoColumn => {
if (!(autoColumn.field in columnProperties)) return;
const customColumn = customizedColumns.get(autoColumn.field);
customColumn && customColumn.set('visible', true);
});
return customizedColumns.toJSON();
}
Loading