Skip to content

Commit

Permalink
fix(use-values): update values when evaluated values changed
Browse files Browse the repository at this point in the history
Closes #809
  • Loading branch information
Niklas Kiefer committed Sep 22, 2023
1 parent 5ea8290 commit 1abc02d
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export default function DropdownList(props) {
useEffect(() => {
const individualEntries = dropdownContainer.current.children;
if (individualEntries.length && !mouseControl) {
individualEntries[focusedValueIndex].scrollIntoView({ block: 'nearest', inline: 'nearest' });
const focusedEntry = individualEntries[focusedValueIndex];
focusedEntry && focusedEntry.scrollIntoView({ block: 'nearest', inline: 'nearest' });
}
}, [ focusedValueIndex, mouseControl ]);

Expand Down
13 changes: 13 additions & 0 deletions packages/form-js-viewer/src/render/hooks/usePrevious.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
useEffect,
useRef
} from 'preact/hooks';


export default function usePrevious(value) {
const ref = useRef();

useEffect(() => ref.current = value);

return ref.current;
}
30 changes: 20 additions & 10 deletions packages/form-js-viewer/src/render/hooks/useValuesAsync.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useEffect, useMemo, useState } from 'preact/hooks';
import { useEffect, useState } from 'preact/hooks';
import { normalizeValuesData } from '../components/util/valuesUtil';
import useExpressionEvaluation from './useExpressionEvaluation';
import useService from './useService';
import usePrevious from './usePrevious';

