Problem/Motivation

#2263569: Bypass form caching by default for forms using #ajax. is moving forms away from relying on /system/ajax, since that requires that forms always be cached.

views_ui_add_ajax_trigger() currently relies on the form being cached.

Proposed resolution

Remove the system.ajax #ajax URL from views_ui_add_ajax_trigger()

Remaining tasks

N/A

User interface changes

N/A

API changes

N/A

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett’s picture

Status: Active » Needs review
FileSize
859 bytes
54.12 KB

This will fail. It includes #2263569: Bypass form caching by default for forms using #ajax. (which is passing), but removes the workaround for the Views UI.

The steps to reproduce:
Go to admin/structure/views/add
Note that "sorted by" is "Newest first"
Switch the "Show" select list from "Content" to "Comments"

Expected:
"sorted by" will become "Unsorted", with no error

Actual:
Form validation error for "sorted by".

There are several select lists that are updated when switching the base table of the view.

In HEAD, the original form corresponding to 'node' (the default), is cached. On #ajax, the node-based form is loaded, and the callback operates on that to switch everything over to the new base table (comment).

With the patch, it instead rebuilds the whole form but with the input of 'comments' for the base table, and the old node-based sort key for the sort.
This is taken into consideration when building the form, so before the callback has even run, the form is built with a base table of 'comment' and the node-based sort key is considered invalid.

The do-not-test patch is just the change needed after the other issue lands.

Status: Needs review » Needs work

The last submitted patch, 1: 2500523-views_ui-1.patch, failed testing.

dawehner’s picture

I guess we need to replace the entire form with the ajax callback
OR
start with no default and force people to always select an entity type first?

tim.plunkett’s picture

Even if we start with no default, that would only solve the initial switch, it wouldn't help if you switched the base table after picking some selects.

effulgentsia’s picture

Priority: Normal » Critical
Issue tags: +blocker
dawehner’s picture

FileSize
4.86 KB

One possible approach would be to prefix the entire form, or rather put it into a wizard plugin specific form structure, like 'wizard__' . 'node'
The additional step then would require to rewrite a bunch of other stuff ... or actually quite a lot.

This interdiff just shows what this could like ...

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
2.69 KB

This was discussed on the "D8 critical issues discussion" call, I was not present, but @catch basically confirmed what I wanted to do, which is throw away all of the irrelevant POST data when changing a #ajax-ified select.

Discussion starts here https://www.youtube.com/watch?v=7HcVntLrryU&feature=youtu.be&t=2560

This attempts to remove the offending user input, but doesn't work for "Include an RSS feed" checkbox within the "Create a Page" section.

jibran’s picture

+1 for the idea.

Status: Needs review » Needs work

The last submitted patch, 7: 2500523-views-7.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
7.54 KB
5.89 KB

Logic wasn't complete yet. Still doesn't work for the feed checkbox.
Added a unit test for \Drupal\views\Plugin\views\wizard\WizardPluginBase::getSelected() which helped me refactor it.

Status: Needs review » Needs work

The last submitted patch, 10: 2500523-views-10.patch, failed testing.

dawehner’s picture

So yeah what we kind of discussed has been to change the UI into a two step process, similar to the field UI.

jibran’s picture

+++ b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
@@ -518,6 +518,13 @@ public static function getSelected(FormStateInterface $form_state, $parents, $de
+        // If the user-submitted value has changed, remove all other options in
+        // that set of values.

I think we should explore this option first before moving to two step from. If we'll be able to fix it like this then we don't have to deal with UX issues. Thoughts??

The last submitted patch, 10: 2500523-views-10.patch, failed testing.

dawehner’s picture

I think we should explore this option first before moving to two step from.

I totally agree ...

  1. +++ b/core/lib/Drupal/Core/Form/FormBuilder.php
    @@ -236,10 +236,13 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
    +      // - input: Any user submitted data is expected to survive within the same
    +      //   page request.
           if ($check_cache) {
             $cache_form_state = $form_state->getCacheableArray();
             $cache_form_state['always_process'] = $form_state->getAlwaysProcess();
             $cache_form_state['temporary'] = $form_state->getTemporary();
    +        $cache_form_state['input'] = $form_state->getUserInput();
    

    I don't know much about fapi, but this particular change feels really dangerous

  2. +++ b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
    @@ -480,16 +480,16 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    -  public static function getSelected(FormStateInterface $form_state, $parents, $default_value, $element) {
    +  public static function getSelected(FormStateInterface $form_state, array $parents, $default_value, array $element) {
    

    +1 for moving the additional logic into this method. This indeed is the natural place for it

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
1.28 KB
8.52 KB

This should handle the feed row style bug. It needs to be determined dynamically, same as the others.

Not sure if that's the last cause of fails or not...

Status: Needs review » Needs work

The last submitted patch, 16: 2500523-views-16.patch, failed testing.

dawehner’s picture

Nice!
Tim do you mind explaining what is going on in the first hunk, see comment #15

effulgentsia’s picture

Re #18, that first hunk is trying to make it so that the form builder function (e.g., ViewAddForm::form() via its call to WizardPluginBase::getSelected()) can modify $form_state['input']. Which is potentially problematic if the form is cached, because in such a situation, the form builder wouldn't run. Even if we remove the case of caching on the initial GET request, we should consider what would happen if some other module made the form cacheable after a validation error (e.g., Mollom does so that if you answered the captcha correctly, but got a validation error for some other field, you don't need to re-answer the captcha again). In such a case, when you submit after correcting your validation problems, the form is retrieved from cache, so the form builder function is skipped, so its changes to $form_state['input'] don't happen, which means $form_state['input'] is different as an unintended side-effect of enabling Mollom (or any other module that turns on form caching after a validation error).

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
5.48 KB
11.07 KB

More work, less fails. Something is still wrong though.

tim.plunkett’s picture

Because these form elements are not executing the form, the form will be rebuilt after each one.
All rebuilt forms are cached.
Therefore, ... um I don't know any more. I need some time away from this code, I'll be back tomorrow.

Status: Needs review » Needs work

The last submitted patch, 20: 2500523-views-18.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
2.16 KB

Does this work?

effulgentsia’s picture

  1. +++ b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
    @@ -504,7 +504,7 @@ public static function getSelected(FormStateInterface $form_state, $parents, $de
    -    $user_input = &$form_state->getUserInput();
    +    $user_input = $form_state->isRebuilding() ? $form_state->getUserInput() : [];
    

    On the first AJAX submission, when the form is uncached, this lets the form get built the first time the same way it did for the GET request.

  2. +++ b/core/modules/views_ui/admin.inc
    @@ -119,7 +119,7 @@ function views_ui_add_ajax_trigger(&$wrapping_element, $trigger_key, $refresh_pa
    -    'duplicate_wrapper' => !empty($seen_ids[$triggering_element['#ajax']['wrapper']]),
    +    //'duplicate_wrapper' => !empty($seen_ids[$triggering_element['#ajax']['wrapper']]),
    

    Without this, the first AJAX submission results in views_ui_ajax_update_form() returning $form['displays'] but without that element having a #prefix with the wrapper ID. I think because that first submission builds the form twice, so the 2nd build looks to be a wrapper duplication but isn't. Then, AJAX submissions 2-N end up doing a replaceWith() with the correct content returned from the server but don't have a wrapper element in the DOM to do it on, so the front-end doesn't get updated to match what's on the back-end.

Status: Needs review » Needs work

The last submitted patch, 23: 2500523-views-23.patch, failed testing.

tim.plunkett’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
8.83 KB
10.43 KB
8.95 KB

Yep, that's it. The getUserInput() change corresponds directly to my debugging, except I was checking the wrong flag and trying to fake getting back to an empty array. Doing it this way is exactly what I wanted to do, but couldn't figure out.

The duplicate_wrapper change also resolves the last 10% of what I was trying to do. Changing code in views_ui_add_ajax_wrapper() now that duplicate_wrapper is no more.

Copying my test changes, which are still necessary due to the double build.

The do-no-test is just git diff -w

Hopefully @dawehner can review, because I think we're done here.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community
  1. +++ b/core/modules/views/src/Plugin/views/wizard/WizardPluginBase.php
    @@ -496,15 +496,18 @@ public static function getSelected(FormStateInterface $form_state, $parents, $de
    -    $user_input = &$form_state->getUserInput();
    ...
    +    $user_input = $form_state->isRebuilding() ? $form_state->getUserInput() : [];
    

    I'm a bit confused that we no longer need a reference, but I see, we just get values, so there is no need for a reference.

  2. +++ b/core/modules/views_ui/admin.inc
    @@ -117,9 +113,6 @@ function views_ui_add_ajax_trigger(&$wrapping_element, $trigger_key, $refresh_pa
    -    // Keep track of duplicate wrappers so we don't add the same wrapper to the
    -    // page more than once.
    -    'duplicate_wrapper' => !empty($seen_ids[$triggering_element['#ajax']['wrapper']]),
    

    Yeah I don't see an issue with doing it.

  3. +++ b/core/modules/views_ui/admin.inc
    @@ -166,29 +159,25 @@ function views_ui_add_limited_validation($element, FormStateInterface $form_stat
    +  // This was earlier stored in a property on the element.
    

    Does that documentation add any value on top of version control, I don't really think so

+++ b/core/modules/views/tests/src/Unit/WizardPluginBaseTest.php
@@ -0,0 +1,112 @@
+    $data['invalid_type'] = [
...
+    $data['no_options'] = [
...
+    // A valid form element with no user input.
+    $data['no_user_input'] = [

Awesome!

tim.plunkett’s picture

27.3, that's just outdented docs, notice the do-not-test patch using -w doesn't have that line

Fabianx’s picture

RTBC + 1, Great work! - Fantastic simple solution, can we document that approach somewhere, please?

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 40004e3 and pushed to 8.0.x. Thanks!

@Fabianx I talk about documenting the approach in IRC with @tim.plunkett. He said

i could, but i doubt other non-merlinofchaos implementations had FAPI workarounds of that magnitude...
we already say "you probably shouldn't use getUserInput()"

I'm inclined to agree.

  • alexpott committed 40004e3 on 8.0.x
    Issue #2500523 by tim.plunkett, effulgentsia, dawehner: Rewrite...

Status: Fixed » Closed (fixed)

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