3

What I want to achieve is to track form submits. But because of the many variations that we use for the submit button I want to change my current code:

$(document).on('click','input[type="submit"], button[type="submit"]',function(){

to something that is universal. And I believe the best approach is the $("form")-annotation.

The problem is that for example if a form has an ajax script on it, it gets blocked by my additional script code. Basically what I want to achieve is to have both worlds.

So the first one is what the website currently has (not every websites though):

$("form").submit(function () {
  // ....do something, maybe ajax submit of the form.....
});

and my additional that I want to add without editing any current scripts already found in the website:

$("form").submit(function () {
    $.getJSON("....");
});

The solution for me should be that the second script (the additional) will not interfere with any other form scripts.

AN IDEA

To add a class by using jQuery addClass to the forms of current page.

What is a solution for this?

Sebastian G. Marinescu
  • 2,374
  • 1
  • 22
  • 33
EnexoOnoma
  • 8,454
  • 18
  • 94
  • 179
  • None of the forms have id's? – StaticBeagle Jun 05 '16 at 15:21
  • @RatHat because this will be a solution on the "one size fits all" it must not play with ids. Of course, if somehow can I add through jquery a unique class to the form ? – EnexoOnoma Jun 05 '16 at 16:54
  • You seem to have tried it... doesn't it work? What are you doing inside the `additional`-Callback? Just the `getJSON` call? – Sebastian G. Marinescu Jun 05 '16 at 17:03
  • I think you should use `Promises` or jQuery's `Deferred`. – Ciprianis Jun 05 '16 at 17:06
  • @Ciprianis Why should he use `Promises` ?! This seems too much for this particular use-case... – Sebastian G. Marinescu Jun 05 '16 at 17:09
  • http://stackoverflow.com/questions/1515069/jquery-check-if-event-exists-on-element – BeNdErR Jun 05 '16 at 17:31
  • @Sebastian G. Marinescu - Well, from what I understand he wants to register form submissions. The idea was to submit the form and based on the received answer to register a submission or not. – Ciprianis Jun 05 '16 at 18:11
  • 2
    @Ciprianis I have no idea what that has to do with promises of jQuery's deferred. This question is about whether or not you can find a hook that's strong enough to "take over" `stopImmediatePropagation` or not and does so in an unobtrusive way - sort of a way to be _notified_ of the event but not actually interfere with other handlers. – Benjamin Gruenbaum Jun 05 '16 at 18:14
  • @Ciprianis Ok, now I think I understand your approach. But that would mean, that he has to update every already existing event-handler, isn't it? – Sebastian G. Marinescu Jun 05 '16 at 18:15

5 Answers5

4

I created a little Snippet to demonstrate the issue:

$(document).ready(function() {

  // Registering form-submission as the first would be a possibility
  $('form').on('submit', function(e) {
    console.log(e.target);
    console.info('My first callback is executing');

    // Do some stuff here, but don't mess with the event-object 
    // (like preventing default or stopping the event-chain)
  });

  // Then afterwards everything else that *might* catch the event
  $('form').on('submit', function(e) {
    console.log(e.target);
    console.info('My second callback is executing');

    // Usually some Ajax-Handler-Callback, that handles sending the form,
    // will preventDefault() and stopImmediatePropagation() - that is why 
    // your general first listener must be registered before any of these callbacks

    console.warn('Second callback stops the event from bubbling/propagating');
    e.stopImmediatePropagation();
    e.preventDefault();
  });

  // This will never happen
  $('form').on('submit', function(e) {
    console.log(e.target);
    console.info('My third callback will never execute');
  });
  
  // Using a delegated event-listener with `useCapture` lets this execute first
  document.addEventListener('submit', function(e) {
    console.info('Capturing the event natively');
  }, true);

});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<h1>My Website with a multi-handled form</h1>

<form class="" action="" method="post">
  <input type="text" name="test" value="">
  <button type="submit" name="button">Send</button>
</form>

Output of the Snippet, when submitting the form:

Capturing the event natively

<form class action method="post">…</form>

My first callback is executing

<form class action method="post">…</form>

My second callback is executing
Second callback stops the event from bubbling/propagating


What did just happened?

By pressing the submit-button, our form emits the submit-event. The Browser starts with the event-propagation in a specified event-order. There are two phases of event-propagation: event-capturing and event-bubbling.

Now our first called event-listener is the one with the useCapture-directive.
This is during the capture-phase of the event-propagation.

Explanation for useCapture taken from MDN:

capture: A Boolean that indicates that events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree.

When done, the Browser starts with the bubbling-phase of the event-propagation.

This is where all $('element').on() and element.addEventListener() (without the useCapture option) registered listeners are called in their appearing order.

During this phase our second listener is not only preventing default (not submitting the form the standard-way), but also stopping the event-propagation by calling e.stopImmediatePropagation().

After that the event-chain/event-propagation stops immediately.

That is why our third listener will never execute.

On a side note: When using jQuery and exiting an event-callback with return false, jQuery will execute e.preventDefault() and e.stopPropagation() automatically.

See: http://api.jquery.com/on/


Conclusion

You basically have two possibilities for your scenario:

  1. Register your default general event-listener before anything else (first event-registration in Snippet).

  2. Register an event-listener during the capture-phase, to capture the event and handle things before the other listeners from the bubbling-phase get called (last event-registration in Snippet).

With both methods you should be able to do your stuff without interfering with other event-listeners.

Community
  • 1
  • 1
Sebastian G. Marinescu
  • 2,374
  • 1
  • 22
  • 33
  • Hello and thank you for your answer. I can not test it right now, however if I get this right, I have to place the "second handler" script and the "delegated event-handler" below the script that submits the form? – EnexoOnoma Jun 05 '16 at 22:49
  • Hi @Xalloumokkelos, I just added more explanation to my answer. I hope everything is understandable now, if not let me know what confuses you. – Sebastian G. Marinescu Jun 06 '16 at 12:04
1

Use this:

    $(document).on("submit", "form", function (e){

Complete example:

    <form id="form1"><input type="text" /><input type="submit" /></form>
    <form id="form2"><input type="text" /><input type="submit" /></form>

Js:

    $(document).on("submit", "form", function (e) {
      var oForm = $(this);
      var formId = oForm.attr("id");
      var firstValue = oForm.find("input").first().val();
      alert("Form '" + formId + " is being submitted, value of first input is: " + firstValue);
      return false;
     })

     [JS fiddle]: http://jsfiddle.net/pZ3Jn/
0

What I want to achieve is to track form submits.

Why not just use $(document).on('submit', 'form', function(){});?

It will be triggered on every form submit, no matter how it is being submitted.

$(document).ready(function() {

  // Some already existing event handler
  $('#bir').on('submit', function(e) {
    console.log($(this).attr('id'));
    e.preventDefault();
  });

  // Your universal form event handler
  $(document).on('submit', 'form', function(e) {
    console.log('Additional functionality for: ' + $(this).attr('id'));
  });

});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<form id="bir">
  <input type="submit" />
</form>

<form id="ikki">
  <input type="submit" />
</form>
Uzbekjon
  • 11,655
  • 3
  • 37
  • 54
  • 1
    But what if an already existing event handler would also `e.stopImmediatePropagation()` ? – Sebastian G. Marinescu Jun 05 '16 at 17:24
  • Well, the solution would be to do what you are suggesting, registering the "universal" event handler first. I think it is better to register one event handler on `document` as above than on every `form` element with `$('form')`. – Uzbekjon Jun 05 '16 at 17:28
  • Yeah, using delegated event-handlers is always nice, if you are handling multiple stuff on the same page... but if another event-handler would stop the bubbling, the delegated event-handler would _never_ execute - wouldn`t it? – Sebastian G. Marinescu Jun 05 '16 at 17:38
  • 1
    What I am trying to say - If in your example the form with the id `ikki` would also have an event-handler, but that would stop the event-chain ( `e.stopImmediatePropagation()` ), then your delegated event-handler would still **not execute**! – Sebastian G. Marinescu Jun 05 '16 at 17:45
  • Yeah, I understand what you mean and the solution is to register the "universal" handler first (as you are suggesting). However, I am yet to see anyone using `e.stopImmediatePropagation()`. – Uzbekjon Jun 06 '16 at 04:26
  • Ok, but many tutorials show the use of `return false;` at the end of a submit-handler, and most of the tutorials or people are using jQuery, so then it becomes relevant, see: http://stackoverflow.com/questions/1357118/event-preventdefault-vs-return-false – Sebastian G. Marinescu Jun 06 '16 at 11:10
0

I've ran into this issue a few times before and my solution was to capture all form nodes and associate them with a special action . This may not be practical but is a possible solution for you also .

Example

//Getting all form elements 

var formNodes = document.getElementsByTagName('form');

//loop through nodelist and add submit event and special class to each. 

for(var i = 0; i < formNodes.length; i++){

  formNodes[i].addEventListener('submit'  , registerAction)
  formNodes[i].className += "form-" + i;
}


/*This function captures the submitted form and determines 
the action to carry out based off class name .
e.preventDefault will stop page from reloading in case of
making ajax requests.
*/

function registerAction(e){
  e.preventDefault();

  var formTarget = $(e.target).attr('class');

 switch(formTarget){

   case "form-0" :
     // Do something ...

     break;
   case "form-1" :
     // Do something else...
     break;

   default:
     break;


 }

  return false;
}

Keep in mind that the logic inside registerAction can be alter to fit your needs in this situation I used "case statement" because I feel it makes the most sense .

This is not perfect but I hope it gives you an idea..

KpTheConstructor
  • 3,153
  • 1
  • 14
  • 22
0

The problem is that for example if a form has an ajax script on it, it gets blocked by my additional script code.

No, it doesn't. You can bind many handlers on one element.

For rare cases, see the other suggestions, but If I got you right, your basic assumption was that binding a handler on an element cancel the previous one. Well, it doesn't.

Alon
  • 10,381
  • 23
  • 88
  • 152