Skip to content
  1. Extras
  2. PageBuilder
  3. Section catalog
  4. Pro
  5. Quiz

Quiz ​

Multi-step quiz: answers, contacts, optional price calculation, email notification. Do not call PageBuilderQuiz from the template: FetchIt invokes it from the section chunk. PageBuilderFetchIt renders the form through Fenom.

Quiz

Информация

Requires PageBuilder Pro and FetchIt.

Why a quiz ​

  • Collect leads step by step without a separate quiz builder
  • pricing mode with live total and price_delta on options
  • survey mode without contact step by default

Prerequisites ​

  1. pagebuilderpro transport and capability pro.
  2. FetchIt extra. Without it the section shows "Form temporarily unavailable. Install FetchIt."
  3. Recipient: Notify email on the section or system setting emailsender.

Add the section ​

  1. Resource → Sections tab → Add section.
  2. In the catalog open Quiz (category conversion).
  3. Or Examples tab → preset Quiz: kitchen estimate (quiz-kitchen): mode pricing, three steps, contacts, quiz_key = kitchen.

After inserting a preset you can edit fields. Examples tab is visible when pagebuilder_catalog_examples_enabled is on.

If you do not set button labels, the frontend uses the lexicon. The next step is pagebuilder_fe_quiz_next (Next). The quiz-kitchen preset sets the start label to Начать подбор.

Typical pages ​

Similar sections ​

  • Contact form for a single form without steps
  • CTA with a link instead of collecting answers

Block parameters ​

ParameterValue
keyquiz
LayerPro
Categoryconversion (conversion)
Chunkpagebuilderpro_quiz
Requirementspro, FetchIt

Editor fields ​

Mode (mode) ​

Type select. lead: steps and contact. pricing: same plus total. survey: steps, contact off by default. Unknown value is treated as lead by the handler.

Cover ​

FieldTypePurpose
title, introtext / textareaCover
cover_imageimageOptional
start_as_panelyesnoCover as first wizard panel with Start button
start_labeltextStart button text on cover
base_price, currency_suffixnumber / textOnly when mode = pricing

Steps (steps) ​

Type repeater. Required.

FieldRule
titleRequired
typesingle (radio), multi (checkbox), text, info
requiredEmpty required step blocks Next and submit. Ignored for info
optionsFor single / multi: label, image, price_delta (counts toward total only in pricing)

POST: answer[stepIndex] or answer[stepIndex][]. Option indices go in POST. Labels appear in the email.

Contact ​

FieldRule
contact_enabledIf unset: on for lead/pricing, off for survey
contact_fieldsSame as contact_form: name, label, type, required. Field name: [a-z][a-z0-9_]*
consent_textConsent paragraph under fields (no checkbox)
success_message, redirect_urlAfter success. Redirect after 800 ms
quiz_keyRequired. POST pb_quiz_key. Keys unique on the page
notify_emailRecipient. Empty → emailsender

Visitor answers are not written to published_json. Section JSON holds schema only.

Labels ​

submit_label, prev_label, next_label, contact_title, total_label override lexicon pagebuilder:frontend.

Save and test ​

  1. Save MODX resource publishes published_json. Handler reads published when publishedRevision > 0, otherwise draft.
  2. Template should already have [[!PageBuilder]].
  3. On frontend: steps, progress, live total in pricing, contact and Submit.

What the visitor sees ​

Section pb-quiz with wizard panels. AJAX via FetchIt → PageBuilderQuiz.

Email ​

Subject: PageBuilder quiz ({quiz_key}). Body: key, mode, pagetitle, answers, in pricing an Estimate line, contacts. Total: base_price + price_delta of selected options.

Fallback ​

StateUI
FetchIt not installedForm unavailability message
HoneypotSilent success without email
Validation errorFetchIt error + jump to panel with error
No section with quiz_keypagebuilder_fe_quiz_not_found

Preset: .../sections/presets/quiz-kitchen.json

Chunk template ​

Fenom chunk pagebuilderpro_quiz:

