Skip to content

Commit

Permalink
Merge pull request #241 from kbss-cvut/add-tree
Browse files Browse the repository at this point in the history
Add tree to typeahead
  • Loading branch information
blcham authored Nov 29, 2023
2 parents c22f5eb + d456341 commit ae53faa
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 18 deletions.
8 changes: 4 additions & 4 deletions src/components/answer/DateTimeAnswer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ const DateTimeAnswer = (props) => {

// workaround because it is not possible to construct Date only with time
let value;
if (isTime && props.value) {
if (isTime && props.value && props.value !== "0") {
value = new Date(`0 ${props.value}`);
} else {
value = props.value ? new Date(props.value) : null;
}
} else if (isDate && props.value && props.value !== "0") {
value = new Date(props.value);
} else value = new Date();

// DatePicker does not know dateFormat "x", translate to datetime
const datePickerFormat =
Expand Down
84 changes: 73 additions & 11 deletions src/components/answer/TypeaheadAnswer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const TypeaheadAnswer = (props) => {
const intl = configurationContext.options.intl;

const [isLoading, setLoading] = useState(true);
const [options, setOptions] = useState(
const [optionsList, setOptionsList] = useState(
processTypeaheadOptions(props.options, intl)
);

Expand All @@ -57,7 +57,7 @@ const TypeaheadAnswer = (props) => {
);
if (!isCancelled) {
setLoading(false);
setOptions(processTypeaheadOptions(options, intl));
setOptionsList(processTypeaheadOptions(options, intl));
}
} catch (error) {
Logger.error(
Expand All @@ -73,37 +73,99 @@ const TypeaheadAnswer = (props) => {
loadFormOptions();
} else {
setLoading(false);
setOptions(processTypeaheadOptions(question[Constants.HAS_OPTION], intl));
setOptionsList(
processTypeaheadOptions(question[Constants.HAS_OPTION], intl)
);
}

return () => {
isCancelled = true;
};
}, []);

const checkNonSelectableOptions = (tree) => {
const question = props.question;

for (let o of Object.values(tree)) {
if (
JsonLdUtils.hasValue(
question,
Constants.HAS_NON_SELECTABLE_VALUE,
o.value
)
) {
o.isDisabled = true;
}
}
};

const generateOptions = () => {
const question = props.question;
const possibleValues = question[Constants.HAS_OPTION];
if (!possibleValues || !possibleValues.length) {
return [];
}

const options = {};
const relations = [];

for (let pValue of possibleValues) {
let label = JsonLdUtils.getLocalized(
pValue[Constants.RDFS_LABEL],
configurationContext.options.intl
);

options[pValue["@id"]] = {
value: pValue["@id"],
label: label,
children: [],
disjoint: [],
};
for (let parent of Utils.asArray(pValue[Constants.BROADER])) {
relations.push({
type: "parent-child",
parent: parent["@id"],
child: pValue["@id"],
});
}
}

for (let relation of relations) {
if (relation.type === "parent-child") {
options[relation.parent]?.children.push(relation.child);
}
}

checkNonSelectableOptions(options);
setOptionsList(Object.values(options));
};

useEffect(() => {
setOptions(
processTypeaheadOptions(props.question[Constants.HAS_OPTION], intl)
);
}, [intl]);
generateOptions();
}, []);

const handleOptionSelectedChange = (option) => {
props.onChange(option ? option.id : null);
};

const valueKey = Utils.findKeyInObjects(optionsList, ["name", "value"]);
const labelKey = Utils.findKeyInObjects(optionsList, ["name", "label"]);

