Problem/Motivation

In order to alter the properties of a field widget form element defined by another module, modules must loop over every field of every form in either hook_form_alter() or hook_field_attach_form(), searching for the correct field or widget type. This adds unnecessary overhead and makes field and form customization far more complicated than it needs to be. For example, a module enhancing the functionality of core taxonomy fields must check every form for taxonomy field widgets.

Proposed resolution

Create a hook_field_widget_form_alter() (with widget-specific variant hook_field_widget_WIDGET_TYPE_form_alter()) to allow the widget form to be altered directly.

Remaining tasks

None.

  1. Create new hooks Done
  2. Verify functionality Done
  3. Document new hooks Done
  4. Review by field API maintainer Done
  5. Review final version of patch in #1204230-30: Missing hook_field_widget_form_alter() Done
  6. Sign-off by field API maintainer Done
  7. Review patch #33 Done

User interface changes

None.

API changes

Minor, backwards-compatible addition only. No existing code will be affected.

Two new hooks will become available:

  • hook_field_widget_form_alter(&$element, &$form_state, $context)
  • hook_field_widget_WIDGET_TYPE_form_alter(&$element, &$form_state, $context)

Original report by @j0rd

I'm trying to do a simple task.

I've created a field, and I want to put a custom validator on it. I'm using a textfield, and want to make sure for this particular instance, it's a URL link.

Thing is, I might add this field into more than one entity/bundle or what ever they are called these days. So I'd really like to attach it to the field instance (aka field_name) instead of the normal form_validation. This seems like the best place for it to be.

Unfortunately there's no hook to get at this. The closest I can get is hook_field_widget_form(), but unfortunately this is only called with the module that defines the widget...so as far as I can tell...I'm at a dead-end with out hacking core. There was some promising looking module_invokes in _field_invoke(), but again, those fall short.

I also think it would be a wise idea to assist developers to create a field_FIELDNAME_validator() type shortcut to a hook like this.

Maybe there's a way to do this, that I don't know about....but right now I'm stuck. I tried looking this up and only found two other people with the same problem I'm having.

So, is this something I can already do, but can't figure out? Or is this not important? Or is this something we'd like to add into core to ease developers lives & code portability.

PS. I know I could define my own widget to get my own validation...but IMHO, this is slightly overkill.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

j0rd’s picture

xjm’s picture

Version: 7.x-dev » 8.x-dev

Another possible (but as-yet missing) solution would be a hook_field_validate_alter(). This would be especially useful in my use case--I need to alter the list of terms a user may submit in term reference fields of any widget on any entity type, and I want to be able to enforce that in validation in a portable way.

sun’s picture

Title: We need a hook_field_widget_form_alter(), or I just need to be enlightened. » Missing hook_field_widget_form_alter()
Category: feature » task
Status: Active » Needs review
Issue tags: +DrupalWTF, +API addition
FileSize
1.41 KB

Status: Needs review » Needs work

The last submitted patch, drupal8.field-widget-form-alter.3.patch, failed testing.

xjm’s picture

Status: Needs work » Needs review

Looks like it needs to be $instance['entity_type'].

xjm’s picture

Status: Needs review » Needs work

Oops, misclick.

field_multiple_value_form() doesn't get the entity as an arg apparently?

Edit: the only place I found the entity data in field_multiple_value_form() was inside $form. Would it needed to be added as an argument?

xjm’s picture

Issue tags: +D7 Form API challenge
xjm’s picture

For anyone else struggling with this, here's the sort of workaround I'm implementing atm. (This is half-baked and needs some checks for whether stuff is defined, etc., but you get the idea.) Looping through everything on every form is definitely less than ideal, but it's the best I've come up with at present given that we want to handle any entity types. I actually don't even know if it will work for non-nodes yet, but at least the possibility is there...

Edit: improving it a little.

function taxonomy_access_form_alter(&$form, &$form_state, $form_id) {
  if (is_array($form_state) && !empty($form_state['field'])) {
    foreach ($form_state['field'] as $fieldname => $el) {
      foreach ($el as $langcode => $field) {
        if ($field['field']['type'] == 'taxonomy_term_reference') {
          if ($field['instance']['widget']['type'] == 'taxonomy_autocomplete') {
            // Add our damn autocomplete handler.                                                                             
            $form[$fieldname][$langcode]['#autocomplete_path']
              = 'taxonomy_access/autocomplete/' . $fieldname;

            // Add our damn validator.                                                                                        
            $form[$fieldname][$langcode]['#element_validate']
              = array('taxonomy_access_autocomplete_validate');

            // Set our damn default value.                                                                                    
            $form[$fieldname][$langcode]['#default_value']
              = taxonomy_access_autocomplete_default_value($form_state['build_info']['args'][0]->{$fieldname}[$langcode]);
          }
        }
      }
    }
  }
}
xjm’s picture

Status: Needs work » Needs review
FileSize
1.34 KB

Couple folks on IRC pointed out we don't need to pass $entity_type and $entity as separate arguments. So here's a reroll without those in $context.

xjm’s picture

