getkirby / ideas

This is the backlog of ideas and feature requests from the last two years. Use our new feedback platform to post your new ideas or vote on existing ideas.
https://feedback.getkirby.com
20 stars 0 forks source link

Conditional fields: `then` option #257

Open nilshoerrmann opened 5 years ago

nilshoerrmann commented 5 years ago

Since Kirby 3.1.0, we have the option to conditionally show and hide fields using when. Would it be possible to also have a then option in the blueprints that takes field settings that are applied as soon as the when condition is met? I’m thinking of different placeholder texts based on conditions or of different textarea sizes depending on the layout needed for a condition.

Pseudo blueprint:

when:
  test:
    type: interview
  then:
    show: false
when:
  test:
    type: project
  then:
    show: true
    placeholder: hello
afbora commented 5 years ago

I think such a feature would be very useful :+1:

then:
    anyFieldProperty: value
lukasbestle commented 4 years ago

Unfortunately itβ€˜s not possible to have two or multiple different whens in the same YAML structure as the when keys would override each other. But this syntax could work:

when:
  - test:
      type: interview
    then:
      show: false
  - test:
      type: project
    then:
      show: true
      placeholder: hello
      required: true
lukasbestle commented 4 years ago

While we are at it, we could also add testOr and testNot tests. Only one of them could be used at a time though.

nilshoerrmann commented 4 years ago

Regarding negations, it would also be possible to think of it like this:

when:
  - test:
      type: interview
    then:
      show: false
    otherwise:
      placeholder: type here

The then entry could be optional for full negations.

It would also be possible to incorporate AND/OR comparisons directly:

when:
  - test:
      - type: interview
      - category: article
    comparison: or
    then:
      show: false

comparison: and could be the default (or vice versa) so that you don't have to type it everytime.

bastianallgeier commented 4 years ago

@nilshoerrmann that solves the and/or problem quite nicely!

distantnative commented 4 years ago

What the suggested syntax probably wouldn't cover yet is a (A or B) and C condition. Maybe we could make it

when:
  tests:
    -
      conditions:
        - type: interview
        - category: article
      comparison: or
      then:
        show: false
  comparison: and
  then:
    required: true

So possibly having comparison and then on both levels? Or too complicated?

afbora commented 4 years ago

As the issue gets longer, the queries get longer and more complicated πŸ˜…

How do we solve complex conditions with query and keep when simple? https://github.com/getkirby/ideas/issues/231

distantnative commented 4 years ago

The difficulty of queries is, that they can't be evaluated in the Panel (in JS/Vue) themselves. But we would need constant API requests to update the value of the query.

But yes, I see your point... this is getting very complicated and long.

nilshoerrmann commented 4 years ago

As the issue gets longer, the queries get longer and more complicated πŸ˜…

That's true. Looking at this as user, I'd prefer to have different syntaxes for different tasks. So I only have to use one of these complex syntax constructs when it's actually needed. So keeping the current simple syntax as a base.

lukasbestle commented 4 years ago

What if we support only a simple data structure for now (like Nils proposed above) that is still much more powerful than the current implementation and add query or query-like when option later when we have a good solution for that?

As queries are strings, it would be very simple to determine whether the test option is a query (that could be evaluated in the frontend if we write a parser) or if it's a list of fields that will be evaluated with an operator (which is what I would call the and/or option in Nils' example).

Jayshua commented 4 years ago

I'd like to propose a slightly different option for the expression portion of the when property that I think would simplify both the implementation and visual syntax.

I suggest starting with a full expression AST and optimizing from there. That way we know that the system is capable of expressing just about anything from the start, and can make some special syntax for the common cases.

test:
  kind: and
  terms:
    - kind: field
      field: type
      value: interview
    - kind: field
      field: category
      value: article
    - kind: or
      terms:
        - kind: field
          field: shippingMethod
          value: UPS
        - kind: field
          field: shippingMethod
          value: FedEx

This expression can be evaluated with a few lines of JavaScript:

let evalExpression = page => expression =>
  expression.kind === "and"
    ? expression.terms.every(evalExpression(page))
  : expression.kind === "or"
    ? expression.terms.some(evalExpression(page))
  : expression.kind === "not"
    ? !evalExpression(expression.term)
  : expression.kind === "field"
    ? page[expression.field] === expression.value
  : null;

This system is capable of expressing arbitrary boolean expressions of any complexity. But it is kind of hard to read. A few syntax transforms can make it easier:

  1. Expression objects with no kind field are evaluated like this: a. Having an and field denotes an and condition b. Having an or field denotes an or condition c. Having a not field denots a not condition d. Otherwise it is a field condition

Which lets us write the condition like this:

test:
  and:
    - type: interview
    - category: article
    - or:
      - shippingMethod: UPS
      - shippingMethod: FedEx
  1. Term lists that are objects (not lists) are expanded to an array of objects each containing one key/value pair from the original object

Which lets us write the condition like this:

test:
  and:
    type: interview
    category: article
    or:
      - shippingMethod: UPS
      - shippingMethod: FedEx

Note that the or condition still needs to be a list since it has two terms for the same field. Otherwise it could be written the same as the and condition, without the -s.

The expansions can be performed in a few lines of JavaScript:

// Expansion #2
const expandTerms = list =>
  Array.isArray(list)
    ? list
  : Object.entries(list).map(x => Object.fromEntries([x]));

// Expansion #1
const expandCondition = test =>
  test.kind // Base, verbose case
    ? { kind: test.kind, terms: expandTerms(test.terms).map(expandCondition) }
  : test.and // Case a
    ? { kind: "and", terms: expandTerms(test.and).map(expandCondition) }
  : test.or // Case b
    ? { kind: "or", terms: expandTerms(test.or).map(expandCondition) }
  : test.not // Case c
    ? { kind: "not", term: expandCondition(test.not) }
  // Case d
  : Object.entries(test).map(([field, value]) => ({ kind: "field", field, value }))[0];

A few tests:

// Converted directly from the YAML above
let test = {
  "and": {
    "type": "interview",
    "category": "article",
    "or": [
      { "shippingMethod": "UPS" },
      { "shippingMethod": "FedEx" }
    ]
  }
};

let page = {"type": "interview", "category": "article", "shippingMethod": "UPS"};
evalExpression(page)(expandCondition(test));
// => true

let page = {"type": "report", "category": "article", "shippingMethod": "UPS"};
evalExpression(page)(expandCondition(test));
// => false

let page = {"type": "interview", "category": "article", "shippingMethod": "FedEx"};
evalExpression(page)(expandCondition(test));
// => true

let page = {"type": "interview", "category": "article", "shippingMethod": "DHL"};
evalExpression(page)(expandCondition(test));
// => false

Pro: Simple, yet powerful enough to express arbitrary expressions

Con: Fields called "and", "or", "not" in the blueprint cannot be referenced. Using them requires falling back to the verbose syntax. Seems rather unlikely someone would be using fields with those names though.

Pro: You can fall back to the verbose syntax if necessary

Pro: The expression evaluation is small and simple, so can trivially be implemented in both PHP and JS. (Necessary if when ever supports indicating whether a field is required.)

Pro: Easily expanded to support conditions other than equality by specifying kinds like 'greaterThan' or 'regex'. Could also expand it to support reusable fragments by using kind: 'reference', reference: 'predeclaredCondition'.

Pro: Easily expanded to include other boolean operations like implies. (I've found implication to be very useful when deciding whether a field is required. (type === 'interview' || type === 'meeting') implies (category === 'report' || category === 'book') means category can be anything if type isn't interview or meeting. The equivalent expression using and/or/not is more complex.

Con: This requires a bit of logic around the handling of the when property so as to avoid breaking existing blueprints. (I don't think it would be too much though.)

Pro: This is independent of how the "then/otherwise" bit ends up working. It deals only with the conditional part of the when.

Here's what it would look like in a blueprint:

type:
  label: Type
  type: select
  options:
    - interview
    - report

category:
  label: Category
  type: select
  options:
    - article
    - editorial

shippingMethod:
  label: Shipping Method
  type: select
  options:
    - UPS
    - USPS
    - FedEx
    - DHL

conditionalField:
  label: Conditional Field
  type: text
  when:
    and: # Could default to "and", but this would be handled outside of the expression evaluation I wrote above.
      type: interview
      category: article
      or:
        - shippingMethod: UPS
        - shippingMethod: FedEx

# Still works if when/then/otherwise is implemented
anotherConditionalField:
  label: Another Conditional Field
  type: text
  when:
    test:
      and: # Again, could default to "and", but that would not be part of the expression evaluation
        type: interview
        category: article
        or:
          - shippingMethod: UPS
          - shippingMethod: FedEx

    then:
      required: true
      placeholder: "Placeholder Here"

    otherwise:
      show: false
nilshoerrmann commented 4 years ago

This should be moved to https://github.com/getkirby/kirby in my eyes.

distantnative commented 4 years ago

In https://github.com/getkirby/kirby we really would like to try limiting it to bugs - I know sometimes we put enhancements or features in there as well but especially since Nolt will also serve for creating the roadmap, this should go on Nolt still (in my view).

nilshoerrmann commented 4 years ago

Okay πŸ‘ I would leave summarizing the topic to one of you others.