Không thể submit form programmatically khi form có 1 input[name=submit]

Xem xét đoạn code dưới đây:

<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
function getTokenFromApi(){
 return '1234567890';
}

function addTokenFromAPI (e) {
  e.preventDefault();
  var token = getTokenFromApi();
  $('<input>')
    .attr('type','hidden')
    .attr('name', 'token')
    .val('1234567890')
    .appendTo('form');
  $("form")
    .unbind('submit')
    .trigger('submit');
}

$(document).ready(function(){
  $("form").on('submit', addTokenFromAPI);
});
</script>
</head>
<body>

<form action="/action_page.php">
  First name: <input type="text" name="FirstName" value="Mickey"><br>
  Last name: <input type="text" name="LastName" value="Mouse"><br>
  <input type="submit" value="Submit" name="submit">
</form> 

</body>
</html>

Mô tả: khi click vào button Submit, form gọi method preventDefault để thêm vào 1 field mới, (có thể là token trả về từ api nào đó) sau đó unbind('submit') để remove handler, rồi lại submit lần nữa để chạy handler default là submit form.

Expected: click 1 lần form add thêm input token rồi submit
Actual: click 1 lần form chỉ thêm input token, click lần 2 form mới submit.

Test: các bạn có thể copy code và test tại:
https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_event_submit

Yêu cầu:

  • giải thích nguyên nhân
  • cách khắc phục.

Câu hỏi chỉ nhằm mục đích để các bạn trao đổi kiến thức.
Những comment kiểu như: thời nào rồi còn dùng jquery?, sao không dùng ajax mà dùng form submit? reactjs dễ hơn nhiều? xin vui lòng đừng

3 Likes

Mình thấy bạn có dòng

var token = getTokenFromApi();

không biết cái đó là sao ta? Là 1 built-in function của jquery?
Mình không rành javascript/jquery lắm, nên không rõ chỗ đó

Sao không bỏ dòng này đi:

Hàm đó bạn ấy có định nghĩa phía trên đấy. Mục đích là lấy token hợp lệ, nhưng bạn ấy chỉ làm thử nên hàm chỉ trả về chuỗi '12345678890' thôi.

3 Likes

e.preventDefault() để tránh form submit

Vd như token bị lỗi:

function getTokenFromApi( ){
 return {token: '1234567890', error: null};
}

function addTokenFromAPI (e) {
  e.preventDefault();
  var result = getTokenFromApi();
  if(result.error)
    alert('Có lỗi xảy ra');
 else { 
    $('<input>')
      .attr('type','hidden')
      .attr('name', 'token')
      .val(result.token)
      .appendTo('form');
    $("form")
      .unbind('submit')
      .trigger('submit');
 }
}

nếu không có e.preventDefault() form luôn được submit và ridirect sang page mới.

2 Likes

Theo mình check thì là do $('form').trigger('submit') của bạn không chạy.

Test thì đơn giản thôi, bạn tạo 1 button, onClick thì gọi trigger submit, sẽ thấy nó không submit form.

Nguyên nhân của việc không submit form thì mình chưa đào sâu, nhưng code mặc định chạy nên mình sửa theo đúng ý bạn theo code mặc định

var handler = function(e){
	e.preventDefault();
    
    var token = 'return from api';
      $('<input>')
      .attr('type','hidden')
      .attr('name', 'token')
      .val('1234567890')
      .appendTo('form');
    $(this) //$('form') both work
      .unbind('submit')
      .trigger('submit');

    //Để chứng minh là trigger('submit') không chạy, bạn có thay bằng đoạn code dưới sau unbind
   // Object.getPrototypeOf($('form')[0]).submit.call($('form')[0])
}

$(document).ready(function(){
  $("form").submit(handler);
});

