Score:0

Form field states with media fields that use media library widget and conditionally displaying visibility based on other media fields

rw flag

I am trying to get some media fields to hide/show conditionally using $form fields #states API, based on other media fields (all the media fields are using the "Media Library" widget in form display.) I'm working with a node/add and edit for a content type in Drupal 9.5.4 and using hook_form_alter() to manage the states.

There are 3 media fields in total - if one has media added the other two should become invisible/hidden, but if the media that was added is removed then the other two fields should display again - which seems to be impossible (as far as showing them again).

After many hours of trial and error the only success I have achieved is hiding 2 of the 3 fields when the third one of them has media added.

  • using input[name="field_name[selection][0][target_id] as the conditional controller which is problematic because technically that input doesn't exist in the form until media has been added thru the media library widget. So removing that media "will not" make the other media fields show again as the input is also removed from the form.

It was also kind of dumb luck that I managed to get that input to hide the 2 unused media fields on the form. As I was initially trying to write the #states like I was for some of the other (none media fields in the form).

For example: working with non-media field using the following worked great!

$form['field_business_name']['widget'][0]['value'][#states] = [
  'visible' => [
    'select[name="field_location_type"]' => ['value' => 1],
  ],
];

"but" trying to use the same format for the controller with a media field (using the media library widget) would not work.. For example:

$form['field_media_video_linked'][#states] = [
  'invisible' => [
    'input[name="field_media_video[selection][0][target_id]"]' => ['!value' => ''], // value not empty
  ],
];

However if the input was wrapped in another array (nested) it does work like this:

$form['field_media_video_linked'][#states] = [
  'invisible' => [
    ['input[name="field_media_video[selection][0][target_id]"]' => ['!value' => ''],],
  ],
];

But without the nesting it doesn't work?

That struck me as odd and was the only reason I found that to work because I added an 'or', to the controller which required I wrap or nest the different control options in another array (as I was testing just using another input from a different "none-media" field to at least make sure I could use states to hide media fields, which does work)

So as of now using one media field to hide another media field works except if the control field/input is removed by clicking "Remove" on the media item the hidden media fields no-longer show and won't return to a visible state "and" I believe that is because when you remove an added media item the input input[name="field_media_video[selection][0][target_id]"] gets removed from the form as well and since it no-longer exists there is nothing to check that the value is now empty. Frustrating... So using an 'or', with 'visible' for 'input[name="field_media_video[selection][0][target_id]"]' => ['value' => ''], //value is empty Does nothing!

I'm either addressing this the wrong way or there is a better way to work with states on media fields when the conditional controller is also a media field (that uses the media library widget) and I'd really like to keep using the media library widget, even tho I did find I could switch the widget type to "Inline Entity Form Simple" and I could then use 'input[name="field_media_video[0][field_media_video_file][0][fids]"]' => ['!value' => ''], //not empty as the controller to make other fields hidden/invisible and then 'input[name="field_media_video[0][field_media_video_file][0][fids]"]' => ['value' => ''], //is empty to then make them visible again, being the actual input is always in the form (whereas with Media Library widget no input exists until an item/file is added. If all else fails I'll have no choice but to switch the widget type to make this work but it's not what I want to do as there are added benefits to using the library widget as a user could select an uploaded file that "already exists" in their account versus uploading the same content to a new field.

I've spent hours looking through documentation and there is literally nothing for examples or use cases with media related fields when dealing with states.

So, is there a better way "or" what do I need to do to get this working with the "expected" results (keeping the field widgets as media library)?

I can provide additional information if needed, I use dpm() to get the $form, $form_state and field info for the form on load.

Score:0
rw flag

This is probably not the way to address media fields states, however it is a solution (or work around I was able to come up with).

Using hook_form_alter() the same hook I'm using to work with the media fields as conditional fields - I added a new field (input type radios) to the form to use as the controller which dictates the media fields visibility, disabled and required #states for media fields using the media library widget. That in combination with a little jquery magic and css got the job done effectively.

Here is exactly what I did (in hook_form_alter) I created and added the radios field to the forms I'm working with which will be css display none:

$form['field_media_set'] = [
  '#type' => 'radios',
  '#options' => [
    'upload' => 'Video Upload',
    'link' => 'Linked Video',
    'gallery' => 'Gallery Images',
    'other' => 'No Media',
  ],
  '#atributes' => [
    'name' => 'field_media_set',
  ],
  '#default_value' => 'other',
  '#weight' => 12,
]; 

I Didn't bother with any data storage for the field as it is just being used to control the 3 media fields using the media library widget in form display (video upload, linked video, and gallery images "which is actually 2 media fields, one for a main/poster image and the other to add up to 12 additional supporting images)

Next I created a new library and added it to the libraries.yml calling it post-library and listed a post.css and post.js file in the library like this:

post-library:
  version: 1.x
  js:
    js/post.js: {}

  dependencies:
    - core/jquery

  css:
    theme:
      css/post.css: {}

In the post.css file I added:

div#edit-field-media-set.form-radios {display: none;}

In the post.js I added:

function checkMediaFields() {

  var $uploadVideo = $('input[name="field_upload_video[selection][0][target_id]"]');
  var $linkVideo = $('input[name="field_link_video[selection][0][target_id]"]');
  var $gallery = $('input[name="field_gallery_poster_image[selection][0][target_id]"]');
  var $galImages = $('input[name="field_gallery_images[selection][0][target_id]"]');

  if ($uploadVideo.length || $linkVideo.length || $gallery.length || $galImages.length) {
    if ($uploadVideo.length) {
      $('input[name="field_media_set"][value="upload"]').prop('checked', true).change();
    }
    if ($linkVideo.length) {
      $('input[name="field_media_set"][value="link"]').prop('checked', true).change();
    }
    if ($gallery.length || $galImages.length) {
      $('input[name="field_media_set"][value="gallery"]').prop('checked', true).change();
    }
  } else {
    $('input[name="field_media_set"][value="other"]').prop('checked', true).change();
  }
}

$(document).ajaxComplete(function() {
  checkMediaFields();
});

As you can see from the js/jQuery there are 4 inputs (what's important to note here is those are the inputs that get added to the form once a media item has been added through the media library widget - "If no media is added those inputs DO NOT EXIST", and that is why those alone cannot be used as the control condition because if you remove the added media those inputs also get removed from the form.

Also note in the js/jQuery the use of "ajaxComplete" function, this is required to trigger the checkMediaFields functions which you can see checks for the existence of the inputs that get added to the form when media is added with a media field. Having that run after ajax completes is key because the media library form display widgets use ajax to upload or add the media in question to the form, without that the function to check for any media related inputs won't get triggered when a media field using media library widget adds new media to the form.

Next in the node-add and node-edit twig templates (example: page--node--add--contenttype-post.html.twig) I added the library I created above:

{{ attach_library('custom_module_name/post-library') }}

Now back in hook_form_alter() here is how I set up the conditional states for the media fields:

$form['field_upload_video']['#states'] = [
  'visible' => [
    [
      'input[name="field_media_set"]' => ['value' => 'other'],
    ],
    'or',
    [
      'input[name="field_media_set"]' => ['value' => 'upload'],
    ],
  ],
  'disabled' => [
    [
      'input[name="field_media_set"]' => ['value' => 'link'],
    ],
    'or',
    [
      'input[name="field_media_set"]' => ['value' => 'gallery'],
    ],
  ],
];
$form['field_link_video']['#states'] = [
  'visible' => [
    [
      'input[name="field_media_set"]' => ['value' => 'other'],
    ],
    'or',
    [
      'input[name="field_media_set"]' => ['value' => 'link'],
    ],
  ],
  'disabled' => [
    [
      'input[name="field_media_set"]' => ['value' => 'upload'],
    ],
    'or',
    [
      'input[name="field_media_set"]' => ['value' => 'gallery'],
    ],
  ],
];
$form['field_gallery_poster_image']['#states'] = [
  'visible' => [
    [
      'input[name="field_media_set"]' => ['value' => 'other'],
    ],
    'or',
    [
      'input[name="field_media_set"]' => ['value' => 'gallery'],
    ],
  ],
  'required' => [
    [
      'input[name="field_media_set"]' => ['value' => 'gallery'],
    ],
  ],
  'disabled' => [
    [
      'input[name="field_media_set"]' => ['value' => 'upload'],
    ],
    'or',
    [
      'input[name="field_media_set"]' => ['value' => 'link'],
    ],
  ],
];
$form['field_gallery_images']['#states'] = [
  'visible' => [
    [
      'input[name="field_media_set"]' => ['value' => 'other'],
    ],
    'or',
    [
      'input[name="field_media_set"]' => ['value' => 'gallery'],
    ],
  ],
  'required' => [
    [
      'input[name="field_media_set"]' => ['value' => 'gallery'],
    ],
  ],
  'disabled' => [
    [
      'input[name="field_media_set"]' => ['value' => 'upload'],
    ],
    'or',
    [
      'input[name="field_media_set"]' => ['value' => 'link'],
    ],
  ],
];

All of that combined "gets the job done!"

Things worth mentioning: Before using the created radios field "field_media_set" I tried a few other field types to use as the controller like a dropdown select field and a set of number input fields (which were intended to be used like the radios field - however those alternate form fields would only work once. What I mean by that is even tho the jQuery would update the fields in the event a previously added media element was "removed" the hidden media fields would not show again. Even when using .change() or .keyup() when jQuery made any changes to the controlling fields. For whatever reason only radio checkboxes works 100% of the time for this type of scenario.

There may be a better way (but with the limited documentation concerning the use of media fields using the media library widget as a conditional controller) this is what I came up with.

Hopefully this will help others in a similar situation.

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.