Как преодолеть ограничение вложенности HTML-формы?

Я знаю, что XHTML не поддерживает вложенные теги форм, и я уже читал другие ответы здесь по поводу относительно этой темы, но я до сих пор не нашел элегантного решения проблемы.

Некоторые говорят, что вам это не нужно, и что они не могут придумать сценарий, если бы это было необходимо. Ну, лично я не могу придумать сценарий, который мне не нужен.

Давайте посмотрим на очень простой пример:

Вы создаете приложение для блога, и у вас есть форма с несколькими полями для создания нового сообщения и панель инструментов с "действиями", такими как "Сохранить", "Удалить", "Отмена".

<form
 action="/post/dispatch/too_bad_the_action_url_is_in_the_form_tag_even_though_conceptually_every_submit_button_inside_it_may_need_to_post_to_a_diffent_distinct_url"
method="post">

    <input type="text" name="foo" /> <!-- several of those here -->
    <div id="toolbar">
        <input type="submit" name="save" value="Save" />
        <input type="submit" name="delete" value="Delete" />
        <a href="/home/index">Cancel</a>
    </div>
</form>

Наша цель - написать форму, не требующую JavaScript, просто старую HTML-форму и кнопки отправки.

Поскольку URL-адрес действия определен в теге Form, а не в каждой отдельной кнопке отправки, наш единственный вариант - опубликовать общий URL-адрес, а затем начать "if... then... else", чтобы определить имя кнопки, которая был представлен. Не очень элегантно, но наш единственный выбор, так как мы не хотим полагаться на JavaScript.

Единственная проблема заключается в том, что нажатие "Удалить" отправит ВСЕ поля формы на сервер, хотя единственное, что необходимо для этого действия, - это скрытый ввод с идентификатором пост-идентификатора. В этом небольшом примере нет ничего сложного, но у меня есть формы с сотнями (так сказать) полей и вкладок в моих LOB- приложениях, которые (из-за требований) должны отправлять все за один раз, и в любом случае это кажется очень неэффективным и трата. Если бы поддержка вложенности форм поддерживалась, я бы по крайней мере мог обернуть кнопку "Удалить" внутри собственной формы только полем post-id.

Вы можете сказать "Просто внедрите" Удалить "как ссылку вместо отправки". Это было бы неправильно на многих уровнях, но самое главное, потому что побочные эффекты, такие как "Удалить", никогда не должны быть запросом GET.

Итак, мой вопрос (особенно к тем, кто говорит, что им не нужна вложенность форм): что ВЫ делаете? Есть ли какое-то элегантное решение, которое я пропускаю, или нижняя строка на самом деле "Требуется JavaScript или отправьте все"?

Ответы

Ответ 1

Я бы реализовал это точно так, как вы описали: отправьте все на сервер и выполните простой if/else, чтобы проверить, какая кнопка была нажата.

И затем я бы выполнил вызов Javascript, связанный с формой onsubmit, которое будет проверять перед отправкой формы, и только отправьте соответствующие данные на сервер (возможно, через вторую форму на странице с идентификатором, необходимым для обработки вещь как скрытый ввод или обновить местоположение страницы с данными, которые вам нужно передать как запрос GET, или сделать сообщение Ajax на сервере или...).

Таким образом, люди без Javascript могут использовать форму просто отлично, но загрузка сервера смещена, потому что люди, у которых есть Javascript, представляют только одну часть данных. Если вы сосредоточены на поддержке только одного или другого, это действительно ограничивает ваши варианты без необходимости.

В качестве альтернативы, если вы работаете за корпоративным брандмауэром или чем-то еще, и у всех отключен Javascript, вам может понадобиться сделать две формы и работать с маской CSS, чтобы она выглядела так, как кнопка удаления является частью первой формы.

Ответ 2

Я знаю, что это старый вопрос, но HTML5 предлагает пару новых опций.

