Best way to add an extra (nested) form in the middle of a tabbed form
Asked Answered
P

9

12

I've got a web application, consisting mainly of a big form with information. The form is split into multiple tabs, to make it more readable for the user:

<form>
<div id="tabs">
  <ul>
    <li><a href="#tab1">Tab1</a></li>
    <li><a href="#tab2">Tab2</a></li>
  </ul>
  <div id="tab1">A big table with a lot of input rows</div>
  <div id="tab2">A big table with a lot of input rows</div>
</div>
</form>

The form is dynamically extended (extra rows are added to the tables). Every 10 seconds the form is serialized and synchronized with the server.

I now want to add an interactive form on one of the tabs: when a user enters a name in a field, this information is sent to the server and an id associated with that name is returned. This id is used as an identifier for some dynamically added form fields.

A quick sketchup of such a page would look like this:

<form action="bigform.php">
<div id="tabs">
  <ul>
    <li><a href="#tab1">Tab1</a></li>
    <li><a href="#tab2">Tab2</a></li>
  </ul>
  <div id="tab1">A big table with a lot of input rows</div>
  <div id="tab2">
    <div class="associatedinfo">
    <p>Information for Joe</p>
    <ul>
      <li><input name="associated[26][]" /></li>
      <li><input name="associated[26][]" /></li>
    </ul>
    </div>

    <div class="associatedinfo">
    <p>Information for Jill</p>
    <ul>
      <li><input name="associated[12][]" /></li>
      <li><input name="associated[12][]" /></li>
    </ul>
    </div>
    <div id="newperson">
      <form action="newform.php">
        <p>Add another person:</p> 
        <input name="extra" /><input type="submit" value="Add" />
      </form>
    </div>
  </div>
</div>
</form>

The above will not work: nested forms are not allowed in HTML. However, I really need to display the form on that tab: it's part of the functionality of that page. I also want the behaviour of a separate form: when the user hits return in the form field, the "Add" submit button is pressed and a submit action is triggered on the partial form.

What is the best way to solve this problem?

Prunella answered 27/12, 2010 at 23:43 Comment(1)
use one big form with multiple buttons. each button can have a different value, which is how your back-end code can tell which button was actually pushed to submit the form. your back end code can then do different things depending on which button was pushed. or you can even make the button on the "sub form" call AJAX instead of submitting the form, which allows you to query the server and get a response without refreshing the page. you can then use javascript to use the AJAX query response to create the extra form field(s).Lapful
T
10

One thing that you could do is instead of setting the type of the "Add" input element to "submit", set it to "button" and add an id attribute for registering a click handler:

<!-- ... -->
    <div id="newperson">
      <p>Add another person:</p> 
      <input id="extra_input" name="extra" /><input id="add_button" type="button" value="Add" />
    </div>
<!-- ... -->

The reason for input type "button" is that the resulting button on the page looks exactly like a submit button, but it does nothing by default when clicked, allowing you to customize its effect. Also, input type "button" is supported in all browsers that support HTML 3.2 or later including HTML 5.

Somewhere in your Javascript you probably have a function that you wish to be called whenever the user clicks the "Add" button. Supposing that it is called addAnotherPerson, you simply call addAnotherPerson within a click handler:

// This is jQuery code, but all JS frameworks that I have seen have a similar feature to register a click handler on a DOM element referenced by ID.
$("#add_button").click(function (event) {
    addAnotherPerson();
    $("#extra_input").val(""); // reset the value
});

You also need to add a keydown handler to the "extra" text input to perform the same action when the user presses down the Enter key:

$("#extra_input").keydown(function (event) {
    if (event.keyCode == 0xa || event.keyCode == 0xd) {
        addAnotherPerson();
        $("#extra_input").val(""); // reset the value
    }
});

Be sure to add the attribute autocomplete="off" to the "extra" text input.

The addAnotherPerson function could send an asynchronous POST request containing the value in the "extra" text box, updating the DOM afterward if appropriate.

EDIT: If you also want to support users who have Javascript disabled, then by adding name attributes to every submit button with a unique name attribute value for each, your server-side application will be able to tell which button that the user clicked.

Take, for example, this code from http://www.alanflavell.org.uk/www/trysub.html:

<table>
<tr><td align=left><label for="aquavit_button"><input id="aquavit_button" type="submit" name="aquavit" value="[O]"> Aquavit</label></td></tr>
<tr><td align=left><label for="beer_button"><input id="beer_button" type="submit" name="beer" value="[O]"> Beer</label></td></tr>
<tr><td align=left><label for="champagne_button"><input id="champagne_button" type="submit" name="champagne" value="[O]"> Champagne</label></td></tr>
<tr><td align=left><label for="water_button"><input id="water_button" type="submit" name="water" value="[O]"> Dihydrogen monoxide</label></td></tr>
</table>

