/**
* 按PHP语言的标准形式,获取form表单关联的合法数据:不包括文件控件
* [说明]已测试并对比过所有具有 字符实体、空白字符、[] 特殊情况的name属性和数据,以及提交按钮、多选下拉列表,结果完全一致
* [说明]此方法代码量虽大,但远比new FormData()方法更加细致入微
* @param {Element} submitButton [默认:表单中首个提交按钮]提交按钮:请使用提交事件的对象的submitter属性
* @param {String} prefixToKeys [默认:'']为每个索引拼接的前缀,防止与原型重复
* @returns {Object} 一或二维的全为字符串的JSON数据
*/
HTMLFormElement.prototype.getValues = function (submitButton, prefixToKeys) {
const forms = Object.values(this)
// 如果参数2不在form中,或者不是submit按钮,改为form中首个submit按钮
if (forms.indexOf(submitButton) < 0 || !isSubmit(submitButton)) {
let i = 0, len = form.length
for (; i < len; i++) if (isSubmit(form[i])) break
submitButton = form[i]
}
// 重置索引前缀,用于防止索引与原型冲突
prefixToKeys = prefixToKeys || ''
// 多选数据允许的最大整数索引
const MAX_SAFE_INTGER = 0x7FFFFFFF
// 用于存储合法数据
const values = {}
// 用于存储合法数据的表单元素,且与values所有索引一一对应
const elements = {}
// 遍历控件,获取并转化所有合法数据
form.forEach(function (e) {
// 获取name属性,并解析转化字符实体、换行符,再并去除左边空格
let name = parse(e.name).replace(/^ +/, '')
, k1 = name.indexOf('[') // 获取首个多选起始字符位置
, k2
// 如果控件被禁用、name为空、首字符是多选起始字符、文件控件则忽略,会中断并继续下一项
if (!name || e.disabled || k1 === 0 || e.tagName === 'INPUT' && e.type === 'file') return
// 单选:name中所有空格替换为'_'
if (k1 < 0) setValue(e, name.split(' ').join('_'), values, elements)
// 其它
else {
// 把多选起始字符之前的所有空格替换为'_'
const prefix = name.substring(0, k1).split(' ').join('_')
// 获取'['之后的首个']'位置
k2 = name.indexOf(']', ++k1)
// 单选:name中首个'['转为下划线,并保留后面文本
if (k2 < 0) setValue(e, prefix + '_' + name.substring(k1), values, elements)
// 多选
else {
// 用于设置多选时的二维索引:获取'['和']'之间的文本,后面的忽略
k1 = name.substring(k1, k2)
// 重置name
name = prefixToKeys + prefix
// 多选时调整为对象(不能是数组)
if (typeof values[name] !== 'object') values[name] = {}, elements[name] = {}
// 用于判断索引采用的形式
let isSet = true
// 满足条件时采用自增的整数索引,否则索引不变
if (k2 = k1 === '' || k1 === ' ') {
// 获取当前name数据的最大的索引
k1 = Math.min(MAX_SAFE_INTGER, getMaxIntKey(values[name]) + 1)
// 仅当 预设索引<安全整数 时才自增和设置数据
isSet = k1 < MAX_SAFE_INTGER
}
if (isSet) setValue(e
, k1
, values[name]
, elements[name]
// 如果select标签多选时设置了二维索引,那么只获取最后一项,即反转k2;为真时执行
, !k2
)
}
}
})
// 清除空的多选项
Object.keys(values).forEach(function (k) {
if (typeof values[k] === 'object' && Object.keys(values[k]).length === 0) {
delete values[k]
delete elements[k]
}
})
/**
* 获取select标签多选时的数据
* @param {HTMLSelectElement} select select标签
* @param {String|Number} name 设置的索引
* @param {Boolean} isLastSelected [false]是否只选择select标签的最后一项
* @returns {Object}
*/
function getSelectMultipleValues(select, name, isLastSelected) {
let i = 0, j = select.length, r = {}
// 只取最后一项选中的
if (isLastSelected) while (j--) if (select[j].selected) {
r[prefixToKeys + name] = select[j].value
break
}
// 获取所有选中值:此时name为自然数,且不大于最大整数
else for (; i < j; i++) if (select[i].selected) r[prefixToKeys + Math.min(name++, MAX_SAFE_INTGER)] = select[i].value
return r
}
/**
* 获取对象中最大的整数索引,且不大于最大整数
* @param {Object} obj 获取的对象
* @returns {Number}
*/
function getMaxIntKey(obj) {
let k = Object.keys(obj), i = k.length, max = -1, pl = prefixToKeys.length, t
while (i--) {
// 因为有前缀所以要先去除
t = k[i].substring(pl)
// 获取最大整数索引
if (t <= MAX_SAFE_INTGER && t.match(/^[1-9]\d*$/) || t === '0') max = Math.max(max, +t)
}
return max
}
/**
* 判断参数是否是提交按钮
* @param {Element} e 判断的标签
* @returns {Boolean}
*/
function isSubmit(e) {
return e && e.type === 'submit' && (e.tagName === 'BUTTON' || e.tagName === 'INPUT')
}
/**
* 正确转化特殊文本:字符实体、换行符
* @param {String} str 转化的文本
* @returns {String}
*/
function parse(str) {
return str.replace(/&(#\d|\w)+;/g, function (v) {
const e = document.createElement('span')
e.innerHTML = v
return e.textContent
}).replace(/\n|\r/g, '\r\n')// 注意:win系统下为'\r\n',其它系统可能不一致
}
/**
* 指定索引传入控件数据
* @param {Element} e 表单控件
* @param {String} name 设置的索引
* @param {Object} values 存储数据的1维对象
* @param {Object} elements 存储控件的1维对象
* @param {Boolean} isLastSelected [false]是否只选择select标签的最后一项
*/
function setValue(e, name, values, elements, isLastSelected) {
const n = e.tagName
const t = e.type // 只有小写;未设置或不支持时,均会转为默认类型
if (n === 'INPUT' && t !== 'reset' && t !== 'button') {
name = prefixToKeys + name
// 如果是提交按钮,且与设定的一致,则录入
if (t === 'submit') {
if (e === submitButton) elements[name] = e, values[name] = parse(e.value)
}
// 如果不是提交按钮:如果不是单/复选框,记录;否则记录选中的
else if (t !== 'radio' && t !== 'checkbox' || e.checked) elements[name] = e, values[name] = parse(e.value)
} else if (n === 'SELECT') {
// 若无选项则不记录
if (e.length === 0) return
// 如果是多选,补充到数据对象中(已对索引补前缀);否则直接设置
e.multiple ? Object.assign(values, getSelectMultipleValues(e, name, isLastSelected)) : (name = prefixToKeys + name, elements[name] = e, values[name] = parse(e.value))
} else if (n === 'TEXTAREA' || e === submitButton) {
name = prefixToKeys + name
elements[name] = e
values[name] = parse(e.value)
}
}
return values
}
/**
* PHP语言对html表单的name属性的转化方式(左边数字表示优先级,0最大)
* 0)所有表单控件均有type属性,如果未设置或不支持均会转为默认值,并且会自动转为全小写
* 0)严格区分大小写
* 0)'[]'两个字符分别为多选起始符、多选终止符
* -1)解析并转化字符实体、换行符
* -2)去除左边的空格(非字符实体)
* -3)控件被禁用、name为空(去除'[xxx]'部分后)、首字符是'['、文件控件则忽略
* -4)多选时,自增的最大整数索引(设为N)为2^31-1,即0x7FFFFFFF
* -4)出现相同的name名称(去除'[xxx]'部分后),如果之前不是多选,或者与之前选择类型不同,那么之前的数据一定会被覆盖掉
* -5)把首个'['之前的所有空格替换为'_'
* -6)如果存在'[',但其后不存在']',则会把首个'['替换为'_',并保留所有文本
* -7)如果存在'[',且其后存在']',则会以首个'['到其后首个']'之间的文本(设为S)作为二维索引,忽略其后的文本,但是:
* 如果S为''或' ',则以当前数据中最大的自然数索引+1(称:预设索引),但是:
* 预设索引<=N时,预设索引不变
* 预设索引>N时,会忽略当前数据,即保持索引为N的初始数据不变,也不再添加任何数据
* 否则直接以S作为二维索引(无转化)存储数据
* 基于以上规则,如果是select标签多选,那么当前数据会插入被选中的数据,而不是新增单独一组
* -8)按钮中仅具有name合法属性的[type="submit"]的提交按钮可以上传自身value,但是:
* 如果是通过当前按钮提交的,则上传当前按钮的value值
* 否则找到关联form的首个具有name合法属性的[type="submit"]的提交按钮的数据,如果未被禁用则上传,否则不上传
*/
JS获取form下的所有有效的数据
于 2022-01-16 19:58:11 首次发布