빌드노트
form.submit()으로 보내면 required 검사가 빠지는 이유
form.submit()은 required 검사와 submit 이벤트를 생략한다. 새 Chrome 프로필과 로컬 수신 서버로 네 제출 경로를 비교하고, 검증·이벤트·버튼의 name=value가 필요한 경우 requestSubmit에 무엇을 넘길지 설명한다.
버튼을 누르면 빈 필수 입력을 막는데 스크립트의 form.submit()으로는 그대로 전송된다면, 호출한 메서드부터 확인한다. submit()은 브라우저의 입력 제약 검증과 submit 이벤트를 생략한다. 버튼으로 제출할 때의 검증과 이벤트 처리가 필요하면 requestSubmit()을 쓰고, 버튼의 이름·값까지 필요하면 그 버튼을 인자로 넘긴다.
기존 제출 함수에서 호출을 바꾸려면 먼저 실제 폼과 제출 버튼을 선택한다. 다음 코드는 id="demo"인 폼 안에 활성화된 id="publish", type="submit", name="intent", value="publish" 버튼이 있을 때의 수정이다. 선택자는 자신의 개발 화면에 맞추고, 두 요소가 선택됐는지 확인한 뒤 호출한다.
const form = document.querySelector('#demo');
const publish = form.querySelector('#publish');
form.requestSubmit(publish);
이 호출은 검증을 통과하면 폼의 submit 리스너도 실행한다. 리스너가 preventDefault()로 기본 전송을 취소할 수 있다는 뜻이다. MDN의 submit() 설명 (새 탭에서 열림)은 직접 submit()을 호출할 때 검증과 이 이벤트가 생략된다고 명시한다. 따라서 submit() 앞에 유효성 검사만 추가하는 것으로는 제출 이벤트나 버튼 정보까지 복원되지 않는다.
같은 폼에서 네 제출 경로를 비교했다
이 글을 위해 Windows 11의 새 프로필에서 headless Chrome 156.0.8078.47을 실행했다. 합성 폼에는 name="title"인 필수 텍스트 입력과 위 발행 버튼을 두었다. 입력은 빈 문자열 또는 synthetic-title이었다. click·invalid·submit 이벤트와 함께, 127.0.0.1 수신 서버에 도착한 POST 본문을 기록했다. 기본 비교에서는 제출을 취소하지 않았고, 응답은 iframe으로 받아 폼을 유지했다.
빈 입력으로 각 경로를 한 번씩 실행하자 차이가 났다.
- 실제 버튼 클릭은
click → invalid였고 서버 수신은 0건이었다. form.submit()은 관찰한 세 이벤트 없이 서버에 1건을 보냈다. 본문은title=이었다.form.requestSubmit()과form.requestSubmit(publish)는 각각invalid만 기록했고 서버 수신은 0건이었다.
값을 synthetic-title로 채운 뒤에는 네 경로 모두 서버가 1건씩 받았다. 그러나 제출 버튼 정보는 달랐다.
- 실제 클릭은
click → submit, 버튼을 넘긴requestSubmit(publish)는submit만 기록했다. 두 경우 모두 이벤트의submitter.id는publish였고 본문은title=synthetic-title&intent=publish였다. - 인자 없는
requestSubmit()은submit을 기록했지만submitter가null이었다. 본문은title=synthetic-title로 버튼 값이 없었다. submit()은 이번에도 세 이벤트가 없었고, 본문은title=synthetic-title이었다.
submit()이라는 메서드 호출이 같은 이름의 이벤트를 발생시키는 것은 아니었다. 반대로 빈 필수 입력에서 requestSubmit()의 submit 로그가 없는 것은 검증 실패로 중단됐기 때문이다. HTML 표준의 제출 절차 (새 탭에서 열림)도 검증 실패를 확인한 뒤 이벤트를 보내기 전에 돌아가도록 정한다.
버튼 값이 필요하면 인자를 생략하지 않는다
검증을 복원하는 것과 어떤 버튼으로 제출했는지 전달하는 것은 별개다. 서버가 intent=publish로 발행 동작을 고른다면 requestSubmit(publish)처럼 해당 버튼을 지정한다. 화면에 발행 버튼이 하나뿐이어도 인자 없는 requestSubmit()이 그 버튼을 자동으로 고르지는 않았다.
MDN의 requestSubmit() 인자 설명 (새 탭에서 열림)에 따르면 버튼을 생략하면 폼 자체를 제출 절차의 출발점으로 사용한다. 표준의 전송 항목 구성 규칙 (새 탭에서 열림)은 제출을 시작한 버튼이 아닌 버튼을 제외한다. 버튼 이름·값이나 버튼별 제출 설정을 쓰지 않는 폼이라면 인자 없는 호출로 검증과 이벤트를 유지할 수 있다.
지정하는 요소는 해당 폼에 연결된 제출 버튼이어야 한다. 표준의 메서드 정의 (새 탭에서 열림)는 제출 버튼이 아니면 TypeError, 다른 폼의 제출 버튼이면 NotFoundError로 구분한다. 버튼의 formaction·formmethod 같은 설정도 제출에 영향을 준다는 것은 문서에서 확인했다. 이 예외와 주소·방식 재정의는 이번에 직접 실행하지 않았다.
또한 버튼을 넘긴 호출에도 click 이벤트는 없었다. 클릭 리스너에만 둔 전처리를 requestSubmit()이 실행해 줄 것으로 기대하면 빠질 수 있다. 제출에 공통으로 필요한 처리를 폼의 submit 리스너에 둘지, 제출 요청 전에 명시적으로 부를지 정한다. 이미 제출 리스너에서 취소·비동기 처리를 하는 코드를 바꾼다면 기존 흐름도 함께 살펴야 한다.
자기 폼에서는 이벤트와 전송을 함께 확인한다
개발 화면에서 첫 코드의 선택 두 줄을 먼저 실행하고, 마지막 제출 호출 전에 다음 리스너를 한 번 붙인다. 필수 입력에서 막혔는지, 제출 이벤트까지 도달했는지 확인하기 위한 코드다. form은 앞에서 선택한 폼이다.
form.addEventListener('invalid', e => console.log('invalid', e.target.name), true);
form.addEventListener('submit', e => console.log('submit', e.submitter?.id ?? null));
마지막 true는 입력 요소의 invalid를 폼에서 캡처하기 위한 설정이다. 이 관찰 코드는 전송을 취소하지 않는다. 개발자 도구 Console과 Network의 Preserve log를 켜고, 실제 계정 데이터 대신 개발용 값으로 비교한다. 전송으로 화면이 바뀌면 폼과 버튼 선택 및 리스너 연결을 새 화면에서 다시 한다.
본문의 두 코드도 합성 폼에서 별도로 실행했다. title을 비우고 form.requestSubmit(publish)를 호출하면 invalid title이 기록되고 POST는 없었다. synthetic-title을 넣고 호출하면 submit publish가 기록됐으며 서버는 title=synthetic-title&intent=publish를 받았다. 자기 화면에서는 Network의 해당 요청 Payload까지 확인한다. submit 로그만으로 요청이 도착했거나 버튼 값이 포함됐다고 판단하지 않는다.
호출을 바꾼 뒤 결과가 다르면 다음 두 조건을 먼저 구분한다.
- 빈 필수 입력도 전송된다면 폼의
novalidate와 선택한 버튼의formnovalidate를 본다. DOM에서는form.noValidate,publish.formNoValidate로 읽는다. 둘 중 하나를 켠 별도 실험에서는 빈 값의requestSubmit(publish)도invalid없이submit으로 이어졌고 서버가title=&intent=publish를 받았다. - 유효한 값인데 기본 전송이 없다면 기존
submit리스너의preventDefault()와 직접 요청을 보내는 코드를 본다. 취소 리스너를 켠 실험에서 클릭과 두requestSubmit경로는 서버 수신 0건이었다.submit()은 그 리스너를 실행하지 않아 1건을 보냈다. AJAX 폼에서는 메서드를 바꾸면서 기존 핸들러가 실행되므로 별도 전송 코드와 겹치지 않는지도 확인한다.
submit is not a function은 다른 경계다. name="submit"인 입력을 추가한 조건에서는 form.submit이 입력 요소를 가리켜 TypeError가 났고 요청도 없었다. 검증을 건너뛰어 보낸 것이 아니라 메서드 호출 자체가 성립하지 않은 경우다. 필드 이름과 서버가 받는 이름의 계약을 맞춰 충돌을 해소한다. MDN은 id="submit"도 메서드를 가릴 수 있다고 설명하지만, 이번에 실행한 것은 name 조건이다.
직접 확인한 범위는 네이티브 HTML 폼과 Chrome 한 버전의 로컬 POST다. 요청이 수신점에 도착했다는 관찰이며 실제 저장·발행 성공을 뜻하지 않는다. Firefox·Safari·모바일, Enter 키, 프레임워크별 제출은 확인하지 않았다. 브라우저 검증을 복원하더라도 서버의 필수값 검사는 유지한다. 빈 값의 직접 제출처럼 브라우저 검사를 거치지 않은 요청도 서버에 도달할 수 있기 때문이다.