/**
* @enum { String }
Expand Down Expand Up @@ -34,11 +35,11 @@ export default function(field) {
const [ valuesGetter, setValuesGetter ] = useState({ values: [], error: undefined, state: LOAD_STATES.LOADING });
const initialData = useService('form')._getState().initialData;

const evaluatedValues = useMemo(() => {
if (valuesExpression) {
return useExpressionEvaluation(valuesExpression);
}
}, [ valuesExpression ]);
const evaluatedValues = valuesExpression && useExpressionEvaluation(valuesExpression);

// re-calculate values if evaluated expression changes
const previousEvaluatedValues = usePrevious(evaluatedValues);
const evaluatedValuesChanged = !compare(previousEvaluatedValues, evaluatedValues);

useEffect(() => {

Expand All @@ -56,9 +57,12 @@ export default function(field) {
} else if (staticValues !== undefined) {
values = Array.isArray(staticValues) ? staticValues : [];

// expression
} else if (evaluatedValues && Array.isArray(evaluatedValues)) {
values = evaluatedValues;
// expression
} else if (valuesExpression) {

if (evaluatedValues && Array.isArray(evaluatedValues)) {
values = evaluatedValues;
}
} else {
setValuesGetter(buildErrorState('No values source defined in the form definition'));
return;
Expand All @@ -69,11 +73,17 @@ export default function(field) {

setValuesGetter(buildLoadedState(values));

}, [ valuesKey, staticValues, initialData ]);
}, [ valuesKey, staticValues, initialData, valuesExpression, evaluatedValuesChanged ]);

return valuesGetter;
}

const buildErrorState = (error) => ({ values: [], error, state: LOAD_STATES.ERROR });

const buildLoadedState = (values) => ({ values, error: undefined, state: LOAD_STATES.LOADED });

// helper //////////////////////

function compare(a, b) {
return JSON.stringify(a) === JSON.stringify(b);
}
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,93 @@ describe('Select', function() {
});


it('should render options from values expression', function() {

// given
const onChangeSpy = spy();

const options = [
...expressionFieldInitialData.list1,
...expressionFieldInitialData.list2
];

const { container } = createSelect({
onChange: onChangeSpy,
value: 'value2',
isExpression: () => true,
evaluateExpression: () => options,
field: expressionField,
initialData: expressionFieldInitialData
});

const select = container.querySelector('.fjs-input-group');

// when
fireEvent.focus(select);

// then
expect(getSelectValues(container)).to.eql([
'Value 1',
'Value 2',
'Value 3',
'Value 4'
]);
});


it('should update options when evaluation changed', function() {

// given
const onChangeSpy = spy();

const options = [
...expressionFieldInitialData.list1,
...expressionFieldInitialData.list2
];

let result = createSelect({
onChange: onChangeSpy,
value: 'value2',
isExpression: () => true,
evaluateExpression: () => options,
field: expressionField,
initialData: expressionFieldInitialData
});

const select = result.container.querySelector('.fjs-input-group');

// when
fireEvent.focus(select);

// assume
expect(getSelectValues(result.container)).to.eql([
'Value 1',
'Value 2',
'Value 3',
'Value 4'
]);

// and when
options.push({ label: 'Value 5', value: 'value5' });

createSelect({
field: expressionField,
isExpression: () => true,
evaluateExpression: () => options
}, result.rerender);

// then
expect(getSelectValues(result.container)).to.eql([
'Value 1',
'Value 2',
'Value 3',
'Value 4',
'Value 5'
]);

});


it('should clear', function() {

// given
Expand Down Expand Up @@ -1010,7 +1097,7 @@ const expressionFieldInitialData = {
]
};

function createSelect(options = {}) {
function createSelect(options = {}, renderFn = render) {
const {
disabled,
readonly,
Expand All @@ -1022,7 +1109,7 @@ function createSelect(options = {}) {
value
} = options;

return render(WithFormContext(
return renderFn(WithFormContext(
<Select
disabled={ disabled }
readonly={ readonly }
Expand All @@ -1036,4 +1123,12 @@ function createSelect(options = {}) {
), {
container: options.container || container.querySelector('.fjs-form')
});
}

function getSelectValues(container) {
const listItems = container.querySelectorAll('.fjs-dropdownlist-item');

return Array.from(listItems).map(listItem => {
return listItem.innerText;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ describe('Taglist', function() {

// then
const tags = container.querySelectorAll('.fjs-taglist-tag');
expect(tags).to.have.length(2);
expect(getTagValues(container)).to.eql([ 'Value 1', 'Value 3' ]);

const tag = tags[0];
const tagLabelArea = tag.querySelector('.fjs-taglist-tag-label');
Expand All @@ -161,6 +161,43 @@ describe('Taglist', function() {
});


it('should update tags via valuesExpression - evaluation changed', function() {

// given
const result = createTaglist({
value: [ 'value1', 'value3' ],
onchange: () => { },
isExpression: () => true,
evaluateExpression: () => [
...expressionFieldInitialData.list1,
...expressionFieldInitialData.list2
],
field: expressionField,
initialData: expressionFieldInitialData
});

// assume
expect(getTagValues(result.container)).to.eql([ 'Value 1', 'Value 3' ]);

// when
createTaglist({
value: [ 'value1', 'value5' ],
onchange: () => { },
isExpression: () => true,
evaluateExpression: () => [
...expressionFieldInitialData.list1,
...expressionFieldInitialData.list2,
...[ { label: 'Value 5', value: 'value5' } ]
],
field: expressionField,
initialData: expressionFieldInitialData
}, result.rerender);

// then
expect(getTagValues(result.container)).to.eql([ 'Value 1', 'Value 5' ]);
});


it('should render dropdown when filter focused', function() {

// when
Expand Down Expand Up @@ -937,7 +974,7 @@ const expressionFieldInitialData = {
]
};

function createTaglist(options = {}) {
function createTaglist(options = {}, renderFn = render) {
const {
disabled,
readonly,
Expand All @@ -948,7 +985,7 @@ function createTaglist(options = {}) {
value
} = options;

return render(WithFormContext(
return renderFn(WithFormContext(
<Taglist
disabled={ disabled }
readonly={ readonly }
Expand All @@ -961,4 +998,10 @@ function createTaglist(options = {}) {
), {
container: options.container || formContainer.querySelector('.fjs-form')
});
}

function getTagValues(container) {
const tags = container.querySelectorAll('.fjs-taglist-tag');

return Array.from(tags).map(tag => tag.textContent);
}

0 comments on commit 1abc02d

Please sign in to comment.