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

Add conditional rendering #403

Merged
merged 15 commits into from
Nov 25, 2022
Merged

Add conditional rendering #403

merged 15 commits into from
Nov 25, 2022

Conversation

barmac
Copy link
Member

@barmac barmac commented Nov 14, 2022

This PR implements conditional rendering via a Field#conditional.hide property. Condition needs to be a FEEL unary test which is parsed and evaluated by feelin. It is defined via the properties panel. Form designer can use any variables passed to the form object to define the condition.

Closes #374
Closes #401
Closes #415


Follow-up issues:

@barmac
Copy link
Member Author

barmac commented Nov 14, 2022

@nikku FEEL free to provide your feedback :)

@barmac
Copy link
Member Author

barmac commented Nov 14, 2022

Try this out via npm run start:playground.

"type": "textfield",
"id": "Field",
"key": "text",
"condition": "=amount > 2"
Copy link
Member Author

Choose a reason for hiding this comment

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

Consider renaming.

@@ -41,6 +41,7 @@
"@bpmn-io/snarkdown": "^2.1.0",
"classnames": "^2.3.1",
"didi": "^9.0.0",
"feelin": "^0.41.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

We haven't moved it out of @nikku'S repo/org, right? Should we do it?

Copy link
Member

@nikku nikku Nov 16, 2022

Choose a reason for hiding this comment

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

Camunda can consider to fork it (i.e. publish via @bpmn-io/feelin or the like). In this case it is likely the core modeling team to take responsibility over it.

But at this time I don't see it as strictly being required.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can do that as soon as we want to implement something in the library.

}

