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

Question: Is it possible to use a custom function for conditions within the schema in JSON format? #1418

Closed
bytecast-de opened this issue Oct 26, 2023 · 15 comments · Fixed by #1424
Assignees
Labels

Comments

@bytecast-de
Copy link

Scope: react-form-renderer / conditions

Description

I am looking for a way to define a condition via custom function (https://www.data-driven-forms.org/schema/is#customfunction) BUT i cannot use a javascript object, the schema has to be in JSON format since it will be retrieved from the backend.

I've looked into the ActionMapper (https://www.data-driven-forms.org/mappers/action-mapper), but as far as I understand, the mapped functions can only be applied to a field property. In my case I would need to assign a mapped function inside a (possibly nested) condition - some sort of "ConditionMapper" if that makes any sense. See example below.

My question is: Can this somehow be done with the current implementation, or would this be a feature request?

Schema

This is the javascript version, works as documented:

const schema = {
  title: 'Is condition',
  fields: [
    {
      component: componentTypes.TEXT_FIELD,
      name: 'field-1',
      label: 'Field 1'
    },
    {
      component: componentTypes.TEXT_FIELD,
      name: 'field-2',
      label: 'Field 2',
      condition: { when: 'field-1', is: (value, config) => someCustomLogic(value, config) },
    },
  ],
};

This would be the corresponding JSON-Version, I have no idea if/how this can be done at the moment:

const schemaJSON = {
  "title": "Is condition",
  "fields": [
    {
      "component": "text-field",
      "name": "field-1",
      "label": "Field 1"
    },
    {
      "component": "text-field",
      "name": "field-2",
      "label": "Field 2"
      "condition": { "when": "field-1", is: ["??? somehow mapped function ???"] },
    },
  ],
};
@Hyperkid123
Copy link
Member

Hi @bytecast-de this is not possible at the moment.

I think we can come up with something similar to the action mapper though. I'll have to think about some reasonable format. Do you have some syntax in mind?

@bytecast-de
Copy link
Author

Hi @Hyperkid123,

thank you for your quick response.

Based on the examples from the docs, there are some variations that come to mind from "user" perspective:

// value and config are always passed as arguments, "age" is the first "variable arg" from config 
const olderThan = (value, config, age): boolean => {
  return calculateAge(value) > age;
}

// this will be passed to the FormRenderer
const conditionMapper = {
  "olderThan": olderThan,
}

// variant 1: using "is" with an array (function name + variable arguments), similar to ActionMapper
const schemaJSON = {
  "title": "Is condition",
  "fields": [
      {
        "component": "text-field",
        "name": "field-2",
        "label": "Field 2",
        "condition": { "when": "field-1", "is": ["olderThan", 18] },
      },
  ],
};

// variant 2: using a new condition type with same ActionMapper-like array as in variant 1
const schemaJSON = {
  ...
      "condition": { "when": "field-1", "isFunction": ["olderThan", 18] },
  ...
};

// variant 3: using "is" (or a new condition type), but with a config object instead of an array
const schemaJSON = {
  ...
    "condition": { "when": "field-1", "is": {"fn": "olderThan", "params": [18]} },
  ...
};

Variant 1 possibly won't work, as far as I've seen in the source, you can currently pass an array to "is", that behaves like "oneOf" - which is an undocumented feature, btw, or i missed it in the docs ;-) It maybe would be hard to make a distinction between the new feature and the old behaviour with arrays.

Variant 2 is probably more work - i don't know how many other parts must be changed, to add another condition type.

Variant 3 is in my opinion the most self-explaining and could possibly be used within the "is"-condition without the need to add another condition.

Please let me know what you think about it, and if I can be of any help with this feature.

@Hyperkid123
Copy link
Member

@bytecast-de thanks for the suggestions.

I was thinking about something similar, bit there is an issue. All of the examples of is implementation are valid values that can appear in a form state. This is not an issue within the action mapper as we map the entire prop to an action.

I think we will have to add some flag to the condition object that states that a particular condition is supposed to be mapped to a function

"condition": { "when": "field-1", "is": {"fn": "olderThan", "params": [18]}, mappedAttributes: { is: true } },

We can have an attribute like mappedAttributes that would specify which condition attribute is supposed to be mapped to a function at runtime. There can be many attributes in that can be mapped, not just the is. This mappedAttributes syntax is in a way similar to the subscription configuration.

@rvsia opinion?

@rvsia
Copy link
Contributor

rvsia commented Oct 27, 2023

@Hyperkid123

  1. regarding the format of mapping, we should stay consistent with usage of action mappers so I prefer this variant: ["olderThan", 18]

  2. regarding mappedAttributes

Following attributes could be mapped to some function, nothing else makes sense in the condition context:

  1. when
  2. is
  3. set

And we could use mappedAttributes (or similar name) to map them as we do with the actions:

mappedAttributes: { is: ['olderThan', 18] }

@Hyperkid123
Copy link
Member

@rvsia agreed.

I hope I'll have some time later this week to implement the condition mapper. Feel free to pick the issue if you want.

@bytecast-de
Copy link
Author

@Hyperkid123 @rvsia thanks for your feedback, sounds great to me - really looking forward to this feature.

I'd like to add that this is an awesome project, much appreciated!

@Hyperkid123 Hyperkid123 self-assigned this Nov 7, 2023
@Hyperkid123
Copy link
Member

OK I got some time on my hands. I'll work on the implementation

@Hyperkid123
Copy link
Member

@rvsia if we were to follow the current option for condition function config, it does not make sens to send anything other than the mapped attribute name. We do not pass any custom argument function conditions. Its always just a value and the condition config,

@rvsia
Copy link
Contributor

rvsia commented Nov 8, 2023

@rvsia

Would it make sense to change it to higher-order-function to allow customization from the schema? 🤔

conditionMapper: {
   ageValidation: (age = 18) => (value, config) => calculateAge(value, config) > (age)
}

// alcohol related page
schema = {
   name: 'age',
   component: 'date-picker',
   condition: {
              mappedAttributes: {
                   is: ['ageValidation', 18]
              },
   }
}

// senior sale related page :D 
schema = {
   name: 'age',
   component: 'date-picker',
   condition: {
              mappedAttributes: {
                   is: ['ageValidation', 65]
              },
   }
}

@Hyperkid123
Copy link
Member

Hyperkid123 commented Nov 8, 2023

We can do that or append the arguments after the regular arguments. HOF will follow the pattern set within the validation mapper.

@rvsia
Copy link
Contributor

rvsia commented Nov 8, 2023

@Hyperkid123 I would not append the arguments after regular - that could limit extensibility in the future.

@Hyperkid123
Copy link
Member

@rvsia I've added the HOC. Right now the mapped conditions will work only for the is and when attributes. If we ever required the set, it can be added later on.

@rvsia
Copy link
Contributor

rvsia commented Nov 10, 2023

@rvsia rvsia added the released label Nov 10, 2023
@Hyperkid123
Copy link
Member

@bytecast-de You can check the docs here: https://www.data-driven-forms.org/mappers/condition-mapper

@bytecast-de
Copy link
Author

@Hyperkid123 great job, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants