Problem/Motivation
When a user makes a change using the off-canvas tray and there is a form validation error the main page will be rerouted to the route of the form itself. In the case of the Settings Tray module this is "/admin/structure/block/manage/{block}/off-canvas". So the form that was just in the tray is now in the main page content. This doesn't really make sense if the purpose of the Settings Tray module is expose settings without having to make the user go to the admin sections of the site.
This same problem with a form in a dialog not showing the errors in the dialog can be seen on the Block Layout page. When placing a block if you have a validation error, for example a "?" character in the machine name, you will be rerouted away from the modal dialog and taken to the block form page out of the dialog.
Proposed resolution
When are in validation error on form submit show the validation message in the tray above the form itself. Do not redirect to the form route.
To do this these changes are needed:
- Change the form to use ajax submit. Otherwise the form system will redirect on validation error
- Check if there are form errors in the submit callback
- If there are error re-render the form with errors at the top of the form.
- If there are no errors redirect to the form to the destination provided when opening the dialog. In the case of the the Settings Tray module this will the page the use is currently on when opening the tray.
Because this will be a common problem for any module that wants to open a form in the off-canvas tray this issue creates OffCanvasFormDialogTrait which can be used by any form in the tray.
To see an example of how this works see \Drupal\off_canvas_test\Form\TestForm
Because of #2895477: Native browser form validation does not fire when submit buttons use #ajax you can actually test errors right now but not putting in Block Title.
Remaining tasks
None
User interface changes
On a form validation error in the tray the user will not be redirected to form itself but will see the validation message the tray.
API changes
OffCanvasFormDialogTrait will be available for other forms to use.
Data model changes
None
| Comment | File | Size | Author |
|---|---|---|---|
| #92 | 2785047-92.patch | 10.95 KB | tedbow |
| #92 | interdiff-90-92.txt | 2.94 KB | tedbow |
| #90 | interdiff-85-90.txt | 1.86 KB | effulgentsia |
| #90 | 2785047-90.patch | 11.01 KB | effulgentsia |
| #87 | nth_form.png | 86.21 KB | tedbow |
Comments
Comment #2
naveenvalechaComment #3
tkoleary commentedComment #4
tkoleary commentedComment #5
tkoleary commentedComment #6
tedbowOk so this issue is actually really tricky.
I don't think this is actually true. Client-side validation error guess would for required fields but any form validation errors would actually cause the form to be redirected away from the modal to form path directly.
So there are some problems in core that will cause this not to work. I will try to list them here and then find existing issues or create new ones.
Comment #7
tedbowOk here is a start to this.
For now it justs.
Switches the OffCanvas block forms to use ajax.
Displays the messages if any when the form is submitted.
This patch also introduces a fake validation error to test out the validation message because it is actually hard to produce.
There error message actually won't be formatted b/c of this issue #2799611: Style Javascript messages in the offcanvas tray
This also adds tests that tests simple form.
This problem with showing validation messages applies to any forms in dialogs there just aren't many forms in dialogs in core. Related new issue #2857158: Block placement validation errors incorrectly leave the modal
Comment #9
tedbowOk, so this adds the redirect if the form doesn't have any errors. It redirects to the current page and then re-opens the form automatically.
Currently I am checking for form errors in \Drupal\outside_in\Render\MainContent\AjaxRenderer::renderResponse to determine if a redirect should be done. This probably hacky but I didn't see another way.
I just found \Drupal\editor\Form\EditorImageDialog::submitForm() which actually returns and AjaxResponse with attached commands.
It seems like it might be a good idea to do the same in \Drupal\outside_in\Block\BlockEntityOffCanvasForm::submitForm() but I have to look at EditorImageDialog and see whats going on there.
Probably in that case it would make sense to make a new trait OffCanvasFormTrait that submit functions could call. Then something like
Comment #11
tedbowFixed js coding standard, missing semicolon.
Also
The selector needs to be more specific so it doesn't wipe out messages elsewhere.
Though maybe when a ajax validation error comes back other messages outside of the dialog should be removed?
"currentQuery" might not exist.
Removed hardcoded html.
From IS
This is what the current patch does. But since the page has to reload if the form save correctly(to reload the page changes) it feels kind of weird to re-open the dialog. If the save is success if feels like we should not re-open the tray.
I also noticed that because of #2847664: Edit mode behaves differently if entered in by clicking "quick edit" vs toolbar
If you click the contextual link to open the dialog when you save it and get redirected you will be taken out of edit mode. But that is fixed by the patch in the other issue.
Comment #12
GrandmaGlassesRopeManWe're using
hasOwnProperty()here.The
inkeyword here.Again,
hasOwnProperty()Perhaps we can be consistent in our method for checking to see if a property on an Object exists.
Comment #13
tedbow@drpal ok changed to use hasOwnProperty instead of "in"
Also fixed failing test. When using ajax submit I guess we need jquery.form library.
and moved messages to the top of the form.
Comment #15
tedbowFixing the test
Comment #16
tedbowWhoops for to actually upload the patch. Interdiff still valid for this patch.
Comment #17
tkoleary commentedThis is way better.
Comment #18
wim leersI find this code extremely difficult to understand. Lots of wrapping, no comments explaning what's going on, lots of jQuery-style code.
I think this can benefit a lot from creating helper methods.
Why not refactor the parent class in this class?
Not doing that now means there is significant risk of the code diverging, and possibly even security fixes in the parent class not being inherited here.
This says form errors, but the issue says messages. So this is issue is now dealing with both messages and AJAXy form validations, which can show messages.
Two birds, one stone — great!
But now we're not testing that messages (e.g. validation errors) for non-AJAXy forms are also shown correctly.
Comment #19
tedbow@Wim Leers thanks for the review. I have not had time to address it yet but your points seem right.
re #2 while working on #2866554: Add Quick Edit off canvas form. for the Webform module I see they ran into a similar problem but were able to add the redirect command with doing the kind of hack I was. The code is in WebformDialogTrait which we can probably learn from.
I was attempting to keep all of the changes in the experimental module. Should have made an issue to move it pre the module being stable. But might not need this at all now.
Just wanted to make this note before I forgot.
Comment #20
tedbowOk as mention in #19 in this patch I took WebformDialogTrait and chaning it to \Drupal\outside_in\OffCanvasFormDialogTrait
Hopefully Webform would just be able to use this trait directly. Thanks @jrockowitz for pointing me to this code. Can someone get commit credit if their code was stolen for a fix? ;)
So re @Wim Leers' review in #18
1. outside_in.js is no longer being changed because we don't introduce a new renderer.
2. This renderer is no longer needed. Yay!
3.
So would this be if JS is off. Off-Canvas won't work at all then. I am probably missing something. But haven't had time to look into it.
Comment #21
tedbowNeeds re-roll because of #2862625: Rename offcanvas to two words in code and comments. and other recent commits
Comment #22
tedbowComment #23
jofitzRerolled.
Comment #25
tedbow@Jo Fitzgerald thanks for the re-roll!
I think the only problem remaining are integrating the changes with #2862625: Rename offcanvas to two words in code and comments. which was commited since the patch that needs to re-rolled here.
Basically that issue we were trying to resolve that fact that we(ok maybe me) had been inconsistent about 'offcanvas' vs 'off-canvas' or 'off_canvas' basically now it should always be consider 2 words.
Picked a few examples below. But basically "offcanvas" all lower case should appear in the patch(or module).
So for example the test module should be off_canvas_test not offcanvas_test, affects namespace, file and folder names etc
For the test module you can enable off_canvas_test and manually go to routes in off_canvas_test.routing.yml to see if it is working.
Thanks
Should be 'drupal_dialog_off_canvas'
Should be '#drupal-off-canvas'
Change to '/off-canvas-form'
Comment #26
jofitzSplit offcanvas into two words in other places.
Comment #27
tedbow@Jo Fitzgerald reroll look good now! Thanks!
Ok this patch just removes some local test changes I made locally and left in #20.
Comment #29
tedbowI can't get this to fail locally. And I have no idea why these changes would cause this error:
$this->assertJsCondition('jQuery("' . $block_selector . ' ' . $label_selector . '").html() == "' . $new_page_text . '"');Also there is todo to remove this and now #2837676: Provide a better way to validate all javascript activity is completed is fixed so I will create this issue.
Comment #30
tedbowActually I was wrong in #29 that test failure makes total sense.
This patch changes the OffCanvas form to use AJax submit and then redirect.
Also the changes I made in #27 uncommented some of the test cases in OutsideInBlockFormTest::providerTestBlocks() . These cases have 'new_page_text' which triggered the assert.
The assert that was failing already had a todo to remove once #2837676: Provide a better way to validate all javascript activity is completed was fixed.
It is fixed so we can now use \Drupal\FunctionalJavascriptTests\JSWebAssert::waitForElement()
This should make the tests pass.
The only other change I had to make was to remove "?" and "." characters from the test data in 'new_page_text'. These were causing a problem in ":contains()". Since this was just test data it is fine to remove them.
Comment #32
tedbowLooks this patch added another @todo for #2837676: Provide a better way to validate all javascript activity is completed
Removing that 1 also. Canceled the previous test.
Comment #33
tedbowOk this re-rolls again then does:
Comment #35
tedbowOk this should get that test passing.
Add the patch in this issue #2883483: Assert that calls to waitForElementVisible() actually return element in OutsideIn javascript tests. because it just fixes a test mess up we did earlier and it is really hard to figure what is actually happen without.
Then I just added an ajax after the form is submitted because now we have different way of submitting forms in the tray.
Comment #36
tim.plunkettMaybe something like "Determines if the current request is for an AJAX modal dialog."
You can have
return in_array();directly, no need for the ternary or extra parensAdds
Is this how we do things now? Ideally we wouldn't use #prefix/#suffix, and wouldn't handwrite the HTML. Maybe something like #theme_wrappers?
This is a very strange place to break the line.
One idea would be to have each of these conditionals set up a $command, and add it below right before the return
Couldn't this delegate?
Yikes! Is that really how we have to do things?
I don't think explicitly returning NULL is needed.
Why the extra check first?
Not sure that the reassignment of $main_content is needed here.
Wouldn't this work just fine?
{@inheritdoc}
Tests
I don't understand these changes
Asserts
Comment #37
tedbow@tim.plunkett thanks for the review!
1. Fixed
2. fixed
3. fixed
4. I tried adding
$form['#theme_wrappers'] = 'outside_in_form_wrapper';with the an added template and theme hook. But this resulted in the form element not being rendered. not sure why.The only place I could see this happening core was \Drupal\forum\Form\ForumForm::form()
$form['#theme_wrappers'] = ['form__forum'];I couldn't figure out what was happening there.
I do see in core at least
$form['#prefix'] = '<div id="editor-image-dialog-form">';and
$form['#prefix'] = '<div id="views-preview-wrapper" class="views-preview-wrapper views-admin clearfix">';and
$form['#prefix'] = '<div class="views-form">';#theme_wrappersdoes seem more correct. Maybe somebody knows what is happening here.5. I am command will always be set in submitFormDialog() so just assigning to $command then adding in return statement because addCommand() will return back the response.
6. I realized this can be written in 1 line
return (new AjaxResponse())->addCommand(new CloseDialogCommand('#drupal-off-canvas'));And then also submitFormDialog() in the case where the dialog should be closed we can just call closeDialog()
7. Hmm. I was going to use \Drupal\Core\Routing\RedirectDestination::get() directly and follow the example in \Drupal\Core\Routing\RedirectDestinationInterface::get() doc.
\Drupal\Core\Url::fromUserInput(\Drupal::destination()->get())->setAbsolute()->toString()But I looked at RedirectDestination::get() and if 'destination' is not in the query string it will use
$this->urlGenerator->generateFromRoute('<current>We only want to do a redirect command if the 'destination' was sent in the query string. Otherwise the path will be the path of the form in the try not the main page the user is looking at.
We do not want to redirect the page to the form.
8. removed/fixed.
9. Same reason as 7. If 'destination' is not in query string then \Drupal\Core\Routing\RedirectDestinationTrait::getRedirectDestination() will actually use the current request(form in tray). We don't want that.
10. I think originally I thought there was problem with '#weight' not working here. Like the sort had already be run at this point and I couldn't get the weight to work.
but testing again I can't get the problem so fixing as you suggested.
11. fixed
12. fixed
13. This is because when adding this line
$new_page_text_locator = "$block_selector $label_selector:contains($new_page_text)";If $new_page_text has a "." or "?" in it this messes up the css selector. So since they are just test text it is easier to remove those characters.
14. fixed.
Comment #38
tim.plunkett4) On second thought, #theme_wrappers won't work here because adding it to a top level $form element will prevent form internals from working. This needs its own issue. However, if the id="off-canvas-form" part doesn't need to be on a div but could be on the form, this would also work:
The more correct fix is this, which includes out-of-scope changes IMO:
7) Ugh, I forgot how broken RedirectDestination is. #2745911: Block add links should respect destination had a similar problem. We should document this though, since that's not how the service should work.
Comment #39
tedbow4. Ok this actually fixes the problem
$form['#attributes']['id'] = 'off-canvas-form';Seems like a simple solution.
7. Added a comment on why we can't use \Drupal\Core\Routing\RedirectDestination::get() directly
New changes:
Also added some changes to \Drupal\Tests\outside_in\FunctionalJavascript\OutsideInBlockFormTest::openBlockForm() because I was getting test failure locally. It has been tricky with this module to make sure the ajax activity is completed before we try another action and this has cause random failures in the past.
In openBlockForm() I added a wait for the contextual links to show up because these are needed for the opening of the block forms to work.
This issue changes the workflow from a regular form submit to an ajax form submit and then a redirect command so test changes were expected.
Comment #40
tim.plunkettThanks for the changes. Manually tested it as well as reviewed the automated tests, looks great!
Comment #41
webchickThere were no screenshots for this issue, so I set out to make some while reviewing the behaviour.
I can confirm that the messages show up in the settings tray as intended ("after"):
However, the fact that this screenshot was even possible seems to be a regression, because normally HTML5 required handling would take care of that, and does in the current code ("before"):
@tedbow said this is because the submission handler was switched to AJAX, and there might be a problem with how it's set. Setting back to needs review to take a closer look at that.
Comment #42
tedbowOk looked at the html5 validation issue. Haven't figured out the solution but I think it is problem with using an ajax submit on any form and the clientside validation not be fired because the actually form itself is not being submitted. Talked to @drpal and @tim.plunkett about this and think the solution is to file an issue with the ajax system.
I did find 2 other problems when reviewing it.
Had to update this to use drupal_dialog.off_canvas because https://www.drupal.org/node/2844261 was committed and changes the string to [DIALOG_TYPE].[DIALOG_RENDER]. Just replaced "_" with ".".
We don't actually need this any more.
This element will handle showing any messages. So don't need the container in OffCanvasRenderer.
This should a ReplaceCommand. Right now this results a
element inside another
element both having the same id.
Comment #44
tim.plunkett.messages__wrapper is a class only added by Bartik templates
I don't see this being used anywhere else. Additionally, TRUE doesn't mean anything when rendered out, I see this in the markup
data-off-canvas-form=""I don't understand the second sentence
Gets
Also seems to be unnecessary
Also I think the test failure is due to
in OffCanvasDialogTest
Comment #45
tedbow1. This is not necessary anymore anyways I think this was needed when we adding the container in OffCanvasRender but we aren't anymore as noted in #42 2 & 3
2. Yep removed this and also in \Drupal\off_canvas_test\Form\TestForm
3. Updated sentence. Basically \Drupal\block\BlockForm::submitForm() will always set a redirect which won't work with ajax submit anyways.
4. fixed
5. fixed.
Test failure in #42 was because I forgot update OffCanvasDialogTest when removing the message container from OffCanvasRender
Fixed.
Comment #46
tim.plunkettLooks great!
Opened #2895477: Native browser form validation does not fire when submit buttons use #ajax as a follow-up for the validation issue, as it is for all #ajax and not related to outside-in.
Comment #47
effulgentsia commentedI don't know if it's just my computer, but this doesn't seem to work at all on my machine (I tried both Chrome and Firefox). When I QuickEdit the site branding block, upon changing the site name, the page is refreshed with the settings tray closed, and the message in the usual place, just like in HEAD.
Can someone post a screenshot or video of this working?
Comment #48
rootwork@effulgentsia It looks like this is only getting triggered when the message is an error, not for other kinds of messages:
Error messages look the same as webchick's screenshots from #41.
Comment #49
effulgentsia commentedThanks. If #48 is the intention of this issue's scope, then the issue title and summary need updating. Or is that not the intention and the patch needs fixing?
Comment #50
tedbowComment #51
tedbow@rootwork @effulgentsia yes this just for form validation message. I updated the summary.
Comment #52
tedbowUploading 2785047-45-forced_errr-do-not-test.patch was a mistake
Comment #53
xjmI've asked in #2830584: Use modals for creating, updating, and deleting workflows, with a new DialogFormTrait that a dedicated issue be split off for the ModalFormTrait that issue is adding, so we should probably use that one here. Setting NR for now, but probably we can postpone it on this hypothetical issue for the trait, since we should avoid adding more API surface to the "not an API" (reference: #2894584: Settings Tray provides functionality, not an API: mark PHP and JS as internal).
Thanks!
Comment #54
tedbowOk this moves OffCanvasFormDialogTrait to DialogFormTrait.
I updated #2830584-113: Use modals for creating, updating, and deleting workflows, with a new DialogFormTrait with the exact trait. Hopefully tests pass in both.
Comment #56
tedbowOk I see the problem \Drupal\Core\Form\DialogFormTrait::getDialogSelector() I need determine which main content format was used to originally make the form not the current that in ajax submit will be "drupal_ajax".
So in buildFormDialog I actually need to save the format when originally building the form. Otherwise you can't if it was modal or the off-canvas tray. Don't have time to fix right now.
Comment #57
tedbowOk I removed getDialogSelector() all together and added $dialog_selector parameter to buildFormDialog() because presumably you can actually change the selector anyways. So this would allow that.
Comment #58
effulgentsia commentedYay, #57 works correctly on my machine :)
This is an incomplete review, but just getting a couple questions started:
Why is "drupal_ajax" in this list? Also, instead of hard-coding "drupal_dialog.off_canvas", could we test for
drupal_dialog.*anddrupal_modal.*?Now that we have the trait, is there a reason we need or want these as explicit dependencies?
Comment #59
effulgentsia commentedWe have no real caller or test coverage of passing TRUE for
$create_cancel. Are we sure we want the parameter? If so, we need test coverage for it.Comment #60
effulgentsia commentedWe should move this test outside of the
outside_inmodule. We already have aAjaxTestDialogFormtest. Perhaps it makes sense to merge into that one? Or, if we want to keep that one without the trait, then maybe an additional class alongside that one that does use the trait?Comment #61
effulgentsia commentedAdding
#submitassumes that$form['actions']['cancel']is a button, but for most forms, it's a link. While we do still have some forms that use a Cancel button instead of a link, I think the better pattern is for it to be a link, so we must cover that case. I wonder if for this issue, if it would be enough to simply hide ('#access' => FALSE)$form['actions']['cancel'], regardless of whether it's a button or link, since presumably the dialog itself has its own close button. And then we can have a follow-up for adding code to unhide the form's cancel button/link, and make it work in either case.[EDIT: I might be wrong on this code not working for links. #ajax['callback'] might be enough to handle the link case and the form-specific stuff would just be ignored. But if that's true and we want to leave it like this rather than just hide the element, let's add test coverage for it, and improve the code comments to clarify that both are handled.]
Comment #62
effulgentsia commentedFor non-modal dialogs, this wouldn't be unique. Besides, messing with the form's HTML ID could have undesired side-effects on contrib/custom code.
FormBuilder::prepareForm()sets$form['#attributes']['data-drupal-selector'], so can we use that for ourReplaceCommandselector?Comment #63
effulgentsia commentedWhy are we setting the event instead of letting
RenderElement::preRenderAjaxForm()default it to mousedown for buttons and click for links? That function documents why it uses mousedown, and if we feel a need to override that, we need to document why we're doing that. Otherwise, if this trait is used by a form with an autocomplete textfield, it might encounter the kinds of errors thatRenderElement::preRenderAjaxForm()is trying to avoid.Comment #64
effulgentsia commentedYay for this patch reducing to only 2 lines of code what's needed for forms in dialogs to fix this bug!
However, it seems to me we shouldn't need these 2 lines at all. Is it possible to make all forms in dialogs "just work", without needing this opt-in? Could
DialogRendererand/orsystem_form_alter()accomplish the same thing as the trait, but on behalf of all forms in dialogs automatically?Comment #65
tedbow@effulgentsia thanks for the review. No patch attached right now just addressing some of the review.
#64
My thought is that since forms don't work well now if you use ajax submit and the dialog system that people who were using forms in this way in contrib probably had to do there own workarounds. So they might have added their own ajax callbacks, redirects, etc.
So I think opting in all forms if they are in the dialog would be dangerous and could break existing contrib and custom forms.
One option though might be to use some sort of opt in property like
$form['#dialog_form'] = TRUEThen we could see if DialogRenderer and/or system_form_alter() could handle it.
I am going to mention this idea on #2830584: Use modals for creating, updating, and deleting workflows, with a new DialogFormTrait which is also using this trait.
#62
That sounds like a good idea
#59
I was creating this trait to work with both this issue and #2830584: Use modals for creating, updating, and deleting workflows, with a new DialogFormTrait
The other issue used the
$create_cancelbut it might not make sense there.But yes if it stays it would need test coverage.
#57.1
'drupal_ajax' is in the list because if it is not the this will not pass during the ajax call back for the submit buttons and the whole process will not work. (just checked to be sure)
But I also just realized that this will also catch forms that are not in a dialog using an ajax call back.
The idea of this trait is that you could use on a form and then if the form is a dialog it will work but also if it is not in dialog it will still work. I think this would mess that up and probably if we want test coverage for both scenarios if it seems like that is good use of the trait.
One thought I had for a solution would be to do something like
When the form is first rendered.
Then if the request format is
drupal_ajaxthen we check 'reneder_in_dialog'Yes we should just be checking for
drupal_dialog.*anddrupal_modal.*. Now that #2844261: Allow dialog links to specify a renderer in addition to a dialog type is in we can be sure this are dialog formats.Comment #66
timmillwoodRe #64: It'd be awesome if we could make forms in dialogs "just work". The trait was added in #2830584: Use modals for creating, updating, and deleting workflows, with a new DialogFormTrait because we have 6 forms all being updated to open in dialogs, so as you can imagine there was a lot of code duplication.
As both workflows and outside_in are in a pretty tight deadline for stability in 8.4.0 I would like to see the new trait marked @internal and added in for these two modules to use. We can then start investigating better solutions. This could involve updating, removing, or marking the trait as @api.
I believe the trait in #57 and #2830584: Use modals for creating, updating, and deleting workflows, with a new DialogFormTrait are now slightly different so we'd need to sync up, or as @xjm suggested, break the trait out into a new issue.
Comment #67
tedbowI agree with @timmillwood's idea to mark the trait as @internal for now. I also think as we find more use cases for the tray in core we will have more forms that would use this trait or the integration in the form system.
If we leave the behavior as an @internal trait for now we have the flexibility to change it if we find the next few use cases make use want to change it slightly.
Comment #68
tedbowThis had to be rerolled after #2872104: Resolve @todo in \Drupal\Tests\outside_in\FunctionalJavascript\OutsideInBlockFormTest
A straight re-roll though did makes tests fail
I had to add an extra wait after submit the off-canvas form via and the new text appearing on the screen.
Comment #69
tedbowThis just adds the changes @timmillwood made to the shared trait here #2830584-120: Use modals for creating, updating, and deleting workflows, with a new DialogFormTrait
The changes are in \Drupal\Core\Form\DialogFormTrait::getDestinationUrl
I am pretty sure this will fail as they do locally but not exactly sure why.
Comment #70
effulgentsia commentedSeems like that's a bug with the submit button then, since it should be triggering a "drupal_dialog" request, not a "drupal_ajax" request. I see two possible fixes for that:
One would be to change ajax.es6.js to only do
when there isn't already a wrapper format query parameter in the URL. That would prevent ajax.js from overriding the existing value. This might be worth opening a separate issue for.
Another would be for the submit button to explicitly re-specify its dialogType and dialogRenderer settings. Here's a patch that does this.
Comment #71
effulgentsia commentedDo we actually need this? FormBuilder::buildForm() already disables redirects for all AJAX forms, so I don't think this is doing anything, and can be safely removed. Or if not, what's the scenario in which this is doing something? Also, see below for something related.
This trait's implementation of getRedirectUrl() is cumbersome. It would make much more sense to call
$form_state->getRedirect(). Except we can't if form redirects are disabled (even for a form we want to redirect). So I opened #2896828: Don't automatically disable redirects for AJAX forms. No need to make any changes to this until that issue gets resolved, and I will certainly not block this issue on that one. Just noting it for the curious. Maybe a @todo comment here linking to that issue would be nice though.Comment #72
timmillwoodI think #69 isn't testing the cancel button, because the test isn't failing as expected. It started failing in #2830584: Use modals for creating, updating, and deleting workflows, with a new DialogFormTrait once we added tests for closing the dialog with the cancel button. I'm not sure how to properly redirect to a destination path in a way which works for sites in a subfolder and sites that are not.
Comment #73
wim leersI agree.
Comment #74
tedbow@timmillwood #72 yes the dialog provided by Settings Tray don't use a cancel button. It uses the default close button that JQuery Provides
Comment #75
tedbowok. talked to @xjm @effulgentsia and @tim.plunkett(form system maintainer) about this again
It seems the current idea is for each module, Settings Tray and Workflow to add there own trait and mark it as @internal. Since these both experimental modules it doesn't make sense to add code to core/lib, even if we mark it as internal.
So I went back our trait and didn't try to make it work for more than the Off-canvas dialogs. I am assuming since this a trait internal to Settings Trait we don't need to handle any other dialog types and that work will be done in #2896535: Create a helper trait for Forms in ajax dialogs
So started this patch from from #70 but removed common trait.
Then I did:
Added trait from #45 So now it is using OffCanvasFormDialogTrait again.
Marked OffCanvasFormDialogTrait as @internal with @todo to remove in #2896535: Create a helper trait for Forms in ajax dialogs (issue maybe updated soon with better description
Added \Drupal\outside_in\OffCanvasFormDialogTrait::getRequestWrapperFormat to make @effulgentsia patch in #70 work with OffCanvasFormDialogTrait
Changed to isModalDialog to isOffCanvasDialog because since this is internal trait for Settings Tray we don't need to handle other dialogs.
Removed unneeded buildForm as per #71.1
Looking at comments since #54 where we started to use the common trait and sees what still applies.
#57.1 added this but we don't need it because are only dealing with 1 type of type the selector will always be the same.
#58.1 is no longer an issue since are only dealing with drupal_dialog.off_canvas we can simply test for
return $wrapper_format === 'drupal_dialog.off_canvas';#57.2
Removed depedencies
#59
is now
protected function buildFormDialog(array &$form, FormStateInterface $form_state) {So no longer applies.
#60
No longer applies because the test should in
outside_insince the trait is in the module again.#61
We still have this logic but should we remove it. Since this is an internal trait we don't need to cover the case where there will be a "cancel" button.
#62 No longer applies because you can't have more than 1 off-canvas for open.
#63 still applies but haven't had time to look into it.
#64 #2896535: Create a helper trait for Forms in ajax dialogs should be updated to decide if a trait is actually the best solution
Comment #76
tedbowOk this patch removes more stuff from OffCanvasFormDialogTrait that were really about making a general trait to work with any forms in the tray.
but since we aren't using it like that we don't need the functionality or the test coverage for that.
Question, do we even need
isOffCanvasDialog()since this an internal trait? This forms will always be in a off-canvas dialog. I think if javascript is disable the contextual links will not work anyway.This would let us remove getRequestWrapperFormat() getDialogType() and getDialogRenderer().
Comment #77
tedbowThis patch removes
isOffCanvasDialog()along the other functions need for it in response to my own question in #76Comment #79
tedbowThis should not use
$this->requestStackIt should just use
$this->getRequest()The above was only working because this had been called before it which set
$this->requestStack.When this function was removed it fails.
Whether we roll back my last 2 patches this change should remain.
Comment #80
wim leersNice simplification! But I think we can simplify further, and at the same time have stronger test coverage, which then will also make it easier to drop the custom code once #2896535: Create a helper trait for Forms in ajax dialogs lands, because we'll have stronger confidence that things keep working, because we've got test coverage for all the edge cases that Settings Tray cares about!
This is the only place where this trait is being used. Why then make it a trait? Why not just add it to
BlockEntityOffCanvasForm?This comment should be removed: nothing else should use this.
What's the point of these methods?
getRedirectUrl()only callsgetDestinationUrl(), so it's just a wrapper. We can definitely remove this one.getDestinationUrl()is a wrapper forgetRedirectDestinationPath()that has a tiny bit of additional logic.Finally,
getRedirectDestinationPath()is a wrapper forgetRedirectDestination()that has a tiny bit of additional logic.This is where it's finally used.
I think a single method would be far clearer. And you're still relying on
\Drupal\Core\Routing\RedirectDestination::get()implicitly, with additional layers on top. I think having your own code would actually be simpler — something like:Ok so this is the only other place where the trait is used — but why aren't we testing something artificial test-only form, rather than the actual form?
In other words: why not provide a custom block whose off-canvas form has this kind of form, and then apply this test coverage to the actual Settings Tray form for that block?
Then we have all this test coverage for the edge cases, and it'll be applied to the actual form where we'll want it to be working :)
Comment #81
tedbow@Wim Leers thanks for the review.
re #80 1,2
Originally I got the trait, in another form from @jrockowitz's work in Webform because he was using something similar in #2866554: Add Quick Edit off canvas form.
The original idea was that other modules in contrib that put form in the tray and would run into the same problem. But as know now this a dialog problem not a off-canvas dialog problem so #2896535: Create a helper trait for Forms in ajax dialogs
Yes it makes sense now to move this logic back to into
BlockEntityOffCanvasForm. Since it is internal trait contrib can't use it anyways.2. trait gone
3, 4. Simplified this down to \Drupal\outside_in\Block\BlockEntityOffCanvasForm::getRedirectUrl()
Not checking UrlHelper::isExternal($query->get('destination')) because other modules should not be using this because of #2894584: Settings Tray provides functionality, not an API: mark PHP and JS as internal
Not sure when this would be external.
5. Removed /core/modules/outside_in/tests/modules/off_canvas_test/src/Form/TestForm.php this was added when I thought the trait might be used by other modules so I thought generic testing would be good. No longer needed.
6. The only thing that is not already tested in \Drupal\Tests\outside_in\FunctionalJavascript\OutsideInBlockFormTest::testBlocks() is validation messages shown in off-canvas dialog. The redirect is testing when saving the form.
Added \Drupal\Tests\outside_in\FunctionalJavascript\OutsideInBlockFormTest::testValidationMessages()
Comment #82
wim leersThanks for the backstory! Adding those as related issues.
Comment #83
wim leersYAY, so much simpler now!
Using
\Symfony\Component\HttpFoundation\Request::get()is a bad practice, because it inspects the query string, request attributes and request body. UsinggetRequest()->query->has('destination')seems better.This if-test can be combined with the previous one.
Why not instead add this to a
\Drupal\outside_in_test\Plugin\Block\OutsideInTestBlock?Comment #84
tedbow1. fixed
2. fixed.
3 Moved a block plugin. change to always through error. Improved test error message.
Comment #85
effulgentsia commentedThis patch removes unused use statements and fixes #62, #63, and #71.1.
Comment #86
effulgentsia commentedThis fixes a pre-existing FAPI bug where HEAD is clobbering the pre-existing
data-drupal-selectorthat got set in prepareForm(). Possibly this should be split into a separate issue, in which case we can go back to hard-coding the HTML ID inBlockEntityOffCanvasFormuntil that separate issue is fixed. However, I posted #85 to see if it passes tests.Comment #87
tedbowI was testing manually I found this weird problem. When submitting form provided by \Drupal\outside_in_test\Plugin\Block\ValidationErrorBlock
This form always produces an error for testing.
First time generating form
You can see here it is directly nested under the 1 div.
but then each time you submit it gets wrapped in a new div. So eventually
😦
This happens with both #84 and #85.
Comment #88
GrandmaGlassesRopeMan@tedbow - #87 should be fixed with #736066: ajax.js insert command sometimes wraps content in a div, potentially producing invalid HTML and other bugs.
Comment #89
tedbow@drpal thanks for info.
Ok setting it backs to need review. I tested the patch mentioned in #88 and it fixes the issue.
Comment #90
effulgentsia commentedI opened #2897377: $form['#attributes']['data-drupal-selector'] is set to a random value so can't be used for its intended purpose to address #86, and updated this patch to work around the bug in the meantime.
Comment #91
wim leers@drpal thanks for pointing to that issue, agreed that that's the cause.
@effulgentsia thanks for #85 + #86 + #90! Agreed that it makes sense to add a work-around here and have a separate issue at #2897377: $form['#attributes']['data-drupal-selector'] is set to a random value so can't be used for its intended purpose. Also: I'd never have found that bug :O
This is looking great — I'm only finding nits now :) After these are addressed, I'll RTBC this.
s/provide for/provided by/
Note that this is using PHP 7 syntax … which means it doesn't actually work in PHP 5.5 & 5.6. In those versions, it'll just print these literally.
It doesn't really matter since this is never asserted.
But it's probably wise to just use the Unicode characters directly here.
See for yourself at https://3v4l.org/Of6HV
This comment is outdated.
And even this can be removed — the validation error will occur 100% of the time now.
Comment #92
tedbow@effulgentsia #90 thanks for opening issue and the fix.
@Wim Leers thanks for (final?🙃) review!
1. fixed
2. ☹️ Ok tried HTML entities but not all are supported. Not using emojis, core will be a little less fun. Fixed
3. Removed this comment and added 1 about the error always happening
4. Removed
Comment #93
tedbowComment #94
wim leersComment #95
effulgentsia commentedTicking credit boxes for reviewers.
Comment #97
effulgentsia commentedPushed to 8.4.x. Thanks all!
Comment #98
tedbow@effulgentsia thanks for your feedback and improvement to the issue and committing! Thanks all!
Comment #100
tedbowChanging to new settings_tray.module component. @drpal thanks for script help! :)