الرأي القائل بأن JavaScript لا يعرف كيفية التفاعل مع نظام الملفات ليس صحيحًا تمامًا. بدلاً من ذلك ، يتعلق الأمر بحقيقة أن هذا التفاعل محدود بشكل كبير مقارنة بلغات البرمجة من جانب الخادم مثل Node.js أو PHP. ومع ذلك ، فإن JavaScript قادر على تلقي (استقبال) وإنشاء بعض أنواع الملفات ومعالجتها محليًا بنجاح.
في هذا المقال سننشئ ثلاثة مشاريع صغيرة:
- نقوم بتنفيذ استلام ومعالجة الصور والصوت والفيديو والنصوص بتنسيق txt و pdf
- لنقم بإنشاء مولد ملفات JSON
- دعنا نكتب برنامجين: أحدهما سيشكل أسئلة (بتنسيق JSON) ، والآخر سيستخدمها لإنشاء اختبار
إذا كنت مهتمًا ، من فضلك اتبعني.
كود المشروع على جيثب .
نستقبل الملفات ونعالجها
أولاً ، لنقم بإنشاء دليل حيث سيتم تخزين مشاريعنا. دعنا نسميها "Work-With-Files-in-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;
}
يمكنك جعل التصميم يرضيك.
لا تنس تضمين النص البرمجي إما في الرأس مع سمة "تأجيل" (نحتاج إلى الانتظار حتى يتم رسم DOM (يتم تقديمه) ؛ يمكنك بالطبع القيام بذلك في النص البرمجي عن طريق معالجة حدث "التحميل" أو "DOMContentLoaded" لكائن "النافذة" ، ولكن التأجيل أقصر بكثير) ، أو قبل علامة الإغلاق "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
تعطيل تعامل المتصفح مع أحداث "السحب والإفلات":
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
بالنسبة للمشروع الثاني ، أنشئ مجلد "Create-JSON" في الدليل الجذر (Work-With-Files-in-JavaScript).
قم بإنشاء ملف "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>
وتتحقق المستخدمة في التصميم .
أضف نوعين من الأنماط المخصصة:
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 (مع المطلوب ، يصبح سلوك نموذج يتكون من عدة حقول مطلوبة أمرًا مزعجًا).
أضف نوعين من الأنماط المخصصة:
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 ، باستثناء أننا أزلنا القيم الافتراضية للمدخلات وتمرير وسيطة "الاسم" كقيمة للسمة التي تحمل الاسم نفسه (يجب أن تكون هذه السمة فريدة لكل سؤال - وهذا يجعل ذلك ممكنًا تبديل خيارات الإجابة ، اختر واحدًا من عدة خيارات). القيمة الافتراضية للاسم هي الوقت بالمللي ثانية منذ 1 يناير 1970 ، وهي بديل بسيط لمولدات القيمة العشوائية النانوية المستخدمة للحصول على معرف فريد (من غير المحتمل أن يكون لدى المستخدم الوقت لإنشاء سؤالين في 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 or 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 ، وقم بالتسجيل ، وانقر فوق العمال على اليمين ، ثم انقر فوق الزر "إنشاء عامل".
قم بتغيير اسم العامل إلى "بيانات" (هذا اختياري). في الحقل "{} Script" ، أدخل الكود التالي وانقر على الزر "Save and Deploy":
//
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. للقيام بذلك ، تحتاج فقط إلى تحديد عنوان URL الخاص بالعامل بدلاً من "data.json" في وظيفة "getData". في حالتي ، يبدو الأمر كما يلي: getData ('https://data.aio350.workers.dev/') ، ثم (...).
تحولت مقالة طويلة. أتمنى أن تجد شيئًا مفيدًا فيه.
شكرآ لك على أهتمامك.