Вчера мы показали на сайте простую игрульку — тест по выбору языка программирования.

Как устроен тест по выбору языка программирования

Добавляем вопросы на страницу прямо во время теста

Вчера мы показали на сайте простую игрульку — тест по выбору языка программирования. Несмотря на его внешнюю несерьёзность, под капотом у него довольно хитрый механизм. Рассказываем, как это устроено и как вам сделать такое же.

В чём идея

В одном из пабликов мы увидели картинку о выборе языка программирования и решили сделать такой же, но в формате «Кода». Суть проста: по очереди отвечаем на вопросы, каждый из которых ведёт нас по своему пути, и в итоге получаем какой-то ответ. Картинка была вроде такой, специально показываем мелко, чтобы не вникать в сами вопросы: 

Как устроен тест по выбору языка программирования

Ограничения проекта

Если бы это была отдельная страница на нашем учебном сервере, всё было бы просто: написали отдельно страницу и скрипт — и тест готов. Но задача была в том, чтобы встроить тест в стандартную страницу Вордпресса, на котором работает сайт журнала «Код».

В Вордпрессе страница собирается в тот момент, когда пользователь запрашивает её с сервера, а до этого она лежит в виде отдельных модулей: заголовка, подвала, обвеса с похожими статьями и другими штуками. Мы не можем зайти в файл стилей и добавить в него нужное нам оформление только ради одной статьи — это будет неряшливо и непрактично. Нужно придумать такое решение, которое добавит на страницу нужные стили и не затронет остальные статьи.

По этой же причине мы не хотим подключать отдельно файл со скриптом — весь код надо будет описать прямо в теле страницы. Это тоже нужно учесть при работе над проектом.

А ещё нам нельзя показывать сразу все вопросы и варианты ответов, иначе пропадёт интрига. Значит, нам нужно решить, как добавлять вопросы на страницу прямо по ходу теста. Стандартный способ «разместить заранее блок с вопросом и скрыть его» нам не подходит: скрытый блок всё равно занимает место на странице и вёрстка поплывёт. А ещё в этом случае нужно будет удалять неиспользуемые вопросы, что тоже увеличивает сложность проекта.

Что делаем

Единственный инструмент, который нам подходит в Вордпрессе, — стандартный блок «HTML-код». В нём можно размещать любой код: система распознает все теги и добавит их на страницу так, как будто мы сделали это вручную.

Если учесть все ограничения, то получится, что нам нужен в этом блоке такое:

  • HTML-код теста, имитирующий поведение Вордпресса;
  • скрипт, который добавит на страницу все вопросы в нужной последовательности;
  • пустые блоки <div>, куда скрипт отправит текст вопросов с вариантами ответа.

Пишем HTML-код

Так как Вордпресс — это просто конструктор, который работает со стандартными HTML-тегами, то для абзацев текста он тоже использует стандартный тег <p>. Зная это, мы можем добавить первый вопрос и две кнопки:

<p>Вы хотите много зарабатывать?</p>
<button id = 'a11' onclick='an11()'>Да</button>
<button id = 'a10' onclick='an10()'>Нет</button>

Как устроен тест по выбору языка программирования
Есть первый вопрос, но кнопки выглядят плохо и не реагируют на нажатия

Так как нам нужно добавлять вопросы прямо по ходу теста, сразу предусмотрим для них место в блоках. Туда же добавим блок для вывода результата теста жирным начертанием. Плюс такого решения в том, что пустые блоки <div> не занимают место в вёрстке, поэтому нижняя плашка «Практикума» не уедет вниз:

<!-- второй вопрос -->
<div id = 'q2'></div>
<!-- третий вопрос -->
<div id = 'q3'></div>
<div id = 'q4'></div>
<div id = 'q5'></div>
<div id = 'q6'></div>

<!-- результат -->
<strong>
	<div id = 'result'></div>
</strong>

Последнее, что нам осталось сделать, — добавить новой строчкой тег скрипта:

<script type="text/javascript"></script>

Он пока пустой, но мы сейчас это исправим.

Пишем скрипт

Так как нам нужно управлять почти всем, что мы добавили, сразу получим доступ к блокам и кнопкам:

// получаем доступ к результатам теста
var result = document.getElementById('result');
var result_text = document.getElementById('result_text');

// получаем доступ ко всем элементам с вопросами
var q2 = document.getElementById('q2');
var q3 = document.getElementById('q3');
var q4 = document.getElementById('q4');
var q5 = document.getElementById('q5');
var q6 = document.getElementById('q6');

// и доступ к первым кнопкам в тесте
var a11 = document.getElementById('a11');
var a10 = document.getElementById('a10');

А теперь тот приём, который мы будем использовать всё время:

  1. Находим элемент по его ID.
  2. С помощью свойства innerHTML записываем в него новое содержимое.
  3. Так как это всё выполняется в браузере, то, если мы запишем туда HTML-теги, браузер их сразу выполнит, как будто они уже были на странице.
  4. В итоге мы сможем создавать любые вопросы в нужном месте и управлять ходом теста.

Самое интересное, что с помощью этого же приёма мы можем сразу добавить в раздел <head> стили для кнопок (потому что напрямую изменить его в Вордпрессе мы не можем):

// ждём, пока документ полностью загрузится
document.addEventListener("DOMContentLoaded", function(event) {
  // находим раздел <head> и добавляем в него тег со стилем для этой страницы
  // в нём будет единственный стиль, который отвечает за внешний вид кнопок
  document.getElementsByTagName('head')[0].innerHTML +="<style type='text/css'>button {background: transparent; color: #222; border-color: #4676d7; padding: 10px;margin-bottom: 20px; margin-left: 10px;}"
});

Как устроен тест по выбору языка программирования
Скрипт всё сделал как нужно. Теперь у нас есть красивые кнопки

Обрабатываем нажатие на кнопки

Каждый выбор ответа приводит к тому, что в тесте появляется новый вопрос. Мы уже заранее прописали названия обработчиков нажатия на кнопки — an10 и an11. Настало время написать эти функции внутри скрипта:

// хочет много зарабатывать
function an11() {
  // запрещаем дальнейшие нажатия на кнопки
  a10.disabled=true;
  a11.disabled=true;
  a10.style.visibility='hidden';
  // показываем следующий вопрос
  q2.innerHTML ='<p>Вы часто совершаете ошибки или сомневаетесь в себе?</p><button id = \'a21\' onclick=\'an21()\'>Да</button><button id = \'a20\' onclick=\'an20()\'>Нет</button>'
}

// не хочет много зарабатывать
function an10() {
  // запрещаем дальнейшие нажатия на кнопки
  a10.disabled=true;
  a11.disabled=true;
  a11.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — Delphi</p>';
}

Вот что мы только что сделали:

  1. Сделали две функции, каждая из которых привязана к своей кнопке на странице.
  2. Первая функция с цифрой 1 в конце отвечает за ответ «Да», а вторая, которая заканчивается на ноль, — за ответ «Нет».
  3. В каждой функции мы запретили дальнейшие нажатия на кнопки и оставили видимой ту, на которую нажали. Так будет виден путь, по которому мы придём к какому-то ответу.
  4. В конце первой функции мы обратились к пустому блоку для второго вопроса и добавили в него новые теги — абзаца с новым вопросом и двумя кнопками.
  5. В конце второй функции обратились к блоку с результатом и добавили в него ответ.

Смысл в том, чтобы на каждом этапе или выдавать ответ, или добавлять в следующий пустой блок новые теги с вопросом и кнопками. Единственное, что нам нужно заранее предусмотреть в скрипте, — это следить за названиями функций в кнопках (чтобы они были уникальные) и сразу добавлять в скрипт обработчики для них.

Например, мы только что добавили новый вопрос в блок q2. Это значит, что браузер выведет текст вопроса и добавит ниже две кнопки — «Да» и «Нет». К первой кнопке у нас привязан обработчик an21() (2 — номер вопроса, 1 — положительный ответ), а ко второй — обработчик an20() (2 — номер вопроса, 0 — отрицательный ответ). Это значит, что нам нужно сразу добавить в скрипт эти две функции и потом не забыть их наполнить.

В начале каждой функции нужно не забыть получить доступ к кнопкам через document.getElementById(). Мы не можем сделать это в самом начале скрипта, потому что на старте у нас всего две кнопки, а остальные мы добавляем динамически. Это значит, что браузер при запуске не увидит этих кнопок на странице и не даст к ним доступ, когда они появятся. Правило простое: добавляете новый контент — сразу получите к нему доступ.

Главная сложность такого теста — следить за тем, чтобы у нас срабатывали правильные обработчики, а текст добавлялся в нужные блоки. Если понять этот принцип, то тест можно делать каким угодно сложным, например, сделать 50 вопросов и к каждому дать по 4 варианта ответа.

Чтобы было понятнее, посмотрите полный код скрипта, где уже прописаны все обработчики и добавление новых вопросов:

// получаем доступ к результатам теста
var result = document.getElementById('result');
var result_text = document.getElementById('result_text');

// получаем доступ ко всем элементам с вопросами
var q2 = document.getElementById('q2');
var q3 = document.getElementById('q3');
var q4 = document.getElementById('q4');
var q5 = document.getElementById('q5');
var q6 = document.getElementById('q6');

// и доступ к первым кнопкам в тесте
var a11 = document.getElementById('a11');
var a10 = document.getElementById('a10');

  
// ждём, пока документ полностью загрузится
document.addEventListener("DOMContentLoaded", function(event) {
  // находим раздел <head> и добавляем в него тег со стилем для этой страницы
  // в нём будет единственный стиль, который отвечает за внешний вид кнопок
  document.getElementsByTagName('head')[0].innerHTML +="<style type='text/css'>button {background: transparent; color: #222; border-color: #4676d7; padding: 10px;margin-bottom: 20px; margin-left: 10px;}"
});


// хочет много зарабатывать
function an11() {
  // запрещаем дальнейшие нажатия на кнопки
  a10.disabled=true;
  a11.disabled=true;
  a10.style.visibility='hidden';
  // показываем следующий вопрос
  q2.innerHTML ='<p>Вы часто совершаете ошибки или сомневаетесь в себе?</p><button id = \'a21\' onclick=\'an21()\'>Да</button><button id = \'a20\' onclick=\'an20()\'>Нет</button>'
}

// не хочет много зарабатывать
function an10() {
  // запрещаем дальнейшие нажатия на кнопки
  a10.disabled=true;
  a11.disabled=true;
  a11.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — Delphi</p>';
}

// Часто сомневается
function an21() {
  // получаем доступ к кнопкам
  var a20 = document.getElementById('a20');
  var a21 = document.getElementById('a21');
  // запрещаем дальнейшие нажатия на кнопки
  a20.disabled=true;
  a21.disabled=true;
  a20.style.visibility='hidden';
  // показываем следующий вопрос
  q3.innerHTML = '<p>Очень?</p> <button id = \'a31\' onclick=\'an31()\'>Да</button> <button id = \'a30\' onclick=\'an30()\'>Нет</button>';
}

// Не часто сомневается
function an20() {
  // получаем доступ к кнопкам
  var a21 = document.getElementById('a21');
  var a20 = document.getElementById('a20');
  // запрещаем дальнейшие нажатия на кнопки
  a20.disabled=true;
  a21.disabled=true;
  a21.style.visibility='hidden';
  // показываем следующий вопрос
  q3.innerHTML = '<p>Вы инженер или математик?</p> <button id = \'a91\' onclick=\'an91()\'>Да</button> <button id = \'a90\' onclick=\'an90()\'>Нет</button>';
}

// Есть друзья
function an31() {
  var a30 = document.getElementById('a30');
  var a31 = document.getElementById('a31');
  // запрещаем дальнейшие нажатия на кнопки
  a31.disabled=true;
  a30.disabled=true;
  a30.style.visibility='hidden';
  // показываем следующий вопрос
  q4.innerHTML = '<p>У вас есть друзья?</p> <button id = \'a41\' onclick=\'an41()\'>Да</button> <button id = \'a40\' onclick=\'an40()\'>Нет</button>';
}

// Не часто сомневается
function an30() {
  var a30 = document.getElementById('a30');
  var a31 = document.getElementById('a31');
  // запрещаем дальнейшие нажатия на кнопки
  a30.disabled=true;
  a31.disabled=true;
  a31.style.visibility='hidden';
  // показываем следующий вопрос
  q4.innerHTML = '<p>Вы читаете каждую статью в «Коде»?</p> <button id = \'a61\' onclick=\'an61()\'>Да</button> <button id = \'a60\' onclick=\'an60()\'>Нет</button>';
}

// Друзья тоже тупят
function an41() {
  var a41 = document.getElementById('a41');
  var a40 = document.getElementById('a40');
  // запрещаем дальнейшие нажатия на кнопки
  a41.disabled=true;
  a40.disabled=true;
  a40.style.visibility='hidden';
  // показываем следующий вопрос
  q5.innerHTML = '<p>Они тоже часто совершают ошибки или сомневаются в себе?</p> <button id = \'a51\' onclick=\'an51()\'>Да</button> <button id = \'a50\' onclick=\'an50()\'>Нет</button>';
}

// Друзей нет
function an40() {
  var a41 = document.getElementById('a41');
  var a40 = document.getElementById('a40');
  // запрещаем дальнейшие нажатия на кнопки
  a41.disabled=true;
  a40.disabled=true;
  a41.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — PHP</p>';
}

// Друзья тоже тупят
function an51() {
  var a51 = document.getElementById('a51');
  var a50 = document.getElementById('a50');
  // запрещаем дальнейшие нажатия на кнопки
  a51.disabled=true;
  a50.disabled=true;
  a50.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — JavaScript</p>';
}

// Друзья не тупят
function an50() {
  var a51 = document.getElementById('a51');
  var a50 = document.getElementById('a50');
  // запрещаем дальнейшие нажатия на кнопки
  a51.disabled=true;
  a50.disabled=true;
  a51.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — Ruby</p>';
}

// Читает каждую статью в Коде
function an61() {
  var a61 = document.getElementById('a61');
  var a60 = document.getElementById('a60');
  // запрещаем дальнейшие нажатия на кнопки
  a60.disabled=true;
  a61.disabled=true;
  a60.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — Python</p>';
}

// Не читает каждую статью в Коде
function an60() {
  var a61 = document.getElementById('a61');
  var a60 = document.getElementById('a60');
  // запрещаем дальнейшие нажатия на кнопки
  a61.disabled=true;
  a60.disabled=true;
  a61.style.visibility='hidden';
  // показываем следующий вопрос
  q5.innerHTML = '<p>Вам нравится Windows?</p> <button id = \'a71\' onclick=\'an71()\'>Да</button> <button id = \'a70\' onclick=\'an70()\'>Нет</button>';
}

// Нравится Windows
function an71() {
  var a71 = document.getElementById('a71');
  var a70 = document.getElementById('a70');
  // запрещаем дальнейшие нажатия на кнопки
  a71.disabled=true;
  a70.disabled=true;
  a70.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — C#</p>';
}

// Не нравится Windows
function an70() {
  var a71 = document.getElementById('a71');
  var a70 = document.getElementById('a70');
  // запрещаем дальнейшие нажатия на кнопки
  a71.disabled=true;
  a70.disabled=true;
  a71.style.visibility='hidden';
  // показываем следующий вопрос
  q6.innerHTML = '<p>Любите хвататься своим умом?</p> <button id = \'a81\' onclick=\'an81()\'>Да</button> <button id = \'a80\' onclick=\'an80()\'>Нет</button>';
}

// Нравится Windows
function an81() {
  var a81 = document.getElementById('a81');
  var a80 = document.getElementById('a80');
  // запрещаем дальнейшие нажатия на кнопки
  a81.disabled=true;
  a80.disabled=true;
  a80.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — Swift</p>';
}

// Не нравится Windows
function an80() {
  var a81 = document.getElementById('a81');
  var a80 = document.getElementById('a80');
  // запрещаем дальнейшие нажатия на кнопки
  a81.disabled=true;
  a80.disabled=true;
  a81.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — Perl</p>';
}

// Инженер
function an91() {
  var a91 = document.getElementById('a91');
  var a90 = document.getElementById('a90');
  // запрещаем дальнейшие нажатия на кнопки
  a91.disabled=true;
  a90.disabled=true;
  a90.style.visibility='hidden';
  // показываем следующий вопрос
  q4.innerHTML = '<p>Любите старину?</p> <button id = \'a101\' onclick=\'an101()\'>Да</button> <button id = \'a100\' onclick=\'an100()\'>Нет</button>';
}

// Не инженер
function an90() {
  var a91 = document.getElementById('a91');
  var a90 = document.getElementById('a90');
  // запрещаем дальнейшие нажатия на кнопки
  a91.disabled=true;
  a90.disabled=true;
  a91.style.visibility='hidden';
  // показываем следующий вопрос
  q4.innerHTML = '<p>Вы интроверт?</p> <button id = \'a111\' onclick=\'an111()\'>Да</button> <button id = \'a110\' onclick=\'an110()\'>Нет</button>';
}

// Любит старину
function an101() {
  var a101 = document.getElementById('a101');
  var a100 = document.getElementById('a100');
  // запрещаем дальнейшие нажатия на кнопки
  a101.disabled=true;
  a100.disabled=true;
  a100.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — Pascal</p>';
}

// Не любит старину
function an100() {
  var a101 = document.getElementById('a101');
  var a100 = document.getElementById('a100');
  // запрещаем дальнейшие нажатия на кнопки
  a101.disabled=true;
  a100.disabled=true;
  a101.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — Scala</p>';
}

// Интроверт
function an111() {
  var a111 = document.getElementById('a111');
  var a110 = document.getElementById('a110');
  // запрещаем дальнейшие нажатия на кнопки
  a111.disabled=true;
  a110.disabled=true;
  a110.style.visibility='hidden';
  // показываем следующий вопрос
  q5.innerHTML = '<p>А хотите славы и внимания?</p> <button id = \'a121\' onclick=\'an121()\'>Да</button> <button id = \'a120\' onclick=\'an120()\'>Нет</button>';
}

// Не интроверт
function an110() {
  var a111 = document.getElementById('a111');
  var a110 = document.getElementById('a110');
  // запрещаем дальнейшие нажатия на кнопки
  a111.disabled=true;
  a110.disabled=true;
  a11.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — С</p>';
}

// Хочет славы
function an121() {
  var a121 = document.getElementById('a121');
  var a120 = document.getElementById('a120');
  // запрещаем дальнейшие нажатия на кнопки
  a121.disabled=true;
  a120.disabled=true;
  a120.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — Java</p>';
}

// Не хочет славы
function an120() {
  var a121 = document.getElementById('a121');
  var a120 = document.getElementById('a120');
  // запрещаем дальнейшие нажатия на кнопки
  a121.disabled=true;
  a120.disabled=true;
  a121.style.visibility='hidden';
  // выводим результат
  result.innerHTML = '<p>Ваш выбор — С++</p>';
}

Текст:

Михаил Полянин

Редактор:

Максим Ильяхов

Художник:

Алексей Сухов

Корректор:

Екатерина Череповицына

Вёрстка:

Кирилл Климентьев

Соцсети:

Виталий Вебер

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
medium