Как в квизе Тильды показать разный результат по баллам
Квиз в Тильде — это форма с вариантами ответа, и заканчивается она тем же, чем любая форма: заявкой. Баллы не складывает ни один её скрипт, ветку результата не выбирает тоже — это придётся сделать своим кодом. Зато есть два готовых механизма, на которые своё считание опирается: скрытое поле, которое доезжает до заявки, и подстановка значений полей в текст. Оба проверены на её же рантайме, и у второго есть условие, о котором в справке не сказано.
Короткий ответ
Баллы Тильда не считает: в её наборе скриптов такой функции нет. Считать нужно
своим кодом, а результат положить в скрытое поле — оно доезжает до заявки. Чтобы
показать результат на странице, есть подстановка {{form.Поле}} в текст записи,
но обновляется она по событию change: после записи значения кодом его нужно
послать вручную.
Из чего на самом деле состоит квиз
Квиз, который считает баллы и по сумме выбирает ответ, — это калькулятор с
вариантами вместо полей ввода, и собирается он из тех же частей. Квиз в Тильде —
это форма, обёрнутая в .t-quiz. Так её и опознаёт рантайм форм:
function t_forms__isQuiz(t) {
return t && t.closest('.t-quiz');
}
Отсюда следует одна практическая вещь: квизу не достаётся новый вид сообщения об успехе. В той же функции, что решает, каким показывать блок благодарности, квиз стоит первым в списке исключений:
function t_forms__isNewSuccessBox(t) {
return !!t && (!t_forms__isQuiz(t) &&
!['t396','t447','t651','t653','t706','t708','t945','t1122'].some(e => t.closest('.' + e)));
}
То есть экран результата у квиза свой, и подменять его чужими средствами не нужно — нужно наполнять.
Своего кода подсчёта в наборе нет. В снимке рантайма — 53 файла скриптов, и единственное упоминание квиза по имени во всех них одно:
e.querySelector('.t-quiz') && t_onFuncLoad('t_quiz__adjustStepToHeight', function () {
t_quiz__adjustStepToHeight({ rec: e });
});
Подгонка высоты шага. Ни quizScore, ни calcScore, ни любой другой счётчик по
файлам не находится. Условные поля у Тильды есть — рантайм форм уважает атрибут
data-hidden-by-condition="true" и выкидывает такие поля из заявки:
function t_forms__getConditionCheckHandler(t) {
var e = Array.from(t.querySelectorAll('[data-hidden-by-condition="true"]'));
return { isHiddenByCondition: function (r) {
return e.some(function (t) { return t === r || t.contains(r) });
} };
}
Но условие — это сравнение значения одного поля, а не сумма по нескольким. Показать разный текст по ответу на один вопрос настройками можно. Разный по сумме баллов — нет.
Что уезжает в заявку, если ответили не на всё
Замер на стенде с её собственным рантаймом: три группы вариантов, отвечена одна.
| Что | Результат |
|---|---|
| поля в заявке | Q1="да" |
| поля неотвеченных вопросов | отсутствуют, не пустые — ключа нет вовсе |
ошибки от window.tildaForm.validate | Q2:req, Q3:req |
Два вывода. Первый: неотвеченный вопрос не оставляет в заявке следа, и по заявке
нельзя отличить «пропустил» от «не было такого вопроса» — если это важно, пишите
пропуск сами. Второй: обязательность у группы вариантов держится на одном
атрибуте data-tilda-req="1" у первого переключателя, а проверка считает
отмеченные по имени поля во всей форме:
var F = r.querySelectorAll('[name="' + b + '"]:checked').length;
Пока имена групп разные, всё честно. Совпали имена — совпадёт и проверка.
Куда положить посчитанный балл
Скрытое поле. Замер: считаем сумму своим кодом, пишем в два скрытых поля — одно
внутри штатной группы .t-input-group, второе просто в форме, — и смотрим, что
собирает штатный сериализатор.
| Поле | В заявке |
|---|---|
Result — в группе с data-field-type="hd" | Result="практик" |
Score — обычный <input type="hidden"> в форме | Score="3" |
| пустое скрытое поле | уезжает как "", а не пропадает |
Оба доехали. Сериализатору группа не нужна — ему нужны name и отсутствие
disabled:
function t_serializeArray(t) {
for (var e = [], r = t.querySelectorAll('input, textarea, button, select'),
n = t_forms__getConditionCheckHandler(t).isHiddenByCondition, a = 0; a < r.length; a++)
if (!(!r[a].name || r[a].disabled ||
['file','reset','submit','button'].indexOf(r[a].type) > -1 || n(r[a])))
…
}
Значит для заявки достаточно самого простого варианта: положить в форму
<input type="hidden" name="Result"> и записать туда значение до отправки. Только
disabled не ставьте — это единственный способ случайно потерять поле.
Считать удобно в tildaform:beforesend: событие всплывает, поэтому слушать можно
на документе, а e.target — это форма.
document.addEventListener('tildaform:beforesend', function (e) {
var form = e.target;
var score = 0;
['Q1', 'Q2', 'Q3'].forEach(function (name) {
var checked = form.querySelector('[name="' + name + '"]:checked');
if (checked && checked.value === 'да') score += 1;
});
var field = form.querySelector('[name="Result"]');
if (field) {
field.value = score >= 3 ? 'практик' : score >= 2 ? 'середина' : 'новичок';
field.dispatchEvent(new Event('change', { bubbles: true }));
}
});
Одно предупреждение про это событие: оно не отменяемое. Проверено по способу, которым
Тильда его создаёт, — initEvent(e, !0, !1): всплывает, но preventDefault на нём
ничего не значит. Считать в нём можно, останавливать отправку — нет.
Показать результат на странице: подстановка и её условие
У Тильды есть родная подстановка значений полей в текст. Регулярное выражение, которым она ищет переменные, поддерживает и значение по умолчанию:
function t_forms__getVariableRegexp() {
return /{{(form|tilda).([A-zА-я0-9_-]+)(?:=([^{}]+))?}}/g;
}
То есть {{form.Result}}, {{tilda.lead_id}} и {{form.Result=пока нет}}. Каждая
найденная переменная заменяется на <span class="t-variable-placeholder">, и
дальше в него пишется значение поля — найденного по группе:
function t_forms__findFormField(t, e) {
var r = t.querySelector('.t-input-group[data-field-name="' + e + '"]');
…
}
Здесь и лежат оба подводных камня. Замер, четыре случая на одном стенде:
| Как устроено поле | Что напечаталось |
|---|---|
| видимое текстовое поле в штатной группе | Иван — значение поля |
скрытое поле в группе с data-input-lid, значение в разметке | практик |
скрытое поле в группе без data-input-lid | Result — само имя переменной |
| переменная, для которой поля нет вовсе | Score — само имя переменной |
{{form.Missing=пока нет}}, поля нет | пока нет — значение по умолчанию |
Первый камень: группа обязана иметь все три атрибута — data-input-lid,
data-field-type, data-field-name. Без первого инициализация поля падает с
Please, set all the required data-attrs to initialize FormInput. в консоли, а
внешняя операция при этом успешно завершается: на странице просто печатается имя
переменной, и ничего не сломано настолько, чтобы это заметить. Печатает его
последняя строка подстановки — значение, иначе значение по умолчанию, иначе имя:
t.textContent = r || n || o;
Второй камень серьёзнее, и он ровно про наш случай — значение, записанное кодом:
| Момент | Что в тексте |
|---|---|
| сразу после подстановки, поле пустое | Result |
| значение записано кодом, событий нет | Result |
после dispatchEvent(new Event('change', {bubbles: true})) | практик |
Причина видна в том же файле: поле подписывается на события своей группы, и подстановка обновляется только по ним.
e.addEventListener('change', _);
…
o.onChange(function () { return n(t) }, { uniqueKey: r.name });
Присваивание input.value = … из кода событий не создаёт — это свойство DOM, а не
действие человека. Отсюда правило: записали значение своим кодом — пошлите полю
change с bubbles: true. Одна строка, и подстановка начинает работать.
Порядок, который работает
- В квизе — обычные группы вариантов, имена вопросов разные.
- В форму добавлено скрытое поле под результат. Если результат нужно ещё и показать текстом, оно лежит в штатной группе со всеми тремя атрибутами.
- На
tildaform:beforesendсвой код считает сумму, выбирает ветку, пишет значение в поле и посылает полюchange. - Текст результата на странице собран из
{{form.Result}}— со значением по умолчанию на случай, если посетитель дошёл до текста мимо формы. - В заявку уезжают и ответы, и посчитанный результат: по заявке видно не только что человек нажимал, но и что ему показали.
Ветку можно выбирать не только по сумме. Всё, что доступно коду в момент
beforesend, — набор отмеченных вариантов, время на прохождение, порядок
ответов. Считать по сумме — самый простой случай, а не единственный.
Что проверить у себя
// 1. Квиз ли это вообще — есть ли обёртка, по которой его узнаёт рантайм
document.querySelectorAll('.t-quiz').length;
// 2. Имена вопросов не должны совпадать
[...document.querySelectorAll('.t-form [data-field-name]')]
.map(g => g.getAttribute('data-field-name'));
// 3. Что уедет в заявку прямо сейчас
t_serializeArray(document.querySelector('.t-form'))
.map(f => f.name + '=' + f.value);
// 4. Все три атрибута у группы результата
(g => g && ['data-input-lid','data-field-type','data-field-name']
.map(a => a + '=' + g.getAttribute(a)))(
document.querySelector('.t-input-group[data-field-name="Result"]'));
// 5. Живые переменные в тексте и то, что в них попало
[...document.querySelectorAll('.t-variable-placeholder')]
.map(p => p.dataset.variable + ' → ' + p.textContent);
Если в пятой проверке имя переменной равно её значению — подстановка не сработала, и смотреть надо на третью и четвёртую.
Свободные моды библиотеки — в каталоге. Если квиз должен не показать картинку, а посчитать смету, отправить результат в CRM разными путями по ветке или собрать шаг из ответов на предыдущие — это уже работа под задачу: доработка форм.