Just confirmed that this patch applies to 8.x and 7.x, and allows me to alter the widget form very cleanly. E.g., with the following:

function taxonomy_access_field_widget_form_alter(&$element, &$form_state, $context) {
  if ($context['field']['type'] == 'taxonomy_term_reference') {
    $element['#access'] = FALSE;
  }
}

Brilliant!

xjm’s picture

Assigned: Unassigned » xjm
Status: Needs review » Needs work

To add this, we would also need to add the hook documentation in field.api.php. Looking into this now.

xjm’s picture

Status: Needs work » Needs review
FileSize
3.16 KB

Attached patch includes hook documentation.

sun’s picture

Status: Needs review » Needs work

we don't need to pass $entity_type and $entity as separate arguments

Is this info available through other means or...?

+++ b/modules/field/field.api.php
@@ -786,6 +787,11 @@ function hook_field_widget_info_alter(&$info) {
+ * Other modules may alter the form element provided by this function using
+ * hook_field_widget_form_alter().
+ *
+ * @see hook_field_widget_form_alter()

The additional @see is not necessary here.

+++ b/modules/field/field.api.php
@@ -836,6 +842,30 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco
+ * @param $element
+ *   The form element as rendered by hook_field_widget_form().

"The field widget form element as constructed by ..."

17 days to next Drupal core point release.

xjm’s picture

#13: The entity is available in $form_state['build_info'], and the entity type is both there and in $instance IIRC.

I'll fix the other documentation issues, thanks.

xjm’s picture

Status: Needs work » Needs review
FileSize
3.13 KB

Same as #12 but with hook documentation corrections.

catch’s picture

Looks good to me at first glance, we usually put @see at the bottom of doxygen rather than the top.

It feels like $form/$form_state are going to be pretty different depending on where the widget is, is that a feature or not?

xjm’s picture

It feels like $form/$form_state are going to be pretty different depending on where the widget is, is that a feature or not?

This does seem like a possibility, but the hook is not introducing new behavior that is not already present in hook_field_widget_form(), which gets all of the above and then some as arguments:
hook_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element)

Perhaps we should consider changing the signature to match this, though.

catch’s picture

If it's not new behaviour that seems fine.

Matching signatures wouldn't do any harm.

sun’s picture

The signature cannot be matched further and should not I think -- the only difference is in $form vs. $element, but hook_field_widget_form() actually produces $element, not extending $form, so we're passing on $element to the _alter().

xjm’s picture

Checked with sun about this. For future reference: To use drupal_alter(), we need the first argument to be the thing we're altering--the field widget form element, in this case--and then up to two additional parameters, the last of which is an associative array containing as many other parameters as we want to feed the alter hook. $form_state is the next most reasonable thing to alter by reference, so it makes sense to have that as the second argument.

So, the signature is correct as it is.

sun’s picture

@yched or another Field API/module maintainer needs to sign off this patch.

droplet’s picture

Will it backport to D7 ??

catch’s picture

Issue tags: +Needs backport to D7

Should be IMO, tagging.

yched’s picture

Status: Needs review » Needs work
+++ b/modules/field/field.api.php
@@ -836,6 +840,30 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco
+ *   - field: The field structure.
+ *   - instance: The field instance.

should be "the field instance structure"

+++ b/modules/field/field.api.php
@@ -836,6 +840,30 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco
+  if ($context['field']['type'] == 'mytype') {
+    $element['#access'] = FALSE;
+  }

Nitpick : we might want to give an example discriminating on a specific widget type (i.e checking $context['instance']['widget']['type']). Sounds more "real life".

+++ b/modules/field/field.form.inc
@@ -76,6 +76,9 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode,
+          // Allow modules to alter the field widget form element.
+          $context['form'] = $form;

This is reusing the $context var built for the hook_field_widget_properties_alter() call a couple lines above. I'm not too fond of this, let's please build a fresh, dedicated $context var.

+++ b/modules/field/field.form.inc
@@ -76,6 +76,9 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode,
+          drupal_alter(array($function, 'field_widget_form'), $element, $form_state, $context);

This allows two variants :
a "targeted" hook_WIDGETMODULE_field_widget_form_alter(), and a "generic" hook_field_widget_form_alter().
- I think the core practice is to put the "targeted" variant second.
- The "targeted" variant needs to be documented as well.
- I don't think targeting by WIDGETMODULE is the right approach here. The same module can provide several widgets that are structured very differently. I'd say we'd rather want to target by widget type. As a developer, I know which widget I want to alter, I don't care which module provides it. Also, that fits with the "widgets as plugins" direction we'll want to push for if/when the WSCC initiative comes up with a plugins system.

Also, the alter hook doesn't receive the other params that the original hook_field_widget_form() 'callback' receives ($langcode, $items, $delta) ? I'd think those should b added to the context ?

More generally on the feature : I'm not opposed to this, but the symmetry between widgets and formatters raises the question of a similar hook_field_formatter_view_alter() hook on the formatter side - which we cautiously avoided so far for performance reasons (a listing page can call 100s of formatters, and field rendering is already quite slow).

14 days to next Drupal core point release.

xjm’s picture