Во-первых, нужно отделить форму от панели инструментов в разметке, добавить другую форму для действия удаления и связать кнопки на панели инструментов с соответствующими формами с помощью атрибута form.

<form id="saveForm" action="/post/dispatch/save" method="post">
    <input type="text" name="foo" /> <!-- several of those here -->  
</form>
<form id="deleteForm" action="/post/dispatch/delete" method="post">
    <input type="hidden" value="some_id" />
</form>
<div id="toolbar">
    <input type="submit" name="save" value="Save" form="saveForm" />
    <input type="submit" name="delete" value="Delete" form="deleteForm" />
    <a href="/home/index">Cancel</a>
</div>

Этот параметр довольно гибкий, но в оригинальном сообщении также упоминалось, что может потребоваться выполнить различные действия с одной формой. HTML5 снова приходит на помощь. Вы можете использовать атрибут formaction для кнопок отправки, поэтому разные кнопки в одной и той же форме могут отправлять разные URL-адреса. Этот пример просто добавляет метод clone на панель инструментов вне формы, но он будет работать так же, как и в форме.

<div id="toolbar">
    <input type="submit" name="clone" value="Clone" form="saveForm"
           formaction="/post/dispatch/clone" />
</div>

http://www.whatwg.org/specs/web-apps/current-work/#attributes-for-form-submission

Преимущество этих новых функций заключается в том, что они делают все это декларативно без JavaScript. Недостатком является то, что они не поддерживаются в старых браузерах, поэтому вам придется выполнять многопоточность для старых браузеров.

Ответ 3

У вас есть формы бок о бок на странице, но не вложенные. затем используйте CSS, чтобы все кнопки выстроились в порядок?

<form method="post" action="delete_processing_page">
   <input type="hidden" name="id" value="foo" />
   <input type="submit" value="delete" class="css_makes_me_pretty" />
</form>

<form method="post" action="add_edit_processing_page">
   <input type="text" name="foo1"  />
   <input type="text" name="foo2"  />
   <input type="text" name="foo3"  />
   ...
   <input type="submit" value="post/edit" class="css_makes_me_pretty" />
</form>

Ответ 4

В HTML5 есть идея "владелец формы" - атрибут "форма" для элементов ввода. Он позволяет эмулировать вложенные формы и будет решать проблему.

Ответ 5

Вид старой темы, но этот может быть полезен для кого-то:

Как упоминалось выше, вы можете использовать фиктивную форму. Мне пришлось преодолеть эту проблему некоторое время назад. Сначала я полностью забыл об этом ограничении HTML и просто добавил вложенные формы. Результат был интересным - я потерял свою первую форму из вложенных. Затем оказалось, что это "трюк", чтобы просто добавить фиктивную форму (которая будет удалена из браузера) до фактических вложенных форм.

В моем случае это выглядит так:

<form id="Main">
  <form></form> <!--this is the dummy one-->
  <input...><form id="Nested 1> ... </form>
  <input...><form id="Nested 1> ... </form>
  <input...><form id="Nested 1> ... </form>
  <input...><form id="Nested 1> ... </form>
  ......
</form>

Прекрасно работает с Chrome, FireFox и Safari. IE до 9 (не уверен около 10), и Opera не обнаруживает параметры в основной форме. Глобальный $_REQUEST пуст, независимо от входных данных. Внутренние формы, кажется, хорошо работают везде.

Не тестировали другое предложение, описанное здесь - fieldset вокруг вложенных форм.

РЕДАКТИРОВАТЬ: Frameset не работает! Я просто добавил основную форму после остальных (не более вложенных форм) и использовал jQuery "clone" для дублирования входов в форме при нажатии кнопки. Добавлен .hide() для каждого из клонированных входов, чтобы сохранить макет без изменений и теперь он работает как шарм.

Ответ 6

Я думаю, что Джейсон прав. Если ваше действие "Удалить" является минимальным, сделайте это в форме отдельно и выровняйте его с другими кнопками, чтобы интерфейс выглядел как единая форма, даже если это не так.

