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

Show all fields in record detail view, indicate record type with icon #238

Merged
merged 8 commits into from
Dec 11, 2024
26 changes: 20 additions & 6 deletions frontend/vre/field/field.model.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import Backbone from 'backbone';
import { canonicalSort } from '../utils/generic-functions';
import {fieldList, properties} from "../utils/record-ontology";
import {
canonicalSort,
typeTranslation,
} from '../utils/generic-functions';
import {
fieldList,
properties,
biblioProperties,
bioProperties,
} from "../utils/record-ontology";
import {getStringLiteral} from "../utils/jsonld.model";

// A single field of a single record.
Expand All @@ -12,7 +20,8 @@ export var Field = Backbone.Model.extend({
*/
getMainDisplay() {
// Currently, only normalizedText is supported.
return this.get('value')['edpoprec:originalText'];
const value = this.get('value');
return value && value['edpoprec:originalText'];
},
getFieldInfo() {
const property = properties.get(this.id);
Expand Down Expand Up @@ -52,9 +61,14 @@ export var FlatFields = Backbone.Collection.extend({
this.listenTo(this.record, 'change', _.flow([this.toFlat, this.set]));
},
toFlat: function(record) {
const content = record.toJSON();
const fieldNames = Object.keys(content).filter((name) => fieldList.includes(content[name]["@type"]));
const fields = fieldNames.map((name) => ({key: name, value: content[name]}));
const properties = (
typeTranslation(record).isBibliographical ?
biblioProperties : bioProperties
);
const fields = properties.map(prop => ({
key: prop.id,
value: record.get(prop.id),
}));
return fields;
},
});
13 changes: 11 additions & 2 deletions frontend/vre/record/record.detail.view.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import _ from 'lodash';
import { CompositeView } from '../core/view.js';
import { vreChannel } from '../radio';
import { FlatAnnotations } from '../annotation/annotation.model';
import { RecordFieldsView } from '../field/record.fields.view';
import { RecordAnnotationsView } from '../field/record.annotations.view';
import { FlatFields } from '../field/field.model';
import { VRECollectionView } from '../collection/collection.view';
import { typeTranslation } from '../utils/generic-functions.js';
import { GlobalVariables } from '../globals/variables';
import recordDetailTemplate from './record.detail.view.mustache';
import typeIconTemplate from './record.type.icon.mustache';

var renderOptions = {
partials: {
typeIcon: typeIconTemplate,
}
};

export var RecordDetailView = CompositeView.extend({
template: recordDetailTemplate,
Expand Down Expand Up @@ -49,12 +58,12 @@ export var RecordDetailView = CompositeView.extend({
},

renderContainer: function() {
this.$el.html(this.template({
this.$el.html(this.template(_.assign({
title: this.model.getMainDisplay(),
uri: this.model.id,
databaseId: this.model.get("edpoprec:identifier"),
publicURL: this.model.get("edpoprec:publicURL"),
}));
}, typeTranslation(this.model)), renderOptions));
return this;
},

Expand Down
5 changes: 4 additions & 1 deletion frontend/vre/record/record.detail.view.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
data-dismiss="modal"
aria-label="Close"
><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{title}}</h4>
<h4 class="modal-title">
{{>typeIcon}}
{{title}}
</h4>
<ul class="inline-list">
{{#databaseId}}<li>ID in database: {{databaseId}}</li>{{/databaseId}}
{{#publicURL}}<li><a href="{{publicURL}}" target="_blank">Show record in original database</a></li>{{/publicURL}}
Expand Down
8 changes: 8 additions & 0 deletions frontend/vre/utils/filtered.collection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Collection } from 'backbone';
import { deriveFiltered } from '@uu-cdh/backbone-collection-transformers';

/**
* @class
* @extends Collection
*/
export var FilteredCollection = deriveFiltered();
23 changes: 23 additions & 0 deletions frontend/vre/utils/generic-functions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from 'lodash';
import { Model } from 'backbone';

/**
* Perform the following transformation:
Expand Down Expand Up @@ -31,3 +32,25 @@ var canonicalOrder = {
'With Note': 48,
'Subject Headings': 52,
};

/**
* Translate from compacted JSON-LD `@type` strings to payload objects suitable
* for decision making in a Mustache template.
* @param recordType {string|Model} recordType - a JSON-LD URI shorthand with
* the `edpoprec:` prefix, or a model that has such a string as its `'@type'`
* attribute.
* @returns {object} A newly created object with at most one own enumerable
* property. The key of the property is either `'isBibliographical'` or
* `'isBiographical'`, depending on the passed `recordType`. The value of the
* property is `true` in both cases.
*/
export function typeTranslation(recordType) {
if (recordType instanceof Model) recordType = recordType.get('@type');
switch (recordType) {
case 'edpoprec:BibliographicalRecord':
return {isBibliographical: true};
case 'edpoprec:BiographicalRecord':
return {isBiographical: true};
}
return {};
}
82 changes: 81 additions & 1 deletion frontend/vre/utils/record-ontology.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
* Models, collections and utilities related to the record ontology.
*/

import {JsonLdNestedCollection} from "./jsonld.model";
import _ from 'lodash';
import { FilteredCollection } from './filtered.collection.js';
import { JsonLdNestedCollection } from './jsonld.model';

/**
* A Backbone collection to access the properties defined by the ontology.
Expand All @@ -15,6 +17,84 @@ export var PropertyList = JsonLdNestedCollection.extend({
export var properties = new PropertyList();
properties.fetch();

/**
* Regular expression matching `'edpoprec:Record'`,
* `'edpoprec:BiographicalRecord'` and `'edpoprec:BibliographicalRecord'`. The
* first capturing group contains the full qualification before `'Record'` (if
* present), while the second capturing group contains only the part `'Bio'` or
* `'Biblio'`.
*/
var recordTypePattern = /^edpoprec:((Bi(?:bli)?o)graphical)?Record$/;

/**
* Determine whether the given domain matches the target class.
* @param {string} target - Can be `'Biblo'` or `'Bio'`, to indicate which
jgonggrijp marked this conversation as resolved.
Show resolved Hide resolved
* record type is considered acceptable. Any other value effectively means that
* only the more generic type `'edpoprec:Record'` is acceptable.
* @param {object} domain - JSON-LD declaration of a single domain of a
* property.
* @returns {boolean} `true` if the domain matches `target` or if it is
* `'edpoprec:Record'`, `false` otherwise.
*/
function domainFitsQualification(target, domain) {
var match = domain['@id'].match(recordTypePattern);
if (!match) return false;
var qualified = match[1];
var qualifier = match[2];
return (!qualified || qualifier === target);
}

// The next comment only describes a type for JSDoc; it does not actually refer
// to any statement or object.
jgonggrijp marked this conversation as resolved.
Show resolved Hide resolved
/**
* Unary predicate over JSON-LD objects. Functions of this type can be used to
* find or filter over arrays of such objects. One possible way to obtain such a
* predicate function is by partially applying {@link domainFitsQualification}.
* @callback ldObjectPredicate
* @param {object} object - JSON-LD obhect to accept or reject.
* @param {string} object['@id'] - JSON-LD objects must have an '@id' property
* and the predicate is allowed to assume this.
* @returns {boolean} `true` if the object is accepted, `false` otherwise.
*/

/**
* Determine whether a given property has any domain matching the given
* criterion. When the first argument is partially applied in advance, the
* remaining function is suitable as a filtering criterion for a
* {@link FilteredCollection}.
* @param {ldObjectPredicate} criterion - Predicate against which the domains of the property should be matched.
* @param {Backbone.Model} property - Model-wrapped JSON-LD representation of an
* RDF property.
* @returns {boolean} `true` if at least one domain of the property matches the criterion, `false` otherwise.
*/
function appliesToSuitableDomains(criterion, property) {
var domain = property.get('rdfs:domain');
if (!domain) return false;
if (!domain.length) domain = [domain];
return _.find(domain, criterion);
}

// Partial applications of the above two functions so we can use them for the
// filtered collections that we will define next.
var includesBiographical = _.partial(domainFitsQualification, 'Bio'),
includesBibliographic = _.partial(domainFitsQualification, 'Biblio'),
appliesToBiographies = _.partial(appliesToSuitableDomains,
includesBiographical),
appliesToBibliographs = _.partial(appliesToSuitableDomains,
includesBibliographic);

/**
* Subset of {@link properties} containing only the properties that apply to
* biographical records.
*/
export var bioProperties = new FilteredCollection(properties, appliesToBiographies);

/**
* Subset of {@link properties} containing only the properties that apply to
* bibliographical records.
*/
export var biblioProperties = new FilteredCollection(properties, appliesToBibliographs);

/**
* A list of all field types according to the record ontology.
* This is hardcoded for now because there is no trivial way to infer this
Expand Down
12 changes: 2 additions & 10 deletions frontend/vre/utils/tabulator-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Model, Collection} from 'backbone';
import {MappedCollection} from './mapped.collection.js';
import {properties} from './record-ontology';
import {getStringLiteral} from './jsonld.model';
import {typeTranslation} from './generic-functions.js';
import recordTypeIcon from '../record/record.type.icon.mustache';

/**
Expand Down Expand Up @@ -65,15 +66,6 @@ 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,
Expand Down Expand Up @@ -159,7 +151,7 @@ standardColumns.unshift({
title: 'Type',
visible: true,
headerContextMenu: columnChooseMenu,
formatter: cell => recordTypeIcon(typeTranslation[cell.getValue()]),
formatter: cell => recordTypeIcon(typeTranslation(cell.getValue())),
hozAlign: 'right',
tooltip: (e, cell) => cell.getValue().slice(9, -6),
width: 48,
Expand Down
Loading