fenom
{set $quizKey = $quiz_key|default:'quiz'}
{set $mode = $mode|default:'lead'}
{set $suffix = $currency_suffix|default:'₽'}
{set $contactOn = $contact_enabled_resolved|default:1}
{set $basePrice = $base_price|default:0}
{set $coverAsStep = $start_as_panel|default:0}
<section
  class="pb-section pb-section--quiz pb-quiz{if $cssClass} {$cssClass|escape}{/if}"
  data-pb-section="quiz"
  data-pb-quiz
  data-pb-quiz-mode="{$mode|escape}"
  data-pb-quiz-base="{$basePrice|escape}"
  data-pb-quiz-suffix="{$suffix|escape}"
  data-pb-quiz-contact="{if $contactOn}1{else}0{/if}"
  data-pb-quiz-cover="{if $coverAsStep}1{else}0{/if}"
  data-pb-quiz-required-msg="{$lex_step_required|escape}"
  {if $id} id="pb-{$id|escape}"{/if}
>
  <div class="pb-section__inner pb-quiz__inner">
    {if !$coverAsStep}
      {if $title}
        <h2 class="pb-heading pb-quiz__title">{$title|escape}</h2>
      {/if}
      {if $intro}
        <p class="pb-quiz__intro">{$intro|escape}</p>
      {/if}
      {if $cover_image}
        <div class="pb-quiz__cover">
            {include 'pagebuilder_partial_image' image=$cover_image alt=($title|default:'') class='pb-quiz__cover-image'}
        </div>
      {/if}
    {/if}

    {if !$fetchit_available}
      <p class="pb-quiz__fallback" role="status">{$lex_form_unavailable|escape}</p>
    {else}
      {'!PageBuilderFetchIt' | snippet : [
        'snippet' => 'PageBuilderQuiz',
        'form' => 'pagebuilderpro_quiz_form',
        'quiz_key' => $quizKey,
        'resource_id' => $resource_id|default:0,
        'successMessage' => ($success_message ?: $lex_success_default),
        'validationErrorMessage' => $lex_validation_error,
        'clearFieldsOnSuccess' => 0,
        'mode' => $mode,
        'base_price' => $basePrice,
        'currency_suffix' => $suffix,
        'contact_enabled_resolved' => $contactOn,
        'start_as_panel' => $coverAsStep,
        'title' => $title,
        'intro' => $intro,
        'cover_image' => $cover_image,
        'steps' => $steps,
        'contact_fields' => $contact_fields,
        'consent_text' => $consent_text,
        'submit_label' => $submit_label,
        'prev_label' => $prev_label,
        'next_label' => $next_label,
        'start_label' => $start_label,
        'contact_title' => $contact_title,
        'total_label' => $total_label,
        'lex_prev' => $lex_prev,
        'lex_next' => $lex_next,
        'lex_start' => $lex_start,
        'lex_submit' => $lex_submit,
        'lex_contact_title' => $lex_contact_title,
        'lex_answer_label' => $lex_answer_label,
        'lex_step_required' => $lex_step_required,
        'lex_total_prefix' => $lex_total_prefix,
        'lex_estimate_prefix' => $lex_estimate_prefix,
        'lex_field_required' => $lex_field_required,
        'lex_field_email' => $lex_field_email,
        'cssClass' => $cssClass,
        'id' => $id,
      ]}
    {/if}
  </div>
</section>

Fenom chunk pagebuilderpro_quiz_form:

fenom
{set $quizKey = $quiz_key|default:'quiz'}
{set $mode = $mode|default:'lead'}
{set $suffix = $currency_suffix|default:'₽'}
{set $contactOn = $contact_enabled_resolved|default:1}
{set $basePrice = $base_price|default:0}
{set $prevText = $prev_label|default:$lex_prev}
{set $nextText = $next_label|default:$lex_next}
{set $submitText = $submit_label|default:$lex_submit}
{set $startText = $start_label|default:$lex_start}
{set $contactTitle = $contact_title|default:$lex_contact_title}
{set $totalPrefix = $total_label|default:($mode == 'pricing' ? $lex_estimate_prefix : $lex_total_prefix)}
{set $coverAsStep = $start_as_panel|default:0}
{set $requiredMsg = $lex_step_required|default:''}