If the user clicks on the "[O]" button next to "Beer", then the browser sends a "beer" POST variable, but not an "aquavit", "champagne", or "water" POST variable. Try it out at http://www.alanflavell.org.uk/www/trysub.html.

One tricky issue with this technique is the case where the user submits the form by pressing the Enter key within a text input. Firefox sends a POST variable for the first submit button in the form ("aquavit" in this case) whereas Internet Explorer does not send a POST variable for any of the submit buttons. You can patch over this difference by adding a hidden, nameless submit button to the very start of the form:

<form action="bigform.php"><input type="submit" style="display:none;" />
<!-- ... -->

EDIT2: If you always make sure that the value in the "extra" text input is cleared by Javascript before the big form is submitted, then you will be able to tell if the user has Javascript disabled and they press the Enter key in the "extra" text input (indicating that they want the add-a-person action to run) by checking whether the "extra" POST variable is not empty on the server side. If it is not empty, then you can assume that the user has Javascript disabled and that they want to add a person.

Add this code:

$("#bigform").submit(function (event) {
    $("#extra_input").val(""); // reset the value in the "extra" text input
    return true;
});
Trichite answered 31/12, 2010 at 16:54 Comment(0)
I
6

HTML should be semantic, and it should be considered first - i.e. it should have a clear structure and meaning without an understanding of what javascript lies on top. Like the way a <ul> should only contain <li> elements, a <form> should have only a single purpose. Using javascript to enhance a form by retrieving dynamic data is fine, but if an extra HTTP POST is used (the type that modifies data on the server), then this really represents a second purpose, and thus a second form should be used. A second form, with its own "action" attribute, signals that a distinct behaviour is happening here, and is not only more conceptually sound, but easier to analyse, debug or extend when necessary.

<form id="bigform" action="bigform.php">
    <!-- Tabs here -->
</form>
<form id="newperson" action="newform.php">
   <input name="extra">
   <button type="submit" value="Add">
   <!-- Note also the use of <button> instead of <input> -->
</form>

The important thing is that you can still achieve the desired flow within the main form. On your main form, instead of using a nested form, use a <fieldset>. This fieldset will behave as a proxy for the newperson form. The input inside here can have an ID, but should not have a name since its value is not meant to be submitted with the main form. As already mentioned in other answers, the button should be changed to a type of "button" and not "submit".

<form id="bigform" action="bigform.php">
    <!-- Tabs here -->
    <fieldset id="newpersonProxy">
        <input id="extra">
        <button type="button" value="Add">
    </fieldset>
</form>

Now add the desired behaviour unobtrusively through Javascript, by:

  1. Hiding the second form, #newperson.
  2. Responding to "submits" by binding to the keyup event of any input within #newpersonProxy, as well as to the click event of the button. For the keyup event, use event.which instead of event.keyCode as jQuery normalises this. You want event.which == 13 for the enter key.
  3. Then copying the value from the proxy inputs over to the real form, before calling $("#newperson").submit().

This is effectively delegating the responsibility of serializing and sending the data to the second form, allowing any sort of logic to be attached to the second form, and be de-coupled from the first.

Further, as functioning HTML was considered first, a simple tweak can allow the forms to work perfectly without Javascript - just hide the <fieldset> using CSS, and show it with Javascript. If Javascript is turned off, the tabs will break apart, the nested fieldset will be hidden, and the second form will be displayed (and fully functioning, just not via AJAX).

Ingle answered 6/1, 2011 at 3:14 Comment(0)
D
2

Something like the following should do it.

If JavaScript is disabled you will need to detect if the user pressed the "Add" submitButton with:

$_POST["submitButton"] == "Add"


    <!-- other stuff goes here -->

    <div id="newperson">
        <p>Add another person:</p> 
        <input name="extra" id="extra" /><input name="submitButton" id="addPerson" type="submit" value="Add" />
    </div>
  </div>
</div>
</form>
<script>
    var extra = $("#extra"),
        addPerson = $("#addPerson").click(function(e){
        // val would be the new person's name, e.g., "David"
        var val = extra.val();
        // you will want to give the user some AJAXy feedback here.

        $.post(this.form.action, {"extra": val}, function(response){
            // rest the extra div.
            extra.val("");

            /* 'response' could look like:                             */
            // <div class="associatedinfo">
            //      <p>Information for David</p>
            //      <ul>
            //        <li><input name="associated[13][]" /></li>
            //        <li><input name="associated[13][]" /></li>
            //      </ul>
            //   </div>
            $(response).insertBefore("#newperson");

            // or 'response' could be json data, or whatever. I have a feeling
            // you know enough about jQuery to handle the response-data.  :-)

        });
        // prevent it from bubbling up.
        e.preventDefault();
    });

    extra.keydown(function(e){
        // submit the new person's name via ajax if enter is pressed.
        if(e.keyCode === 13){
            addPerson.click();
            e.preventDefault();
        }
    });
