Javascript-форум (https://javascript.ru/forum/)
-   Элементы интерфейса (https://javascript.ru/forum/dom-window/)
-   -   Форма внутри формы (https://javascript.ru/forum/dom-window/77618-forma-vnutri-formy.html)

eLDeR 28.05.2019 11:09

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

Есть Код JS
$(function() {
		$("#form_status_added").click(function () {
		var elemCount = document.getElementsByClassName('param').length + 1;
			$("<div class='param mt-3'><div class='row'><div class='col-12'><div class='alert alert-primary text-center' id='totals'><strong>"+elemCount+"</strong></div></div><div class='col-12'><textarea rows='5' class='form-control mb-3' type='text' name='rabota"+elemCount+"[]' placeholder='Наименование видов работ'></textarea></div></div></div><div class='row'><div class='col-12 mb-2 text-center'><strong>Материал</strong></div></div><div class='row' id='mater"+elemCount+"'><div class='col-8'><input class='form-control mb-3' type='text' name='mater_name"+elemCount+"[]' placeholder='Название'></div><div class='col-2'><input class='form-control mb-3' type='text' name='mater_units"+elemCount+"[]' placeholder='Количество'></div><div class='col-2'><input class='form-control mb-3' type='text' name='mater_price"+elemCount+"[]' placeholder='Цена за ед.'></div></div><div class='row' id='nextmater"+elemCount+"'><div class='col-12'><input class='btn btn-primary btn-lg btn-block' type='button' value='Добавить Материал' id='form_"+elemCount+"'></div></div><div class='row'><div class='col-12'><hr></div></div>" ).insertBefore("#next");
			document.getElementById('total').value = elemCount;
		});
	 });
	$(function() {
		$("form_1").click(function () {
			$("<div class='row' id='mater1'><div class='col-8'><input class='form-control mb-3' type='text' name='mater_name1[]' placeholder='Название'></div><div class='col-2'><input class='form-control mb-3' type='text' name='mater_units1[]' placeholder='Количество'></div><div class='col-2'><input class='form-control mb-3' type='text' name='mater_price1[]' placeholder='Цена за ед.'></div></div>" ).insertBefore("#nextmater1");
		});
	 });

$(document).ready(function () {
	var elemtotal = document.getElementsByClassName('param').length;
	document.getElementById('total').value = elemtotal;
});

Код HTML
<div class="param mt-3">
			<div class="row">
			<div class="col-12"><div class="alert alert-primary text-center" id="totals"><strong>1</strong></div></div>
				<div class="col-12"><textarea rows="5" class="form-control mb-3" type="text" name="rabota1[]" placeholder="Наименование видов работ"></textarea></div>
			</div>
			<div class="row">
				<div class="col-12 mb-2 text-center"><strong>Материал</strong></div>
			</div>

			<div class="row" id="smeta_mater1">
				<div class="col-8"><input class="form-control mb-3" type="text" name="smeta_mater_name1[]" placeholder="Название"></div>
				<div class="col-2"><input class="form-control mb-3" type="text" name="smeta_mater_units1[]" placeholder="Количество"></div>
				<div class="col-2"><input class="form-control mb-3" type="text" name="smeta_mater_price1[]" placeholder="Цена за ед."></div>
			</div>
			<div class="row" id="nextmater1">
				<div class="col-12">
					<input id="form_1" class="btn btn-primary btn-lg btn-block" type="button" value="Добавить Материал">
				</div>
			</div>
			<div class="row"><div class="col-12"><hr></div></div>
		</div>
		<div class="row" id="next">
			<div class="col-6">
				<input class="btn btn-primary btn-lg btn-block" type="button" value="Добавить поле" id="form_status_added">
			</div>
			<div class="col-6">
				<input type="hidden" name="total" id="total" value="">
				<button name="updates" class="btn btn-primary btn-lg btn-block">Обновить</button>
			</div>
		</div>


Так вот. Это добавление внутри добавления.
Примерно визуализированное
https://jsfiddle.net/069fojyx/

Не работает #form_1

И как написать скрипт для всего добавленного по полю #form_status_added

laimas 28.05.2019 11:26

Цитата:

Сообщение от eLDeR
Это добавление внутри добавления.
Не работает #form_1

Если добавляемое динамически, значит либо делегировать ближайшему родителю обработчик, либо устанавливать его элементу после добавления. Такого селектора быть не может: $("form_1"), наверное так $("#form_1"), но id должно быть уникальным, а значит по классу $(".form_1").

eLDeR 28.05.2019 14:21

"#form_1" заработало
Я новичок в этой теме и неспеша разбираюсь

при добавлении основного дополнительного поля все добавляется с уникальным индификатором, но дальше добавления не работает, даже с этим
$(function() {
		$("#form_1").click(function () {
			$("<div class='row' id='smeta_mater1'><div class='col-8'><input class='form-control mb-3' type='text' name='smeta_mater_name1[]' placeholder='Название'></div><div class='col-2'><input class='form-control mb-3' type='text' name='smeta_mater_units1[]' placeholder='Количество'></div><div class='col-2'><input class='form-control mb-3' type='text' name='smeta_mater_price1[]' placeholder='Цена за ед.'></div></div>" ).insertBefore("#nextmater1");
		});
	});
	$(function() {
		$("#form_2").click(function () {
			$("<div class='row' id='smeta_mater2'><div class='col-8'><input class='form-control mb-3' type='text' name='smeta_mater_name2[]' placeholder='Название'></div><div class='col-2'><input class='form-control mb-3' type='text' name='smeta_mater_units2[]' placeholder='Количество'></div><div class='col-2'><input class='form-control mb-3' type='text' name='smeta_mater_price2[]' placeholder='Цена за ед.'></div></div>" ).insertBefore("#nextmater2");
		});
	});

И чем отличается ID и CLASS у JS ?

laimas 28.05.2019 14:34

Цитата:

Сообщение от eLDeR
И чем отличается ID и CLASS у JS ?

ID, это атрибут для уникального значения, ну так же как ваш паспорт, по которому можно идентифицировать только вас. Добавлять на страницу элементы с одинаковыми ID нельзя, так ка при обращении по нему будет возвращен только первый найденный.

Использовать разные ID можно, но только не для такого:

$("#form_1").click(function () ...
$("#form_2").click(function () ...
...

ибо это расточительство.

Используя имя класса можно получить коллекцию элементов, то есть установить единый обработчик для всех элементов. А в случае их динамического добавления на страницу, делегировать обработчик ближайшему их общему родителю, который гарантированно присутствует на странице. В jQuery это так:

$("селектор родителя").on("событие", "селектор элементов делегирующих обработку, имя их класса и т.п.", function() {
   //здесь this, это источник события, то есть элемент по которому щелкнули и т.п.
   //если им оперировать далее как jq-объектом, то это будет $(this) 
}}

eLDeR 29.05.2019 09:41

Так? (пока не работает)
<div class="row">
	<div class="col-12 smeta_mater">
		<div class="row smeta_list">
			<div class="col-8"><input class="form-control mb-3" type="text" name="smeta_mater_name1[]" placeholder="Название"></div>
			<div class="col-2"><input class="form-control mb-3" type="text" name="smeta_mater_units1[]" placeholder="Количество"></div>
			<div class="col-2"><input class="form-control mb-3" type="text" name="smeta_mater_price1[]" placeholder="Цена за ед."></div>
			</div>
		</div>
	</div>
</div>
<div class="row">
	<div class="col-12">
		<input id="addmater" class="btn btn-primary btn-lg btn-block" type="button" value="Добавить Материал">
	</div>
</div>


$(".param").on('click', '#addmater', function() {
	var list = ('.smeta_mater');
	item = list.find('.smeta_list').last().clone();
	
	item.find('input').val('');
	
	list.append( item );
});

laimas 29.05.2019 10:01

Если кнопка id="addmater" всегда на странице и ею производится добавление, то зачем делегировать ее события родителю? Вы ведь до этого совсем иное показывали, добавляя блоки, по которым нужно было щелкать, вот тогда и нужно было делегировать обработку.

А в данном примере, это просто обработчик щелчков по кнопке, копировать всегда первый дочерний блок DIV (тут даже и класса не нужно) родителя div.smeta_mater, который удалить нельзя, в отличие от добавляемых, возможность удаления которых в общем-то нужна.

$("#addmater").click(function() {
    var list = ('div.smeta_mater');
    list.children().first().clone().appendTo(list).find('input').val('');
     
    //item.find('input').val(''); - а так будут возвращены поля, и только они будут вставлены
    //если так поступать, тогда item.find('input').val('').end(); и затем вставлять
});

eLDeR 30.05.2019 09:24

Все равно не могу понять

Есть div c class='param' ( первый блок с индификатором 1 )
В нем есть еще одна группа в обертке div с class='mater1' в котором есть div с class='list1' который нужно дублировать по кнопке с id='addmater1'

Нажимая по кнопке с id='form_status_added' добавляется новый div c class='param' ( уже с индификатором 2 ) и у всех остальных тоже он 2 ( mater2 list2 addmater2

https://jsfiddle.net/2v4gnbL5/

Я никак не пойму как мне дописать код чтоб все работало

Пробую разобраться но не понимаю

laimas 30.05.2019 10:04

Во-первых - texarea, это и есть текстовое поле и в отличие от input оно по определению своему не нуждается в указании type="text", и что это ненужный для него атрибут укажет даже редактор подсветкой.

Во-вторых - пора бы понять, что действиями типа name='rabota"+elemCount+"[]' и т.п., результатом которых сервер получит ключ плюс их нумерацию, вы заставляете сервер заниматься пустой и бесполезной работой. Эти действия кроме вредительства ничего не дают ни клиенту, ни серверу. Именовать поля нужно как fieldname[], без всяких номеров, и сервер получит массив с ключом fieldname с индексами от 0 до ...
Тоже самое касается и class='mater1', и id='addmater1' и прочей пустой и никчемной нумерации.

В третьих - в обработчике $("#form_status_added").click(function ... достаточно клонировать первый дочерний элемент формы, вставляя его перед $(this).closest('.row').

В четвертых - не знаю куда вставлять добавления по кнопке addmater, но как было сказано выше, выбросить и нумерацию и вообще ID у этой кнопки, вместо него добавьте этой кнопке какой либо класс, пусть к примеру это будет added. По имени этого класса делегировать обработку этих кнопок форме, как это делается было показано ранее.

Malleys 30.05.2019 12:40

А зачем вам дублировать содержимое? Вы можете использовать уже прописанный в HTML код как шаблон для создания последующих частей!

Вот код, который добавляет материалы и поля! https://jsfiddle.net/ga5sw7fu/

Почистите код от лишних классов и <div>-ов!

laimas 30.05.2019 12:52

Malleys, только именовать поля нужно так:

rabota[0]
mater_name[0][]
mater_units[0][]
mater_price[0][]

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

Malleys 30.05.2019 12:59

Цитата:

Сообщение от laimas
Malleys, только именовать поля нужно так:

Да! Но, так он сам наверное такое захочет сделать!

В спецификации HTML5 сказано, что нет никаких ограничений на имена, которые можете использовать в атрибуте класса, однако рекомендуется использовать значения, которые описывают сущность/природу содержимого, а не такие значения, которые описывают желаемое представление контента.(https://www.w3.org/TR/html52/dom.htm...f-global-class)

Т. е. в том примере с Bootstrap лучше так не делать, хотя можно. Например, лучше <div class="material-totals"></div>, а не <div class="alert alert-primary text-center" id="totals"><strong>1</strong></div></div>

Также настоятельно рекомендуется рассматривать элемент <div> как крайнюю меру, когда уже никакой другой элемент не подходит. Использование более подходящих элементов вместо элемента <div> обеспечивает лучшую доступность и код, который легче поддерживать. (https://www.w3.org/TR/html52/groupin...he-div-element)

laimas 30.05.2019 13:05

Цитата:

Сообщение от Malleys
В спецификации HTML5 сказано, что нет никаких ограничений на имена

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

eLDeR 30.05.2019 17:25

Вроде все получилось, но почему то выскакивает ошибка

Uncaught TypeError: Cannot set property 'textContent' of null
и
Uncaught TypeError: Cannot set property 'name' of null

Переделал этот код

<form action="#" method="POST" class="form-horizontal">
	<div class="param mt-3">
		<div class="row">
			<div class="col-12"><div class="alert alert-primary text-center" id="totals"><strong>1</strong></div></div>
			<div class="col-12"><textarea rows="5" class="form-control mb-3 rabota" type="text" name="rabota1" placeholder="Наименование видов работ"></textarea></div>
		</div>
		<div class="row">
			<div class="col-12 mb-2 text-center"><strong>Материал</strong></div>
		</div>
		<div class="row">
			<div class="col-12 mater">
				<div class="row list" id="material_row">
					<div class="col-8"><input class="form-control mb-3 mater_name" type="text" name="mater_name1[]" placeholder="Название"></div>
					<div class="col-2"><input class="form-control mb-3 mater_units" type="text" name="mater_units1[]" placeholder="Количество"></div>
					<div class="col-2"><input class="form-control mb-3 mater_price" type="text" name="mater_price1[]" placeholder="Цена за ед."></div>
				</div>
			</div>
		</div>
		<div class="row">
			<div class="col-12">
				<input id="add_material" class="btn btn-primary btn-lg btn-block" type="button" value="Добавить Материал">
			</div>
		</div>
		<div class="row"><div class="col-12"><hr></div></div>
	</div>
	<div class="row" id="next">
		<div class="col-6">
			<input class="btn btn-primary btn-lg btn-block" type="button" value="Добавить поле" id="form_status_added">
		</div>
		<div class="col-6">
			<button name="updates" class="btn btn-primary btn-lg btn-block">Обновить</button>
		</div>
	</div>
</form>


var template = document.querySelector(".param").cloneNode(true);
addEventListener("click", ({ target }) => {
	switch(target.id) {
  	case "form_status_added":
    	var elemCount = document.querySelectorAll(".param").length + 1;
      var node = template.cloneNode(true);
      node.querySelector("#totals").textContent = elemCount;
      node.querySelector(".rabota").name = "rabota"+elemCount;
      node.querySelector(".mater_name").name = "mater_name"+elemCount+"[]";
      node.querySelector(".mater_units").name = "mater_name"+elemCount+"[]";
      node.querySelector(".mater_price").name = "mater_name"+elemCount+"[]";
      document.querySelector("#next").before(node);
    	break;
      
      case "add_material":
      var node = target.closest(".param").querySelector("#material_row:last-of-type");
      node.after(node.cloneNode(true));
    	break;
  }
});


И перенеся на сайт мне выдает ошибку
?m=edit:148 Uncaught TypeError: Cannot read property 'cloneNode' of null
at ?m=edit:148

и ничего не работает

laimas 30.05.2019 17:38

eLDeR, используйте jQuery коли он у вас и так есть, и вы в нем что-то можете. А этот код в общем-то не полный, в нем нет очистки значений клонируемых полей. А именование как было неудобным (querySelector(".rabota").name = "rabota"+elemCount), так и осталось. А именование других полей приведет к тому, что у вас наборы добавляемых полей (материалы) не будут соответствовать индексам добавляемых блоков (полей).

Ну возьмите простой php код, поместите в него форму с таким именованием полей и отправьте ее, чтобы убедится в том, что вы получите массив неудобный для обработки.

Malleys 30.05.2019 18:09

Цитата:

Сообщение от laimas
jQuery коли он у вас и так есть

И как это упростит манипулирование полями и материалами? Я понимаю, вы бы сказали React.js, у вас есть данные и вы пишете интерфейс при помощи класса наследующего от React.Component, который позволит менять состояние полей и материалов... А jQuery здесь просто спор об именах методов, он просто обёртка не привносящая никакой концепции!

laimas 30.05.2019 18:19

Цитата:

Сообщение от Malleys
И как это упростит манипулирование полями и материалами?

А окончание предложения прочитать лень? Вы кому хотите React предложить? Только мне не надо пепла на голову, не я о нем заикнулся.

Может хватит пустым красноречием заниматься? ;)

Malleys 30.05.2019 19:04

Цитата:

Сообщение от laimas
Может хватит пустым красноречием заниматься?

Какое красноречие, вы предлагаете выуживать данные из DOM, не легче их сразу хранить в объекте и трансформировать как надо? Вы даже не представляете сколько лишнего нужно написать для поддержания индексов в порядке! Поэтому я и предложил готовое решение в виде React, которое хорошо справляется с данной задачей!

Цитата:

Сообщение от laimas
Только мне не надо пепла на голову, не я о нем заикнулся.

А что причёска сгорит? Просто в данном подходе с jQuery или без минус в том, что нет хранилища данных... Нужно начать с чистых данных и уже ими манипулировать... А React использовать или web-компоненты... без разницы, я думаю первый будет проще для начинающего в данном случае, поскольку есть метод setState.

laimas 30.05.2019 19:27

Цитата:

Сообщение от Malleys
Какое красноречие, вы предлагаете выуживать данные из DOM, не легче их сразу хранить в объекте и трансформировать как надо?

Да итиего мать, ну разве вы не видите, что он не понимает вами писаного? А то что у него в проекте широко используется jQuery, так это понятно, и то что он что-то понимает в нем и может на нем писать тоже ясно. И проблем для него гораздо меньше будет.

Так к чему выпендриваться, да еще предлагать React? Или вы это для меня его сватаете? Да нехрен мне он нужен, мне заняться больше нечем, как разводить демагогию что лучше, а что хуже.

Malleys 30.05.2019 19:55

Цитата:

Сообщение от laimas
Да итиего мать, ну разве вы не видите, что он не понимает вами писаного?

Из-за вашей упёртости и нежелания понимать, что jQuery написанный на JavaScript, выдаёт те же методы, что и в DOM API, только под другими названиями, и того, что jQuery не очень хорошо подходит для решения этой задачи, я даже не заметил вопрос...
Цитата:

Сообщение от laimas
А этот код в общем-то не полный, в нем нет очистки значений клонируемых полей. А именование как было неудобным (querySelector(".rabota").name = "rabota"+elemCount), так и осталось. А именование других полей приведет к тому, что у вас наборы добавляемых полей (материалы) не будут соответствовать индексам добавляемых блоков (полей).

Да, это так и есть... поэтому я и перенёс решение на react, поскольку перенос в jquery не помогает решить ни одну из этих проблем, которые вы перечислили! Вот я написал далее решение, в котором решены все эти проблемы... А тот пример, про который вы говорите, в нём я только показал, что не нужно дублировать разметку (и правда её приходилось бы редактировать в двух местах!)

eLDeR,
Цитата:

Сообщение от eLDeR
Вроде все получилось, но почему то выскакивает ошибка

Uncaught TypeError: Cannot set property 'textContent' of null
и
Uncaught TypeError: Cannot set property 'name' of null

Скрипт должен идти после формы.

Вот тоже самое, но при помощи React.js https://codepen.io/Malleys/pen/BeGBVL?editors=0010
Добавил удаление полей, материалов и сериализацию данных, вам только осталось написать реализацию отправки этих данных. (Сейчас выводит их в alert)

laimas 30.05.2019 21:47

Цитата:

Сообщение от Malleys
Из-за вашей упёртости и нежелания понимать
что jQuery не очень хорошо подходит для решения этой задачи, я даже не заметил вопрос...

Я просто хрнею, иногда, от ваших слов. Кроме демагогии и желания убедить того кто, мягко сказать, не врубается, иметь JQ и что-то писать на нем, залезть в такие дебри для него, что мама не горюй. Слава богу, что без тезисов "JQ отстой, ату его, даешь React". Кошмар какой-то, ей богу.

Malleys 31.05.2019 02:27

Цитата:

Сообщение от laimas
Кошмар какой-то, ей богу.

Даёшь DOM API. В общем сделал то решение до конца, которое без библиотек https://codepen.io/Malleys/pen/RmqRPa?editors=0010

Цитата:

Сообщение от laimas
иметь JQ и что-то писать на нем, залезть в такие дебри для него, что мама не горюй.

Даёшь jQuery. Так покажите адекватное решение на jQuery! Вы много написали, но решения ещё не видно!

Цитата:

Сообщение от laimas
Кроме демагогии и желания убедить того кто, мягко сказать, не врубается...

Так вот покажите, как правильно! Почему вы решили, что единственно истинное решение будет с использованием jQuery?

Вот некоторые решения:
laimas, я не утверждаю, что эти решения самые правильные, их можно решить и другими способами... но эти решения учитывают, что человек может захотеть хранить данные формы в локальном хранилище, поскольку хочет, чтобы введённые данные не потерялись (позвонили, а потом когда вернулся на страницу, данные исчезли из-за того, что вкладка была выгружена, села батарейка и пр.), может захотеть иметь несколько таких форм на странице и т. д.

Цитата:

Сообщение от laimas
желания убедить того кто, мягко сказать, не врубается...

Те, кто только начинают изучать JavaScript, могут быть введены в заблуждение и считать, что jQuery является самой сутью JavaScript. Однако это не соответствует действительности, а сутью JavaScript является его ядро, которое описано в спецификации EcmaScript. Такое ядро поддерживается любой платформой, которая предназначена для работы с JavaScript. Это может быть браузер Google Chrome, программа node.js, оболочка для создания десктопных приложении Electron и мобильных приложении Cordova и пр. А jQuery является функцией, написанной для манипуляции DOM, т. е. она бессмысленна там, где нет такой модели!

Стоит однако отметить, что эта функция была очень полезна, когда вам нужно было, чтобы ваша программа работала и в IE тоже. Правда вместо того, чтобы предложить реализацию отсутствующих методов и функции в старых браузерах, jQuery была написана как нечто обособленное от DOM API, что привнесло однородность и путаницу в написании кода 13 лет назад, и совершенно не нужно сегодня, поскольку усложняет работу с Shadow DOM API, с наследованием (оно работает с багами), имеет свой собственный синтаксис селекторов, несовместимый со стандартом (вам придётся запутаться между двумя: jquery legacy и css, который работает, как в стилях, так и в DOM API), устаревшие методы jQuery (сегодня DOM API стандартизировано и работает одинаково во всех современных браузерах, во всяком случае вам придётся использовать новый функционал без jQuery, потому что он не поддерживает его!)

Если вам нужна поддержка старых браузеров, то вы по прежнему можете писать стандартный и понятный код, подключив Polyfill Service.


Часовой пояс GMT +3, время: 18:46.