Attached patch:

  1. Moves the hook documentation's @see to the bottom of the docblock as suggest by catch in #16.
  2. Creates separate context variables $properties_context and $form_context for the respective alter hooks.
  3. Adds langcode, items, and delta to the context (as well as adding the entity type explicitly for consistency, since this is the pattern when the widget is built in field_multiple_value_form().
  4. Uses overriding a widget autocomplete path for an example.

There are two points I haven't addressed:

+++ b/modules/field/field.api.php
@@ -836,6 +840,30 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco
+ *   - field: The field structure.
+ *   - instance: The field instance.

should be "the field instance structure"

To clarify (since you quoted two lines). Should it read:

 *   - field: The field structure.
 *   - instance: The field instance structure.
+++ b/modules/field/field.form.inc
@@ -76,6 +76,9 @@ function field_default_form($entity_type, $entity, $field, $instance, $langcode,
+          drupal_alter(array($function, 'field_widget_form'), $element, $form_state, $context);

This allows two variants :
a "targeted" hook_WIDGETMODULE_field_widget_form_alter(), and a "generic" hook_field_widget_form_alter().
- I think the core practice is to put the "targeted" variant second.
- The "targeted" variant needs to be documented as well.
- I don't think targeting by WIDGETMODULE is the right approach here. The same module can provide several widgets that are structured very differently. I'd say we'd rather want to target by widget type. As a developer, I know which widget I want to alter, I don't care which module provides it. Also, that fits with the "widgets as plugins" direction we'll want to push for if/when the WSCC initiative comes up with a plugins system.

I am not sure how to address this.

yched’s picture

Thx @xjm.

- $properties_context and $form_context :
I think both can be called $context. Those are one-off variables anyway, created just for the sake of the alter call.

- "field: The field structure. / instance: The field instance structure."
Yup :-)

- variants :
I'd suggest

drupal_alter(array('field_widget_form', 'field_widget_' . $instance['widget']['type'] . '_form'), ...)
xjm’s picture

Ah, it makes sense now. I agree this is a much better hook. In that case I will move the widget-specific example to that hook doc, and act based on field type in the generic one.

xjm’s picture

Status: Needs work » Needs review
FileSize
5.07 KB

Attached incorporates suggestions from #24-26.

xjm’s picture

Status: Needs review » Needs work

Oops, some trailing whitespace.

xjm’s picture

#28 but without the trailing whitespace.

xjm’s picture

Status: Needs work » Needs review
xjm’s picture

Same patch applies to D7 fine. I confirmed that both the generic and specific hooks work as advertised.

xjm’s picture

Issue summary: View changes

Updated issue summary.

xjm’s picture

Issue summary: View changes

Added direct reference to most recent patch.

yched’s picture

Thanks @xjm.

Polished a little further :
- Changed hook_field_widget_NAME_form_alter() to hook_field_widget_WIDGET_TYPE_form_alter()
- Adjusted the $context elements to match the args received by hook_field_widget_form(). The $form_state stays it's own parameter, as it's alterable.
- Adjusted the @see references in the phpDocs.

It's now up to someone else to RTBC, though :-/

yched’s picture

Issue summary: View changes

Typo and clarity.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Thanks!

xjm’s picture

Noticed a typo (follwoing). I'll reroll quicklike. Yay green!

xjm’s picture

xjm’s picture

Issue summary: View changes

update status

xjm’s picture

Issue summary: View changes

Clearer indication that this is a small API addition, not a change.

xjm’s picture

Issue summary: View changes

Clearer.

xjm’s picture

Issue summary: View changes

Updated tasks.

goron’s picture

In case it matters, I'm using this patch and the hooks work well.

I'm wondering - do you know when these changes will be added to a D7 release, or in which release they'll be included?

Thanks.

j0rd’s picture

I don't think this patch has many eyes on it. Tweet / Google+ about it and see if you can get more people to test it and hopefully it'll help get this in faster once more people say it works as expected.

mhahndl’s picture

@j0rd: This patch has many eyes on it - i visit this page daily. Missing hook_field_widget_form_alter() is a D7 release blocker for several modules, so this patch is important for a lot of people.
@xjm: Thanks for the patch. It works as expected.

alexbern’s picture

@j0rd: We are very eager to see this in core as well. +1 from my side

xjm’s picture

This is somewhat blocking a stable release of TAC at present.

goron’s picture

It seems that this patch does have some eyes on it, and has been tested a few times. It is also necessary for a module I would like to release.

I'm not familiar with the process of getting patches committed to core. Is there anything we can do to speed it along? I personally don't many people in my twitter/google+ who I can share this with.

xjm’s picture

catch’s picture

Priority: Normal » Major

Moving to major since it's blocking stable contrib releases.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

This looks like a pretty straight-forward oversight in the API. I'd been holding off on this patch while we got our bug counts down, but that seems to have been accomplished in the last couple of weeks, so cool.

Committed and pushed to 8.x and 7.x. Thanks a lot.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

xjm’s picture

Assigned: xjm » Unassigned
xjm’s picture

Issue summary: View changes

Corrected name of specific hook in summary.