matepaiva / wp-graphql-crb

Wordpress wrapper to expose Carbon Fields to WpGraphQL queries.
23 stars 5 forks source link

discussion: Conditional Fields #14

Open matepaiva opened 3 years ago

matepaiva commented 3 years ago

I was thinking in a good semantic way of matching Conditional Fields with GraphQL. Something like that:

{
  institutionalContents {
    nodes {
      title
      excerpt
      action { # this name comes from set_conditional_logic.field 
        ... on NewsletterSubscribeAction { # this name comes from set_conditional_logic.value + set_conditional_logic.field
          textFieldPlaceholder
          textFieldLabel
          buttonText
        }
        ... on LinkAction {
          href
          label
        }
      }
    }
  }
}

The idea is using Inline Fragments, grouping fields with the same set_conditional_logic within the same Carbon Container. I don't know if it is a good idea so I want to know your opinion. In the case above, the carbon fields would be something like that:

      Field::make('select', 'action', 'Ação')->set_options([
        'link' => 'Link',
        'newsletter_subscribe' => 'Newsletter Subscribe',
      ]),

      // Link
      Field::make('text', 'href', 'href')->set_conditional_logic([['field' => 'action', 'value' => 'link']]),
      Field::make('text', 'label', 'label')->set_conditional_logic([['field' => 'action', 'value' => 'link']]),

      // Newsletter Subscribe
      Field::make('text', 'text_field_label', 'text_field_label')->set_conditional_logic([['field' => 'action', 'value' => 'newsletter_subscribe']]),
      Field::make('text', 'text_field_placeholder', 'text_field_placeholder')->set_conditional_logic([['field' => 'action', 'value' => 'newsletter_subscribe']]),
      Field::make('text', 'button_text', 'button_text')->set_conditional_logic([['field' => 'action', 'value' => 'newsletter_subscribe']]),

Please have a look @Mooxdesign @ojohnny @moxxuk @benada002 :)

benada002 commented 3 years ago

I'd find such an interface a great addition. But I'd also add the value of set_conditional_logic to the interface. Like:

{
  institutionalContents {
    nodes {
      title
      excerpt
      action { # this name comes from set_conditional_logic.field
        conditionalLogicValue # this comes from set_conditional_logic.value
        ... on NewsletterSubscribeAction { # this name comes from set_conditional_logic.value + set_conditional_logic.field
          textFieldPlaceholder
          textFieldLabel
          buttonText
        }
        ... on LinkAction {
          href
          label
        }
      }
    }
  }
}
matepaiva commented 3 years ago

@benada002, the conditionalLogicValue would be more or less the same as the __typename of the Inline Fragment, wouldn't it?

benada002 commented 3 years ago

Yes, you're completely right. But I think in case your logic is based on a boolean value it would be kind of nice to have it. Or how are planning on handling a boolean value?

benada002 commented 3 years ago

Or if you want to show the value somewhere (I don't know how likely this situation is) but in this case you'd have to clean __typename to use it since the field name is also included in the __typename, right?

matepaiva commented 3 years ago

Oh, I see. Because it's actually overriding the action field (in the example). Maybe we could avoid overriding it nesting all conditional fields in conditionalFields?

Other thing that I am not sure is how to handle when set_conditional_logic has other properties, for example:

Field::make( 'text', 'crb_facebook', 'Facebook URL' )
    ->set_conditional_logic( array(
        'relation' => 'AND', // Optional, defaults to "AND"
        array(
            'field' => 'crb_show_socials',
            'value' => 'yes', // Optional, defaults to "". Should be an array if "IN" or "NOT IN" operators are used.
            'compare' => '=', // Optional, defaults to "=". Available operators: =, <, >, <=, >=, IN, NOT IN
        )
    ) ),
benada002 commented 3 years ago

If you're using an interface (https://graphql.org/learn/schema/#interfaces and https://www.wpgraphql.com/functions/register_graphql_interface_type/) I don't think you need nesting. I also belive a reason field is a better choice, because then you would also get the right type?!

Is your second concern more about the __typename length if you join it together, and it would have multiple rules (like for the example in your comment: CrbFacebookAndCrbShowSocialsYesAnotherFieldNameAnotherValue)?

matepaiva commented 3 years ago

Sorry, I have to step back a bit because I think my initial concept is wrong. To expose the GraphQL query below, I should not use conditional fields. I should use complex fields with multiple groups.

{
  institutionalContents {
    nodes {
      action {
        type: __typename
        ... on InstitutionalContentActionLink {
          href
          label
          style
        }
        ... on InstitutionalContentActionNewsletterSubscribe {
          label
          style
        }
      }
    }
  }
}

So the query above would be something like this in carbon fields:

Container::make('post_meta', 'Configurar ação')
    ->where('post_type', '=', $post_type)
    ->add_fields([
      Field::make('complex', 'action', 'Ação')
        ->set_required(true)
        ->set_max(1)
        ->add_fields('link', 'Link', [
          Field::make('text', 'href', 'href')->set_attribute('placeholder', '/assine-agora ou https://...'),
          Field::make('text', 'label', 'Texto')->set_attribute('placeholder', 'ex.: Assine agora'),
          Field::make('select', 'style', 'Estilo')
            ->set_options([
              'primary' => 'Primário',
              'secondary' => 'Secundário',
              'tertiary' => 'Terciário',
            ])
            ->set_default_value('primary')
        ])
        ->add_fields('newsletter_subscribe', 'Assine à newsletter', [
          Field::make('text', 'label', 'Texto')->set_default_value('Assine agora'),
          Field::make('select', 'style', 'Estilo')
            ->set_options([
              'primary' => 'Primário',
              'secondary' => 'Secundário',
              'tertiary' => 'Terciário',
            ])
            ->set_default_value('primary')
        ])
    ]);
});

That said, I don't think we actually need to create anything special for set_conditional_logic. My bad for introducing the idea. It's not necessary to expose the set_conditional_logic because they have nothing to do with the front-office, they are only used to handle back-office logic. For instance, set_conditional_logic is not exposed via REST.

Although we could create a way to expose automatically the container above as a query. For now I am making it by hand and it's ok because it's a small project, but I think it could be a great improvement in this plugin.

I am using the code below to expose it by hand today:

add_action('graphql_register_types', function () use ($post_type) {
  register_graphql_object_type('InstitutionalContentActionLink', ['fields' => [
    'label' => ['type' => 'String'],
    'href' => ['type' => 'String'],
    'style' => ['type' => 'String']
  ]]);
  register_graphql_object_type('InstitutionalContentActionNewsletterSubscribe', ['fields' => [
    'label' => ['type' => 'String'],
    'style' => ['type' => 'String'],
  ]]);

  register_graphql_union_type('InstitutionalContentAction', [
    'typeNames' => [
      'InstitutionalContentActionLink',
      'InstitutionalContentActionNewsletterSubscribe'
    ],
    'resolveType' => function ($institutionalContentAction) {
      $type = $institutionalContentAction['_type'] ?? null;
      switch ($type) {
        case 'link':
          return 'InstitutionalContentActionLink';

        case 'newsletter_subscribe':
          return 'InstitutionalContentActionNewsletterSubscribe';

        default:
          return '';
      }
    }
  ]);

  register_graphql_field('InstitutionalContent', 'action', [
    'type' => 'InstitutionalContentAction',
    'resolve' => fn ($institutionalContent) => carbon_get_post_meta($institutionalContent->ID, 'action')[0] ?? null
  ]);
});