return (
<FormGroup size="small">
<Form.Label>{props.label}</Form.Label>
<IntelligentTreeSelect
valueKey="name"
labelKey="name"
valueKey={valueKey}
labelKey={labelKey}
valueIsControlled={false}
value={options.filter((option) => option.id === props.value)}
value={optionsList.filter((option) => option.id === props.value)}
multi={false}
options={options}
options={optionsList}
isSearchable={true}
isLoading={isLoading}
isClearable={true}
renderAsTree={true}
optionLeftOffset={5}
isDisabled={
isLoading ||
configurationContext.componentsOptions.readOnly ||
Expand Down
3 changes: 3 additions & 0 deletions src/constants/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ export default class Constants {
static RDFS_LABEL = JsonLdUtils.RDFS_LABEL;
static RDFS_COMMENT = JsonLdUtils.RDFS_COMMENT;
static DEFAULT_HAS_CHILD = JsonLdUtils.DEFAULT_HAS_CHILD;
static BROADER = "http://www.w3.org/2004/02/skos/core#broader";
static HAS_NON_SELECTABLE_VALUE =
"http://onto.fel.cvut.cz/ontologies/form/has-non-selectable-value";

static ICONS = {
QUESTION_COMMENTS: "questionComments",
Expand Down
14 changes: 11 additions & 3 deletions src/stories/Answer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import Answer from "../components/Answer";
import { Meta, StoryObj } from "@storybook/react";

import question from "./assets/question/questionWithMedia.json";
import questionTypeHead from "./assets/question/questionTypeHead.json";
import questionTypeahead from "./assets/question/questionTypeHead.json";
import questionTypeaheadWithTree from "./assets/question/simple-single-tree-select.json";
import questionDate from "./assets/question/questionDate.json";
import questionCheckBox from "./assets/question/questionCheckBox.json";
import questionMaskedInput from "./assets/question/questionMaskedInput.json";
Expand Down Expand Up @@ -48,10 +49,17 @@ export const RegularInput: Story = {
},
};

export const TypeHead: Story = {
export const Typeahead: Story = {
args: {
...Template.args,
question: questionTypeHead,
question: questionTypeahead,
},
};

export const TypeaheadWithTree: Story = {
args: {
...Template.args,
question: questionTypeaheadWithTree,
},
};

Expand Down
25 changes: 25 additions & 0 deletions src/stories/TypeaheadAnswer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import TypeaheadAnswer from "../components/answer/TypeaheadAnswer";
import { ComponentMeta, ComponentStory } from "@storybook/react";

import questionExpanded from "./assets/question/questionExpanded.json";
import possibleValues from "./assets/possibleValues.json";
import tree from "./assets/question/simple-single-tree-select.json";

export default {
title: "Components/TypeaheadAnswer",
component: TypeaheadAnswer,
} as ComponentMeta<typeof TypeaheadAnswer>;

const Template: ComponentStory<typeof TypeaheadAnswer> = () => {
return (
<TypeaheadAnswer
question={tree}
answer={tree}
label={""}
onChange={() => void undefined}
/>
);
};

export const TypeaheadAnswerTree = Template.bind({});
85 changes: 85 additions & 0 deletions src/stories/assets/question/simple-single-tree-select.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"@id": "ma-vlastnika-section-777",
"@type": "http://onto.fel.cvut.cz/ontologies/documentation/question",
"http://onto.fel.cvut.cz/ontologies/form-layout/has-layout-class": "type-ahead",
"http://onto.fel.cvut.cz/ontologies/form/has-non-selectable-value": "fyzicka-osoba",
"http://onto.fel.cvut.cz/ontologies/form/has-possible-value": [
{
"@id": "fyzicka-osoba",
"http://www.w3.org/2000/01/rdf-schema#label": "Fyzická osoba",
"http://www.w3.org/2002/07/owl#disjointWith": {
"@id": "pravnicka-osoba"
}
},
{
"@id": "pravnicka-osoba",
"http://www.w3.org/2000/01/rdf-schema#label": "Právnická osoba"
},
{
"@id": "fyzicka-osoba--nezletila",
"http://www.w3.org/2000/01/rdf-schema#label": "Fyzická osoba nezletilá",
"http://www.w3.org/2002/07/owl#disjointWith": {
"@id": "pravnicka-osoba"
},
"http://www.w3.org/2004/02/skos/core#broader": {
"@id": "fyzicka-osoba"
}
},
{
"@id": "fyzicka-osoba--chytra",
"http://www.w3.org/2000/01/rdf-schema#label": "Fyzická osoba chytrá",
"http://www.w3.org/2002/07/owl#disjointWith": {
"@id": "pravnicka-osoba"
},
"http://www.w3.org/2004/02/skos/core#broader": {
"@id": "fyzicka-osoba"
}
},
{
"@id": "fyzicka-osoba--hloupa",
"http://www.w3.org/2000/01/rdf-schema#label": "Fyzická osoba hloupá",
"http://www.w3.org/2002/07/owl#disjointWith": [
{
"@id": "pravnicka-osoba"
},
{
"@id": "fyzicka-osoba--chytra"
}
],
"http://www.w3.org/2004/02/skos/core#broader": {
"@id": "fyzicka-osoba"
}
},
{
"@id": "podtrida-fyzicka-osoby-s-velice-dlouhym-nazvem",
"http://www.w3.org/2000/01/rdf-schema#label": "Podtřída fyzické osoby s velice dlouhým názvem",
"http://www.w3.org/2002/07/owl#disjointWith": {
"@id": "pravnicka-osoba"
},
"http://www.w3.org/2004/02/skos/core#broader": {
"@id": "fyzicka-osoba"
}
},
{
"@id": "jeste-specifickejsi-podtrida-fyzicka-osoby-s-velice-dlouhym-nazvem",
"http://www.w3.org/2000/01/rdf-schema#label": "Ještě specifickejší podtřída fyzické osoby s velice dlouhým názvem",
"http://www.w3.org/2002/07/owl#disjointWith": {
"@id": "pravnicka-osoba"
},
"http://www.w3.org/2004/02/skos/core#broader": {
"@id": "podtrida-fyzicka-osoby-s-velice-dlouhym-nazvem"
}
}
],
"http://onto.fel.cvut.cz/ontologies/form/has-question-origin": {
"@id": "ma-vlastnika-section-777-qo"
},
"http://www.w3.org/2000/01/rdf-schema#label": "Má vlastníka",
"http://onto.fel.cvut.cz/ontologies/documentation/has_answer": [
{
"http://onto.fel.cvut.cz/ontologies/documentation/has_data_value": ""
}
],
"http://onto.fel.cvut.cz/ontologies/documentation/has_related_question": [],
"http://onto.fel.cvut.cz/ontologies/form/has-comment": []
}
18 changes: 18 additions & 0 deletions src/util/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,22 @@ export default class Utils {

return null;
}

/**
* Finds the first key in an array of objects that matches any of the specified keys.
*
* @param {Object[]} objectList - The array of objects to search through.
* @param {string[]} keysToCheck - An array of keys to check in each object.
* @returns {?string} - The first key that exists in any of the objects, or null if none are found.
*/
static findKeyInObjects(objectList, keysToCheck) {
for (let i = 0; i < objectList.length; i++) {
for (let j = 0; j < keysToCheck.length; j++) {
if (objectList[i].hasOwnProperty(keysToCheck[j])) {
return keysToCheck[j];
}
}
}
return null;
}
}

0 comments on commit ae53faa

Please sign in to comment.