labo-components
Version:
401 lines (363 loc) • 13.6 kB
JSX
import React from "react";
import PropTypes from "prop-types";
//TODO fix autosuggest https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
import Autosuggest from "react-autosuggest"; //See: https://github.com/moroshko/react-autosuggest
import IDUtil from "../../../util/IDUtil";
import ExternalAPI from "../../../api/ExternalAPI";
import debounce from "debounce";
import { AnnotationEvents } from "../AnnotationClient";
import Strings from "../_Strings";
import ClassificationSelector from "./ClassificationSelector";
import { getAnnotationsPerType } from "../AnnotationHelpers";
import { CLASSIFICATION } from "../../../util/AnnotationConstants";
export default class ClassificationForm extends React.PureComponent {
constructor(props) {
super(props);
this.config = this.props.annotationClient.config.motivationConfig[
"classification"
];
this.xhrs = [];
this.getSuggestions = debounce(this.getSuggestions.bind(this), 400);
this.state = {
vocabulary: this.config.vocabularies
? this.config.vocabularies[0]
: null,
value: "", //the label of the selected classification (autocomplete)
suggestionId: null, //stores the id/uri of the selected classification (e.g. GTAA URI)
suggestions: [], //current list of suggestions shown
isLoading: false, //loading the suggestions from the server
showClassificationSelector: false, // show classification selector
};
}
/* --------------- ANNOTATION CLIENT EVENT HANDLING -------------- */
componentDidMount() {
this.props.annotationClient.events.bind(
AnnotationEvents.ON_SAVE,
this.onSave
);
}
componentWillUnmount() {
this.props.annotationClient.events.unbind(
AnnotationEvents.ON_SAVE,
this.onSave
);
//cancel all previous outgoing requests
for (let x = this.xhrs.length; x > 0; x--) {
this.xhrs[x - 1].abort();
this.xhrs.pop();
}
}
onSave = () => {
this.forceUpdate();
};
/* ------------------- CRUD / loading of classifications ------------------- */
addClassification = (e) => {
if (this.state.value != "") {
let suggestionId = this.state.suggestionId;
if (this.state.vocabulary && this.state.vocabulary == "custom") {
suggestionId = IDUtil.guid();
}
const classification = {
id: suggestionId,
label: this.state.value,
vocabulary: this.state.vocabulary,
annotationType: "classification",
};
//after setting the state save the (annotation) body
this.setState(
{
value: "",
suggestionId: suggestionId,
},
() => {
this.props.annotationClient.saveBodyElement(
classification,
false,
true,
this.props.annotation
);
}
);
}
};
setVocabulary = (event) =>
this.setState({ vocabulary: event.target.value });
getSuggestions(value, callback) {
//cancel all previous outgoing requests
for (let x = this.xhrs.length; x > 0; x--) {
this.xhrs[x - 1].abort();
this.xhrs.pop();
}
const xhr = ExternalAPI.autocomplete(
this.state.vocabulary,
value,
'',
callback
);
this.xhrs.push(xhr);
}
/* ------------------- REACT-AUTOSUGGEST FUNCTIONS ------------------- */
loadSuggestions = (value) => {
this.setState({
isLoading: true,
suggestions: [],
});
if (value.value === this.state.chosenValue) {
this.setState({
isLoading: false,
});
} else {
this.getSuggestions(value.value, (data) => {
if (!data || data.error || !Array.isArray(data)) {
this.setState({
isLoading: false,
suggestions: [],
});
console.error("Autocomplete failed");
} else {
this.setState({
isLoading: false,
suggestions: data,
});
}
});
}
};
onSuggestionsFetchRequested = (value) => this.loadSuggestions(value);
onSuggestionsClearRequested = () => this.setState({ suggestions: [] });
getSuggestionValue = (suggestion) => {
this.setState({ suggestionId: suggestion.value });
return suggestion.label.split("|")[0];
};
//TODO the rendering should be adapted for different vocabularies
renderSuggestion = (suggestion) => {
const arr = suggestion.label.split("|");
let label = arr[1];
const scopeNote = arr[2] ? "(" + arr[2] + ")" : "";
if (this.state.vocabulary == "GTAA") {
switch (arr[1]) {
case "Persoon":
label = (
<span className="label label-warning">Persoon</span>
);
break;
case "Maker":
label = <span className="label label-warning">Maker</span>;
break;
case "Geografisch":
label = (
<span className="label label-success">Locatie</span>
);
break;
case "Naam":
label = <span className="label label-info">Naam</span>;
break;
case "Onderwerp":
label = (
<span className="label label-primary">Onderwerp</span>
);
break;
case "Genre":
label = <span className="label label-default">Genre</span>;
break;
case "B&G Onderwerp":
label = (
<span className="label label-danger">
B&G Onderwerp
</span>
);
break;
default:
label = (
<span className="label label-default">Concept</span>
);
break;
}
} else if (this.state.vocabulary == "DBpedia") {
label = <span className="label label-default">Concept</span>;
} else if (this.state.vocabulary == "UNESCO") {
switch (arr[1]) {
case "Education":
label = (
<span className="label label-warning">{arr[1]}</span>
);
break;
case "Science":
label = (
<span className="label label-warning">{arr[1]}</span>
);
break;
case "Social and human sciences":
label = (
<span className="label label-success">{arr[1]}</span>
);
break;
case "Information and communication":
label = <span className="label label-info">{arr[1]}</span>;
break;
case "Politics, law and economics":
label = (
<span className="label label-primary">{arr[1]}</span>
);
break;
case "Countries and country groupings":
label = (
<span className="label label-default">{arr[1]}</span>
);
break;
default:
label = (
<span className="label label-default">{arr[1]}</span>
);
break;
}
}
return (
<span>
{arr[0]} {label} {scopeNote}
</span>
);
};
onChange(event, { newValue }) {
this.setState({
chosenValue: newValue,
value: newValue,
});
}
// after selection a suggestion, add it immediately
onSelect = (e, { suggestionValue }) => {
this.setState(
{
chosenValue: suggestionValue,
value: suggestionValue,
},
() => {
this.addClassification();
}
);
};
onSubmit = (e) => {
e.preventDefault();
this.addClassification();
};
/* ------------------- CLASSIFICATION SELECTOR ------------------- */
showClassificationSelector = () => {
this.setState({ showClassificationSelector: true });
};
hideClassificationSelector = () => {
this.setState({ showClassificationSelector: false });
};
toggleClassificationSelector = () => {
this.setState({
showClassificationSelector: !this.state.showClassificationSelector,
});
};
toggleClassificationFromSelector = (_classification) => {
// Check if there is an existing classification with the same id
let existingClassification = null;
getAnnotationsPerType([CLASSIFICATION], this.props.annotation)[
CLASSIFICATION
].some((classification) => {
if (classification.id == _classification.id) {
existingClassification = classification;
return true;
}
return false;
});
if (existingClassification) {
this.props.deleteAnnotation(existingClassification);
return;
}
// Create a fresh classification and add it to the annotation
const classification = Object.assign({}, _classification);
delete classification.annotationId;
this.props.annotationClient.saveBodyElement(
classification,
false,
true,
this.props.annotation
);
};
/* ------------------- RENDER FUNCTIONS ------------------- */
renderVocabularySelector = (
vocabularies,
activeVocabulary,
setVocabFunc
) => {
const options = vocabularies.map((v, index) => (
<option key={"__v__" + index} value={v}>
{v}
</option>
));
options.push(
<option key="__v__custom" value="custom">
Custom (no external lookup)
</option>
);
return (
<select value={activeVocabulary} onChange={setVocabFunc}>
{options}
</select>
);
};
render() {
const vocabSelector = this.renderVocabularySelector(
this.config.vocabularies,
this.state.vocabulary,
this.setVocabulary
);
const inputProps = {
placeholder: "Search",
value: this.state.value,
onChange: this.onChange.bind(this),
};
const currentClassificationsButton = (
<div
className="current-classifications-button"
title={Strings.ANNOTATION_TYPE_CLASSIFICATION_CURRENT}
onClick={this.showClassificationSelector}
></div>
);
const classificationSelector = this.state.showClassificationSelector ? (
<ClassificationSelector
activeAnnotation={this.props.annotation}
onClose={this.hideClassificationSelector}
onSelect={this.toggleClassificationFromSelector}
/>
) : null;
return (
<div className={IDUtil.cssClassName("classification-form")}>
{classificationSelector}
<form onSubmit={this.onSubmit}>
<div className="filter">
<strong>
{Strings.ANNOTATION_TYPE_CLASSIFICATION_VOCABULARY}
</strong>
{vocabSelector}
<div className="spacer" />
{currentClassificationsButton}
</div>
<Autosuggest
ref={(input) => (this.classifications = input)}
suggestions={this.state.suggestions}
onSuggestionsFetchRequested={
this.onSuggestionsFetchRequested
}
onSuggestionsClearRequested={
this.onSuggestionsClearRequested
}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={this.renderSuggestion}
inputProps={inputProps}
onSuggestionSelected={this.onSelect}
/>
</form>
</div>
);
}
}
ClassificationForm.propTypes = {
annotationClient: PropTypes.object.isRequired,
annotation: PropTypes.object.isRequired,
deleteAnnotation: PropTypes.func.isRequired,
};