<form class="pb-quiz__form" method="post" novalidate>
  <input type="hidden" name="pb_quiz_key" value="{$quizKey|escape}" />
  <input type="hidden" name="nospam" value="" tabindex="-1" autocomplete="off" aria-hidden="true" class="pb-quiz__honeypot pb-quiz__sr-only" />

  <div class="pb-quiz__message pb-quiz__message--success" data-success role="status"></div>
  <div class="pb-quiz__message pb-quiz__message--error" data-validation-error role="alert"></div>

  <div class="pb-quiz__progress" data-pb-quiz-progress hidden>
    <div
      class="pb-quiz__progress-track"
      role="progressbar"
      aria-valuemin="1"
      aria-valuenow="1"
      aria-valuemax="1"
      aria-labelledby="pb-{$quizKey|escape}-progress-label"
      data-pb-quiz-progress-meter
    >
      <div class="pb-quiz__progress-bar" data-pb-quiz-progress-bar style="width: 0%"></div>
    </div>
    <p class="pb-quiz__progress-label" id="pb-{$quizKey|escape}-progress-label" data-pb-quiz-progress-label></p>
  </div>

  {if $mode == 'pricing'}
    <p class="pb-quiz__total" data-pb-quiz-total aria-live="polite">
      {$totalPrefix|escape}
      <strong data-pb-quiz-total-value>{$basePrice|escape}</strong>
      <span>{$suffix|escape}</span>
    </p>
  {/if}

  <div class="pb-quiz__panels">
    {if $coverAsStep}
      <fieldset class="pb-quiz__panel pb-quiz__panel--cover" data-pb-quiz-panel="cover" data-pb-quiz-step-type="info" data-pb-quiz-required="0">
        {if $title}
          <legend class="pb-quiz__step-title" id="pb-{$quizKey|escape}-cover-title">{$title|escape}</legend>
        {else}
          <legend class="pb-quiz__sr-only" id="pb-{$quizKey|escape}-cover-title">{$startText|escape}</legend>
        {/if}
        {if $intro}
          <p class="pb-quiz__step-desc">{$intro|escape}</p>
        {/if}
        {if $cover_image}
          <div class="pb-quiz__cover">
            {include 'pagebuilder_partial_image' image=$cover_image alt=($title|default:'') class='pb-quiz__cover-image'}
          </div>
        {/if}
      </fieldset>
    {/if}

    {foreach $steps as $stepIndex => $step}
      {include 'pagebuilderpro_quiz_step'
        step=$step
        stepIndex=$stepIndex
        quizKey=$quizKey
        mode=$mode
        suffix=$suffix
        hideInitially=($coverAsStep || $stepIndex != 0)
        lex_answer_label=$lex_answer_label
        lex_step_required=$requiredMsg
      }
    {/foreach}

    {if $contactOn}
      <fieldset class="pb-quiz__panel" data-pb-quiz-panel="contact" hidden>
        <legend class="pb-quiz__step-title" id="pb-{$quizKey|escape}-contact-title">{$contactTitle|escape}</legend>
        <div class="pb-quiz__fields">
          {foreach $contact_fields as $field}
            {set $fname = $field.name|default:''}
            {set $flabel = $field.label|default:$fname}
            {set $ftype = $field.type|default:'text'}
            {set $frequired = $field.required|default:0}
            {set $fid = 'pb-' ~ $quizKey ~ '-' ~ $fname}
            {set $ferr = $fid ~ '-error'}
            <div class="pb-quiz__field" data-custom="{$fname|escape}">
              <label class="pb-quiz__label" for="{$fid|escape}">
                {$flabel|escape}{if $frequired}<span class="pb-quiz__required" aria-hidden="true">*</span>{/if}
              </label>
              {if $ftype == 'textarea'}
                <textarea
                  class="pb-quiz__control"
                  id="{$fid|escape}"
                  name="{$fname|escape}"
                  rows="3"
                  {if $frequired}required aria-required="true"{/if}
                ></textarea>
              {else}
                <input
                  class="pb-quiz__control"
                  id="{$fid|escape}"
                  type="{if $ftype == 'email'}email{elseif $ftype == 'phone'}tel{else}text{/if}"
                  name="{$fname|escape}"
                  {if $ftype == 'email'}autocomplete="email"{elseif $ftype == 'phone'}autocomplete="tel" inputmode="tel"{elseif $fname == 'name'}autocomplete="name"{/if}
                  {if $frequired}required aria-required="true"{/if}
                />
              {/if}
              <span class="pb-quiz__error" id="{$ferr|escape}" data-error="{$fname|escape}" hidden></span>
            </div>
          {/foreach}
        </div>
        {if $consent_text}
          <p class="pb-quiz__consent">{$consent_text|escape}</p>
        {/if}
      </fieldset>
    {/if}
  </div>

  <div class="pb-quiz__nav">
    <button class="pb-button pb-button--secondary pb-quiz__prev" type="button" data-pb-quiz-prev hidden>{$prevText|escape}</button>
    <button class="pb-button pb-quiz__next" type="button" data-pb-quiz-next data-pb-quiz-next-label="{$nextText|escape}" data-pb-quiz-start-label="{$startText|escape}">
      {if $coverAsStep}{$startText|escape}{else}{$nextText|escape}{/if}
    </button>
    <button class="pb-button pb-quiz__submit" type="submit" data-pb-quiz-submit hidden>{$submitText|escape}</button>
  </div>
</form>

Fenom chunk pagebuilderpro_quiz_step:

fenom
{set $stepType = $step.type|default:'single'}
{set $stepRequired = $step.required|default:0}
{set $hidden = $hideInitially|default:($stepIndex != 0)}
{set $titleId = 'pb-' ~ $quizKey ~ '-step-' ~ $stepIndex ~ '-title'}
{set $errorId = 'pb-' ~ $quizKey ~ '-step-' ~ $stepIndex ~ '-error'}
<fieldset
  class="pb-quiz__panel{if $stepType == 'info'} pb-quiz__panel--info{/if}"
  data-pb-quiz-panel="step"
  data-pb-quiz-step="{$stepIndex}"
  data-pb-quiz-step-type="{$stepType|escape}"
  data-pb-quiz-required="{if $stepRequired && $stepType != 'info'}1{else}0{/if}"
  {if $hidden}hidden{/if}
>
  <legend class="pb-quiz__step-title" id="{$titleId|escape}">{$step.title|escape}{if $stepRequired && $stepType != 'info'}<span class="pb-quiz__required" aria-hidden="true">*</span>{/if}</legend>
  {if $step.description}
    <p class="pb-quiz__step-desc">{$step.description|escape}</p>
  {/if}

  {if $stepType == 'info'}
    {* informational block — no inputs *}
  {elseif $stepType == 'text'}
    <label class="pb-quiz__sr-only" for="pb-{$quizKey|escape}-step-{$stepIndex}">{$lex_answer_label|escape}</label>
    <textarea
      class="pb-quiz__control"
      id="pb-{$quizKey|escape}-step-{$stepIndex}"
      name="answer[{$stepIndex}]"
      rows="3"
      data-custom="step_{$stepIndex}"
      {if $stepRequired}required aria-required="true"{/if}
    ></textarea>
    <span class="pb-quiz__error" id="{$errorId|escape}" data-error="step_{$stepIndex}" hidden>{$lex_step_required|escape}</span>
  {else}
    <div class="pb-quiz__options" role="group" aria-labelledby="{$titleId|escape}" data-custom="step_{$stepIndex}">
      {foreach $step.options as $optIndex => $option}
        {include 'pagebuilderpro_quiz_option'
          option=$option
          optIndex=$optIndex
          stepIndex=$stepIndex
          stepType=$stepType
          stepRequired=$stepRequired
          mode=$mode
          suffix=$suffix
        }
      {/foreach}
    </div>
    <span class="pb-quiz__error" id="{$errorId|escape}" data-error="step_{$stepIndex}" hidden>{$lex_step_required|escape}</span>
  {/if}
</fieldset>

Fenom chunk pagebuilderpro_quiz_option:

fenom
{set $optLabel = $option.label|default:''}
{set $optDelta = $option.price_delta|default:0}
<label class="pb-quiz__option">
  <input
    class="pb-quiz__option-input"
    type="{if $stepType == 'multi'}checkbox{else}radio{/if}"
    name="answer[{$stepIndex}]{if $stepType == 'multi'}[]{/if}"
    value="{$optIndex}"
    data-pb-quiz-delta="{$optDelta|escape}"
    {if $stepRequired && $stepType != 'multi'}required{/if}
  />
  <span class="pb-quiz__option-body">
    {if $option.image}
      {include 'pagebuilder_partial_image' image=$option.image alt=$optLabel class='pb-quiz__option-image'}
    {/if}
    <span class="pb-quiz__option-label">{$optLabel|escape}</span>
    {if $mode == 'pricing' && $optDelta != 0}
      <span class="pb-quiz__option-delta">{if $optDelta > 0}+{/if}{$optDelta|escape} {$suffix|escape}</span>
    {/if}
  </span>
</label>