Pet project: Trang trắc nghiệm đơn giản SimpleQuiz

SimpleQuiz là gì?

Điều mà tôi đang nói đến chính là giao diện làm bài trắc nghiệm simpleQuiz. Thứ mà tôi sẽ giới thiệu với bạn ngay sau đây, nếu có gì chưa tối ưu hoặc có thể cải tiến thì mời bạn bình luận ở phía dưới để góp ý.

Và cũng xin thứ lỗi nếu bài này khiến bạn mơ hồ, vì thực tế đây là một bài giới thiệu và các bước hướng dẫn không được thực hiện lúc tôi đang xây dựng nên tôi chỉ có thể nói ra các khâu đã làm, code thì đang được public nên các bạn có thể thoải mái tham khảo để hiểu thêm.

Giao diện simpleQuiz
Live demo: https://ythien123456.github.io/simpleQuiz/

Những chức năng mà simpleQuiz có thể thực hiện:
  1. Chuyển câu hỏi trước, sau
  2. Chọn câu hỏi dựa trên bảng số câu
  3. Tự động chuyển câu hỏi sau khi chọn đáp án
  4. Bật/tắt chức năng tự động chuyển câu hỏi
  5. Gắn cờ câu hỏi
Ngoài ra người sử dụng trang web cũng có thể sử dụng bàn phím để làm bài thi với các nút bấm tương ứng như sau:
  • Số 1: Chọn câu A
  • Số 2: Chọn câu B
  • Số 3: Chọn câu C
  • Số 4: Chọn câu D
  • P: Bật/tắt tự động chuyển câu hỏi sau khi đánh đáp án
  • F: Gắn cờ câu hỏi
  • → (Mũi tên phải): Câu hỏi tiếp theo
  • ← (Mũi tên trái): Lùi một câu hỏi
Tuy các chức năng hiện tại không nhiều, nhưng tôi nghĩ là nó đủ để phục vụ cho một dự án làm bài trắc nghiệm có quy mô không quá lớn, cộng thêm việc bạn có thể tùy ý sửa đổi nó theo nhu cầu của bản thân.

Đạo cụ và kiến thức cần có để tự thực hiện
  1. Javascript cơ bản
  2. HTML, CSS cơ bản
  3. Não để hiểu code.
Từ tham khảo, suy luận đến hiện thực

Để thuận lợi cho mục đích phục vụ khóa luận thì tôi đã phải thực hiện một số khảo sát của những hệ thống đi trước, xem họ có những điểm gì hay để mình học hỏi và những điểm nào mình có thể thêm vào hoặc cải thiện với trình độ cá nhân. Và tình cờ tôi phát hiện ra một trang web có một cái trang làm bài khá hợp lý và khoa học, tạo cảm hứng để tôi làm ra simpleQuiz mà có những cải tiến đáng chú ý hơn.

Hmm.. làm sao để có cái form kiểu này?

Bấm vào câu hỏi thì ở trên sẽ hiện ra nội dung câu hỏi tương ứng, làm sao để tạo ra nó? Tôi đã chọn cách đơn giản nhất có thể, và bạn sẽ thất vọng đấy, thứ tôi chọn chính là Dynamic Tabs của Bootstrap3 (sở dĩ tôi chọn Bootstrap3 cũng có lý do riêng, tuy nhiên chắc chắn simpleQuiz có thể được sử dụng trên Bootstrap4 với một chút tinh chỉnh).
Với cái thư viện có sẵn này thì việc xây dựng một cái khung cho hiển thị danh sách câu hỏi ra, chọn câu nào sẽ hiện nội dung câu đó thật đơn giản. Nhưng nó cũng khá mất thời gian khi mà phải tự tùy chỉnh lại sao cho đúng với ý muốn hiển thị của mình.

Dynamic Tabs của Bootstrap 3

Hãy truy cập vào Tryit Editor của w3schools để thử xem DynamicTabs của Bootstrap3 hoạt động như thế nào. Có một điêm rất tiện lợi là sau khi đã khai báo các nav-tabs thì bạn có thể chuyển nội dung của tab đó đến bất cứ đâu trên trang hiện tại để khi bạn nhấn vào tab thì nó sẽ hiện ra đúng ngay vị trí mà bạn đưa vào.

Xây dựng bố cục

Cách chia bố cục của tôi khá đơn giản, tôi sẽ sử dụng grid layout của bootstrap, chia giao diện ra làm hai phần trái và phải. Phần bên trái tôi sẽ sử dụng 8 phần của grid, còn lại 4 phần dành cho bên phải.

Như hình giao diện của SimpleQuiz mà bạn thấy, tôi đã chia bố cục khá rõ ràng và tường mình. Phần bên trái hiển thị câu hỏi và các câu trả lời, phần bên phải sẽ hiển thị danh sách câu và bộ đếm thời gian.

Kết luận rằng phần bố cục thực tế rất đơn giản, chỉ có ba phần chính như tôi đã đề cập, sử dụng grid layout để thực hiện. Đưa các tab sang cột bên phải, nội dung câu hỏi và các thông tin liên quan sẽ nằm ở cột bên trái. Theo tôi thì đây là một cách sắp xếp khá hợp lý và dễ nhìn.

Bố cục và tên class mà tôi sử dụng.
Trình tự xử lý của tôi trong việc xây dựng một giao diện hoàn chỉnh

1. Giao diện danh sách câu
2. Vị trí, bố cục thông tin câu hỏi (số câu hỏi hiện tại, nội dung, các đáp án, nút nộp bài)
3. Cách chọn đáp án
4. Chuyển câu tiếp theo khi chọn đáp án
5. Nút chuyển câu trước-sau
6. Bật/tắt tự động chuyển câu khi chọn đáp án
7. Nút gắn cờ

Có một điều rất quan trọng là bạn phải RẤT cẩn thận khi đặt tên class để những chức năng của Javascript hoạt động một cách trơn tru nhất, nếu bạn vẫn còn thắc mắc thì mọi vấn đề sẽ được lộ rõ khi bắt tay viết code.

Bây giờ tôi sẽ bắt đầu đi qua từng bước và cho bạn biết cách tôi thực hiện nó:

#1: Giao diện danh sách câu

Giao diện tabs mặc định của Bootstrap3 không phải là xấu, tuy nhiên để phù hợp cho hoàn cảnh mà tôi đang làm việc thì tôi phải thực hiện mốt số tuỳ chỉnh để cho phù hợp. Bước này không quá mất thời gian để thực hiện, đơn giản là thêm một vài đoạn mã CSS và bên dưới là kết quả mong muốn.

Danh sách tabs đã tuỳ chỉnh
#2: Vị trí, bố cục, thông tin câu hỏi

Phần câu hỏi tôi sẽ chia ra làm 3 phần: Tiêu đề, nội dung và đáp án. Đường phân cách của mỗi phần sẽ được thực hiện với thẻ <hr>, khá đơn giản.

Bố cục thông tin câu hỏi
#3: Cách chọn đáp án

Từ đây tôi mới bắt đầu thực hiện bắt tay viết Javascript để xử lý. Nếu bạn để ý thì các input kiểu radio thông thường chỉ có thể được chọn click khi nhấn thẳng vào hình tròn (hoặc nhấn vào label theo id của radio). Tuy nhiên cách đó thực sự không tiện lợi và hiệu quả khi cho người dùng sử dụng để làm bài với số lượng câu hỏi lớn cho nên tôi phải nghĩ cách khắc phục chuyện này.

Tôi muốn người dùng chỉ cần đưa chuột vào hàng chữ đáp án (thẻ <li>) thì đáp án đó sẽ đổi màu đánh dấu đang được chọn, lúc nhấn vào thì radio của câu hỏi đó sẽ được chọn, không cần phải bấm vào hình tròn. Bạn có thể xem mô tả này ở trang demo mà tôi đưa ra ở trên.

Có thể bấm ở bấm kỳ đâu trên hàng xám đậm để chọn đáp án
Để làm được điều này tôi cần phải đặt thuộc tính hover cho thẻ <li> ở phần đáp án. Sau đó gán sự kiện sao cho khi nhấn vào đang hover đó thì radio đó sẽ được check.

#4: Chuyển câu sau khi chọn đáp án

Điều này đòi hỏi tôi phải thêm một số thuộc tính vào trong các thẻ của tabs. Điều ta phải giải quyết ở đây là làm sao để biết mình chọn đáp án ở câu số mấy mà chuyển? Nếu thứ tự tabs bị xếp lộn xộn thì sẽ thế nào?

Cách giải quyết của tôi như sau: Viết hàm activeQuestion với một parameter là 'jump', đó là bước nhảy (-1 nếu muốn lùi một câu, 1 nếu muốn tiến một câu, chi tiết có thể xem trong code). Sau đó tôi viết hàm nextQuestion, chọn câu hỏi và nội dung đang có class active, xoá class đó đi và chạy hàm activeQuestion với 'jump=1'.

#5: Nút chuyển câu trước sau

Nút chuyển câu trong giao diện này khá là củ chuối bởi vì nó được đặt trên phần tiêu đề câu hỏi, mà phần tiêu đề sẽ được lặp lại dựa trên số lượng câu hỏi. Từ đó suy ra nếu đề có 40 câu thì có đến tận 40 cặp nút chuyển câu. Nhưng cá nhân tôi nghĩ điều này chỉ ảnh hưởng đến những bài làm có số lượng câu hỏi cả nghìn câu, nhưng chắc không ai đủ sức làm bài trắc nghiệm đó đâu, tuy nhiên sửa phần đó lại thì code sẽ ngắn hơn.

Ở đâu tôi tạo một hàm mới là previousQuestion, cách viết cũng y như hàm nextQuestion nhưng thay đổi bước nhảy lại là 'jump=-1'. Hai nút chuyển câu hỏi trong giao diện khá đơn giản nên có lẽ không cần đề cập tới.

#6: Bật/tắt tự động chuyển câu khi chọn đáp án

Theo mặc định thì khi chọn đáp án, câu hỏi sẽ tự chuyển sang câu tiếp theo, nhưng nếu người dùng khó chịu và muốn tắt thì sao? Dĩ nhiên là phải nghĩ đến chuyện đó.

Cách giải quyết chuyện này thì rất đơn giản rồi, đặt một biến autoSwitch ở đầu file JS là với giá trị boolean là true. Tạo một cái nút để người dùng ấn, khi nhấn vào và nó chuyển sang màu đỏ thì chuyển giá trị đó sang thành false.
Khi chọn đáp án thì kiểm tra giá trị của autoSwitch, nếu true thì chạy hàm nextQuestion và ngược lại.

#7: Nút gắn cờ

Đây không phải là một chức năng quá đặc biệt hay phức tạp, mấu chốt là lấy được số thứ tự của câu hỏi hiện tại, Chọn gắn cờ ở câu hiện tại sẽ hiện lá cờ bên cạnh câu hỏi đó trong danh sách, mục đích là để người làm bài xem lại những câu họ chưa chắc chắn.

Lời kết

Dĩ nhiên những đoạn code này còn có thể được tối ưu nhiều hơn nữa bằng nhiều phương pháp khác nhau, vào một ngày đẹp trời tôi sẽ thực hiện nâng cấp nó nếu cảm thấy cần thiết. Hiện tại tôi cảm thấy nó còn khá khó hiểu, có thể có nhiều đoạn dư và trình bày củ chuối. Bạn thì cảm thấy thế nào? Hãy để lại bình luận bên dưới.

- Thien Nguyen