这是我根据百度前端技术学院的问题需求设计的表单生成器,写的还有很多不足的地方,望各位前辈指点。如果你有好的解决方案欢迎在评论区留言。
这是问题中给的以JavaScript对象形式表示的的配置(仅供参考)
{
label: '名称', // 表单标签
type: 'input', // 表单类型
validator: function () {...}, // 表单验证规
rules: '必填,长度为4-16个字符', // 填写规则提示
success: '格式正确', // 验证通过提示
fail: '名称不能为空' // 验证失败提示
}
但是我觉的这个配置还有不足的:
input
还有很多类型text
,password
等,如何区分各种input
呢?- 如果
type
为select
或textarea
,它们不像input
是一个空元素,它还有子元素,那么子元素该怎么表示呢? - 元素上应该还有一些其他的重要的属性如
name
,value
,min
,max
,multiple
,id
等,这些属性也没有表示。 success
和fail
字段可以去掉,只需要规则字段就可以了。这里的rules
我们假设只有一个规则,使用单数形式rule
。
根据上面所发现的问题,我们重新设计了配置文件
{
label: '名称', // 表单标签
type: 'input', // 表单类型
attributes: {}, // 表单的各项属性
children: [], // 表单的子元素
validator: function () {...}, // 表单验证规
rule: '必填,长度为4-16个字符', // 填写规则提示
}
我们表单元素的形式可以有以下几种:div > label + input/select/textarea + rule
其中select
比较复杂的,因为它还可以包含子元素option
或optgroup
,optgroup
又可以包含option
。
下面是生成表单函数的实现,根据配置信息生成表单元素,主要分以下两步:
创建并设置dom调用createElement或createTextNode生成dom
- 设置dom的各项属性
- 递归调用,生成子元素(如果需要的话)
- 添加节点
添加交互
- 主要是验证输入是否符合规则
// generateFormElement.js
const genrateFormElement = (config) => {
if (typeof config === "string") {
const textNode = document.createTextNode(config);
return textNode;
}
let wrapper = null,
label = null,
formElem = null,
ruleElem = null;
if (config.label) {
wrapper = document.createElement("div");
// 设置 label formElem 的排布方向 在一行排布 还是 在一列排布
if (config.attributes.direction)
wrapper.classList.add(config.attributes.direction);
label = document.createElement("label");
label.setAttribute("for", config.attributes.id);
label.textContent = config.label;
}
formElem = document.createElement(config.type);
for (const attr in config.attributes) {
if (!Object.hasOwn(config.attributes, attr) || attr === "direction")
continue;
formElem.setAttribute(attr, config.attributes[attr]);
}
// 添加子元素,如果有的话。
if (config.children) {
for (const child of config.children) {
formElem.append(genrateFormElement(child));
}
}
if (config.rule) {
ruleElem = document.createElement("span");
ruleElem.textContent = config.rule;
}
// 将label, formElem, ruleElem包裹在wrapper中
if (wrapper) {
label && wrapper.append(label);
formElem && wrapper.append(formElem);
ruleElem && wrapper.append(ruleElem);
}
// 添加交互
if (config.validator) {
formElem.addEventListener("keyup", (event) => {
if (config.validator(event.target.value)) {
console.log("valid success");
ruleElem?.classList.remove("valid-fail");
ruleElem?.classList.add("valid-success");
} else {
console.log("valid fail");
ruleElem?.classList.remove("valid-success");
ruleElem?.classList.add("valid-fail");
}
});
}
return wrapper ? wrapper : formElem;
};
export default genrateFormElement;
手写配置(以后可以改成通过交互操作表单来设置配置信息),验证表单生成函数是否奏效
// index.js
import genrateFormElement from "./generateFormElement.js";
const form = document.querySelector("#generate-form");
/* 预期:
<div class="row">
<label id="name">姓名</label>
<input name="name" id="name" required />
<span>名字不能为空<span>
</div>
*/
const config1 = {
label: "姓名",
type: "input",
attributes: {
name: "name",
id: "name",
direction: "row",
required: true,
},
validator: function (value) {
return value !== "";
},
rule: "名字不能为空",
};
const elem1 = genrateFormElement(config1);
form.append(elem1);
/*
预期:
<div class="column">
<label id="preference">喜好</label>
<select namme="preference" id="preference" required>
<option value="hiking">hiking</option>
<option value="swimming">swimming</option>
<option value="ping-pong">ping-pong</option>
</select>
</div>
*/
const config2 = {
label: "喜好",
type: "select",
attributes: {
name: "preference",
id: "preference",
direction: "column",
required: true,
},
children: [
{
type: "option",
attributes: {
value: "hiking",
},
children: ["hiking"],
},
{
type: "option",
attributes: {
value: "swimming",
},
children: ["swimming"],
},
{
type: "option",
attributes: {
value: "ping-pong",
},
children: ["ping-pong"],
},
],
};
const elem2 = genrateFormElement(config2);
form.append(elem2);
/*
预期:
<div class="column">
<label for="all-preference">所有喜好</label>
<select name="all-preference">
<optgroup label="sports">
<option value="swimming">swimming</option>
<option value="running">running</option>
</optgroup>
<optgroup label="sports">
<option value="swimming">swimming</option>
<option value="running">running</option>
</optgroup>
</select>
<div>
*/
const config3 = {
label: "所有喜好",
type: "select",
attributes: {
name: "all-preference",
id: "all-preference",
direction: "column",
required: true,
},
children: [
{
type: "optgroup",
attributes: {
label: "sports",
},
children: [
{
type: "option",
attributes: {
value: "swimming",
},
children: ["swimming"],
},
{
type: "option",
attributes: {
value: "running",
},
children: ["running"],
},
],
},
{
type: "optgroup",
attributes: {
label: "sports",
},
children: [
{
type: "option",
attributes: {
value: "swimming",
},
children: ["swimming"],
},
{
type: "option",
attributes: {
value: "running",
},
children: ["running"],
},
],
},
],
};
/* 预期:
<div class="column">
<label id="comment">评论</label>
<input name="comment" id="comment" required />
<span>名字不能为空<span>
</div>
*/
const elem3 = genrateFormElement(config3);
form.append(elem3);
const config4 = {
label: "评论",
type: "textarea",
attributes: {
name: "comment",
id: "comment",
direction: "column",
},
};
const elem4 = genrateFormElement(config4);
form.append(elem4);样式的设置:* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
style设置:
html {
block-size: 100%;
}
body {
min-block-size: 100%;
display: grid;
place-content: center;
}
form {
display: flex;
flex-direction: column;
gap: 1ch;
}
/* utils */
.row {
display: flex;
flex-direction: row;
gap: 1ch;
}
.column {
display: flex;
flex-direction: column;
gap: 1ch;
}
.valid-fail {
color: red;
}
.valid-success {
color: green;
}
结果截图:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。