67

I have a form that has two buttons. One for saving a record and the other for cancelling the save procedure. I am using the rails.js (a common AJAX/jQuery plug-in for those of you not in the know) javascript file that works with jQuery for unobtrusive javascript/ajax calls. When I send the form data over ajax, I want the name and value of the button I clicked to be submitted with the rest of the data so that I can make a decision on what to do based on which button was clicked.

The method in the rails.js file uses .serializeArray() for sending form data to the server. The problem is that this doesn't include the name/value pair of the button I've clicked. jQuery's website states that they do this on purpose (eventhough its my opinion that they should):

"The .serializeArray() method uses the standard W3C rules for successful controls to determine which elements it should include; in particular the element cannot be disabled and must contain a name attribute. No submit button value is serialized since the form was not submitted using a button."

How can they assume that a form WASN'T submitted using a button? This makes no sense and a wrong assumption I believe.

Under the W3C rules the button which was activated for the submission of a form is considered a successful control.

Since the developers of jQuery have decided to do this on purpose, can I assume that there is another method that DOESN'T exclude the activated button in a serialization?

EDIT: Here is quick example of what my form might look like...

<!DOCTYPE html5>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
  $(document).ready(function() {
    $('#form').submit(function(e) {
      // put your breakpoint here to look at e
      var x = 0;
    });
  });
</script>
</head>
<body>
  <form id="form">
    <input name="name" type="text"><br/>
    <input name="commit" type="submit" value="Save"/>
    <input name="commit" type="submit" value="Cancel"/>
  </form>
</body>
aarona
  • 35,986
  • 41
  • 138
  • 186

6 Answers6

76

Is [there] another method that DOESN'T exclude the activated button in a serialization?

There is not, the behavior is based on the submit event of the <form>, not of a button, e.g. hitting enter or calling .submit() in JavaScript. You're mixing 2 concepts here, a .serialize() or .serializeArray() may or may not have anything to do with a button click - it's just a separate event altogether, they're not connected. These methods are at a higher level than that: you can serialize a form (or a subset of it) at any time for any reason.

You can however add the submit name/value pair like a normal form submitting from that button would, if you're submitting from a button for example:

$("#mySubmit").click(function() {
  var formData = $(this).closest('form').serializeArray();
  formData.push({ name: this.name, value: this.value });
  //now use formData, it includes the submit button
});
Luke
  • 4,825
  • 2
  • 30
  • 37
Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
  • @Nick, thanks for the quick reply. I will have edit the `rails.js` (and possibly submit a patch) to make this work. I'll give this a shot. – aarona Oct 24 '10 at 11:10
  • @DJTripleThreat - I'm not that familiar with their code, is it possible to replace the event handler externally maybe? If you could post it I'll take a look – Nick Craver Oct 24 '10 at 11:12
  • @Nick - check out the file on gh: http://github.com/rails/jquery-ujs/blob/master/src/rails.js. Look at the handler on lines 34 and 76. Is there a way to find out what button was clicked in the `event` object passed by `.submit(function(e){})`? – aarona Oct 24 '10 at 11:49
  • @DJTripleThreat - You can use `e.target` and pass that to `callRemote` is probably the easiest route, just have it accept an event parameter, use `e.target` inside, you can check `if($(e.target).is(":submit"))...` – Nick Craver Oct 24 '10 at 11:53
  • hmm `e.target` is always the `HTMLFormElement` when coming from `.submit`. I don't see anything that determines which button was clicked. Chrome's Developer tools window says that `e` has a variable called `button` but its always undefined. – aarona Oct 24 '10 at 12:00
  • @DJTripleThreat - sorry for the confusion, you need to also attach the `click` handler to the submit button itself, like this: `$('form[data-remote] :submit').live('submit', function (e) { $(this.form).callRemote(e); return false; });` – Nick Craver Oct 24 '10 at 12:05
  • @Nick, ok I realized that what you are showing me already exists in `rails.js` I removed `data-remote` from my buttons. I think I can get it to work if I add that attribute to my buttons. – aarona Oct 24 '10 at 12:16
  • @DJTripleThreat I handle this by appending the `name` and `value` of `document.activeElement` in the submit handler. I don't think it's perfectly cross-browser compatible, but this as part of a bigger abstraction would probably handle it well. – Tim Sep 09 '14 at 18:31
13

Here's a fairly neat way to solve this:

<form>
    <input type="hidden" name="stuff" value="">
    <button type="submit" onclick="this.form.stuff.value=this.value" value="reset">reset</button>
    <button type="submit" onclick="this.form.stuff.value=this.value" value="delete">delete</button>
</form>
KyleMit
  • 30,350
  • 66
  • 462
  • 664
commonpike
  • 10,499
  • 4
  • 65
  • 58
8

I use the following snippet, basically adds a hidden element with same name

 var form = $("form");
 $(":submit",form).click(function(){
            if($(this).attr('name')) {
                $(form).append(
                    $("<input type='hidden'>").attr( { 
                        name: $(this).attr('name'), 
                        value: $(this).attr('value') })
                );
            }
        });

 $(form).submit(function(){
     console.log($(this).serializeArray());
 });
digitalPBK
  • 2,859
  • 25
  • 26
3

This solution is 'universal' as in it will handle all your input submits, passing each as a form variable on submission.

$(document).ready(function(){
    $('input:submit').each(function(){
        $(this).click(function(){
            var formData = $(this).closest('form').serializeArray();
            formData.push({ name: $(this).attr('name'), value: $(this).val() });
        });
    });
});
Robert Waddell
  • 879
  • 7
  • 15
  • 4
    You don't need the `each` call - just call `click` directly on the jquery object representing all input elements – SpoonMeiser May 01 '14 at 14:16
1
    var form = button.closest('form');
    var serialize = form.serialize();

    if (button.attr('name') !== undefined) {
        serialize += '&'+button.attr('name')+'=';
    }
kjdion84
  • 9,552
  • 8
  • 60
  • 87
0

A bit late, but if you don't want to bind an event to each button on your form, you can include this:

$form.on('submit', function (e) {

    let formData = $(this).serializeArray();

    if (
        e.originalEvent
        && undefined !== e.originalEvent.submitter
        && undefined !== e.originalEvent.submitter.name
        && typeof e.originalEvent.submitter.name == "string"
        && e.originalEvent.submitter.name.length >= 1
        && undefined !== e.originalEvent.submitter.value
    ) {
        formData.push({
            name: e.originalEvent.submitter.name,
            value: e.originalEvent.submitter.value,
        });
    }

    // ...

});

Any submit button on your form that has a string name at least 1 character long and a defined value will be included in the formData array of objects.

Adambean
  • 1,096
  • 1
  • 9
  • 18