Edit:
Giữ nguyên code của bạn, thay đổi đoạn listener thành như dưới là chạy (nguyên nhân thì mình cũng k biết, chắc phải đọc source jquery quá :joy:

$("body").on('form', 'submit', addTokenFromAPI);
2 Likes

Có thể là do e.preventDefault() vẫn còn tác dụng nên không submit.

2 Likes

lúc nào cũng preventDefault() thì nó là vòng lặp vô tận à :thinking:

để cái if ktra nếu có hidden token rồi thì đừng preventDefault, nếu chưa thì mới preventDefault và add token vào.

mà cũng ko cần preventDefault làm gì, 1 cái if là xong luôn :V

function addTokenFromAPI (e) {
  if (!$('input[name="token"]').length) {
    var token = getTokenFromApi();
    $('<input>')
      .attr('type','hidden')
      .attr('name', 'token')
      .val(token)
      .appendTo('form');
  }
}
3 Likes

Mình hơi thắc mắc chút, cái này ở đâu ra vậy :kissing: ?

là của w3schools gửi lên cái gì nó trả về in ra cái ấy :V

như trong form có

<input type="text" name="FirstName" value="Mickey">
<input type="text" name="LastName" value="Mouse">
<input type="submit" value="Submit" name="submit">

nghĩa là POST data gửi lên server có dạng FirstName=Mickey&LastName=Mouse&submit=Submit. Mấy ông web gửi input lên có format đẹp đẽ hơn so với mấy ông console app nhiều, gửi lên có dạng chuỗi định dạng là {name}={value}, nhiều names/values thì nối với nhau bằng dấu &.

Bây giờ muốn thêm 1 cái hidden token trước khi nhấn submit để submit chung về server nữa :V Nếu thành công thì POST data sẽ có thêm &token=...

3 Likes

Đây là cú pháp hoàn toàn bình thường, không có gì sai cả
Ngoài ra, 2 solution bạn đưa ra đều không work đúng. Chắc bạn chưa test.

  • Cách thứ nhất, vẫn bị lỗi phải click 2 lần
  • Cách thứ 2, rơi vào vòng lặp vô tận
1 Like

Theo cách này, Form lúc nào cũng dc submit và redirect sang trang mới.

Giả sử, khi hàm getTokenFromApi(); trả về lỗi

var result = {
    error: true,
    message: 'Chúng tôi nhận thấy bạn đang cố truy cập trái phép vào hệ thống, vui lòng thử lại sau'
};
if( result.error) {
    displayErrorMessage ( result.message);
}
else {
    submitForm();
}

Khi đó, form không dc phép submit.

vậy thì để preventDefault khi có error, thêm 1 if nữa :V

function getTokenFromApi(){
 let success = {
 	error: false,
 	value: '1234567890'
 };
 let error = {
 	error: true,
 	value: 'Something wrong on our end'
 };
 return success; // sửa return error thì nó alert lỗi
}

function addTokenFromAPI (e) {
  if (!$('input[name="token"]').length) {
    let res = getTokenFromApi(); 
    if (res.error) {      // nếu có lỗi
      e.preventDefault(); // thì đừng submit
      alert(res.value);   // báo lỗi
    } else {
      $('<input>')
          .attr('type','hidden') 
          .attr('name', 'token') 
          .val(res.value) 
          .appendTo('form');
    }
  }
}

hoặc return false cũng được, ko cần preventDefault

    if (res.error) {    // nếu có lỗi
      alert(res.value); // báo lỗi
      return false;     // dừng ở đây
    }
3 Likes

Bạn đang đi đúng luồng rồi đó.
Tiếp tục nhé:
Giả sử getTokenFromApi là 1 hàm bất đồng bộ mà chắc chắn bất đồng bộ rồi, vì được gọi từ 1 api khác.

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function async getTokenFromApi(){
  await sleep(200); // or ajax, ...
  let success = {
 	error: false,
 	value: '1234567890'
  };
  let error = {
 	error: true,
 	value: 'Something wrong on our end'
   };
   return success;
}

Khi đó form bạn submit sẽ thiếu field token.

3 Likes

:V :V :V sao xóa cái name="submit" ở nút Submit thì lại chạy được??? :V

thấy nó submit dư cái submit=Submit ngứa mắt xóa thì ko cần nhấn submit 2 lần nữa???

<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function getTokenFromApi(){
  await sleep(200); // or ajax, ...
  let success = {
 	error: false,
 	value: '1234567890'
  };
  let error = {
 	error: true,
 	value: 'Something wrong on our end'
   };
   return success;
}

async function addTokenFromAPI (e) {
  e.preventDefault();
  getTokenFromApi().then(res => {
    if (res.error) {
      alert(res.value);
    } else {
      $('<input>')
          .attr('type','hidden') 
          .attr('name', 'token') 
          .val(res.value) 
          .appendTo('form');
      $("form").unbind('submit').submit();
    }
  });
}

$(document).ready(function(){
  $("form").on('submit', addTokenFromAPI);
});
</script>
</head>
<body>

<form action="/action_page.php">
  First name: <input type="text" name="FirstName" value="Mickey"><br>
  Last name: <input type="text" name="LastName" value="Mouse"><br>
  <input type="submit" value="Submit"> <!-- xóa name="submit" ở đây -->
</form> 

</body>
</html>

gg xem https://stackoverflow.com/questions/5651933/what-is-the-opposite-of-evt-preventdefault ngược lại với preventDefault là gì =] thì thấy đúng là họ xài unbind submit rồi submit lại, nhưng có 1 comment

Another option in this scenario might be to use input type=“button” and bind to the click event instead of using input type=“submit” and binding to the submit event.

input type button là thế nào =] mình mới đi xóa thử cái name=“submit” kia đi thì lại ko cần nhấn 2 lần nữa :V :V :V


