Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

带文件上传的表单提交 #54

Open
naseeihity opened this issue Nov 12, 2018 · 0 comments
Open

带文件上传的表单提交 #54

naseeihity opened this issue Nov 12, 2018 · 0 comments
Assignees
Labels

Comments

@naseeihity
Copy link
Owner

naseeihity commented Nov 12, 2018

基本表单提交

在form元素上始终包含actionmethod是最佳实践, 默认点击type为submit的元素会触发表单提交,该提交为同步请求。
默认表单的内容通过遍历含有name的表单元素的value来获得(file元素默认为文件名),使用get方法时,参数会跟在url后面,通过&符号隔开(?a=1&b=2);使用post方法时,参数会带着请求体中。

<form id="formid" action="uplopad" method="post" enctype="multipart/form-data">
  <input type="file" name="file" id="file">
  <input type="text" name="text" id="text">
  <button id="button" type="submit">提交</button>
</form>

enctype可以用了设置请求的Content-Type, 他的默认值是application/x-www-form-urlencoded表示已编码为URL参数的表单数据。如果表单中包含不可序列化的数据,例如二进制数据(文件),此时就需要将enctype设为multipart/form-data,那么数据将被分为多个部分分别处理:

// 这里为分节符,会与contentType中的boundary字段匹配
------WebKitFormBoundaryHU6MVt10QhaGyngj
Content-Disposition: form-data; name="file"; filename="wallup (1).jpeg"
Content-Type: image/jpeg


------WebKitFormBoundaryHU6MVt10QhaGyngj
Content-Disposition: form-data; name="text"

文本数据
------WebKitFormBoundaryHU6MVt10QhaGyngj--

当然我们只能使用post方法来提交这个请求。

使用FormData + ajax完成带文件的表单提交(原生js)

FormData对象的append方法可以手动像其中添加表单字段,也可以直接通过一个表单元素来直接构建一个FormData对象:

// append, 由此可见利用formData也可以对不是form表单中的元素的值进行提交
var formData = new FormData();
formData.append("userfile", fileInputElement.files[0]);
formData.append("accountnum", 123456);

// dom
var formElement = document.querySelector("form");
var formData = new FormData(formElement);

下面给出一个图片上次的表单提交的请求示例

// from http://react-component.github.io/upload/
function getError(option, xhr) {
  const msg = `cannot post ${option.action} ${xhr.status}'`;
  const err = new Error(msg);
  err.status = xhr.status;
  err.method = 'post';
  err.url = option.action;
  return err;
}

function getBody(xhr) {
  const text = xhr.responseText || xhr.response;
  if (!text) {
    return text;
  }

  try {
    return JSON.parse(text);
  } catch (e) {
    return text;
  }
}

// option {
//  onProgress: (event: { percent: number }): void,
//  onError: (event: Error, body?: Object): void,
//  onSuccess: (body: Object): void,
//  data: Object,
//  filename: String,
//  file: File,
//  withCredentials: Boolean,
//  action: String,
//  headers: Object,
// }
export default function upload(option) {
  const xhr = new XMLHttpRequest();

  if (option.onProgress && xhr.upload) {
    xhr.upload.onprogress = function progress(e) {
      if (e.total > 0) {
        e.percent = e.loaded / e.total * 100;
      }
      option.onProgress(e);
    };
  }

  const formData = new FormData();

  if (option.data) {
    Object.keys(option.data).map(key => {
      formData.append(key, option.data[key]);
    });
  }

  formData.append(option.filename, option.file);

  xhr.onerror = function error(e) {
    option.onError(e);
  };

  xhr.onload = function onload() {
    // allow success when 2xx status
    // see https://github.com/react-component/upload/issues/34
    if (xhr.status < 200 || xhr.status >= 300) {
      return option.onError(getError(option, xhr), getBody(xhr));
    }

    option.onSuccess(getBody(xhr), xhr);
  };


  xhr.open('post', option.action, true);

  // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
  if (option.withCredentials && 'withCredentials' in xhr) {
    xhr.withCredentials = true;
  }

  const headers = option.headers || {};

  // when set headers['X-Requested-With'] = null , can close default XHR header
  // see https://github.com/react-component/upload/issues/33
  if (headers['X-Requested-With'] !== null) {
    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  }

  for (const h in headers) {
    if (headers.hasOwnProperty(h) && headers[h] !== null) {
      xhr.setRequestHeader(h, headers[h]);
    }
  }
  xhr.send(formData);

  // 返回abort方法,用于在组件状态发生改变时手动结束未完成的请求
  return {
    abort() {
      xhr.abort();
    },
  };
}

使用FormData + ajax完成带文件的表单提交(jquery)

$('#button').on('click', function () { 
  var fd = new FormData(document.querySelector("form"));
  // fd.append("CustomField", "This is some extra data");
  $.ajax({
    url: "stash",
    type: "POST",
    data: fd,
    processData: false,  // 不处理数据;(默认传入data中的object数据会被自动序列化为字符串,以匹配默认的contentType,如果想传输除此以外的数据类型,则需要设为false)
    contentType: false   // 不设置内容类型(防止影响分界符的解析)
  });

  return false;
})

jquery提交基础表单

对于不包含文件的普通表单的提交,我们也推荐通过ajax来提交,通过serialize方法快速获取表单的内容。

$.ajax({
  url: form.attr('action'),
  data: form.serialize(),
  type: form.attr('method'),
  dataType: form.attr('datatype') || 'json',
  success: function(){},
  error: function(){},
  complete: function(){}
});

使用原生js实现ajax序列化和表单提交

其实现主要包含两个部分,表单内容的序列化(serialize)和请求头相关参数的设置(根据请求类型和数据类型),其实现比较复杂,这里有一个参考。

参考

  1. 发送表单数据
  2. formData对象
  3. fileupload
@naseeihity naseeihity added the JS label Nov 12, 2018
@naseeihity naseeihity self-assigned this Nov 12, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant