JavaScript不知道如何与文件系统交互的观点并不完全正确。确切地说,与Node.js或PHP之类的服务器端编程语言相比,这种交互受到了极大的限制。尽管如此,JavaScript既可以接收(接收)并创建某些类型的文件,并在本地成功处理它们。
在本文中,我们将创建三个小项目:
- 我们以txt和pdf格式实现图像,音频,视频和文本的接收和处理
- 让我们创建一个JSON文件生成器
- 让我们编写两个程序:一个程序将形成问题(JSON格式),另一个程序将使用它们来创建测试
如果您有兴趣,请关注我。GitHub上的
项目代码。
我们接收并处理文件
首先,让我们创建一个目录,用于存储我们的项目。我们称其为“ JavaScript中的文件处理”或任何您喜欢的文件。
在此目录中,我们将为第一个项目创建一个文件夹。我们称之为“文件读取器”。
在其中创建包含以下内容的文件“ index.html”:
<div>+</div>
<input type="file">
在这里,我们有一个容器文件接收器和一个“文件”类型的输入(要获取文件;我们将处理单个文件;要获取多个文件,输入应添加属性“多个”),该属性将隐藏在容器下。
样式可以包含在单独的文件中,也可以包含在头部内的“样式”标签中:
body {
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
max-width: 768px;
background: radial-gradient(circle, skyblue, steelblue);
color: #222;
}
div {
width: 150px;
height: 150px;
display: flex;
justify-content: center;
align-items: center;
font-size: 10em;
font-weight: bold;
border: 6px solid;
border-radius: 8px;
user-select: none;
cursor: pointer;
}
input {
display: none;
}
img,
audio,
video {
max-width: 80vw;
max-height: 80vh;
}
您可以按自己的喜好进行设计。
不要忘了在脚本中包含带有“ defer”属性的脚本(我们需要等待DOM绘制(渲染);您当然可以通过处理“窗口”对象的“ load”或“ DOMContentLoaded”事件在脚本中完成此操作,但是defer则要短得多) ,或在结束的“ body”标记之前(然后不需要属性和处理程序)。我个人更喜欢第一种选择。
让我们在浏览器中打开index.html:
在编写脚本之前,我们需要为应用程序准备文件:我们需要图像,音频,视频,txt,pdf和其他格式的文本,例如doc。您可以使用我的收藏或建立自己的收藏。
我们经常必须访问“ document”和“ document.body”对象,并将结果多次输出到控制台,因此我建议将代码包装在此IIFE中(这是可选的):
;((D, B, log = arg => console.log(arg)) => {
//
// document document.body D B,
// log = arg => console.log(arg) -
// console.log log
})(document, document.body)
首先,我们为文件接收器,输入和文件声明变量(我们不初始化后者,因为其值取决于传输方法-通过单击输入或将其放入文件接收器):
const dropZone = D.querySelector('div')
const input = D.querySelector('input')
let file
禁用浏览器对“ dragover”和“ drop”事件的处理:
D.addEventListener('dragover', ev => ev.preventDefault())
D.addEventListener('drop', ev => ev.preventDefault())
为了了解我们为什么这样做,请尝试将图像或其他文件传输到浏览器,然后看看会发生什么。并且有自动处理文件的功能 我们将出于教育目的自行实施的措施。
我们处理将文件扔到文件接收器中:
dropZone.addEventListener('drop', ev => {
//
ev.preventDefault()
// ,
log(ev.dataTransfer)
// ( )
/*
DataTransfer {dropEffect: "none", effectAllowed: "all", items: DataTransferItemList, types: Array(1), files: FileList}
dropEffect: "none"
effectAllowed: "all"
=> files: FileList
length: 0
__proto__: FileList
items: DataTransferItemList {length: 0}
types: []
__proto__: DataTransfer
*/
// (File) "files" "DataTransfer"
//
file = ev.dataTransfer.files[0]
//
log(file)
/*
File {name: "image.png", lastModified: 1593246425244, lastModifiedDate: Sat Jun 27 2020 13:27:05 GMT+0500 (, ), webkitRelativePath: "", size: 208474, …}
lastModified: 1593246425244
lastModifiedDate: Sat Jun 27 2020 13:27:05 GMT+0500 (, ) {}
name: "image.png"
size: 208474
type: "image/png"
webkitRelativePath: ""
__proto__: File
*/
//
handleFile(file)
})
我们刚刚实现了最简单的dran'n'drop机制。
我们处理文件接收器上的单击(将单击委派给输入):
dropZone.addEventListener('click', () => {
//
input.click()
//
input.addEventListener('change', () => {
// ,
log(input.files)
// ( )
/*
FileList {0: File, length: 1}
=> 0: File
lastModified: 1593246425244
lastModifiedDate: Sat Jun 27 2020 13:27:05 GMT+0500 (, ) {}
name: "image.png"
size: 208474
type: "image/png"
webkitRelativePath: ""
__proto__: File
length: 1
__proto__: FileList
*/
// File
file = input.files[0]
//
log(file)
//
handleFile(file)
})
})
让我们开始处理文件:
const handleFile = file => {
//
}
我们删除文件接收器并输入:
dropZone.remove()
input.remove()
文件的处理方式取决于其类型:
log(file.type)
//
// image/png
我们将不使用html,css和js文件,因此我们禁止对其进行处理:
if (file.type === 'text/html' ||
file.type === 'text/css' ||
file.type === 'text/javascript')
return;
我们也将无法使用MS文件(MIME类型为“ application / msword”,“ application / vnd.ms-excel”等),因为它们无法通过本机方式进行处理。所有在StackOverflow和其他资源上提供的处理此类文件的方法,都可以归结为使用各种库或不想使用文件系统和本地主机的Google和Microsoft查看器转换为其他格式。同时,pdf文件的类型也以“ application”开头,因此我们将分别处理这些文件:
if (file.type === 'application/pdf') {
createIframe(file)
return;
}
对于其余文件,我们得到它们的“组”类型:
// ,
const type = file.type.replace(/\/.+/, '')
//
log(type)
//
// image
使用switch..case,我们定义了特定的文件处理功能:
switch (type) {
//
case 'image':
createImage(file)
break;
//
case 'audio':
createAudio(file)
break;
//
case 'video':
createVideo(file)
break;
//
case 'text':
createText(file)
break;
// , ,
//
default:
B.innerHTML = `<h3>Unknown File Format!</h3>`
const timer = setTimeout(() => {
location.reload()
clearTimeout(timer)
}, 2000)
break;
}
图像处理功能:
const createImage = image => {
// "img"
const imageEl = D.createElement('img')
//
imageEl.src = URL.createObjectURL(image)
//
log(imageEl)
//
B.append(imageEl)
//
URL.revokeObjectURL(image)
}
音频处理功能:
const createAudio = audio => {
// "audio"
const audioEl = D.createElement('audio')
//
audioEl.setAttribute('controls', '')
//
audioEl.src = URL.createObjectURL(audio)
//
log(audioEl)
//
B.append(audioEl)
//
audioEl.play()
//
URL.revokeObjectURL(audio)
}
视频处理功能:
const createVideo = video => {
// "video"
const videoEl = D.createElement('video')
//
videoEl.setAttribute('controls', '')
//
videoEl.setAttribute('loop', 'true')
//
videoEl.src = URL.createObjectURL(video)
//
log(videoEl)
//
B.append(videoEl)
//
videoEl.play()
//
URL.revokeObjectURL(video)
}
文字处理功能:
const createText = text => {
// "FileReader"
const reader = new FileReader()
//
//
// - utf-8,
//
reader.readAsText(text, 'windows-1251')
//
//
reader.onload = () => B.innerHTML = `<p><pre>${reader.result}</pre></p>`
}
最后但并非最不重要的pdf处理功能:
const createIframe = pdf => {
// "iframe"
const iframe = D.createElement('iframe')
//
iframe.src = URL.createObjectURL(pdf)
//
iframe.width = innerWidth
iframe.height = innerHeight
//
log(iframe)
//
B.append(iframe)
//
URL.revokeObjectURL(pdf)
}
结果:
创建一个JSON文件
对于第二个项目,让我们在根目录(JavaScript中处理文件)中创建一个“ Create-JSON”文件夹。
创建具有以下内容的文件“ index.html”:
<!-- head -->
<!-- materialize css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- material icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- body -->
<h3>Create JSON</h3>
<!-- -->
<div class="row main">
<h3>Create JSON</h3>
<form class="col s12">
<!-- "-" -->
<div class="row">
<div class="input-field col s5">
<label>key</label>
<input type="text" value="1" required>
</div>
<div class="input-field col s2">
<p>:</p>
</div>
<div class="input-field col s5">
<label>value</label>
<input type="text" value="foo" required>
</div>
</div>
<!-- -->
<div class="row">
<div class="input-field col s5">
<label>key</label>
<input type="text" value="2" required>
</div>
<div class="input-field col s2">
<p>:</p>
</div>
<div class="input-field col s5">
<label>value</label>
<input type="text" value="bar" required>
</div>
</div>
<!-- -->
<div class="row">
<div class="input-field col s5">
<label>key</label>
<input type="text" value="3" required>
</div>
<div class="input-field col s2">
<p>:</p>
</div>
<div class="input-field col s5">
<label>value</label>
<input type="text" value="baz" required>
</div>
</div>
<!-- -->
<div class="row">
<button class="btn waves-effect waves-light create-json">create json
<i class="material-icons right">send</i>
</button>
<a class="waves-effect waves-light btn get-data"><i class="material-icons right">cloud</i>get data</a>
</div>
</form>
</div>
Materialize 用于样式化。
添加一些自定义样式:
body {
max-width: 512px;
margin: 0 auto;
text-align: center;
}
input {
text-align: center;
}
.get-data {
margin-left: 1em;
}
我们得到以下信息:
JSON文件具有以下格式:
{
"": "",
"": "",
...
}
类型“文本”的奇数输入是键,偶数是值。我们为输入分配默认值(值可以是任意值)。带有``create-json''类的按钮用于获取用户输入的值并创建文件。从“获取数据”类中获取按钮-获取数据。
让我们继续执行脚本:
// "create-json"
document.querySelector('.create-json').addEventListener('click', ev => {
// "submit" , ..
//
// ,
ev.preventDefault()
//
const inputs = document.querySelectorAll('input')
// ,
//
//
// "chunk" "lodash"
// (, ) -
//
// (, ) -
//
const arr = []
for (let i = 0; i < inputs.length; ++i) {
arr.push([inputs[i].value, inputs[++i].value])
}
// ,
console.log(arr)
/*
[
["1", "foo"]
["2", "bar"]
["3", "baz"]
]
*/
//
const data = Object.fromEntries(arr)
//
console.log(data)
/*
{
1: "foo"
2: "bar"
3: "baz"
}
*/
//
const file = new Blob(
//
[JSON.stringify(data)], {
type: 'application/json'
}
)
//
console.log(file)
/*
{
"1": "foo",
"2": "bar",
"3": "baz"
}
*/
// ,
// "a"
const link = document.createElement('a')
// "href" "a"
link.setAttribute('href', URL.createObjectURL(file))
// "download" ,
// -
link.setAttribute('download', 'data.json')
//
link.textContent = 'DOWNLOAD DATA'
// "main"
document.querySelector('.main').append(link)
//
URL.revokeObjectURL(file)
// { once: true }
//
}, { once: true })
通过单击“ CREATE JSON”按钮,将生成“ data.json”文件,并出现“ DOWNLOAD DATA”链接以下载此文件。
该文件怎么办?下载并将其放置在“ Create-JSON”文件夹中。
我们得到:
// ( ) "get-data"
document.querySelector('.get-data').addEventListener('click', () => {
// IIFE async..await
(async () => {
const response = await fetch('data.json')
// ()
const data = await response.json()
console.table(data)
})()
})
结果:
创建一个问题生成器和测试人员
问题发生器
对于第三个项目,让我们在根目录中创建一个“ Test-Maker”文件夹。
创建具有以下内容的文件“ createTest.html”:
<!-- head -->
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<!-- body -->
<!-- -->
<div class="container">
<h3>Create Test</h3>
<form id="questions-box">
<!-- -->
<div class="question-box">
<br><hr>
<h4 class="title"></h4>
<!-- -->
<div class="row">
<input type="text" class="form-control col-11 question-text" value="first question" >
<!-- -->
<button class="btn btn-danger col remove-question-btn">X</button>
</div>
<hr>
<h4>Answers:</h4>
<!-- -->
<div class="row answers-box">
<!-- -->
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="radio" checked name="answer">
</div>
</div>
<input class="form-control answer-text" type="text" value="foo" >
<!-- -->
<div class="input-group-append">
<button class="btn btn-outline-danger remove-answer-btn">X</button>
</div>
</div>
<!-- -->
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="radio" name="answer">
</div>
</div>
<input class="form-control answer-text" type="text" value="bar" >
<div class="input-group-append">
<button class="btn btn-outline-danger remove-answer-btn">X</button>
</div>
</div>
<!-- -->
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="radio" name="answer">
</div>
</div>
<input class="form-control answer-text" type="text" value="baz" >
<div class="input-group-append">
<button class="btn btn-outline-danger remove-answer-btn">X</button>
</div>
</div>
</div>
<br>
<!-- -->
<button class="btn btn-primary add-answer-btn">Add answer</button>
<hr>
<h4>Explanation:</h4>
<!-- -->
<div class="row explanation-box">
<input type="text" value="first explanation" class="form-control explanation-text" >
</div>
</div>
</form>
<br>
<!-- -->
<button class="btn btn-primary" id="add-question-btn">Add question</button>
<button class="btn btn-primary" id="create-test-btn">Create test</button>
</div>
这次,Bootstrap用于样式设置。因为我们将在JS中验证表单,所以我们没有使用“ required”属性(带有必填项,包含多个必填字段的表单的行为会令人讨厌)。
添加一些自定义样式:
body {
max-width: 512px;
margin: 0 auto;
text-align: center;
}
input[type="radio"] {
cursor: pointer;
}
我们
得到以下信息:我们有一个问题模板。我建议使用动态导入将其移动到一个单独的文件中,以用作组件。创建具有以下内容的文件“ Question.js”:
export default (name = Date.now()) => `
<div class="question-box">
<br><hr>
<h4 class="title"></h4>
<div class="row">
<input type="text" class="form-control col-11 question-text">
<button class="btn btn-danger col remove-question-btn">X</button>
</div>
<hr>
<h4>Answers:</h4>
<div class="row answers-box">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="radio" checked name="${name}">
</div>
</div>
<input class="form-control answer-text" type="text" >
<div class="input-group-append">
<button class="btn btn-outline-danger remove-answer-btn">X</button>
</div>
</div>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="radio" name="${name}">
</div>
</div>
<input class="form-control answer-text" type="text" >
<div class="input-group-append">
<button class="btn btn-outline-danger remove-answer-btn">X</button>
</div>
</div>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="radio" name="${name}">
</div>
</div>
<input class="form-control answer-text" type="text" >
<div class="input-group-append">
<button class="btn btn-outline-danger remove-answer-btn">X</button>
</div>
</div>
</div>
<br>
<button class="btn btn-primary add-answer-btn">Add answer</button>
<hr>
<h4>Explanation:</h4>
<div class="row explanation-box">
<input type="text" class="form-control explanation-text">
</div>
</div>
`
这里我们具有与createTest.html中相同的所有东西,除了我们删除了输入的默认值并将``name''参数作为具有相同名称的属性的值传递(此属性对于每个问题必须是唯一的-这使得可能切换答案选项,请选择以下之一)。名称的默认值是自1970年1月1日以来的时间,以毫秒为单位,是用于获取唯一标识符的Nanoid随机值生成器的一种简单替代方法(用户不太可能有时间在1毫秒内创建两个问题)。
让我们进入主脚本。
我将创建一些辅助(工厂)功能,但这不是必需的。
次要功能:
//
const findOne = (element, selector) => element.querySelector(selector)
//
const findAll = (element, selector) => element.querySelectorAll(selector)
//
const addHandler = (element, event, callback) => element.addEventListener(event, callback)
//
// Bootstrap ,
// DOM
// - (),
// 1
const findParent = (element, depth = 1) => {
// ,
// ,
let parentEl = element.parentElement
// , ..
while (depth > 1) {
//
parentEl = findParent(parentEl)
//
depth--
}
//
return parentEl
}
在我们的例子中,寻找父元素,我们将达到第三层嵌套。由于我们知道这些级别的确切数目,因此我们可以使用if..else if或switch..case,但是递归选项的用途更加广泛。
再一次:没有必要引入工厂功能,您可以轻松地使用标准功能。
查找主要容器和要提问的容器,并禁用表单提交:
const C = findOne(document.body, '.container')
// const C = document.body.querySelector('.container')
const Q = findOne(C, '#questions-box')
addHandler(Q, 'submit', ev => ev.preventDefault())
// Q.addEventListener('submit', ev => ev.preventDefault())
按钮初始化功能删除问题:
//
const initRemoveQuestionBtn = q => {
const removeQuestionBtn = findOne(q, '.remove-question-btn')
addHandler(removeQuestionBtn, 'click', ev => {
//
/*
=> <div class="question-box">
<br><hr>
<h4 class="title"></h4>
=> <div class="row">
<input type="text" class="form-control col-11 question-text" value="first question" >
=> <button class="btn btn-danger col remove-question-btn">X</button>
</div>
...
*/
findParent(ev.target, 2).remove()
// ev.target.parentElement.parentElement.remove()
//
initTitles()
}, {
//
once: true
})
}
按钮初始化功能删除答案选项:
const initRemoveAnswerBtns = q => {
const removeAnswerBtns = findAll(q, '.remove-answer-btn')
// const removeAnswerBtns = q.querySelectorAll('.remove-answer-btn')
removeAnswerBtns.forEach(btn => addHandler(btn, 'click', ev => {
/*
=> <div class="input-group">
...
=> <div class="input-group-append">
=> <button class="btn btn-outline-danger remove-answer-btn">X</button>
</div>
</div>
*/
findParent(ev.target, 2).remove()
}, {
once: true
}))
}
按钮初始化功能添加答案选项:
const initAddAnswerBtns = q => {
const addAnswerBtns = findAll(q, '.add-answer-btn')
addAnswerBtns.forEach(btn => addHandler(btn, 'click', ev => {
//
const answers = findOne(findParent(ev.target), '.answers-box')
// const answers = ev.target.parentElement.querySelector('.answers-box')
// "name"
let name
answers.children.length > 0
? name = findOne(answers, 'input[type="radio"]').name
: name = Date.now()
//
const template = `
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="radio" name="${name}">
</div>
</div>
<input class="form-control answer-text" type="text" value="">
<div class="input-group-append">
<button class="btn btn-outline-danger remove-answer-btn">X</button>
</div>
</div>
`
//
answers.insertAdjacentHTML('beforeend', template)
//
initRemoveAnswerBtns(q)
}))
}
我们将初始化按钮的功能组合为一个:
const initBtns = q => {
initRemoveQuestionBtn(q)
initRemoveAnswerBtns(q)
initAddAnswerBtns(q)
}
初始化问题标题的功能:
const initTitles = () => {
//
const questions = Array.from(findAll(Q, '.question-box'))
//
questions.map(q => {
const title = findOne(q, '.title')
// - + 1
title.textContent = `Question ${questions.indexOf(q) + 1}`
})
}
让我们初始化按钮和问题的标题:
initBtns(findOne(Q, '.question-box'))
initTitles()
添加问题功能:
//
const addQuestionBtn = findOne(C, '#add-question-btn')
addHandler(addQuestionBtn, 'click', ev => {
// IIFE async..await
//
//
//
(async () => {
const data = await import('./Question.js')
const template = await data.default()
await Q.insertAdjacentHTML('beforeend', template)
const question = findOne(Q, '.question-box:last-child')
initBtns(question)
initTitles()
})()
})
测试创建功能:
//
addHandler(findOne(C, '#create-test-btn'), 'click', () => createTest())
const createTest = () => {
//
const obj = {}
//
const questions = findAll(Q, '.question-box')
//
//
const isEmpty = (...args) => {
//
args.map(arg => {
//
//
arg = arg.replace(/\s+/g, '').trim()
//
if (arg === '') {
//
alert('Some field is empty!')
//
throw new Error()
}
})
}
//
questions.forEach(q => {
//
const questionText = findOne(q, '.question-text').value
//
//
const answersText = []
findAll(q, '.answer-text').forEach(text => answersText.push(text.value))
// - "checked" "answer-text"
/*
=> <div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">
=> <input type="radio" checked name="answer">
</div>
</div>
=> <input class="form-control answer-text" type="text" value="foo" >
...
*/
const rightAnswerText = findOne(findParent(findOne(q, 'input:checked'), 3), '.answer-text').value
//
const explanationText = findOne(q, '.explanation-text').value
//
isEmpty(questionText, ...answersText, explanationText)
// " "
obj[questions.indexOf(q)] = {
question: questionText,
answers: answersText,
rightAnswer: rightAnswerText,
explanation: explanationText
}
})
//
console.table(obj)
//
const data = new Blob(
[JSON.stringify(obj)], {
type: 'application/json'
}
)
//
//
if (findOne(C, 'a') !== null) {
findOne(C, 'a').remove()
}
//
const link = document.createElement('a')
link.setAttribute('href', URL.createObjectURL(data))
link.setAttribute('download', 'data.json')
link.className = 'btn btn-success'
link.textContent = 'Download data'
C.append(link)
URL.revokeObjectURL(data)
}
结果:
使用文件中的数据
使用问题生成器,创建如下文件:
{
"0": {
"question": "first question",
"answers": ["foo", "bar", "baz"],
"rightAnswer": "foo",
"explanation": "first explanation"
},
"1": {
"question": "second question",
"answers": ["foo", "bar", "baz"],
"rightAnswer": "bar",
"explanation": "second explanation"
},
"2": {
"question": "third question",
"answers": ["foo", "bar", "baz"],
"rightAnswer": "baz",
"explanation": "third explanation"
}
}
将此文件(data.json)放在“ Test-Maker”文件夹中。
创建具有以下内容的文件“ useData.html”:
<!-- head -->
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<!-- body -->
<h1>Use data</h1>
添加一些自定义样式:
body {
max-width: 512px;
margin: 0 auto;
text-align: center;
}
section *:not(h3) {
text-align: left;
}
input,
button {
margin: .4em;
}
label,
input {
cursor: pointer;
}
.right-answer,
.explanation {
display: none;
}
脚本:
//
const getData = async url => {
const response = await fetch(url)
const data = await response.json()
return data
}
//
getData('data.json')
.then(data => {
//
console.table(data)
//
createTest(data)
})
// name
let name = Date.now()
//
const createTest = data => {
// data -
//
for (const item in data) {
//
console.log(data[item])
// ,
// , ,
const {
question,
answers,
rightAnswer,
explanation
} = data[item]
// name
name++
//
const questionTemplate = `
<hr>
<section>
<h3>Question ${item}: ${question}</h3>
<form>
<legend>Answers</legend>
${answers.reduce((html, ans) => html += `<label><input type="radio" name="${name}">${ans}</label><br>`, '')}
</form>
<p class="right-answer">Right answer: ${rightAnswer}</p>
<p class="explanation">Explanation: ${explanation}</p>
</section>
`
//
document.body.insertAdjacentHTML('beforeend', questionTemplate)
})
//
const forms = document.querySelectorAll('form')
//
forms.forEach(form => {
const input = form.querySelector('input')
input.click()
})
//
//
const btn = document.createElement('button')
btn.className = 'btn btn-primary'
btn.textContent = 'Check answers'
document.body.append(btn)
//
btn.addEventListener('click', () => {
//
const answers = []
//
forms.forEach(form => {
// ()
const chosenAnswer = form.querySelector('input:checked').parentElement.textContent
//
const rightAnswer = form.nextElementSibling.textContent.replace('Right answer: ', '')
//
answers.push([chosenAnswer, rightAnswer])
})
console.log(answers)
//
// ,
/*
Array(3)
0: (2) ["foo", "foo"]
1: (2) ["bar", "bar"]
2: (2) ["foo", "baz"]
*/
//
checkAnswers(answers)
})
// ()
const checkAnswers = answers => {
//
let rightAnswers = 0
let wrongAnswers = 0
// ,
// - () ,
// -
for (const answer of answers) {
//
if (answer[0] === answer[1]) {
//
rightAnswers++
//
} else {
//
wrongAnswers++
//
const wrongSection = forms[answers.indexOf(answer)].parentElement
//
wrongSection.querySelector('.right-answer').style.display = 'block'
wrongSection.querySelector('.explanation').style.display = 'block'
}
}
//
const percent = parseInt(rightAnswers / answers.length * 100)
// -
let result = ''
//
// result
if (percent >= 80) {
result = 'Great job, super genius!'
} else if (percent > 50) {
result = 'Not bad, but you can do it better!'
} else {
result = 'Very bad, try again!'
}
//
const resultTemplate = `
<h3>Your result</h3>
<p>Right answers: ${rightAnswers}</p>
<p>Wrong answers: ${wrongAnswers}</p>
<p>Percentage of correct answers: ${percent}</p>
<p>${result}</p>
`
//
document.body.insertAdjacentHTML('beforeend', resultTemplate)
}
}
结果(如果第三个问题的答案是错误的):
奖金。将数据写入CloudFlare
转到cloudflare.com,注册,单击右侧的Workers,然后单击“创建Worker”按钮。
将工作程序的名称更改为“数据”(这是可选的)。在“ {}脚本”字段中,插入以下代码,然后单击“保存并部署”按钮:
//
addEventListener('fetch', event => {
event.respondWith(
new Response(
//
`{
"0": {
"question": "first question",
"answers": ["foo", "bar", "baz"],
"rightAnswer": "foo",
"explanation": "first explanation"
},
"1": {
"question": "second question",
"answers": ["foo", "bar", "baz"],
"rightAnswer": "bar",
"explanation": "second explanation"
},
"2": {
"question": "third question",
"answers": ["foo", "bar", "baz"],
"rightAnswer": "baz",
"explanation": "third explanation"
}
}`,
{
status: 200,
// CORS
headers: new Headers({'Access-Control-Allow-Origin': '*'})
})
)
})
现在,我们可以从CloudFlare接收数据。为此,只需在“ getData”函数中指定工作人员的URL而不是“ data.json”。在我的情况下,它看起来像这样:getData('https://data.aio350.workers.dev/').then(...)。
长篇文章横空出世。我希望您发现其中有用的东西。
感谢您的关注。