code ban đầu cũng chỉ cần xóa name=“submit” đi là ko còn bị click 2 lần, tại sao lại bị như vậy :V :V


đây :V

I know that using $('form').submit() does not work when the submit button is named with the reserved name “submit”.

jquery “feature” :rofl:

nếu submit button có name là “submit” thì gọi $('form').submit() nó ko chạy, nên bấm lần 1 chỉ chạy unbind handler kia ra thôi, .submit() sau đó ko có tác dụng. Bấm lần thứ 2 ko đụng tới js, handler đã được unbind ra thì nó submit thẳng.

Doc jquery: https://api.jquery.com/submit/

Forms and their child elements should not use input names or ids that conflict with properties of a form, such as submit , length , or method . Name conflicts can cause confusing failures. For a complete list of rules and to check your markup for these problems, see DOMLint.

4 Likes

Chính xác rồi đó bạn,

Câu hỏi này chỉ để mọi người biết lỗi này thôi. Trường hợp này xảy ra cả với vanilla javascript. còn các framework khác mình chưa thử.
Closed

4 Likes

Tìm được solution rồi nhưng mình cũng xin phép đính chính chút .

Chính solution cũng chỉ ra câu này của bạn thớt, khi quote trả lời mình, là sai
“Đây là cú pháp hoàn toàn bình thường, không có gì sai cả”

mình k có nói cú pháp sai, mình nói nó k chạy. Và mình đã đưa đoạn code thay thế để chứng minh (prototype)

Có thể mình edit bài để tránh spam nên code đưa hơi lủng củng nên khi bạn thớt copy vào thì nhầm lẫn đâu đó (cụ thể phần sử dụng prototype of là sử dụng nguyên code cũ, chỉ thay $(form).submit = prototype) chứ mình khẳng định là không có chuyện đưa code không chạy lên :joy: đính chính vậy để đỡ mất công đăng bài mà bị chê là sai tè le hết cả :grimacing:

3 Likes

Sorry, mình có chút hiểu lầm. Mình nghĩ đó là solution.
Câu hỏi này để anh em trao đổi thêm thôi, để tránh gặp lại.

3 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?