try {
const result = unaryTest(condition.slice(1), data);
Copy link
Contributor

Choose a reason for hiding this comment

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

Someone reading this may not know that you slice the first char to remove the "="

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a comment.


// TODO(@barmac): remove once https://github.com/moment/luxon/issues/193 is resolved
function onwarn(warning, warn) {
Copy link
Member

Choose a reason for hiding this comment

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

Sure that we want to ignore this? Can the built bundle still be consumed (conditions be evaluated)?

Copy link
Member

Choose a reason for hiding this comment

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

==> Let's add an integration test for this in form-js.

Copy link
Member Author

Choose a reason for hiding this comment

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

What would you expect to test? Something like 3c06472 ?


return result;
} catch (error) {
return false;
Copy link
Member

Choose a reason for hiding this comment

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

Consider not swallowing the error, but logging it (or emitting it via eventBus.fire(error, ...).

Copy link
Member Author

Choose a reason for hiding this comment

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

Implemented via 848b703

}

try {
const result = unaryTest(condition.slice(1), data);
Copy link
Member

Choose a reason for hiding this comment

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

I guess at this point we don't want to enable context sensitive parsing, right? Our inputs don't contain spaces and other special chars.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure what you mean. I need to pass data in order to correctly evaluate the condition, right?

import { unaryTest } from 'feelin';
import { isString } from 'min-dash';

export class ConditionChecker {
Copy link
Member

Choose a reason for hiding this comment

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

Awesome that this is pluggable. ❤️


/**
*
* @param {{ expression?: string } | undefined} condition
Copy link
Member

Choose a reason for hiding this comment

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

What is the result of this hook?

Return type is missing; unclear if it returns "true" to show or to hide.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added documentation.

Copy link
Member

@nikku nikku left a comment

Choose a reason for hiding this comment

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

Great addition (service + hook). Just found some nit-picks you may act upon.

@barmac
Copy link
Member Author

barmac commented Nov 16, 2022

Thanks, I will address the comments tomorrow :)

Comment on lines 65 to 81
function useVariables() {
const form = useService('formEditor');
const schema = form.getSchema();

const variablesRef = useRef([]);

const variables = getSchemaVariables(schema);
const previousVariables = usePrevious(variables) || [];

const variablesChanged = !areEqual(variables, previousVariables);

if (variablesChanged) {
variablesRef.current = variables.map(key => ({ name: key }));
}

return variablesRef.current;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we move this under /hooks? I can imagine we will need this more often in the future (similar to useCondition in the viewer)

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm I think I could simplify this a bit...

Copy link
Member Author

Choose a reason for hiding this comment

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

OK it's now simplified and exported from hooks.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we have to be careful here. The useService hook that is used in the new useVariables hook now needs a FormEditorContext to work correctly. Note that the properties panel can also be rendered outside of the editor. We use our own useService therefore: https://github.com/bpmn-io/form-js/blob/develop/packages/form-js-editor/src/features/properties-panel/hooks/usePropertiesPanelService.js

We either need the formEditor to be provided as a parameter for this hook or, alternatively, move the useVariables to the properties panels hooks.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah this is probably why the tests started to fail after the change.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK it should be fine now.

*/
check(condition, data = {}) {
if (!condition) {
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we allow to specify this defaultValue? I could imagine other cases where we do not default a condition to true, for example: readonly => default false.

Copy link
Member Author

Choose a reason for hiding this comment

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

This sounds to me more like a form field concern: it knows whether it should display or not.

Copy link
Member Author

Choose a reason for hiding this comment

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

Condition checker can be applied to anything, not only fields.

@barmac
Copy link
Member Author

barmac commented Nov 17, 2022

Based on https://github.com/camunda/product-hub/issues/56#issuecomment-1315173662, I will need to reverse the logic so that we use a "hideIf" field. This will also require changes in the properties panel. Let's do that in a separate issue.

Copy link
Contributor

@Skaiir Skaiir left a comment

Choose a reason for hiding this comment

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

Everything looks quite good, just got a few cleanup suggestions :).

@@ -170,23 +170,24 @@ export default class Form {
const data = this._getSubmitData();

const errors = this.validate();
const filteredErrors = this._applyConditions(errors, data);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should filter this upstream. It seems strange to me that we are still validating the fields which are hidden via condition, as I believe the form should just treat them as non-existent.


return {
...data,
[ _path[ 0 ] ]: value
};
}, {});

const filteredValues = this._applyConditions(rawValues, { ...initialData, ...rawValues });
Copy link
Contributor

@Skaiir Skaiir Nov 16, 2022

Choose a reason for hiding this comment

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

Again here, we're doing all the heavy lifting and filtering after the fact. I believe we should start filtering at the formField level. The way I would see it would be filterSomeHow(formFieldRegistry.getAll()). Or maybe even formFieldRegistry.getVisible().

});


it('should use give precedence to form data to check condition', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

lil typo

@Skaiir
Copy link
Contributor

Skaiir commented Nov 18, 2022

@barmac I'm not sure we should do the reversing in a separate issue. If I understand correctly you'd want to commit the current state once cleaned up to the develop and then in another commit invert the behavior right?

The problem with that is that we should really avoid changes to the form schema, especially if we know they are going to be temporary and if they break backwards compatibility.

I understand we don't want to be blocking this feature, but since this would break the backward compatibility of the schema, we'll still be blocked in practice from releasing until the inversion has been committed to develop.

@barmac
Copy link
Member Author

barmac commented Nov 18, 2022

Thanks for your feedback. It feels like we should have a conversation about this before the PR is merged. I'll set this up.

@christian-konrad
Copy link
Contributor

@barmac can't run it in the playground:

Bildschirmaufnahme.2022-11-23.um.17.56.42.mov

@barmac barmac marked this pull request as ready for review November 24, 2022 16:05
@bpmn-io-tasks bpmn-io-tasks bot added needs review Review pending and removed in progress Currently worked on labels Nov 24, 2022
@barmac
Copy link
Member Author

barmac commented Nov 24, 2022

This is now ready for review. It implements the "hide if" logic and includes getSchemaVariables implementation for hide conditions.

Copy link
Contributor

@pinussilvestrus pinussilvestrus left a comment

Choose a reason for hiding this comment

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

Looks good to me! Just one comment, otherwise, let's merge and bring this out of the door 👍


const variablesList = useVariables();

// keep stable reference until https://github.com/bpmn-io/properties-panel/issues/196 is fixed
Copy link
Contributor

Choose a reason for hiding this comment

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

That might be resolved now via 3a51c37

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed! Let's check this in action.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK done

Copy link
Contributor

@pinussilvestrus pinussilvestrus left a comment

Choose a reason for hiding this comment

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

great work!🥇

@barmac barmac merged commit 7890fc8 into develop Nov 25, 2022
@barmac barmac deleted the 374-conditional-rendering branch November 25, 2022 14:01
@bpmn-io-tasks bpmn-io-tasks bot removed the needs review Review pending label Nov 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants