Jump to content

MediaWiki:Gadget-wix-quiz.js

From Insurer Brain
Revision as of 13:49, 31 March 2026 by Wikilah admin (talk | contribs) (Created page with "/* ================================================================ WIX-QUIZ.JS — Wiki Interactive Experience: Quiz Engine ================================================================ Depends on: ext.gadget.wix-core (window.wix must exist) Finds every container with data-wix-module="quiz", reads the question blocks from the DOM, and manages the full lifecycle: sequencing → answer selection → feedback → next → results. Content-blind:...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* ================================================================
   WIX-QUIZ.JS — Wiki Interactive Experience: Quiz Engine
   ================================================================
   Depends on: ext.gadget.wix-core  (window.wix must exist)

   Finds every container with data-wix-module="quiz", reads the
   question blocks from the DOM, and manages the full lifecycle:
   sequencing → answer selection → feedback → next → results.

   Content-blind: no questions are hardcoded here.
   All content is read from data attributes set by Template:Quiz.

   DOM contract (set by templates):
   ─────────────────────────────────
   Container:   [data-wix-module="quiz"]
   Question:    [data-wix-question]          one per question
                  data-wix-correct           letter of correct answer (a/b/c/d)
                  data-wix-topic             topic label (optional)
                  data-wix-explanation       explanation text (optional)
   Option:      [data-wix-option]            four per question
                  data-wix-letter            a / b / c / d
                  data-wix-text              display text
   ================================================================ */

( function () {
  'use strict';

  /* ── Guard ──────────────────────────────────────────────────── */

  mw.hook( 'wikipage.content' ).add( function () {
    var containers = wix.initModules( 'quiz' );
    if ( !containers.length ) {
      return;
    }
    containers.forEach( initQuiz );
  } );


  /* ── Main init ──────────────────────────────────────────────── */

  function initQuiz( container ) {
    var questions = readQuestions( container );
    if ( !questions.length ) {
      return;
    }

    // Clear the static template HTML and replace with dynamic UI
    wix.empty( container );

    var state = wix.createState(
      { step: 0, score: 0, answers: [] },
      function ( s ) { render( container, questions, s ); }
    );

    render( container, questions, state.get() );

    // Expose state setter on the container for navigation callbacks
    container._wixQuizState = state;
  }


  /* ── Read questions from the template DOM ───────────────────── */

  function readQuestions( container ) {
    var blocks = wix.qsa( '[data-wix-question]', container );
    return blocks.map( function ( block ) {
      var options = wix.qsa( '[data-wix-option]', block );
      return {
        topic:       wix.data( block, 'wix-topic', '' ),
        question:    wix.data( block, 'wix-question', '' ),
        correct:     ( wix.data( block, 'wix-correct', 'a' ) ).toLowerCase(),
        explanation: wix.data( block, 'wix-explanation', '' ),
        options: options.map( function ( opt ) {
          return {
            letter: ( wix.data( opt, 'wix-letter', '' ) ).toLowerCase(),
            text:   wix.data( opt, 'wix-text', '' )
          };
        } )
      };
    } );
  }


  /* ── Render ─────────────────────────────────────────────────── */

  function render( container, questions, state ) {
    wix.empty( container );
    container.classList.add( 'wix-animate-in' );

    if ( state.step >= questions.length ) {
      renderResults( container, questions, state );
    } else {
      renderQuestion( container, questions, state );
    }
  }


  /* ── Question view ──────────────────────────────────────────── */

  function renderQuestion( container, questions, state ) {
    var q = questions[ state.step ];
    var total = questions.length;
    var current = state.step + 1;

    // Progress bar
    var bar = wix.buildProgressBar( container );
    bar.update( current, total );

    // Card
    var card = wix.el( 'div', { className: 'wix-card' } );
    container.appendChild( card );

    // Header: topic + question number
    var headerParts = [];
    if ( q.topic ) {
      headerParts.push( wix.el( 'span', { className: 'wix-topic', textContent: q.topic } ) );
    }
    headerParts.push( wix.el( 'span', { className: 'wix-badge', textContent: String( current ) } ) );
    card.appendChild( wix.el( 'div', { className: 'wix-quiz-header' }, headerParts ) );

    // Question text
    card.appendChild( wix.el( 'p', { className: 'wix-question', textContent: q.question } ) );

    // Options
    var optionsList = wix.el( 'div', { className: 'wix-options' } );
    q.options.forEach( function ( opt ) {
      var letter = wix.el( 'span', {
        className: 'wix-option-letter',
        textContent: opt.letter.toUpperCase()
      } );
      var text = wix.el( 'span', { className: 'wix-option-text', textContent: opt.text } );
      var btn = wix.el( 'button', {
        className: 'wix-option',
        'data-wix-letter': opt.letter
      }, [ letter, text ] );
      optionsList.appendChild( btn );
    } );
    card.appendChild( optionsList );

    // Feedback panel (hidden until answer chosen)
    var feedback = wix.buildFeedback( card );

    // Nav bar
    var isLast = state.step === questions.length - 1;
    var nav = wix.buildNav( container, {
      next: {
        label: isLast ? 'See results' : 'Next',
        primary: true,
        hidden: true
      }
    } );

    // Answer handler
    var answered = false;
    wix.on( optionsList, 'click', '.wix-option', function ( target ) {
      if ( answered ) {
        return;
      }
      answered = true;

      var chosen = wix.data( target, 'wix-letter' );
      var isCorrect = chosen === q.correct;

      // Disable all options
      wix.qsa( '.wix-option', optionsList ).forEach( function ( btn ) {
        btn.disabled = true;
        var letter = wix.data( btn, 'wix-letter' );
        if ( letter === q.correct ) {
          btn.classList.add( 'wix-option--correct' );
        } else if ( letter === chosen && !isCorrect ) {
          btn.classList.add( 'wix-option--wrong' );
        }
      } );

      // Show feedback
      if ( isCorrect ) {
        feedback.show( 'correct', 'Correct!', q.explanation );
      } else {
        var correctText = getOptionText( q, q.correct );
        feedback.show(
          'wrong',
          'Not quite — the correct answer is ' + q.correct.toUpperCase() + ': ' + correctText,
          q.explanation
        );
      }

      // Record answer and show Next
      var newAnswers = state.answers.concat( [ {
        step: state.step,
        chosen: chosen,
        correct: isCorrect
      } ] );
      container._wixQuizState.set( {
        score:   state.score + ( isCorrect ? 1 : 0 ),
        answers: newAnswers
      } );
      nav.show( 'next' );
    } );

    nav.on( 'next', function () {
      container._wixQuizState.set( { step: state.step + 1 } );
    } );
  }


  /* ── Results view ───────────────────────────────────────────── */

  function renderResults( container, questions, state ) {
    var total = questions.length;
    var score = state.score;
    var pct = total > 0 ? Math.round( ( score / total ) * 100 ) : 0;

    var results = wix.el( 'div', { className: 'wix-card wix-results' } );
    container.appendChild( results );

    // Title
    results.appendChild( wix.el( 'p', {
      className: 'wix-results-title',
      textContent: 'Quiz complete'
    } ) );

    // Score ring
    var ring = wix.buildScoreRing( results );
    ring.set( score, total );

    // Colour the percentage
    var pctEl = wix.qs( '.wix-score-pct', results );
    if ( pctEl ) {
      if ( pct >= 80 ) {
        pctEl.classList.add( 'wix-score-pct--high' );
      } else if ( pct >= 50 ) {
        pctEl.classList.add( 'wix-score-pct--mid' );
      } else {
        pctEl.classList.add( 'wix-score-pct--low' );
      }
    }

    // Message
    results.appendChild( wix.el( 'p', {
      className: 'wix-results-msg',
      textContent: resultMessage( pct )
    } ) );

    // Review list
    var reviewList = wix.el( 'div', { className: 'wix-review-list' } );
    state.answers.forEach( function ( ans ) {
      var q = questions[ ans.step ];
      var icon = wix.el( 'span', {
        className: 'wix-review-icon wix-review-icon--' + ( ans.correct ? 'correct' : 'wrong' ),
        textContent: ans.correct ? '✓' : '✗'
      } );
      var text = wix.el( 'span', { textContent: q.question } );
      reviewList.appendChild( wix.el( 'div', { className: 'wix-review-item' }, [ icon, text ] ) );
    } );
    results.appendChild( reviewList );

    // Restart button
    var nav = wix.buildNav( container, {
      restart: { label: 'Try again', outline: true }
    } );
    nav.on( 'restart', function () {
      container._wixQuizState.set( { step: 0, score: 0, answers: [] } );
    } );
  }


  /* ── Helpers ────────────────────────────────────────────────── */

  function getOptionText( q, letter ) {
    for ( var i = 0; i < q.options.length; i++ ) {
      if ( q.options[ i ].letter === letter ) {
        return q.options[ i ].text;
      }
    }
    return '';
  }

  function resultMessage( pct ) {
    if ( pct === 100 ) {
      return 'Perfect score — excellent work!';
    }
    if ( pct >= 80 ) {
      return 'Great result. Review the questions you missed to consolidate your understanding.';
    }
    if ( pct >= 50 ) {
      return 'Good effort. Re-read the relevant sections and try again to strengthen your knowledge.';
    }
    return 'Keep studying and give it another go — each attempt builds familiarity with the material.';
  }

}() );