Unplugged Lesson
Beginner
40 mins
Teacher/Student led
+50 XP
What you need:
IWB/Projector/Large Screen

Drawing Robot

In this lesson, you'll become a robot artist, following step-by-step arrow instructions to create pictures on a dot grid. Learn sequencing by drawing lines in a specific order, understanding how each step builds the final image.
Learning Goals Learning Outcomes Teacher Notes

Teacher Class Feed

Load previous activity

    1 - Introduction

    Today builds on last time’s pattern following, but instead of copying a given pattern, pupils will create a picture by following steps in a specific order—seeing how changing the order changes the result.

    This promotes the core coding ideas of sequencing (order matters) and clear instructions (one action per step).

    The aim is to make algorithms feel concrete:

    a clear, ordered set of steps → a predictable outcome.

    Materials to Prepare:

    • Drawing Tools: Provide crayons or markers in three different colours for each student.

    • Dot Grid Worksheet: Download and print the worksheet for each student, or guide them to have a grown-up help with printing if needed.

    2 - Join the Dots Discussion and Code Link

    Lead a short class discussion about join-the-dot style activities.

    Start with, “Has anyone ever done a join-the-dots picture before? Did you like it? Was it easy or a bit tricky?

    Explain that the dots hide a picture and the numbers are like the code: you go from 1 to 2 to 3 in order.

    Ask, “What happens if we skip a number or swap two numbers?” and prompt students towards the conclusion that the picture comes out wrong.

    Tell them that each little line is one small step, and lots of correct small steps make a big picture.

    Finish by linking to coding: “Computers also follow steps in order. When we follow the numbers carefully, our picture works—just like good code.”

    3 - One Action, One Step

    Explain to the pupils they are still "Robot Artists" who follow code exactly.

    Today, the commands they will follow will be arrows. Emphasise that the pictures will only appears correctly if they follow these arrows in the right order—this order is the sequence of the code.

    4 - undefined

    To practice the concept, we will introduce the Pencil Coding Challenge.

    Tell pupils they’ll program a Pencil to reach the Star by giving clear, step-by-step instructions

    Run a few quick rounds as a whole class before inviting students to complete the puzzles on their own!


    Join‑the‑Dots – Code & Connect (3×3)<\/title> <style> /* Centered layout from the Pencil-to-Star example, but DOT-STYLE board (no boxes) */ #dots-code2 * { box-sizing: border-box; } #dots-code2 { display:block; max-width: 460px; margin: 20px auto; } #dots-code2 { --gap:10px; --radius:12px; --text:#0b1220; --muted:#64748b; --ink:#111827; --accent:#f59e0b; --accent-dark:#92400e; --good:#16a34a; --bad:#dc2626; } .pc-app{ background:#fff; border:1px solid #e5e7eb; border-radius:var(--radius); box-shadow:0 6px 18px rgba(2,6,23,.08); padding:12px; display:grid; gap:var(--gap); color:var(--text); } /* Topbar */ .pc-topbar{ display:flex; justify-content:center; } .pc-fs{ border:1px solid #e3e7ee; background:#fff; border-radius:10px; padding:8px 10px; font-weight:800; cursor:pointer; box-shadow:0 2px 4px rgba(2,6,23,.06); } /* Title + status */ h2{ font-size:18px; margin:0; text-align:center; } .pc-status{ display:flex; gap:8px; align-items:center; justify-content:center; font-size:14px; color:var(--muted); } .pc-status .dot{ width:8px; height:8px; border-radius:50%; background:#cbd5e1; } .pc-status.win .dot{ background:var(--good) } .pc-status.err .dot{ background:var(--bad) } /* --- Board: CENTER everything --- */ .pc-board{ display:flex; flex-direction:column; gap:8px; align-items:center; } /* DOT GRID (SVG) */ .pc-stage{ display:grid; place-items:center; padding:6px; } .pc-svg{ width:min(96%, 320px); height:auto; display:block; margin:0 auto; border-radius:12px; } .pc-svg .bg{ fill:#f8fafc; stroke:#e5e7eb; stroke-dasharray:6 8; } .pc-dot{ fill:#94a3b8; } /* static dots */ .pc-start-ring{ fill:none; stroke: var(--accent); stroke-width:4px; } .pc-start-core{ fill: var(--accent); stroke: var(--accent-dark); stroke-width:2px; } .pc-current{ fill:#0ea5e9; stroke:#075985; stroke-width:2px; } .pc-star{ font-size:24px; dominant-baseline:middle; text-anchor:middle; } .pc-here{ font-size:12px; fill:#0f172a; text-anchor:middle; font-weight:800; } .pc-trail{ fill:none; stroke: var(--ink); stroke-width:6px; stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:6 10; opacity:.9 } .pc-svg.crash{ filter: drop-shadow(0 0 0.6rem rgba(220,38,38,.45)); } /* Program sequence */ .pc-seq{ min-height:36px; background:#fff; border:1px solid #eef0f3; border-radius:10px; padding:8px; display:flex; flex-wrap:wrap; gap:6px; align-items:center; justify-content:center; } .pc-tok{ padding:4px 6px; border-radius:8px; border:1px solid #c7d2fe; background:#e0e7ff; font-weight:700; } .pc-tok.active{ background:#fef3c7; border-color:#f59e0b; box-shadow:0 0 0 2px #f59e0b; transform:scale(1.04); } /* --- Controls: centered pad + action rows --- */ .pc-controls{ display:grid; gap:10px; } .pc-pad{ display:grid; grid-template-columns: repeat(3, 44px); grid-template-rows: repeat(3, 44px); grid-template-areas: ". up ." "left . right" ". down ."; justify-content: center; gap:8px; } .pc-pad .pc-btn{ width:44px; height:44px; padding:0; } .pc-up { grid-area: up; } .pc-left { grid-area: left; } .pc-right{ grid-area: right; } .pc-down { grid-area: down; } .pc-row{ display:flex; gap:8px; justify-content:center; } .pc-btn{ border:1px solid #e3e7ee; background:#fff; border-radius:10px; padding:8px 10px; font-weight:800; cursor:pointer; box-shadow:0 2px 4px rgba(2,6,23,.06); min-width:92px; text-align:center; } .pc-btn:active{ transform: translateY(1px); } .pc-go{ background:linear-gradient(180deg,#22c55e,#16a34a); color:#fff; border-color:#16a34a; } .pc-reset{ background:linear-gradient(180deg,#3b82f6,#2563eb); color:#fff; border-color:#2563eb; } .pc-clear{ background:linear-gradient(180deg,#fee2e2,#fecaca); color:#7f1d1d; border-color:#fecaca; } .pc-new{ background:#f3f4f6; } /* Success banner */ .pc-banner{ position: fixed; inset: 0; display:none; place-items:center; z-index: 2147483647; background: rgba(16,185,129,.92); } .pc-banner.show{ display:grid; } .pc-card{ background:#fff; color:#065f46; border:2px solid #10b981; border-radius:16px; padding:16px; text-align:center; box-shadow:0 14px 30px rgba(2,6,23,.2); animation: pc-pop .35s ease-out; } .pc-big{ font-size:46px; margin-bottom:6px; } .pc-card h4{ margin:6px 0 8px; font-size:20px; } .pc-card button{ margin-top:8px; padding:8px 12px; border:0; border-radius:10px; font-weight:900; background:#16a34a; color:#fff; cursor:pointer; } @keyframes pc-pop { 0%{ transform:scale(.9); opacity:0 } 100%{ transform:scale(1); opacity:1 } } /* Fullscreen */ #dots-code2:fullscreen .pc-app, #dots-code2:-webkit-full-screen .pc-app{ width:100vw; height:100vh; margin:0; border-radius:0; display:grid; grid-template-rows:max-content max-content 1fr max-content; gap:12px; } </style> </head> <body> <div id="dots-code2"></div> <script> (()=>{ const mount = document.getElementById('dots-code2'); if (!mount || mount.dataset.init==='1') return; mount.dataset.init='1'; // UI mount.innerHTML = ` <div class=\"pc-app\"> <div class=\"pc-topbar\"> <button class=\"pc-fs\" id=\"fs\"><span id=\"fst\">⛶ Fullscreen</span></button> </div> <div class=\"pc-board\"> <h2>🟡 Dot‑to‑Star Challenge (3×3)</h2> <div class=\"pc-stage\"> <svg id=\"board\" class=\"pc-svg\" viewBox=\"0 0 300 300\" aria-label=\"3 by 3 dot grid\"> <rect x=\"10\" y=\"10\" width=\"280\" height=\"280\" rx=\"14\" class=\"bg\"/> <g id=\"dots\"></g> <polyline id=\"trail\" class=\"pc-trail\" points=\"\"/> <circle id=\"curr\" class=\"pc-current\" r=\"10\" cx=\"0\" cy=\"0\"/> <circle id=\"startRing\" class=\"pc-start-ring\" r=\"16\" cx=\"0\" cy=\"0\"/> <circle id=\"startCore\" class=\"pc-start-core\" r=\"8\" cx=\"0\" cy=\"0\"/> <text id=\"goalStar\" class=\"pc-star\" x=\"0\" y=\"0\">⭐</text> <text id=\"hereLabel\" class=\"pc-here\" x=\"0\" y=\"0\">⬇ you are here</text> </svg> </div> </div> <div id=\"status\" class=\"pc-status\"><span class=\"dot\"></span><span id=\"stxt\">Ready.</span></div> <div id=\"seq\" class=\"pc-seq\"></div> <div class=\"pc-controls\"> <div class=\"pc-pad\"> <button class=\"pc-btn pc-up\" data-cmd=\"up\">⬆️</button> <button class=\"pc-btn pc-left\" data-cmd=\"left\">⬅️</button> <button class=\"pc-btn pc-right\" data-cmd=\"right\">➡️</button> <button class=\"pc-btn pc-down\" data-cmd=\"down\">⬇️</button> </div> <div class=\"pc-row\"> <button class=\"pc-btn pc-go\" id=\"run\">▶ GO!</button> <button class=\"pc-btn pc-reset\" id=\"reset\">🔄 Reset</button> </div> <div class=\"pc-row\"> <button class=\"pc-btn pc-clear\" id=\"clear\">🧹 Clear</button> <button class=\"pc-btn pc-new\" id=\"new\">🎲 New Board</button> </div> </div> </div> <div id=\"banner\" class=\"pc-banner\" aria-live=\"polite\"> <div class=\"pc-card\"> <div class=\"pc-big\">🌟</div> <h4>Great work!</h4> <p>You reached the star — the dots connected perfectly.</p> <button id=\"closeBanner\">Continue</button> </div> </div> `; // Refs & state const svg = mount.querySelector('#board'); const dotsG = mount.querySelector('#dots'); const trail = mount.querySelector('#trail'); const currEl= mount.querySelector('#curr'); const startRing = mount.querySelector('#startRing'); const startCore = mount.querySelector('#startCore'); const goalEl= mount.querySelector('#goalStar'); const hereEl= mount.querySelector('#hereLabel'); const seqEl= mount.querySelector('#seq'); const st = mount.querySelector('#status'); const stxt = mount.querySelector('#stxt'); const fsBtn= mount.querySelector('#fs'); const fsTxt= mount.querySelector('#fst'); const banner=mount.querySelector('#banner'); const closeBanner=mount.querySelector('#closeBanner'); const GRID_SIZE = 3; const ANIM=350; // slowed down for clearer steps const CMDS={ up:'↑', down:'↓', left:'←', right:'→' }; const pad = 40; // padding in viewBox units const step = (300 - pad*2) / (GRID_SIZE - 1); const xy = (r,c) => [pad + c*step, pad + r*step]; const start = { r: GRID_SIZE-1, c: 0 }; // bottom-left let curr = { ...start }; let goal = { r: 0, c: GRID_SIZE-1 }; // will be randomized on newBoard() let program = []; let running = false; function setStatus(txt, mode){ stxt.textContent=txt; st.classList.remove('win','err'); if (mode) st.classList.add(mode); } function placeStartAndGoal(){ const [sx,sy] = xy(start.r, start.c); startRing.setAttribute('cx', sx); startRing.setAttribute('cy', sy); startCore.setAttribute('cx', sx); startCore.setAttribute('cy', sy); const [cx,cy] = xy(curr.r, curr.c); currEl.setAttribute('cx', cx); currEl.setAttribute('cy', cy); hereEl.setAttribute('x', cx); hereEl.setAttribute('y', cy - 18); // label above current const [gx,gy] = xy(goal.r, goal.c); goalEl.setAttribute('x', gx); goalEl.setAttribute('y', gy+2); } function buildDots(){ dotsG.innerHTML = ''; for(let r=0;r<GRID_SIZE;r++){ for(let c=0;c<GRID_SIZE;c++){ const [x,y] = xy(r,c); const dot = document.createElementNS('http://www.w3.org/2000/svg','circle'); dot.setAttribute('cx', x); dot.setAttribute('cy', y); dot.setAttribute('r', 6); dot.setAttribute('class', 'pc-dot'); dotsG.appendChild(dot); } } } function clearTrail(){ trail.setAttribute('points',''); } function appendTrailPoint(r,c){ const [x,y]=xy(r,c); const pts=trail.getAttribute('points'); trail.setAttribute('points', pts ? (pts+` ${x},${y}`) : `${x},${y}`); } function renderSeq(active=-1){ seqEl.innerHTML=''; program.forEach((c,i)=>{ const t=document.createElement('div'); t.className='pc-tok'+(i===active?' active':''); t.textContent=CMDS[c]; t.title='Click to remove'; t.addEventListener('click', ()=>{ if (running) return; program.splice(i,1); renderSeq(); }); seqEl.appendChild(t); }); if (!program.length){ const hint=document.createElement('span'); hint.style.color='#94a3b8'; hint.textContent='Add steps with the arrows…'; seqEl.appendChild(hint); } } function resetDot(update=true){ curr={...start}; svg.classList.remove('crash'); clearTrail(); appendTrailPoint(curr.r,curr.c); placeStartAndGoal(); setStatus(update?'Dot reset.':'Ready.'); } function newBoard(){ const r = () => Math.floor(Math.random()*GRID_SIZE); let gr=r(), gc=r(); while (gr===start.r && gc===start.c){ gr=r(); gc=r(); } goal={ r:gr, c:gc }; resetDot(); program=[]; renderSeq(); setStatus('New board ready.'); } function addCmd(c){ if (!running){ program.push(c); renderSeq(); } } const move=(r,c,cmd)=>({ r: r + (cmd==='down') - (cmd==='up'), c: c + (cmd==='right') - (cmd==='left') }); async function run(){ if (running) return; if (!program.length){ setStatus('Add some steps first.','err'); return; } running=true; setStatus('Running…'); svg.classList.remove('crash'); clearTrail(); resetDot(false); for (let i=0;i<program.length;i++){ renderSeq(i); const prev={...curr}; curr = move(curr.r, curr.c, program[i]); if (curr.r<0 || curr.r>=GRID_SIZE || curr.c<0 || curr.c>=GRID_SIZE){ setStatus(`Crashed into a wall at step ${i+1}.`,'err'); svg.classList.add('crash'); renderSeq(-1); running=false; return; } // update current position const [cx,cy] = xy(curr.r, curr.c); currEl.setAttribute('cx', cx); currEl.setAttribute('cy', cy); hereEl.setAttribute('x', cx); hereEl.setAttribute('y', cy - 18); // extend dotted trail appendTrailPoint(curr.r, curr.c); // win if (curr.r===goal.r && curr.c===goal.c){ setStatus(`🎉 You reached the star in ${i+1} step${i? 's':''}!`,'win'); renderSeq(-1); await new Promise(r=>setTimeout(r, 1000)); // pause 1s before banner banner.classList.add('show'); running=false; return; } await new Promise(r=>setTimeout(r, ANIM)); } renderSeq(-1); setStatus('Program finished. Try again!'); running=false; } // Fullscreen function toggleFS(){ const req = mount.requestFullscreen || mount.webkitRequestFullscreen || mount.msRequestFullscreen || mount.mozRequestFullScreen; const exit= document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen || document.mozCancelFullScreen; const isFS= document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; if (isFS){ exit && exit.call(document); fsTxt.textContent='⛶ Fullscreen'; } else if (req){ const ret=req.call(mount); if (ret && ret.then) ret.then(()=> fsTxt.textContent='🡼 Exit Fullscreen').catch(()=>{}); else fsTxt.textContent='🡼 Exit Fullscreen'; } } function syncFS(){ const isFS= document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; if (!isFS) fsTxt.textContent='⛶ Fullscreen'; } document.addEventListener('fullscreenchange', syncFS); document.addEventListener('webkitfullscreenchange', syncFS); document.addEventListener('mozfullscreenchange', syncFS); document.addEventListener('MSFullscreenChange', syncFS); // Wire events ['up','left','right','down'].forEach(c=> mount.querySelector(`[data-cmd=\"${c}\"]`).addEventListener('click', ()=> addCmd(c))); mount.querySelector('#run').addEventListener('click', run); mount.querySelector('#reset').addEventListener('click', ()=> resetDot()); mount.querySelector('#clear').addEventListener('click', ()=> { if (!running){ program=[]; renderSeq(); setStatus('Program cleared.'); }}); mount.querySelector('#new').addEventListener('click', newBoard); mount.querySelector('#fs').addEventListener('click', toggleFS); mount.querySelector('#closeBanner').addEventListener('click', ()=> banner.classList.remove('show')); // Init buildDots(); placeStartAndGoal(); newBoard(); renderSeq(); })(); <\/script> </body> </html> </div> <div id="teacherNotes"></div> </div> </div> <div class="card-footer bg-light"> <div class="row"> <div class="col-4"> <input type="hidden" id="pointsElement" value="0" /> <span></span> <!-- Generate a unique ID for this instance --> </div> <div class="col-8 text-end"> <div class="explainerContainer"></div> </div> </div> <div class="row"> <div class="col"> <div class="submit-work-container" id="submit-work-e30f3cd0-283e-4f54-8277-b6c367b302d0" style="display: none;"> <div class="card mt-2 mb-3"> <div class="card-body"> <h4>Send your work to your teacher</h4> <form class="submit-work-form" enctype="multipart/form-data"> <input name="__RequestVerificationToken" type="hidden" value="CfDJ8HGm0UmGDjZGv_yBNY238jzjq2cVc8V8wa-y91KDueymlDA_v5Xuu1WsCUnHOLfYPGeRIs9IDRNrQk9888jxkd2N0qAq55XJ1nZ3NXU8xGy2xK25E_eGlim5ho81vovt40DtqiCriyRQfMe59Gr0IOo" /> <input type="hidden" class="field-id" id="UserProject_FieldID" name="UserProject.FieldID" value="" /> <input type="hidden" class="course-id" id="UserProject_CourseID" name="UserProject.CourseID" value="" /> <input type="hidden" class="module-id" id="UserProject_ModuleID" name="UserProject.ModuleID" value="" /> <input type="hidden" class="unit-id" id="UserProject_UnitID" name="UserProject.UnitID" value="" /> <input type="hidden" class="lesson-id" id="UserProject_LessonID" name="UserProject.LessonID" value="" /> <input type="hidden" class="step-id" id="UserProject_StepID" name="UserProject.StepID" value="" /> <input type="hidden" class="challenge-id" id="UserProject_ChallengeID" name="UserProject.ChallengeID" value="" /> <input type="hidden" class="class-code" id="UserProject_ClassCode" name="UserProject.ClassCode" value="" /> <div class="form-group mb-3"> <label class="form-label" for="UserProject_Description">Your Work</label> <textarea class="form-control summernote-inline" placeholder="Describe your project or share your answer here" data-val="true" data-val-required="The Your Answer or Description of your work field is required." id="UserProject_Description" name="UserProject.Description"> </textarea> <span class="text-danger field-validation-valid" data-valmsg-for="UserProject.Description" data-valmsg-replace="true"></span> </div> <div class="row mb-3"> <div class="col"> <label class="form-label" for="UserProject_FileID">Attach a File</label> <input type="file" name="AttachFile" class="form-control attach-file" /> <br /><span class="text-danger field-validation-valid" data-valmsg-for="UserProject.File" data-valmsg-replace="true"></span> </div> <div class="col text-end"> <div class="btn-group mb-2 me-2" role="group"> <input type="submit" value="Send my work" class="btn btn-primary submit-button" /> </div> <i class="fa-solid fa-spinner-third fa-spin ms-1 log-spinner" style="display: none;"></i> </div> </div> </form> </div> </div> </div> </div> </div> </div> </div> <div class="card mb-4 lesson-step" id="step5" data-step-id="19266"> <div class="card-header bg-gray-700"> <div class="step-title"> <div class="row"> <div class="col"> <h3 class="text-light">5 - undefined</h3> </div> </div> </div> </div> <div class="card-body"> <div class="row"> <div class="col-sm-12 lesson-instructions"> <p>Show the following program on the board and ask where the dot will end up before you draw it. </p><p>Run the arrows in order, then cycle through various other puzzles. </p><p>Ask which picture matched their expectation and why order mattered.</p> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Join‑the‑Dots – Code & Connect (3×3)<\/title> <style> /* Centered layout from the Pencil-to-Star example, but DOT-STYLE board (no boxes) */ #dots-code2 * { box-sizing: border-box; } #dots-code2 { display:block; max-width: 460px; margin: 20px auto; } #dots-code2 { --gap:10px; --radius:12px; --text:#0b1220; --muted:#64748b; --ink:#111827; --accent:#f59e0b; --accent-dark:#92400e; --good:#16a34a; --bad:#dc2626; } .pc-app{ background:#fff; border:1px solid #e5e7eb; border-radius:var(--radius); box-shadow:0 6px 18px rgba(2,6,23,.08); padding:12px; display:grid; gap:var(--gap); color:var(--text); } /* Topbar */ .pc-topbar{ display:flex; justify-content:center; } .pc-fs{ border:1px solid #e3e7ee; background:#fff; border-radius:10px; padding:8px 10px; font-weight:800; cursor:pointer; box-shadow:0 2px 4px rgba(2,6,23,.06); } /* Title + status */ h2{ font-size:18px; margin:0; text-align:center; } .pc-status{ display:flex; gap:8px; align-items:center; justify-content:center; font-size:14px; color:var(--muted); } .pc-status .dot{ width:8px; height:8px; border-radius:50%; background:#cbd5e1; } .pc-status.win .dot{ background:var(--good) } .pc-status.err .dot{ background:var(--bad) } /* --- Board: CENTER everything --- */ .pc-board{ display:flex; flex-direction:column; gap:8px; align-items:center; } /* DOT GRID (SVG) */ .pc-stage{ display:grid; place-items:center; padding:6px; } .pc-svg{ width:min(96%, 320px); height:auto; display:block; margin:0 auto; border-radius:12px; } .pc-svg .bg{ fill:#f8fafc; stroke:#e5e7eb; stroke-dasharray:6 8; } .pc-dot{ fill:#94a3b8; } /* static dots */ .pc-start-ring{ fill:none; stroke: var(--accent); stroke-width:4px; } .pc-start-core{ fill: var(--accent); stroke: var(--accent-dark); stroke-width:2px; } .pc-current{ fill:#0ea5e9; stroke:#075985; stroke-width:2px; } .pc-star{ font-size:24px; dominant-baseline:middle; text-anchor:middle; } .pc-here{ font-size:12px; fill:#0f172a; text-anchor:middle; font-weight:800; } .pc-trail{ fill:none; stroke: var(--ink); stroke-width:6px; stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:6 10; opacity:.9 } .pc-svg.crash{ filter: drop-shadow(0 0 0.6rem rgba(220,38,38,.45)); } /* Program sequence */ .pc-seq{ min-height:36px; background:#fff; border:1px solid #eef0f3; border-radius:10px; padding:8px; display:flex; flex-wrap:wrap; gap:6px; align-items:center; justify-content:center; } .pc-tok{ padding:4px 6px; border-radius:8px; border:1px solid #c7d2fe; background:#e0e7ff; font-weight:700; } .pc-tok.active{ background:#fef3c7; border-color:#f59e0b; box-shadow:0 0 0 2px #f59e0b; transform:scale(1.04); } /* --- Controls: centered pad + action rows --- */ .pc-controls{ display:grid; gap:10px; } .pc-pad{ display:grid; grid-template-columns: repeat(3, 44px); grid-template-rows: repeat(3, 44px); grid-template-areas: ". up ." "left . right" ". down ."; justify-content: center; gap:8px; } .pc-pad .pc-btn{ width:44px; height:44px; padding:0; } .pc-up { grid-area: up; } .pc-left { grid-area: left; } .pc-right{ grid-area: right; } .pc-down { grid-area: down; } .pc-row{ display:flex; gap:8px; justify-content:center; } .pc-btn{ border:1px solid #e3e7ee; background:#fff; border-radius:10px; padding:8px 10px; font-weight:800; cursor:pointer; box-shadow:0 2px 4px rgba(2,6,23,.06); min-width:92px; text-align:center; } .pc-btn:active{ transform: translateY(1px); } .pc-go{ background:linear-gradient(180deg,#22c55e,#16a34a); color:#fff; border-color:#16a34a; } .pc-reset{ background:linear-gradient(180deg,#3b82f6,#2563eb); color:#fff; border-color:#2563eb; } .pc-clear{ background:linear-gradient(180deg,#fee2e2,#fecaca); color:#7f1d1d; border-color:#fecaca; } .pc-new{ background:#f3f4f6; } /* Success banner */ .pc-banner{ position: fixed; inset: 0; display:none; place-items:center; z-index: 2147483647; background: rgba(16,185,129,.92); } .pc-banner.show{ display:grid; } .pc-card{ background:#fff; color:#065f46; border:2px solid #10b981; border-radius:16px; padding:16px; text-align:center; box-shadow:0 14px 30px rgba(2,6,23,.2); animation: pc-pop .35s ease-out; } .pc-big{ font-size:46px; margin-bottom:6px; } .pc-card h4{ margin:6px 0 8px; font-size:20px; } .pc-card button{ margin-top:8px; padding:8px 12px; border:0; border-radius:10px; font-weight:900; background:#16a34a; color:#fff; cursor:pointer; } @keyframes pc-pop { 0%{ transform:scale(.9); opacity:0 } 100%{ transform:scale(1); opacity:1 } } /* Fullscreen */ #dots-code2:fullscreen .pc-app, #dots-code2:-webkit-full-screen .pc-app{ width:100vw; height:100vh; margin:0; border-radius:0; display:grid; grid-template-rows:max-content max-content 1fr max-content; gap:12px; } </style> </head> <body> <div id="dots-code2"></div> <script> (()=>{ const mount = document.getElementById('dots-code2'); if (!mount || mount.dataset.init==='1') return; mount.dataset.init='1'; // UI mount.innerHTML = ` <div class=\"pc-app\"> <div class=\"pc-topbar\"> <button class=\"pc-fs\" id=\"fs\"><span id=\"fst\">⛶ Fullscreen</span></button> </div> <div class=\"pc-board\"> <h2>🟡 Dot‑to‑Star Challenge (3×3)</h2> <div class=\"pc-stage\"> <svg id=\"board\" class=\"pc-svg\" viewBox=\"0 0 300 300\" aria-label=\"3 by 3 dot grid\"> <rect x=\"10\" y=\"10\" width=\"280\" height=\"280\" rx=\"14\" class=\"bg\"/> <g id=\"dots\"></g> <polyline id=\"trail\" class=\"pc-trail\" points=\"\"/> <circle id=\"curr\" class=\"pc-current\" r=\"10\" cx=\"0\" cy=\"0\"/> <circle id=\"startRing\" class=\"pc-start-ring\" r=\"16\" cx=\"0\" cy=\"0\"/> <circle id=\"startCore\" class=\"pc-start-core\" r=\"8\" cx=\"0\" cy=\"0\"/> <text id=\"goalStar\" class=\"pc-star\" x=\"0\" y=\"0\">⭐</text> <text id=\"hereLabel\" class=\"pc-here\" x=\"0\" y=\"0\">⬇ you are here</text> </svg> </div> </div> <div id=\"status\" class=\"pc-status\"><span class=\"dot\"></span><span id=\"stxt\">Ready.</span></div> <div id=\"seq\" class=\"pc-seq\"></div> <div class=\"pc-controls\"> <div class=\"pc-pad\"> <button class=\"pc-btn pc-up\" data-cmd=\"up\">⬆️</button> <button class=\"pc-btn pc-left\" data-cmd=\"left\">⬅️</button> <button class=\"pc-btn pc-right\" data-cmd=\"right\">➡️</button> <button class=\"pc-btn pc-down\" data-cmd=\"down\">⬇️</button> </div> <div class=\"pc-row\"> <button class=\"pc-btn pc-go\" id=\"run\">▶ GO!</button> <button class=\"pc-btn pc-reset\" id=\"reset\">🔄 Reset</button> </div> <div class=\"pc-row\"> <button class=\"pc-btn pc-clear\" id=\"clear\">🧹 Clear</button> <button class=\"pc-btn pc-new\" id=\"new\">🎲 New Board</button> </div> </div> </div> <div id=\"banner\" class=\"pc-banner\" aria-live=\"polite\"> <div class=\"pc-card\"> <div class=\"pc-big\">🌟</div> <h4>Great work!</h4> <p>You reached the star — the dots connected perfectly.</p> <button id=\"closeBanner\">Continue</button> </div> </div> `; // Refs & state const svg = mount.querySelector('#board'); const dotsG = mount.querySelector('#dots'); const trail = mount.querySelector('#trail'); const currEl= mount.querySelector('#curr'); const startRing = mount.querySelector('#startRing'); const startCore = mount.querySelector('#startCore'); const goalEl= mount.querySelector('#goalStar'); const hereEl= mount.querySelector('#hereLabel'); const seqEl= mount.querySelector('#seq'); const st = mount.querySelector('#status'); const stxt = mount.querySelector('#stxt'); const fsBtn= mount.querySelector('#fs'); const fsTxt= mount.querySelector('#fst'); const banner=mount.querySelector('#banner'); const closeBanner=mount.querySelector('#closeBanner'); const GRID_SIZE = 3; const ANIM=350; // slowed down for clearer steps const CMDS={ up:'↑', down:'↓', left:'←', right:'→' }; const pad = 40; // padding in viewBox units const step = (300 - pad*2) / (GRID_SIZE - 1); const xy = (r,c) => [pad + c*step, pad + r*step]; const start = { r: GRID_SIZE-1, c: 0 }; // bottom-left let curr = { ...start }; let goal = { r: 0, c: GRID_SIZE-1 }; // will be randomized on newBoard() let program = []; let running = false; function setStatus(txt, mode){ stxt.textContent=txt; st.classList.remove('win','err'); if (mode) st.classList.add(mode); } function placeStartAndGoal(){ const [sx,sy] = xy(start.r, start.c); startRing.setAttribute('cx', sx); startRing.setAttribute('cy', sy); startCore.setAttribute('cx', sx); startCore.setAttribute('cy', sy); const [cx,cy] = xy(curr.r, curr.c); currEl.setAttribute('cx', cx); currEl.setAttribute('cy', cy); hereEl.setAttribute('x', cx); hereEl.setAttribute('y', cy - 18); // label above current const [gx,gy] = xy(goal.r, goal.c); goalEl.setAttribute('x', gx); goalEl.setAttribute('y', gy+2); } function buildDots(){ dotsG.innerHTML = ''; for(let r=0;r<GRID_SIZE;r++){ for(let c=0;c<GRID_SIZE;c++){ const [x,y] = xy(r,c); const dot = document.createElementNS('http://www.w3.org/2000/svg','circle'); dot.setAttribute('cx', x); dot.setAttribute('cy', y); dot.setAttribute('r', 6); dot.setAttribute('class', 'pc-dot'); dotsG.appendChild(dot); } } } function clearTrail(){ trail.setAttribute('points',''); } function appendTrailPoint(r,c){ const [x,y]=xy(r,c); const pts=trail.getAttribute('points'); trail.setAttribute('points', pts ? (pts+` ${x},${y}`) : `${x},${y}`); } function renderSeq(active=-1){ seqEl.innerHTML=''; program.forEach((c,i)=>{ const t=document.createElement('div'); t.className='pc-tok'+(i===active?' active':''); t.textContent=CMDS[c]; t.title='Click to remove'; t.addEventListener('click', ()=>{ if (running) return; program.splice(i,1); renderSeq(); }); seqEl.appendChild(t); }); if (!program.length){ const hint=document.createElement('span'); hint.style.color='#94a3b8'; hint.textContent='Add steps with the arrows…'; seqEl.appendChild(hint); } } function resetDot(update=true){ curr={...start}; svg.classList.remove('crash'); clearTrail(); appendTrailPoint(curr.r,curr.c); placeStartAndGoal(); setStatus(update?'Dot reset.':'Ready.'); } function newBoard(){ const r = () => Math.floor(Math.random()*GRID_SIZE); let gr=r(), gc=r(); while (gr===start.r && gc===start.c){ gr=r(); gc=r(); } goal={ r:gr, c:gc }; resetDot(); program=[]; renderSeq(); setStatus('New board ready.'); } function addCmd(c){ if (!running){ program.push(c); renderSeq(); } } const move=(r,c,cmd)=>({ r: r + (cmd==='down') - (cmd==='up'), c: c + (cmd==='right') - (cmd==='left') }); async function run(){ if (running) return; if (!program.length){ setStatus('Add some steps first.','err'); return; } running=true; setStatus('Running…'); svg.classList.remove('crash'); clearTrail(); resetDot(false); for (let i=0;i<program.length;i++){ renderSeq(i); const prev={...curr}; curr = move(curr.r, curr.c, program[i]); if (curr.r<0 || curr.r>=GRID_SIZE || curr.c<0 || curr.c>=GRID_SIZE){ setStatus(`Crashed into a wall at step ${i+1}.`,'err'); svg.classList.add('crash'); renderSeq(-1); running=false; return; } // update current position const [cx,cy] = xy(curr.r, curr.c); currEl.setAttribute('cx', cx); currEl.setAttribute('cy', cy); hereEl.setAttribute('x', cx); hereEl.setAttribute('y', cy - 18); // extend dotted trail appendTrailPoint(curr.r, curr.c); // win if (curr.r===goal.r && curr.c===goal.c){ setStatus(`🎉 You reached the star in ${i+1} step${i? 's':''}!`,'win'); renderSeq(-1); await new Promise(r=>setTimeout(r, 1000)); // pause 1s before banner banner.classList.add('show'); running=false; return; } await new Promise(r=>setTimeout(r, ANIM)); } renderSeq(-1); setStatus('Program finished. Try again!'); running=false; } // Fullscreen function toggleFS(){ const req = mount.requestFullscreen || mount.webkitRequestFullscreen || mount.msRequestFullscreen || mount.mozRequestFullScreen; const exit= document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen || document.mozCancelFullScreen; const isFS= document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; if (isFS){ exit && exit.call(document); fsTxt.textContent='⛶ Fullscreen'; } else if (req){ const ret=req.call(mount); if (ret && ret.then) ret.then(()=> fsTxt.textContent='🡼 Exit Fullscreen').catch(()=>{}); else fsTxt.textContent='🡼 Exit Fullscreen'; } } function syncFS(){ const isFS= document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; if (!isFS) fsTxt.textContent='⛶ Fullscreen'; } document.addEventListener('fullscreenchange', syncFS); document.addEventListener('webkitfullscreenchange', syncFS); document.addEventListener('mozfullscreenchange', syncFS); document.addEventListener('MSFullscreenChange', syncFS); // Wire events ['up','left','right','down'].forEach(c=> mount.querySelector(`[data-cmd=\"${c}\"]`).addEventListener('click', ()=> addCmd(c))); mount.querySelector('#run').addEventListener('click', run); mount.querySelector('#reset').addEventListener('click', ()=> resetDot()); mount.querySelector('#clear').addEventListener('click', ()=> { if (!running){ program=[]; renderSeq(); setStatus('Program cleared.'); }}); mount.querySelector('#new').addEventListener('click', newBoard); mount.querySelector('#fs').addEventListener('click', toggleFS); mount.querySelector('#closeBanner').addEventListener('click', ()=> banner.classList.remove('show')); // Init buildDots(); placeStartAndGoal(); newBoard(); renderSeq(); })(); <\/script> </body> </html> </div> <div id="teacherNotes"></div> </div> </div> <div class="card-footer bg-light"> <div class="row"> <div class="col-4"> <input type="hidden" id="pointsElement" value="0" /> <span></span> <!-- Generate a unique ID for this instance --> </div> <div class="col-8 text-end"> <div class="explainerContainer"></div> </div> </div> <div class="row"> <div class="col"> <div class="submit-work-container" id="submit-work-a74dff8f-1d9b-4a79-baed-6804dd57e53e" style="display: none;"> <div class="card mt-2 mb-3"> <div class="card-body"> <h4>Send your work to your teacher</h4> <form class="submit-work-form" enctype="multipart/form-data"> <input name="__RequestVerificationToken" type="hidden" value="CfDJ8HGm0UmGDjZGv_yBNY238jzjq2cVc8V8wa-y91KDueymlDA_v5Xuu1WsCUnHOLfYPGeRIs9IDRNrQk9888jxkd2N0qAq55XJ1nZ3NXU8xGy2xK25E_eGlim5ho81vovt40DtqiCriyRQfMe59Gr0IOo" /> <input type="hidden" class="field-id" id="UserProject_FieldID" name="UserProject.FieldID" value="" /> <input type="hidden" class="course-id" id="UserProject_CourseID" name="UserProject.CourseID" value="" /> <input type="hidden" class="module-id" id="UserProject_ModuleID" name="UserProject.ModuleID" value="" /> <input type="hidden" class="unit-id" id="UserProject_UnitID" name="UserProject.UnitID" value="" /> <input type="hidden" class="lesson-id" id="UserProject_LessonID" name="UserProject.LessonID" value="" /> <input type="hidden" class="step-id" id="UserProject_StepID" name="UserProject.StepID" value="" /> <input type="hidden" class="challenge-id" id="UserProject_ChallengeID" name="UserProject.ChallengeID" value="" /> <input type="hidden" class="class-code" id="UserProject_ClassCode" name="UserProject.ClassCode" value="" /> <div class="form-group mb-3"> <label class="form-label" for="UserProject_Description">Your Work</label> <textarea class="form-control summernote-inline" placeholder="Describe your project or share your answer here" data-val="true" data-val-required="The Your Answer or Description of your work field is required." id="UserProject_Description" name="UserProject.Description"> </textarea> <span class="text-danger field-validation-valid" data-valmsg-for="UserProject.Description" data-valmsg-replace="true"></span> </div> <div class="row mb-3"> <div class="col"> <label class="form-label" for="UserProject_FileID">Attach a File</label> <input type="file" name="AttachFile" class="form-control attach-file" /> <br /><span class="text-danger field-validation-valid" data-valmsg-for="UserProject.File" data-valmsg-replace="true"></span> </div> <div class="col text-end"> <div class="btn-group mb-2 me-2" role="group"> <input type="submit" value="Send my work" class="btn btn-primary submit-button" /> </div> <i class="fa-solid fa-spinner-third fa-spin ms-1 log-spinner" style="display: none;"></i> </div> </div> </form> </div> </div> </div> </div> </div> </div> </div> <div class="card mb-4 card-hover"> <div class="row "> <!-- Image --> <a class="col-lg-3 col-md-12 col-12 bg-cover img-left-rounded" style="background-image: url(/images/bg-happy-face.png);" href="/Home/CodingClub"> <img src="/images/bg-happy-face.png" class="img-fluid d-lg-none invisible" alt=""> </a> <div class="col-lg-9 col-md-12 col-12"> <!-- Card Body --> <div class="card-body"> <h1 class="mb-2">Unlock the Full Learning Experience</h1> <p class="lead"> Get ready to embark on an incredible learning journey! Get access to this lesson and hundreds more in our Digital Skills Curriculum. </p> <div class="row justify-content-center"> <div class="d-grid d-md-block"> <a class="btn btn-primary mb-2 mb-md-0 text-light" href="/GetStarted">Get started <i class="fad fa-chevron-circle-right"></i></a> </div> </div> </div> </div> </div> </div> <div class="row mt-4 pb-4"> <div class="col text-xl-left text-small"> <i class="fa fa-copyright"></i> Copyright Notice <br /> This lesson is copyright of DigitalSkills.org 2017 - 2025. Unauthorised use, copying or distribution is not allowed. </div> </div> </div> </div> </div> </div> </div> <div class="modal fade text-start" id="sendTeacherModal" tabindex="-1" aria-labelledby="sendTeacherModalLabel" aria-hidden="true"> <div class="modal-dialog modal-xl modal-dialog-scrollable"> <div class="modal-content"> <div class="modal-header bg-dark"> <h1 class="modal-title fs-4 text-light" id="sendTeacherModalLabel">Send your work to your Teacher</h1> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body bg-light text-dark"> <div class="container"> <div class="row"> <div class="col-md-6"> <form id="formSubmitProject" enctype="multipart/form-data" action="/CodingClub/SubmitProject" method="post"> <input type="hidden" id="FieldID" name="UserProject.FieldID" value="" /> <input type="hidden" id="CourseID" name="UserProject.CourseID" value="" /> <input type="hidden" id="ModuleID" name="UserProject.ModuleID" value="" /> <input type="hidden" id="UnitID" name="UserProject.UnitID" value="" /> <input type="hidden" id="LessonID" name="UserProject.LessonID" value="" /> <input type="hidden" id="StepID" name="UserProject.StepID" value="" /> <input type="hidden" id="ChallengeID" name="UserProject.ChallengeID" value="" /> <input type="hidden" id="ClassCode" name="UserProject.ClassCode" value="" /> <div class="form-group mb-3"> <label class="form-label" for="UserProject_Description">Your Answer or Description of Your Work</label> <textarea class="form-control summernotesendteacher" placeholder="Describe your project or share your answer here" id="ProjectDescription" data-val="true" data-val-required="The Your Answer or Description of your work field is required." name="UserProject.Description"> </textarea> <span class="text-danger field-validation-valid" data-valmsg-for="UserProject.Description" data-valmsg-replace="true"></span> </div> <div class="mb-3"> <label class="form-label" for="UserProject_URL">Project URL</label> <input class="form-control" placeholder="e.g. https://scratch.mit.edu/projects/514525637" id="ProjectURL" type="text" name="UserProject.URL" value="" /> <small><span class="text-muted">This can be a link to your project (e.g. a Scratch, Microbit, Arcade or CodePen URL link).</span></small> <br /><span class="text-danger field-validation-valid" data-valmsg-for="UserProject.URL" data-valmsg-replace="true"></span> </div> <div class="mb-3"> <label class="form-label" for="UserProject_FileID">Upload File</label> <input type="file" name="AttachFile" class="form-control" id="AttachFile" /> <br /><span class="text-danger field-validation-valid" data-valmsg-for="UserProject.File" data-valmsg-replace="true"></span> </div> <div class="mb-3" id="submitButtonGroup"> <div class="btn-group mb-2 me-2" role="group"> <input type="submit" value="Send my work" class="btn btn-primary" id="submitButton" /> </div> <i id="log-spinner" style="display: none;" class="fa-solid fa-spinner-third fa-spin ms-1"></i> </div> <input name="__RequestVerificationToken" type="hidden" value="CfDJ8HGm0UmGDjZGv_yBNY238jzjq2cVc8V8wa-y91KDueymlDA_v5Xuu1WsCUnHOLfYPGeRIs9IDRNrQk9888jxkd2N0qAq55XJ1nZ3NXU8xGy2xK25E_eGlim5ho81vovt40DtqiCriyRQfMe59Gr0IOo" /></form> </div> <div class="col-md-6"> <div class="accordion" id="accordionExample"> <div class="accordion-item"> <h2 class="accordion-header" id="headingOne"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne"> How to share a Scratch project </button> </h2> <div id="collapseOne" class="accordion-collapse collapse" aria-labelledby="headingOne" data-bs-parent="#accordionExample"> <div class="accordion-body bg-white"> <div class="alert alert-warning mb-2"> You must be <strong>logged into Scratch</strong> to share your project, otherwise the link will not work. </div> <p>To get the URL link of your Scratch project:</p> <ol> <li>Make sure you're logged into Scratch.</li> <li>Click on the <span class="badge bg-warning">Share</span> button in the project editor.</li> <li>Click on the <span class="badge badge-motion"><i class="fas fa-link"></i> Copy Link</span> button.</li> <li>Click on the <span class="text-primary">Copy Link</span> link. This will save the URL of your project page into your clipboard.</li> </ol> <img src="https://codingireland.blob.core.windows.net/images/steps%5Csccom%20share%20project%20link.gif" id="popimg4131" class="popimg img-thumbnail img-fluid"> </div> </div> </div> <div class="accordion-item"> <h2 class="accordion-header" id="headingTwo"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"> How to share a Microbit project </button> </h2> <div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample"> <div class="accordion-body bg-white"> <p>To get the URL link of your Microbit project:</p> <ol> <li>Click on the <i class="fas fa-share-alt"></i> button in the project editor.</li> <li>Click on the <span class="badge bg-primary">Publish Project <i class="fas fa-share-alt"></i></span> button.</li> <li>Click on the <span class="badge bg-primary"> Copy <i class="fas fa-copy"></i></span> button. This will save the URL of your project page into your clipboard.</li> </ol> <img src="https://codingireland.blob.core.windows.net/images/steps%5Cmbcom%20share%20project%20link.gif" id="popimg4130" class="popimg img-thumbnail img-fluid"> </div> </div> </div> <div class="accordion-item"> <h2 class="accordion-header" id="headingThree"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree"> How to share an Arcade project </button> </h2> <div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample"> <div class="accordion-body bg-white"> <p>To get the URL link of your Arcade project:</p> <ol> <li>Click on the <i class="fas fa-share-alt"></i> button in the project editor.</li> <li>Click on the <span class="badge bg-warning">Publish Project <i class="fas fa-share-alt"></i></span> button.</li> <li>Click on the <span class="badge bg-warning">Copy <i class="fas fa-copy"></i></span> button. This will save the URL of your project page into your clipboard.</li> </ol> <img src="https://codingireland.blob.core.windows.net/images/steps%5Carccom%20share%20project%20link.gif" id="popimg4129" class="popimg img-thumbnail img-fluid"> </div> </div> </div> <div class="accordion-item"> <h2 class="accordion-header" id="headingFour"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour"> How to share a CodePen project </button> </h2> <div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour" data-bs-parent="#accordionExample"> <div class="accordion-body bg-white"> <p>To get the URL link of your CodePen project:</p> <ol> <li>Make sure you're logged into CodePen.</li> <li>Open your project in the editor.</li> <li>Click on the <span class="badge bg-secondary">Save</span> button at the top of the editor.</li> <li>Copy the link from the address bar in your web browser (e.g. https://codepen.io/Jane-Smith/pen/gOVXxBQ)</li> </ol> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function () { const modal = document.getElementById('sendTeacherModal'); const form = document.getElementById('formSubmitProject'); const spinner = document.getElementById('log-spinner'); let triggerButton = null; // Store the button that triggered the modal // Handle modal show event to populate fields modal.addEventListener('show.bs.modal', function (event) { // Reset form form.reset(); // Reset Summernote editor if initialized if (typeof $('#ProjectDescription').summernote === 'function') { $('#ProjectDescription').summernote('reset'); } spinner.style.display = 'none'; triggerButton = event.relatedTarget; // Store the triggering link const button = event.relatedTarget; // Button that triggered the modal const fieldId = button.getAttribute('data-field-id'); const courseId = button.getAttribute('data-course-id'); const moduleId = button.getAttribute('data-module-id'); const unitId = button.getAttribute('data-unit-id'); const lessonId = button.getAttribute('data-lesson-id'); const stepId = button.getAttribute('data-step-id'); const challengeId = button.getAttribute('data-challenge-id'); const classCode = button.getAttribute('data-class-code'); const learningPoints = button.getAttribute('data-learning-points'); // Update form fields document.getElementById('FieldID').value = fieldId || ''; document.getElementById('CourseID').value = courseId || ''; document.getElementById('ModuleID').value = moduleId || ''; document.getElementById('UnitID').value = unitId || ''; document.getElementById('LessonID').value = lessonId || ''; document.getElementById('StepID').value = stepId || ''; document.getElementById('ChallengeID').value = challengeId || ''; document.getElementById('ClassCode').value = classCode || ''; document.getElementById('LearningPoints').textContent = `+${learningPoints || 0} XP`; }); // Handle form submission via AJAX form.addEventListener('submit', function (e) { e.preventDefault(); spinner.style.display = 'inline-block'; const formData = new FormData(form); // If using Summernote, update the textarea value if (typeof $('#ProjectDescription').summernote === 'function') { formData.set('UserProject.Description', $('#ProjectDescription').summernote('code')); } fetch('/CodingClub/SubmitProject', { method: 'POST', body: formData, headers: { 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value } }) .then(response => response.json()) .then(data => { spinner.style.display = 'none'; if (data.success) { // On success: close the modal and replace the trigger link with success message bootstrap.Modal.getInstance(modal).hide(); if (triggerButton) { triggerButton.outerHTML = '<span class="text-success">Work sent!</span>'; } } else { // On error: show JavaScript alert with error messages let errorMessage = data.message || 'An error occurred while submitting your work.'; if (data.errors) { errorMessage = Object.values(data.errors).join('\n'); } alert(errorMessage); } }) .catch(error => { spinner.style.display = 'none'; alert('An unexpected error occurred. Please try again.'); console.error('Error:', error); }); }); }); </script> <!-- Explainer Modal --> <div class="modal fade" id="explainerModal" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog modal-lg modal-dialog-centered"> <div class="modal-content"> <div class="modal-header bg-light-secondary"> <h3 class="modal-title d-flex align-items-center" id="explainerTitle"></h3> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <!-- Row for Image and Description --> <div class="d-flex align-items-start"> <img id="explainerImage" class="img-fluid rounded me-3" style="max-width: 200px; display: none;"> <p id="explainerContent" class="fs-4 mb-0"></p> </div> <div id="explainerAdditionalInfo" style="display:none;"> <hr> <!-- More Info Link --> <a href="#!" class="btn btn-sm btn-info" id="explainerAdditionalInfoLink"> <i class="fad fa-circle-info me-1"></i>More info </a> <!-- Buttons (Initially Hidden) --> <div id="explainerButtons" style="display:none;" class="mt-2"> <button id="hideIframeButton" class="btn btn-sm btn-secondary"><i class="fad fa-eye-slash me-1"></i>Hide</button> <a id="openNewTabButton" href="#" target="_blank" class="btn btn-sm btn-primary"> <i class="fad fa-external-link me-1"></i>Open in New Tab </a> </div> <!-- Initially Hidden Iframe --> <iframe id="explainerIframe" style="display:none; width:100%; height:400px; border:0; margin-top:10px;"></iframe> </div> </div> </div> </div> </div> <div class="modal fade" id="imagemodal" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog modal-xl modal-dialog-centered"> <div class="modal-content"> <div class="modal-header bg-secondary"> <span id="imagedescription" class="text-white"></span> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <img src="" id="imagepreview" class="img-fluid mx-auto d-block"> </div> </div> </div> </div> <!-- Modal --> <div class="modal fade" id="newBadgeModal" tabindex="-1" aria-labelledby="newBadgeModalLabel" aria-hidden="true"> <div class="modal-dialog modal-lg"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="newBadgeModalLabel">New Badge 😃</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <div class="row"> <div class="col"> <div id="questionBadgeCard" class="card card-badge shadow"> <div class="card-header"><span class="badgeseriesname"></span> Series</div> <div class="card-body"> <i class="fa fa-question"></i> <br /> <br /> <span></span> </div> <div class="card-footer">?</div> </div> <div id="revealAward" style="display: none;"> <img class="img-fluid" id="imgGIF" style="display: none;" /> <div id="revealBadgeCard" class="card card-badge shadow" style="display: none;"> <div id="revealBadgeCardHeader" class="card-header"><span class="badgeseriesname"></span> Series</div> <div id="revealBadgeCardBody" class="card-body"> <i id="badgeicon"></i> <br /> <br /> <span><span class="badgename"></span></span> </div> <div class="card-footer"><span class="badgetype"></span></div> </div> </div> </div> <div class="col"> <p>You've earned a new award from the <span class="badgeseriesname"></span> series!!!</p> <button class="btn bg-primary text-light" id="btnRevealBadge">Click here to reveal!</button> <div id="divCollection" class="mt-3" style="display:none;"> <p>Your new award has been added to your collection!</p> </div> <hr /> <span id="memberPoints">0</span><span class="text-muted text-small">/250</span> <div class="text-small" id="divMemberRank"></div> </div> </div> </div> </div> </div> </div> <div class="modal fade" id="loggedInMessageModal" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h3><i class="fad fa-level-up" aria-hidden="true"></i> XP</h3> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <p class="lead">You must be logged in to earn learning points.</p> </div> <div class="modal-footer"> <a class="btn btn-success" href="/GetStarted">Get Started</a> <a class="btn btn-primary" href="/Identity/Account/Login">Login</a> </div> </div> </div> </div> <input type="hidden" id="lastStepCompleteTimestamp" value="0" /> <footer class="bg-dark pt-5 pb-5"> <div class="container"> <div class="row"> <div class="col-12 col-md-3"> <img alt="DigitalSkills.org Logo" src="/images/digitalskills/logo-trans-white.png" class="mb-4"> <div class="text-white"> © 2025 DigitalSkills.org <br><br> </div> </div> <!--end of col--> <div class="col-12 col-md-9"> <div class="row no-gutters"> <div class="col-6 col-lg-3"> <h6 class="text-white">Schools</h6> <ul class="list-unstyled schoolLevelList"> <li> <a href="/TeacherTraining">Teacher Training</a> </li> </ul> </div> <!--end of col--> <div class="col-6 col-lg-3"> <h6 class="text-white">Libraries & Youth Centres</h6> <ul class="list-unstyled"> <li> <a href="/Libraries">Libraries</a> </li> <li> <a href="/YouthCentres">Youth Centres</a> </li> <li> <a href="/Join">Parents</a> </li> </ul> </div> <!--end of col--> <div class="col-6 col-lg-3"> <h6 class="text-white">DigitalSkills.org</h6> <ul class="list-unstyled"> <li> <a href="/Home/ContactUs">Contact Us</a> </li> <li> <a href="/Home/Terms">Terms & Conditions</a> </li> <li> <a href="/Home/Terms#privacy">Privacy Policy</a> </li> <li> <a href="/Home/Terms#cookies">Cookie Policy</a> </li> </ul> </div> <!--end of col--> <div class="col-6 col-lg-3"> </div> </div> <!--end of row--> </div> <!--end of col--> </div> <!--end of row--> </div> <!--end of container--> </footer> <section class="pt-4 pb-4 text-center text-white bg-primary"> <div class="container"> <div class="row"> <div class="col"> 🍪 <span class="me-2">Our website uses cookies to make your browsing experience better. By using our website you agree to our use of cookies.</span> <a class="text-white" href="/Home/Terms#cookies">Learn more <i class="fa fa-chevron-right" aria-hidden="true"></i></a> </div> <!--end of col--> </div> <!--end of row--> </div> <!--end of container--> </section> <script src="/lib/jquery/dist/jquery.js"></script> <script src="/lib/popper.js/umd/popper.min.js"></script> <script src="/lib/bootstrap/js/bootstrap.min.js"></script> <!-- Essential utilities --> <script src="/lib/lozad.js/lozad.min.js"></script> <script src="/lib/typed.js/typed.min.js"></script> <script src="/lib/codemirror/codemirror.min.js"></script> <script src="/lib/codemirror/addon/edit/matchbrackets.min.js"></script> <script src="/lib/codemirror/addon/edit/matchtags.min.js"></script> <script src="/lib/codemirror/addon/display/autorefresh.min.js"></script> <script src="/lib/codemirror/mode/xml/xml.min.js"></script> <script src="/lib/codemirror/mode/python/python.min.js"></script> <script src="/lib/clipboard.js/clipboard.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js" integrity="sha256-4mJNT2bMXxcc1GCJaxBmMPdmah5ji0Ldnd79DKd1hoM=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha256-AjM0J5XIbiB590BrznLEgZGLnOQWrt62s3BEq65Q/I0=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/line-numbers/prism-line-numbers.min.js" integrity="sha256-9cmf7tcLdXpKsPi/2AWE93PbZpTp4M4tqzFk+lWomjU=" crossorigin="anonymous"></script> <script src="/js/site.js?v=4I_2tYh0WrxveC36ngEj32mLSgLfCAcNDvppAUQrZGc"></script> <script src="https://scratchblocks.github.io/js/scratchblocks-v3.5-min.js"></script> <script> function getModalDataHelp(code, target) { $.ajax({ url: '/Home/GetHelpData?code=' + code, type: 'GET', success: function (data) { $(target + ' .modal-title').text(data.title); $(target + ' .modal-body').html(data.content); } }); } $(function () { $('.modal-link-help').click(function () { var target = $(this).data('bs-target'); var code = target.split('_')[1]; // extract the code from the modal target ID getModalDataHelp(code, target); }); }); $(document).ready(async function () { var defaultCountryCode = "GB"; $("#navChooseCountry").show(); // Async call to get selectedCountryCode var selectedCountryCode = await $.ajax({ url: '/Home/GetCountryCode', type: 'GET', timeout: 5000, error: function (jqXHR, textStatus, errorThrown) { console.log('Error fetching country code:', textStatus, errorThrown); selectedCountryCode = defaultCountryCode; // Fallback if it fails } }); if (!selectedCountryCode || $.trim(selectedCountryCode) === "" || selectedCountryCode == "undefined") { selectedCountryCode = defaultCountryCode; } $.get('/Home/GetCountries', function (countries) { var dropdown = $('#countryList'); $.each(countries, function (index, country) { var countryLink = $(`<a class="dropdown-item"></a>`) .attr('href', `/Home/SetCountry?countryCode=${country.countryCode}`) .html(` <img src="https://flagcdn.com/16x12/${country.countryCode.toLowerCase()}.png" srcset="https://flagcdn.com/32x24/${country.countryCode.toLowerCase()}.png 2x, https://flagcdn.com/48x36/${country.countryCode.toLowerCase()}.png 3x" width="16" height="12" alt="${country.name}" class="me-1"> ${country.name}`); if (country.countryCode == selectedCountryCode) { $('#countryDropdown img').attr('src', `https://flagcdn.com/16x12/${country.countryCode.toLowerCase()}.png`); $('#countryDropdown img').attr('alt', country.name); //$('#countryDropdown').append("Region"); } dropdown.append($('<li></li>').append(countryLink)); }); }); updateSchoolLevels(selectedCountryCode); // The event handler only stores the selected country in local storage $('.dropdown-menu').on('click', '.dropdown-item', function (event) { var selectedCountryCode = $(this).attr('href').split('=')[1]; localStorage.setItem('selectedCountryCode', selectedCountryCode); }); }); function updateSchoolLevels(countryCode) { $.get(`/Home/GetSchoolLevels?countryCode=${countryCode}`, function (schoolLevels) { var schoolDropdowns = $('.schoolLevelList'); // Iterate over each school dropdown schoolDropdowns.each(function () { var schoolDropdown = $(this); // Remove existing school levels schoolDropdown.find('li').has('.schoollevel').remove(); // Append new school levels $.each(schoolLevels, function (index, level) { // Remove spaces from levelName var sanitizedLevelName = level.levelName.replace(/\s+/g, ''); var classToAdd = schoolDropdown.hasClass('dropdown-menu') ? 'dropdown-item' : ''; var schoolLink = $(`<a class="${classToAdd} schoollevel" href="/Curriculum/${countryCode}/${sanitizedLevelName}">${level.levelName}</a>`); // Add new school levels to the dropdown schoolDropdown.append($('<li></li>').append(schoolLink)); }); }); }); } </script> <script type="text/javascript"> var classId = "-10022"; new ClipboardJS('.btncopy'); const observer = lozad(); // lazy loads elements with default selector as '.lozad' observer.observe(); const animateCSS = (element, animation, prefix = 'animate__') => // We create a Promise and return it new Promise((resolve, reject) => { const animationName = `${prefix}${animation}`; const node = document.querySelector(element); node.classList.add(`${prefix}animated`, animationName); // When the animation ends, we clean the classes and resolve the Promise function handleAnimationEnd() { node.classList.remove(`${prefix}animated`, animationName); resolve('Animation ended'); } node.addEventListener('animationend', handleAnimationEnd, { once: true }); }); $(document).ready(function () { $('.myIframe').css('height', $(window).height() + 'px'); // set the body background $('body').addClass('bg-light-dark'); loadExplainers(10022, ); $('.summernotesendteacher').summernote({ toolbar: [ ['font', ['bold', 'italic', 'underline', 'clear']], ['para', ['ul', 'ol']] ] }); // Call the function to prepare all quizzes prepareAllQuizzes(); }); function loadExplainers(lessonId, ageGroupId) { // Call the API once for the entire lesson $.ajax({ url: "/CodingClub/GetExplainersForLesson", type: "POST", contentType: "application/json", data: JSON.stringify({ lessonId: lessonId, ageGroupId: ageGroupId }), success: function (response) { if (!response.success || !response.explainers) { console.warn("No explainers found for lesson:", lessonId); return; } //console.log(response); // Loop through explainers and assign them to the correct step $.each(response.explainers, function (stepId, stepExplainers) { let stepContainer = $("[data-step-id='" + stepId + "']"); let explainerContainer = stepContainer.find(".explainerContainer"); if (!stepExplainers || stepExplainers.length === 0) return; let explainerLinks = $("<div class='explainer-links'></div>"); //console.log(stepExplainers); $.each(stepExplainers, function (index, explainer) { if(explainer.enabled == true) { let link = $("<a href='#'></a>") .text(explainer.linkText) .attr("data-topic-id", explainer.stepid) // Use stepid for reference .click(function (event) { event.preventDefault(); showExplainerPopup(explainer); }); explainerLinks.append(link); if (index < stepExplainers.length - 1) { explainerLinks.append("<br />"); } } }); // Append explainer links to the step container explainerContainer.append(explainerLinks); }); }, error: function (xhr, status, error) { console.error("Error loading explainers:", error); } }); } function getDisplayCategory(category, categoryPrefix) { switch (category) { case 'Definitions': return categoryPrefix + 'Key Terms'; // e.g., "📖 Key Terms" case 'History': return categoryPrefix + 'Fun Facts'; // e.g., "🏛️ Fun Facts" case 'Applications': return categoryPrefix + 'Real-World Uses'; // e.g., "💡 Real-World Uses" case 'TeacherTips': return categoryPrefix + 'Teaching Tips'; // e.g., "👩‍🏫 Teaching Tips" case 'JobRoles': return categoryPrefix + 'Jobs in Tech'; // e.g., "🚀 Jobs in Tech" default: return category; // Fallback to raw category } } function showExplainerPopup(explainer) { //console.log(explainer.category); document.getElementById('explainerTitle').innerHTML = ` <span class="badge bg-secondary fs-5 me-2" aria-label="Category: ${explainer.category}"> ${getDisplayCategory(explainer.category, explainer.categoryPrefix)} </span>${explainer.title} `; $("#explainerContent").html(explainer.description); // clear this $("#explainerImage").attr("src", "").hide(); if (explainer.imageUrl) { $("#explainerImage").attr("src", explainer.imageUrl).show(); } else { $("#explainerImage").hide(); } // console.log(topic); //console.log(topic.additionalInfoUrl); // Reset Elements $('#explainerIframe').hide().attr('src', ''); $('#explainerButtons').hide(); $('#hideIframeButton').hide(); $('#openNewTabButton').hide(); if (explainer.additionalInfoUrl) { $('#explainerAdditionalInfo').show(); $('#explainerAdditionalInfoLink') .attr('href', explainer.additionalInfoUrl) .show() .off('click') // Remove previous event listeners .on('click', function (event) { event.preventDefault(); // Prevent default link behavior $(this).hide(); // Hide the link $('#explainerIframe').attr('src', explainer.additionalInfoUrl).fadeIn(); // Show iframe $('#explainerButtons').fadeIn(); // Show buttons $('#openNewTabButton').attr('href', explainer.additionalInfoUrl).show(); // Set Open in New Tab link $('#hideIframeButton').show(); // Show hide button }); // Hide Iframe when Hide button is clicked $('#hideIframeButton') .off('click') .on('click', function () { $('#explainerIframe').fadeOut(); $('#explainerButtons').fadeOut(); $('#explainerAdditionalInfoLink').fadeIn(); }); } else { $('#explainerAdditionalInfo').hide(); } // if (topic.videoUrl) { // $("#explainerVideo").attr("src", topic.videoUrl.replace("watch?v=", "embed/")).show(); // } else { // $("#explainerVideo").hide(); // } // Show Bootstrap modal let modal = new bootstrap.Modal(document.getElementById("explainerModal")); modal.show(); } //$('#infomodal').modal('show'); $("#btnprintwithout").on("click", function () { // add the 'd-print-none' class $('.print-toggle').addClass('d-print-none'); $('.print-toggle > .collapse').removeClass('show'); window.print(); }); $(".toggleStep").on("click", function () { var stepID = $(event.target).attr('stepID'); //alert(stepID); }); function popImg() { var src = $(event.target).attr('src'); var desc = $(event.target).attr('data-desc'); $('#imagepreview').attr('src', src); $('#imagedescription').html(desc); $('#imagemodal').modal('show'); } function disableButton(identifier) { // disable this <a> link (using bootstrap classes) and change the text of the lnk to a spinning fontawesome loading icon $(identifier).addClass('disabled'); $(identifier).html('<i class="fad fa-circle-notch fa-spin"></i>'); } function markStepComplete(identifier) { // Get current timestamp var currentTimestamp = new Date().getTime(); // Check if we have a hidden input field for the last step completion timestamp var lastStepCompleteTimestamp = $('#lastStepCompleteTimestamp').val(); // If the lastStepCompleteTimestamp exists and is within 5 seconds, show an alert if (lastStepCompleteTimestamp && (currentTimestamp - lastStepCompleteTimestamp) < 5000) { var secondsRemaining = Math.ceil((5000 - (currentTimestamp - lastStepCompleteTimestamp)) / 1000); alert("Too fast! You can complete this step in " + secondsRemaining + " seconds."); return; } // Update hidden input with the current timestamp $('#lastStepCompleteTimestamp').val(currentTimestamp); if (0 == 0) { const modal = new bootstrap.Modal('#loggedInMessageModal'); modal.show(); return; } disableButton(identifier); var unitid = $(identifier).data('unitid'); var lessonid = $(identifier).data('lessonid'); var stepid = $(identifier).data('stepid'); var bsid = $(identifier).data('bsid'); var fieldid = $(identifier).data('fieldid'); $.ajax({ type: "POST", url: "/CodingClub/MarkStepComplete", beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, data: JSON.stringify({ SchoolClassID: 0, UnitID: unitid, LessonID: lessonid, StepID: stepid, BadgeSeriesID: bsid, FieldID: fieldid }), contentType: "application/json", dataType: "json", success: function (response) { if (response.success) { $(identifier).hide(); $(identifier).next().show(); $("#_a_" + lessonid + "_" + stepid).removeClass('text-dark'); $("#_a_" + lessonid + "_" + stepid).addClass('text-success'); $("#_il_" + lessonid + "_" + stepid).removeClass('bg-secondary'); $("#_il_" + lessonid + "_" + stepid).addClass('bg-success'); var memberPoints = $("#memberPoints"); var currentPoints = Number(memberPoints.text()); memberPoints.text(currentPoints + response.points); memberPoints.removeClass(); memberPoints.addClass("text-success"); memberPoints.addClass("blink_me"); $("#divMemberRank").html(response.rankhtml); if (response.award == true && response.badgeIcon != null) { $('#questionBadgeCard').show(); $('#revealAward').hide(); $('#imgGIF').hide(); $('#revealBadgeCard').hide(); $('#btnRevealBadge').show(); $('#divCollection').hide(); //$('#newBadgeModal').modal('show'); // Show the specific "See Award" link $(`#seeAwardLink_${stepid}`).show(); // Show the correct "See Award" link $('.badgeseriesname').text(response.badgeSeriesName); if (response.badgeFormat == "gif") { $('#imgGIF').attr("src", "https://i.giphy.com/media/" + response.badgeIcon + "/giphy.webp"); $('#imgGIF').show(); } else { $('#revealBadgeCard .badgetype').text(response.badgeType); $('#revealBadgeCard .badgename').text(response.badgeName); $('#revealBadgeCard').removeClass(); $('#revealBadgeCard').addClass('card card-badge shadow card-' + response.badgeType); $('#revealBadgeCardHeader').removeClass(); $('#revealBadgeCardHeader').addClass('card-header bg-' + response.badgeSeriesColor); $('#revealBadgeCardBody').removeClass(); $('#revealBadgeCardBody').addClass('card-body bg-' + response.badgeSeriesColor); $('#revealBadgeCardBody #badgeicon').removeClass(); $('#revealBadgeCardBody #badgeicon').addClass(response.badgeIcon); $('#revealBadgeCard').show(); } } // if (response.newrank == true && response.currentrank != null) { // $('#divNewRank').show(); // } // else { // $('#divNewRank').hide(); // } } else { if (response.msg != "") { alert(response.msg); } else { alert("There was an error"); } } }, failure: function (response) { alert("There was an error"); } }); $("#btnRevealBadge").on("click", function () { var src = $(event.target).attr('src'); $('#questionBadgeCard').hide(); $('#revealAward').show(); animateCSS('#revealAward', 'zoomIn'); $('#btnRevealBadge').hide(); $('#divCollection').show(); animateCSS('#divCollection', 'tada'); }); } function openAwardModal(event, element) { event.preventDefault(); // Prevents scrolling to the top of the page // You can load specific content into the modal here if needed const modal = new bootstrap.Modal(document.getElementById('newBadgeModal')); modal.show(); } function prepareAllQuizzes() { const levels = ["easy", "normal", "hard"]; //const levels = ["easy"]; let currentIndex = 0; function prepareNextLevel() { if (currentIndex < levels.length) { const level = levels[currentIndex]; console.log(`Preparing quiz for level: ${level}`); prepareQuiz(level, function () { console.log(`Quiz for level ${level} prepared.`); currentIndex++; prepareNextLevel(); // Prepare the next level }); } else { console.log("All quizzes prepared."); } } prepareNextLevel(); // Start the chain } function prepareQuiz(level, callback) { let lessonId = 10022; let classCode = ""; $.ajax({ url: '/CodingClub/PrepareQuiz', type: 'GET', data: { lessonId: lessonId, classCode: classCode, level: level }, success: function (data) { if (data && data.totalQuestions) { quizQuestionsCount = data.totalQuestions; } callback(); // Proceed to startQuiz after preparation }, error: function (jqXHR, textStatus, errorThrown) { console.log("PrepareQuiz Error:", textStatus, errorThrown); callback(); // Proceed even if preparation fails (quiz might already exist) } }); } function startQuiz(level) { let lessonId = 10022; let classCode = ""; // Show the loading indicator $("#quiz-loading-indicator").removeClass("d-none"); // Check if quiz exists by attempting to prepare it prepareQuiz(level, function () { $.ajax({ url: '/CodingClub/TakeQuiz', type: 'GET', data: { lessonId: lessonId, classCode: classCode, fid: 12, cid: 705, mid: 1934, uid: 3363, level: level }, success: function (data) { //console.log("Data received: ", data); quizAttemptId = data.quizAttemptId; quizData = data.quizData; currentQuestionIndex = 0; $("#quiz-container").removeClass("d-none"); $("#quizAttemptID").val(quizAttemptId); $("#resultquiz").addClass("d-none"); $("#next-question-btn").removeClass("d-none"); $("span.question-points").text(data.questionPoints); displayQuestion(currentQuestionIndex); }, error: function (jqXHR, textStatus, errorThrown) { console.log("TakeQuiz Error:", textStatus, errorThrown); alert('Error: ' + errorThrown); }, complete: function () { // Hide the loading indicator $("#quiz-loading-indicator").addClass("d-none"); } }); }); } function displayQuestion(index) { //console.log("Displaying question number: ", index + 1); let questionData = quizData[index]; $("#questionNumber").text("Question " + (index + 1) + " out of " + quizData.length + " questions"); $("#question").text(questionData.question); $("#questionContent").html(wrapQuestionContent(questionData.content, questionData.contentType)); $("#options").empty(); $('#quizReportIssue').removeClass("d-none"); $('#quizReportIssueMessage').text(''); $("#quizReportIssueMessage").addClass("d-none"); for (let i = 0; i < questionData.options.length; i++) { let optionElement = $('<div class="form-check mb-3"></div>'); let radioElement = $('<input class="form-check-input" type="radio" name="option">'); radioElement.val(questionData.options[i].quizOptionId); radioElement.attr("id", `option${i}`); let labelElement = $('<label class="form-check-label"></label>'); labelElement.attr("for", `option${i}`); labelElement.text(questionData.options[i].optionText); // Check if content exists for the option if (questionData.options[i].content && questionData.options[i].contentType) { let content = wrapQuestionContent(questionData.options[i].content, questionData.options[i].contentType); labelElement.append(`<br/>${content}`); } optionElement.append(radioElement); optionElement.append(labelElement); $("#options").append(optionElement); } $('html, body').animate({ scrollTop: $('#step-quiz').offset().top }, 500); // 500ms for smooth scrolling } function wrapQuestionContent(content, contentType) { //console.log(content, contentType); // switch (contentType) { // case "scratch": // content = `<code class="sblocks">${content}</code>`; // break; // case "javascript": // content = `<pre><code class="language-javascript">${content}</code></pre>`; // break; // case "python": // content = `<pre class="language-python">${content}</pre>`; // break; // case "code": // content = `<code class="language-markup">${content}</code>`; // break; // default: // content = content; // Leave as is if content type is not recognized // } return content; } function checkAnswer() { let selectedOption = $('#options input[name="option"]:checked'); if (!selectedOption.val()) { alert("Please select an answer!"); return; } let quizOptionId = selectedOption.val(); let quizQuestionId = quizData[currentQuestionIndex].quizQuestionId; let quizAttemptID = $("#quizAttemptID").val(); $.ajax({ url: '/CodingClub/CheckAnswer', type: 'GET', data: { quizQuestionId: quizQuestionId, quizOptionId: quizOptionId, quizAttemptID: quizAttemptID }, success: function (data) { if (data.isCorrect) { selectedOption.parent().addClass("text-success"); correctAnswers++; $("#correct-answer").text("Correct 👍 +" + data.points + " XP"); $("#correct-answer").addClass("alert-success"); $("#correct-answer").removeClass("alert-danger"); $("#correct-answer").removeClass("d-none"); shootStars(data.points); } else { selectedOption.parent().addClass("text-danger"); $("#result").text("Wrong!"); let correctOptionText = quizData[currentQuestionIndex].options.find(option => option.quizOptionId === data.correctOptionId).optionText; $("#correct-answer").html(`The correct answer is:<br /> ${correctOptionText}`); $("#correct-answer").addClass("alert-danger"); $("#correct-answer").removeClass("alert-success"); $("#correct-answer").removeClass("d-none"); } $("#options input[name='option']").attr("disabled", true); // Disable all options console.log("currentQuestionIndex: " + currentQuestionIndex); console.log("quizData.length: " + quizData.length); if (currentQuestionIndex + 1 < quizData.length) { $("#next-question-btn").attr("disabled", false); // Enable the Next Question button } else { $("#next-question-btn").addClass("d-none"); //$("#see-score-btn").removeClass("d-none"); completeQuizAttempt(); } }, error: function (jqXHR, textStatus, errorThrown) { console.log(textStatus, errorThrown); alert('Error: ' + errorThrown); } }); } function nextQuestion() { currentQuestionIndex++; if (currentQuestionIndex < quizData.length) { displayQuestion(currentQuestionIndex); $("#next-question-btn").attr("disabled", true); $("#submit-answer-btn").attr("disabled", false); $("#resultquiz").text(""); $("#correct-answer").text(""); $("#correct-answer").addClass("d-none"); } } function completeQuizAttempt() { $.ajax({ url: '/CodingClub/CompleteQuizAttempt', type: 'POST', data: { quizAttemptId: quizAttemptId, classCode: '', fid: 12, cid: 705, mid: 1934, uid: 3363 }, success: function (data) { // console.log("QuizAttempt status updated successfully!"); // console.log(data); let correctAnswers = data.correctAnswers; let totalQuestions = data.totalQuestions; let score = data.score; let studentProgressId = data.spID; // Display the result to the user $("#resultquiz").html(`<h3>Quiz completed! 👏</h3><p> You answered ${correctAnswers} out of ${totalQuestions} questions correctly, earning ${score} points.</p><p><a href="#" onclick="deleteQuizAttempt(${studentProgressId}); return false;"><i class="fa-duotone fa-solid fa-redo me-1"></i>Retake Quiz</a></p>`); $("#resultquiz").removeClass("d-none"); //$("#quiz-container").addClass("d-none"); // let them take it again //$("#startquiz-btn").show(); //$("#see-score-btn").addClass("d-none"); //$("#correct-answer").addClass("d-none"); }, error: function (jqXHR, textStatus, errorThrown) { console.log(textStatus, errorThrown); alert('Error: ' + errorThrown); } }); } function deleteQuizAttempt(spID) { //console.log(spID); // put a confirm alert here if (!confirm('Are you sure you? This will delete your current quiz attempt.')) { return; } $.ajax({ type: "POST", url: "/CodingClub/DeleteQuizAttempt", beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, data: JSON.stringify({ spID: spID }), contentType: "application/json", dataType: "json", success: function (response) { //console.log(response); if(response.success){ alert('You can now retake the quiz, please wait for the page to reload.'); // reload the page location.reload(); } else{ alert('There was an error'); } }, failure: function (response) { console.log("There was an error"); } }); } $(document).on('submit', '#quizReportIssueForm', function (e) { e.preventDefault(); let quizQuestionId = $('#quizQuestionId').val(); let quizAttemptID = $('#quizAttemptID').val(); let issueReason = $('#issueReason').val(); $.ajax({ url: '/CodingClub/ReportQuizIssue', type: 'POST', data: { quizQuestionId: quizQuestionId, quizAttemptID: quizAttemptID, issueReason: issueReason }, success: function (data) { $('#quizReportIssueMessage').text(data.responseText); $('#quizReportIssue').addClass("d-none"); $("#quizReportIssueMessage").removeClass("d-none"); $('#quizReportIssueModal').modal('hide'); // clear the textbox $('#issueReason').val(''); }, error: function () { $('#quizReportIssueMessage').text('An error occurred while processing your request. Please try again later.'); $("#quizReportIssueMessage").removeClass("d-none"); $('#quizReportIssueModal').modal('hide'); } }); }); function getSubmissions() { var lessonId = 10022; var classCode = ""; //console.log("Getting submissions for lesson: ", lessonId, " and class: ", classCode); $.ajax({ url: '/CodingClub/GetAssessmentSubmissions', type: 'GET', data: { lessonId: lessonId, classCode: classCode }, success: function (data) { //console.log("Submissions received: ", data); $('#assessmentSubmissions').html(data); $('.assessment-submission-placeholder').each(function () { var container = $(this); var studentID = container.data('student-id'); var schoolClassID = container.data('schoolclass-id'); var courseID = container.data('course-id'); var assessmentID = container.data('assessment-id'); //console.log(studentID, schoolClassID, courseID, assessmentID); $.ajax({ url: '/CodingClub/GetAssessmentSubmission', type: 'GET', data: { studentID: studentID, assessmentID: assessmentID, schoolClassID: schoolClassID, courseID: courseID }, success: function (result) { container.html(result); // add a 'read-more-target' class to the div with submission-content // so that the read-more JS can target it var element = $('#submission-content-' + assessmentID + '-' + studentID); element.addClass('read-more-target'); addReadMore($(element)); } }); }); }, error: function (jqXHR, textStatus, errorThrown) { console.log(textStatus, errorThrown); alert('Error: ' + errorThrown); } }); } function viewComments(submissionID, schoolclassID, courseID) { var divComments = $('#comments_' + submissionID); divComments.removeClass('d-none'); // get the comments for this submission and populate them into divComments $.ajax({ url: '/CodingClub/GetComments', type: 'GET', data: { submissionID: submissionID, schoolclassID: schoolclassID, courseID: courseID }, success: function (data) { //console.log(data); if(data.success == false) { divComments.html('<p>No comments yet.</p>'); } else { divComments.html(data); } } }); } </script> <script src="/js/submitproject.js?v=_rRpMIvh_y1SYKfr_0JMNY6dPFfbM_uUmn8tcYWwlMw"></script> <script src="/lib/microsoft/signalr/dist/browser/signalr.js"></script> </body> </html>