Или, конечно же, перепроектируйте свой интерфейс и позвольте людям удалять где-то еще полностью, что не требует, чтобы они вообще увидели огромную форму.

Ответ 7

Один из способов сделать это без javascript - это добавить набор переключателей, которые определяют действие, которое нужно предпринять:

  • Обновить
  • Удалить
  • Что бы

Тогда действие script будет принимать разные действия в зависимости от значения набора радиокниг.

Другим способом было бы разместить две формы на странице, как вы предполагали, просто не вложенные. Макет может быть трудно контролировать:

<form name="editform" action="the_action_url"  method="post">
   <input type="hidden" name="task" value="update" />
   <input type="text" name="foo" />
   <input type="submit" name="save" value="Save" />
</form>

<form name="delform" action="the_action_url"  method="post">
   <input type="hidden" name="task" value="delete" />
   <input type="hidden" name="post_id" value="5" />
   <input type="submit" name="delete" value="Delete" />
</form>

Использование скрытого поля "задачи" в обработке script для соответствующей ветки.

Ответ 8

Используйте iframe для вложенной формы. Если им нужно обмениваться полями, то... он не очень вложен.

Ответ 9

Это обсуждение по-прежнему вызывает интерес для меня. За исходным сообщением "требования", которые OP, кажется, разделяют, то есть форму с обратной совместимостью. Как кто-то, чья работа в момент написания должна иногда поддерживать поддержку IE6 (и на долгие годы), я копаю это.

Без нажатия рамки (все организации захотят заверить себя в совместимости/надежности, и я не использую это обсуждение как оправдание для фреймворка), решения Drupal для этой проблемы интересны. Drupal также имеет прямое отношение к тому, что в рамках структуры была давняя политика "она должна работать без Javascript (только если вы захотите)", то есть проблема с OP.

Drupal использует его довольно обширные функции form.inc для поиска triggering_element (да, это имя в коде). См. Нижнюю часть кода, указанного на странице API, для form_builder (если вы хотите вникнуть в детали, рекомендуется источник - drupal -x.xx/включает/form.inc). Строитель использует автоматическое создание атрибутов HTML и через него может по возвращении обнаруживать, какая кнопка была нажата, и действовать соответственно (их также можно настроить для запуска отдельных процессов).

Помимо разработчика форм, Drupal разделяет действия "удалить" данные на отдельные URL-адреса и формы, вероятно, по причинам, указанным в исходном сообщении. Для этого требуется какой-то шаг поиска/перечисления (стон другой формы!, но удобен для пользователя) в качестве предварительного. Но это имеет то преимущество, что устраняет проблему "отправить все". Большая форма с данными используется по назначению, созданию/обновлению данных (или даже действию "слияния" ).

Другими словами, один из способов решения проблемы состоит в том, чтобы передать форму на две части, тогда проблема исчезнет (и методы HTML могут быть исправлены также через POST).

Ответ 10

Мое решение состоит в том, чтобы кнопки вызывали функции JS, которые пишут, а затем отправляют формы с основной формой

<head>
<script>
function removeMe(A, B){
        document.write('<form name="removeForm" method="POST" action="Delete.php">');
        document.write('<input type="hidden" name="customerID" value="' + A + '">');
        document.write('<input type="hidden" name="productID" value="' + B + '">');
        document.write('</form>');
        document.removeForm.submit();
}
function insertMe(A, B){
        document.write('<form name="insertForm" method="POST" action="Insert.php">');
        document.write('<input type="hidden" name="customerID" value="' + A + '">');
        document.write('<input type="hidden" name="productID" value="' + B + '">');
        document.write('</form>');
        document.insertForm.submit();
}
</script>
</head>

<body>
<form method="POST" action="main_form_purpose_page.php">

<input type="button" name="remove" Value="Remove" onclick="removeMe('$customerID','$productID')">
<input type="button" name="insert" Value="Insert" onclick="insertMe('$customerID','$productID')">