</script>
Danford answered 31/12, 2010 at 20:16 Comment(4)
$("#extra") only works when the DOM is ready. You need to wrap the script within $(function () { and }); or the equivalent syntax $(document).ready(function() { ... });.Trichite
@Daniel Trebbien. No you don't. All A-grade browsers can fire this code just fine. Read this for more info about why Closure Library doesn't even have a dom-ready event.Danford
Interesting. The link you provided suggests that your code works if you place the script after the DOM elements that the script requires to be ready. But, if you placed the script element in head (yeah, I know that it's bad, but it's common) or somewhere before, then it would need to be wrapped in $(function () { and });, right? Does this DOM ready-bypassing technique work in IE 6?Trichite
Yup, it should. It has been a while since I've had to worry about IE6...but I think you just need to be sure you aren't modifying the DOM in IE6 before it is "ready". With that said, if the user manages to type in a name, hit submit, and have the AJAX request return before the DOM is ready we may have an issue with my answer if the $(response).insertBefore("#newperson"); line isn't wrapped with $();.Danford
A
2

This isn't nearly as verbose as the other answers, but..

It should be super easy ;)

  • Get rid of your FORM tags completely
  • Use jQuery to get input field values
  • When a user clicks the submit "a" element that looks like a submit button, it simply makes an ajax request, capturing the values of the input fields.

Here's an example that shows a bit of the formatting I'm referencing, however this retains the form tags and is an older example from when I used the scriptaculous library. Here's the JS, it demonstrates the a bit of the structuring.

If you +1 me and let me know u like this answer, I will develop a functional example for you ;)

Cheers! Kirk

Amaliaamalie answered 6/1, 2011 at 12:58 Comment(0)
J
1

Why don't you nest the form objects and handle ALL the actions in one php code...

# have this form appended to the DOM via ajax/js when a link is clicked
<div id="newperson">
  <p>Add another person:</p> 
  <input type="text" name="associated[new][]" />
</div>

Then use php logic on the backend to create the association if it is "new":

foreach($_POST['associated'] as $obj) {
  if($obj == "new")
    // create a new record
  else
    // do your other processing
}
Johnny answered 31/12, 2010 at 19:2 Comment(0)
D
1

You could move the add form outside of the parent form and hide it using CSS, then, as another poster said, replace the submit button for the add form with a button with a correct id, and finally, hook into both onkeypress on the input, and onclick on the button to simulate a submission of the actual add form.

<form action="bigform.php">

    ....

    <div id="newperson">
      <p>Add another person:</p> 
      <input name="extra" /><button value="Add"/>
    </div>
  </div>
</div>
</form>
<div style='display:none;'>
    <form action="newform.php" id='add_person_form'>
      <input id='add_person_name' />
      <input type="submit" id='add_person_submit' />
    </form>
</div>

<script>
$('#newperson input').keyup(function(e) {
    if(e.keyCode == 13) submitNewPersonForm();
});
$('#newperson button').click(function(e) {
    submitNewPersonForm();
});

function submitNewPersonForm() {
    // copy the content from the placeholder form
    $('#add_person_name').val($("#newperson input").val());
    // submit this form
    $("#add_person_form").submit();
}
</script>

This should do everything you need while retaining the original functionality, as far as I can tell.

Note: The submit button isn't really needed on the #add_person_form form, I just kept it there so you can easily see where I 'moved' your form to.

Doukhobor answered 31/12, 2010 at 19:23 Comment(0)
M
1

The way I've seen similar scenarios handled is to create a modal page for the "inner" form so no nesting is required. I do not see this method significantly hampering the flow of the page.

Otherwise the other suggestions to submit either the "inner" form or the entire form via AJAX can work as well.

Material answered 6/1, 2011 at 21:50 Comment(0)
B
1

You don't need a nested form, you can use another form(not nested) and use absolute positioning to place it anywhere on the page..anyways all this creation of dynamic controls with id related to name entered by users seems very complicated..may be we can have a more simpler solution if you could share the business problem you may be trying to solve.

Badr answered 7/1, 2011 at 13:27 Comment(0)
B
0

You can use ajax or make the other form outside of the first one and position it using absolute DIV layer.

It's easy to read layer position using javascript. First you will make a placeholder with pre-defined height, then you can display the form above placeholder.

<form>

...
  <div style='height: 300px;' class='placeholder'> ... </div>
</form>

<form>...<form>

You display the second form where .placeholder is

Byrn answered 28/12, 2010 at 0:1 Comment(2)
This will ruin the "flow" of the form: I want to be able to tab through it using only the keyboard. I can fix this by setting a load of tabindexes, but the dynamic content makes this a whole lot of bookkeeping.Prunella
Well that's the only option if you want to have exac form behaviour. Otherwise just drop the nested FORM tag and do everything using AJAX. And you can set the tabindex easily on client using a simple ajax function.Byrn

© 2022 - 2024 — McMap. All rights reserved.