Problem/Motivation
Clicking on a link in the inline form errors summary error on top of a form scrolls a linked problematic field into view (like an anchor).
This however doesn't work for CKeditor enabled fields, because the original field is hidden.
This is a major issue since we have non-functional links.
Proposed resolution
Because CKEditor is only enabled when JavaScript is available, we can use a JavaScript event listener to redirect the hash change.
Remaining tasks
None
User interface changes
None, it just works as expected.
API changes
None
Data model changes
None
Comment | File | Size | Author |
---|---|---|---|
#95 | 2729663-94-ckeditor_fragment_link_redirect.patch | 16.08 KB | dmsmidt |
#79 | 2729663-79-ckeditor_fragment_link_redirect.patch | 15.99 KB | dmsmidt |
#79 | interdiff-22729663-77-79.txt | 539 bytes | dmsmidt |
#77 | 2729663-77-ckeditor_fragment_link_redirect.patch | 15.98 KB | dmsmidt |
#77 | interdiff-22729663-73-77.txt | 3.44 KB | dmsmidt |
Comments
Comment #2
SKAUGHTit seems that ckeditor plugin itself (not the drupal module) is setting
visibility: hidden; display: none;
when it becomes active.this is preventing hashed links from jumping-to the element as expected.
Comment #3
SKAUGHTadding working image to summary
Comment #4
SKAUGHTchanging component. this is not due to IFE, attention should be focused more directly to ckeditor.
Comment #5
Wim LeersWell… when you use CKEditor, you don't want to see the
<textarea>
. So…inline_form_errors.module
should provide a JS API to allow CKEditor to inform it of how IDs are remapped.Technically, we could do this using form alters already, but we really can't, because we only want this to happen if the user has JS enabled, because only then will CKEditor be enabled.
Comment #6
SKAUGHTi would actually think the issue needs to be files against ckeditor itself, somehow. As it is an actively scripted element then needs to respond to a jumpto link. -- if CK's rich-textarea "'knew to grab original elements id/name attr's" would be nice, and solve the issue.
Comment #7
Wim LeersUpon further investigation I think you're right :)
Patch attached.
Comment #8
Wim LeersI pinged mlewand to review #7 from a CKEditor API perspective.
Comment #9
Wim LeersComment #10
thpoul CreditAttribution: thpoul at Pixual commentedManual tested and can confirm both the Bug and the Fix. Waiting for patch to turn green to RTBC.
Comment #11
thpoul CreditAttribution: thpoul at Pixual commentedAttached image.
Comment #12
thpoul CreditAttribution: thpoul at Pixual commentedTest passed. RTBCing
Comment #13
SKAUGHTi'll confirm manual testing. also works with second rich-textarea field on same node/add page.
Comment #14
Reinmar CreditAttribution: Reinmar commentedI confirm that this is the best way to do scroll to an editor in case of a classic editor. But it may not work with an inline one, since the
container
will point to the editable element and that element may not have an id.But if you have to deal with classic editors only, then it will be fine.
Comment #15
Wim LeersWe don't have jump links in case of in-place editing, so that is then sufficient confirmation.
Thank you very much!
Comment #16
xjmNow that we have an API for JavaScript Testing in core, is it possible to add a test for this? (See also: https://www.chapterthree.com/blog/javascript-testing-comes-to-drupal-8)
Setting NR for feedback on that.
Should this have a docblock?
Comment #17
SKAUGHTadded a docblock
Comment #20
SKAUGHTreattaching
Comment #21
SKAUGHTComment #24
Wim LeersWow, you hit Drupal CI at a very unstable time :P :D Sorry you had to go through that!
This must fit on one line and within 80 characters.
Most importantly, this still needs a JS test of course.
Comment #25
dmsmidtComment #26
dmsmidtI found an additional problem with this patch:
It creates a problematic loop. The event callback changes the hash and thus practically is call it self again. Resulting in:
Uncaught TypeError: Cannot read property 'container' of null(…)
I fixed it and also the max line length thingy.
I'll work on JS test these coming days.
P.S. @Wim Leers, why did you use "window.addEventListener" when everywhere else in the file we use $(window).on()?
Comment #28
dmsmidtRetry with shorter name.
Comment #29
dmsmidtOk, one() wasn't a good idea since we want to keep the link working for ever.
The JS error is fixed anyhow by checking if we can find an CKEditor instance.
Comment #30
dmsmidtAnd here is the complete thing, with functional javascript test.
Test only patch should fail.
Comment #33
Wim LeersAWESOME!!!!! EXCITING!!!!!
WOOOOOOOOOOOOT!!!!!!!!!!!!!!!!!!!!!!!
s/a CKEditor/CKEditor/
Nit: s/array()/[]/
We don't even need this filter. :)
Do we really need these two permissions?
s/CKEditor enabled/CKEditor-enabled/
Looks like there is a bug in the JS test code:
Comment #34
dmsmidtThanks @Wim, fixed all. Although, 'administer nodes' is still needed.
Also removed the wait(), wasn't needed here and only slowed down the test.
Comment #36
Lendude@dmsmidt looking good, nice work! I already live-reviewed this at Dev Days, so anybody with more knowledge of the actual issue gets a +1 from me to RTBC this.
Comment #37
dmsmidtComment #38
Wim LeersOnly minor things left, then this is RTBC! Thanks so much! :)
From #24:
Nit: s/CKEditor enabled/CKEditor-enabled/
Nit: s/page/'page'/
Nit: s/login/log in/
(Or: just remove this — it's quite obvious. The comment is unnecessary.)
These comments feels unnecessary.
Comment #39
dmsmidtCleanup done!
Comment #40
Wim LeersAwesome! This is an awesome contribution — thanks @dmsmidt!
Given your newfound experience with JS tests … would you be interested in writing more of those? :)
Comment #41
Wim LeersComment #42
dmsmidtThanks you for the quick and elaborative reviews, and of course also a big thanks to @Lendude.
Yes, but my primary focus now is keeping inline form errors in core.
Comment #43
Wim LeersAwesome! Let me know if you need reviews in areas that I can review (see MAINTAINERS.txt). Would love to keep you unblocked, this issue was a very productive collaboration with you :)
Comment #44
alexpottGiven that inline_form_errors is experimental I think this test should be in that module and not ckeditor - less cleanup to do if it is removed.
This looks super useful, and therefore worth generalising to JSWebAssert and making it an assertion.
Comment #45
dmsmidtI can move the test, that is the easiest, and should because it depends on an experimental module.
But, the generic case still should be tested: "there is a hash link to a hidden textarea (because of CKEditor), and this link should be redirected to the correct element".
So I can also rewrite the test to not depend on "Inline Form Errors".
Feedback?
Comment #46
dmsmidtOfftopic:
Currently we just check if the top of the element is in the window vertically. We could make a more generic assertion with some options.
For example it would allows you to assert visibility of [top|right|bottom|left|all] of the element.
Comment #47
Wim LeersI agree that the test should be moved there.
However, I think it'd be valuable to have a similar test inside the
ckeditor
module that does NOT useinline_form_errors
. Because this needs to work correctly for use cases other thaninline_form_errors
too.#45: oh hah, seems like we are thinking the exact same thing :)
#46 + @alexpott's remark that that looks super useful: closely related: https://github.com/WICG/IntersectionObserver/blob/gh-pages/explainer.md.
Comment #48
dmsmidtOk I'll make two tests :-)
Comment #49
dmsmidtNew patch with two tests for CKEditor and inline_form_errors, instead of one in CKEditor which depends on inline_form_errors.
There is overlap between the tests, but since inline_form_errors is experimental this should be ok.
Once inline_form_errors is integrated we can remove some of the duplication, and in case it is removed from core there is no problemo. But let's decide that we don't remove it ;-)
I didn't provide an interdiff because you will go bonkers.
Just note that I renamed
$javascript
to$visibility_check_javascript
for readability.Oh, and I moved the created user in the CKEditor test to a protected property and gave the class a rather generic name (CKEditorIntegrationTest) so that we can easily add some more tests with the same setUp().
Comment #50
dmsmidtComment #51
dmsmidtWoot, here is the test with two additional JSWebAsserts for visibility in the viewport.
You can assert the visibility (or not visibility) of any corner of the element, or the complete element (default).
Note that the assertion messages are formulated the other way around compared to simpletests. It seems that in phpunit the message says what went wrong, instead of what should have happened.
If you like to manually test for example if the new assertions works in a different cases than the given CKEditor test replace:
by
This will check if the complete node is in view, but also makes the node faaaar to large to be completely in view.
I also wrote a helper function to get a screenshot with the viewport drawn on top of it (I may write a patch for Drupal as well to incorporate this helper).
Page before triggering the hash change:
Page after trittering the hash change:
Comment #52
Wim LeersThat looks extremely helpful!
s/or it's specific corner/or its specific corner/
Assigning to @dawehner for review since this is adding new JS function testing assertion methods.
Comment #53
dmsmidtDid it :-) It's online with some examples: #2755873: Add ability to show the actual viewport on screenshots in JavascriptTestBase.
Comment #54
Wim LeersAwesome!
Comment #55
Wim LeersWent for a review anyway. Found a bunch of nits.
I found this particularly strange, but apparently it's what several other functional JS tests do. So that's consistent then.
s/it's/its/
s/css/CSS/
s/xpath/XPath/
Can we put this on the same line?
s/behavior//
I think you meant prerequisite, although this also exists:
:D
Trailing whitespace.
Comment #56
droplet CreditAttribution: droplet commentedIt should be avoided to change the testing factor if we can.
Comment #57
dmsmidt@droplet, what do you mean?
Without that margin the CKEditor is in view without changing the hash.
I could replace it by changing the window size for the test, is that preferable?
However then you are testing with an exotic screensize / viewport size.
Comment #58
Wim Leers#57++
Comment #59
dmsmidt#55 cleanup done!
Comment #60
Wim LeersWonderful, thanks!
Comment #62
LendudeUnrelated fails, back to RTBC
Comment #64
Wim LeersComment #65
effulgentsia CreditAttribution: effulgentsia at Acquia commentedSilly JS newbie question, but why
document.location
in the setter, but onlylocation
in the getter?Comment #66
Wim Leers#65: They're completely equivalent. Do this in your browser console:
They're even identical (i.e. the same exact object), as you can see. Feel free to change either one on commit for consistency.
Comment #67
effulgentsia CreditAttribution: effulgentsia at Acquia commentedWe have no other code in HEAD that uses
document.location
. We have a bunch of code that uses barelocation
and some that useswindow.location
.Comment #68
thpoul CreditAttribution: thpoul at Pixual commented#67 Here is the change to be more consistent with HEAD
Comment #69
Wim LeersThanks @thpoul!
IMVHO it's a whole new level of nitpicking though. If
eslint
doesn't catch it, we don't need to care about it IMO.Comment #70
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThe nitpick was because I'm not knowledgeable enough in JS to know if
location
,document.location
, andwindow.location
are always the same object, or if there are some conditions that cause them to differ (e.g., in the presence of iframes, etc.).I manually tested #68, but found a problem with Back button integration. In HEAD, when I click on the fragment link from the inline form error (per issue summary screenshot), it doesn't scroll me to the body field (hence, this issue), but the browser Back button works: clicking it once takes me back to the URL without the fragment, clicking it again takes me to the state of the form prior to the submission that caused the error, clicking it again takes me back to the prior page I was on before this form, etc. With #68, the scroll problem is fixed, but then clicking Back repeatedly keeps me exactly where I am. I'm guessing because the back button keeps retriggering the
hashchange
event? This is on Chrome.Comment #71
Wim LeersLet's add test coverage for using the Back button.
$session->back()
should do the trick per http://mink.behat.org/en/latest/guides/session.html?highlight=back#using....This may need to use
history.replaceState()
.EDIT: great find btw, @effulgentsia!
Comment #73
thpoul CreditAttribution: thpoul at Pixual commentedThis should fix the back button functionality. I tried extending the test with
$session->back();
but for some reason phantomjs hangs when the url has a fragment (tried without a fragment) while$session->reload();
works just fine. Any ideas why?Comment #74
droplet CreditAttribution: droplet commentedit can be an error I think. e.g.
CKEDITOR.dom.element.get('#random').getEditor()
** be sure the JS test also catch it.
We knew where the CKEditor has been loaded. Another workaround, we maintain a cached map from CKeditor when it's initiated. (& destroy)
e.g.
to `hashchange.ckeditor`
Comment #75
Wim Leers#73:
window.location
vsdocument.location
consistently.back()
in the past. I propose we don't test the "back" button then.Comment #76
dmsmidtThis takes tooo long ;-) Working on it again today.
Comment #77
dmsmidtAlright, here we go.
#74, sorry I don't understand exactly what you mean. But your last point is invalid, we want to react on a generic hashchange, we don't define our own hashchange.ckeditor event.
#75:
window.location
vsdocument.location
is consistent, double checked.back()
function is broken after hash changes. However I managed to test it by going back in the history with JavaScript, jay!Furthermore I removed some code duplication in
FormErrorHandlerCKEditorTest::testFragmentLink()
.Comment #78
droplet CreditAttribution: droplet commented@dmsmidt,
Many way to trigger hash changes and the URL could contain example.com/#random-non-ckeditor-instances.
hashchange.ckeditor
This is namespaced. Not actually a custom event. let's say I don't like the code above. I can unload this function event handler only without affected all my other `hashchange` event in JS.
Comment #79
dmsmidt@droplet, I see, ok lets name space it then, no problemo.
Comment #80
dmsmidtUpping the priority of this because it is a blocker for Inline Form Errors, with is under consideration for removal from core.
See: #2504847-57: [meta] Roadmap for stabilizing Inline Form Errors module (IFE)
Comment #81
Wim Leers#76: I agree :(
#77:
Nice! :)
This looks great. AFAICT all feedback was addressed.
Comment #83
LendudeUnrelated fail, see #2828045: Tests failing with unrelated „Translation: The French translation is not available”, waiting for that to go away before RTBCing again
Comment #84
LendudeBack to RTBC
Comment #86
dmsmidtAgain, unrelated fail.
Comment #89
dmsmidtComment #91
dmsmidt4th time, unrelated fail.
Comment #92
webchickOk, looks like this is solving a major bug, unblocks the inline form errors initiative, and has been reviewed by at least one JavaScript maintainer. Great tests, too! Let's do it! :)
Credit.
Comment #93
webchickShoot, this patch doesn't apply cleanly to 8.3.x. :( Could we get a re-roll for that branch as well so they can be committed simultaneously?
Comment #94
dmsmidtComment #95
dmsmidtRe-roll against 8.3.x
Comment #96
LendudeRe-roll looks great, back to RTBC.
Comment #99
catchCommitted/pushed to 8.2.x, thanks!
Comment #100
Wim LeersYAY!
Comment #101
dmsmidtNice, thanks all. Next!
Comment #102
dmsmidtFor the interested, I explained about the new JS assertions added in this patch here: https://www.triquanta.nl/blog/drupal-8s-functional-javascript-testing-ju...
Comment #103
Wim LeersGreat blog post, with illuminating screenshots!