<input type="submit" name="submit" value="Submit">
</form>
</body>

Ответ 11

Если вы действительно не хотите использовать несколько форм (как Jason sugests), используйте кнопки и обработчики onclick.

<form id='form' name='form' action='path/to/add/edit/blog' method='post'>
    <textarea name='message' id='message'>Blog message here</textarea>
    <input type='submit' id='save' value='Save'>
</form>
<button id='delete'>Delete</button>
<button id='cancel'>Cancel</button>

И затем в javascript (я использую jQuery здесь для удобства) (хотя это довольно сложно для добавления некоторых обработчиков onclick)

$('#delete').click( function() {
   document.location = 'path/to/delete/post/id'; 
});
$('#cancel').click( function() {
   document.location = '/home/index';
});

Также я знаю, это сделает половину страницы не работать без javascript.

Ответ 12

В ответ на вопрос, отправленный Яром в комментарии к его собственному ответу, я представляю некоторый JavaScript, который изменит размер iframe. В случае кнопки формы можно с уверенностью предположить, что iframe будет находиться в одном домене. Это код, который я использую. Вам нужно будет изменить математику/константы для своего сайта:

function resizeIFrame(frame)
{
    try {
        innerDoc = ('contentDocument' in frame) ? frame.contentDocument : frame.contentWindow.document;
        if('style' in frame) {
            frame.style.width = Math.min(755, Math.ceil(innerDoc.body.scrollWidth)) + 'px';
            frame.style.height = Math.ceil(innerDoc.body.scrollHeight) + 'px';
        } else {
            frame.width = Math.ceil(innerDoc.body.scrollWidth);
            frame.height = Math.ceil(innerDoc.body.scrollHeight);
        }
    } catch(err) {
        window.status = err.message;
    }
}

Затем назовите его следующим образом:

<iframe ... frameborder="0" onload="if(window.parent && window.parent.resizeIFrame){window.parent.resizeIFrame(this);}"></iframe>

Ответ 13

Я просто придумал хороший способ сделать это с помощью jquery.

<form name="mainform">    
    <div id="placeholder">
    <div>
</form>

<form id="nested_form" style="position:absolute">
</form>

<script>
   $(document).ready(function(){
      pos = $('#placeholder').position();
      $('#nested_form')
         .css('left', pos.left.toFixed(0)+'px')
         .css('top', pos.top.toFixed(0)+'px');
   });
</script>

Ответ 14

Хорошо, если вы отправляете форму, браузер также отправляет имя и значение ввода. Итак, что вы можете сделать, это

<form   
 action="/post/dispatch/too_bad_the_action_url_is_in_the_form_tag_even_though_conceptually_every_submit_button_inside_it_may_need_to_post_to_a_diffent_distinct_url"  
method="post">  

    <input type="text" name="foo" /> <!-- several of those here -->  
    <div id="toolbar">
        <input type="submit" name="action:save" value="Save" />
        <input type="submit" name="action:delete" value="Delete" />
        <input type="submit" name="action:cancel" value="Cancel" />
    </div>
</form>

так что на стороне сервера вы просто ищете параметр, который начинает строку ширины "action:", а остальная часть сообщает вам, какое действие нужно предпринять

поэтому, когда вы нажимаете кнопку "Сохранить", браузер отправляет вам что-то вроде foo = asd & action: save = Save

Ответ 15

Я обошел проблему, включив флажок в зависимости от того, какую форму он хотел бы сделать. Затем используется 1 кнопка для отправки всей формы.

Ответ 16

В качестве альтернативы вы можете назначить форму actiob "на лету"... может быть, не лучшее решение, но, конечно же, облегчает логику на стороне сервера...

<form name="frm" method="post">  
    <input type="submit" value="One" onclick="javascript:this.form.action='1.htm'" />  
    <input type="submit" value="Two" onclick="javascript:this.form.action='2.htm'" />  
</form>