Steps to reproduce:
1. Define a Drupal behaviour. For example:
Drupal.behaviors.testBehavior = {
attach: function (context) {
if (jQuery(context).find('.js-form-type-email').length > 0) {
console.log('Attach behaviours to the email field');
};
}
};
2. Enable Ajax, using the Contact form for example which works with the JS above.
3. As an end user click the "Send message" button to submit the form. Form is submitted with Ajax, and then validation errors are rendered on the empty required fields, as expected.
However, if you look at the JavaScript console, not all is as it should be.
Expected output:
After the Ajax request, Drupal behaviours JavaScript should be attached once.
Actual output:
After the Ajax request, Drupal behaviours JavaScript is attached twice.
This is because the Drupal.Ajax.prototype.success
method attaches behaviours after a successful Ajax request. The Drupal.AjaxCommands.prototype.insert
method, which is called by the Drupal.AjaxCommands.prototype.webformInsert
also attaches behaviours after inserting content into the DOM.
Comment | File | Size | Author |
---|---|---|---|
#9 | Selection_002.png | 263.94 KB | niknak |
#9 | Selection_002.png | 263.94 KB | niknak |
#3 | Screen Shot 2017-10-11 at 9.00.47 PM.png | 114.3 KB | jrockowitz |
webform js.gif | 692.87 KB | keeganstreet |
Comments
Comment #2
keeganstreet CreditAttribution: keeganstreet at Deloitte Digital commentedA workaround is possible because the context parameter is different in each case. In the first call, context is the
<div>
surrounding the<form>
element, and in the second call, context is the<form>
element itself.Comment #3
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedI am not a JavaScript expert and I am very open to suggestions on how to fix this.
I am not seeing Drupal.AjaxCommands.prototype.webformInsert behavior being called during a webform submission Ajax request.
Comment #4
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedHmm... I think we are running into this issue #736066: ajax.js insert command sometimes wraps content in a div, potentially producing invalid HTML and other bugs which would explain the wrapper div.
Comment #5
keeganstreet CreditAttribution: keeganstreet at Deloitte Digital commentedHmm yeah you're right, the
Drupal.AjaxCommands.prototype.webformInsert
command isn't there, its actually theinsert
command which must be triggering the second call.Comment #6
keeganstreet CreditAttribution: keeganstreet at Deloitte Digital commentedDoes that
insert
command originate from the webform or core? Maybe this is actually a core issue.Comment #7
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedThe insert command is from core but called from the \Drupal\webform\Form\WebformAjaxFormTrait::replaceForm
Comment #8
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedComment #9
niknakI'm working for integrate in my website, a solr search engine with search api and ajax facets.
If adding a webform block, in my search result page, the drupal behaviors for ajax facets is called for each block in page loading (see in attached images) off that if I remove the form block, it is called only once.
Can you fix this issue ?
Thx
Comment #10
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedI was unable to fix it. I need someone who is more knowledgeable about JavaScript to help me figure out the problem.
Comment #11
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedLet's see if I can be of any use here.
First of all, dear niknak, you've uploaded the same image twice in #9. So I do not really understand your issue.
Secondly, back to the original behaviors problem. So I took a simple webform with a single radios element on it and enabled ajax on the webform as in the steps to reproduce.
On plain page load (when you just open the page and haven't submitted the webform yet), I get 11 times, in the following order:
drupal.init.js
- the generic on page load fire where context is the wholedocument
.webform.form.js
- extra attach behaviors are invoked for each contextual link. Inwebform.form.js
, look at the very bottom, where it listens to the eventdrupalContextualLinkAdded
. Each time it has a different context - a different contextual link. Let me return in further detail to this piece of code down below.Then I submit a form without values (so I would get a validation error) and this time I get 5 times, in the following order:
ajax.js
Drupal.AjaxCommands.prototype.insert
function. The context is the new (updated) and rendered into html webform. That's apparent and obvious, you're supposed to invoke attachBehaviors on a context you've just inserted into the DOM.ajax.js
Drupal.Ajax.prototype.success
function. It invokes attachBehaviors on the whole<form>..</form>
DOM object.webform.form.js
- same thing as in the initial page load. attachBehaviors() is invoked for contextual links.Now, let me say a few words about attachBehaviors(). One is wrong to expect attachBehaviors() to be invoked exclusively once for every single DOM element on a page. It is a very difficult task to achieve. There is a much better alternative to it - make sure to run your code only once for a given DOM object by using
.once()
. https://www.drupal.org/docs/8/api/javascript-api/javascript-api-overview - this page has necessary insights about it. So overall this ticket smells to me as "works as designed".Let me return at this moment back to this
webform.form.js
. By usinggit blame
I've identified that jrockowitz has introduced that piece of code as part of #2866554: Add Quick Edit off canvas form.. Which in turn seems like a forced step because of a core bug: #2764931: Contextual links don't work with 'use-ajax' links. As a matter of fact, that core bug is fixed and committed into Drupal core 8.5.x.To summarize, the behavior reported in the main description of this ticket is expected and perfectly fine. You, as a custom module developer, are expected to make your behavior look like the following (I am just taking your behavior and wrapping things with
.once()
):Comment #12
Nigel CunninghamJust confirming what @bucefal91 wrote - .once() is the correct thing to do to avoid a behaviour being applied to a DOM element more than once.
Comment #13
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedAlrightie, I will close this issue then.