MQL5 Developer Onboarding Hub
Build a role-filtered resource hub with persistent onboarding progress.
Introduction
30 Second Summary
When a new developer joins your trading group, they spend hours hunting for the right documentation, setup guides, and community links across scattered bookmarks and chat threads. A single hub that surfaces the right resources based on their role cuts that friction down to zero.
In this project, you will build an MQL5 Developer Onboarding Hub. New developers pick their role, see filtered resources, and track their setup progress across browser sessions.
What You'll Build
When you share this hub with your group, a new developer opens the page, picks their role, and sees a filtered grid of resources tailored to their track, with their onboarding progress intact from last session.
By the end of this project, you'll have:
- A role selector that instantly filters 15+ curated MQL5 resources so each developer sees only what matters for their track.
- Resource cards that link to official MQL5 documentation, code examples, and community pages, organized by category with a dark trading-inspired design.
- A setup checklist that saves onboarding progress to localStorage so developers can close the tab and come back without losing anything.
- Secret Mission: an optional challenge to push your skills further.
Are there any prerequisites?
This project assumes you are comfortable with HTML, CSS, and JavaScript fundamentals. You should know how to create HTML elements, style them with CSS, and write basic event listeners.
Before We Start
Before we start building, take a moment to think about what happens when a new developer joins your MQL5 group today. This step is your chance to lock in the problem your onboarding hub will solve and why it matters.
Set Up the Project Structure
Your onboarding hub will grow to include 18 resources, role-based filtering, and a persistent checklist. A clean project structure keeps each concern (content, styling, behavior) in its own file so the hub stays maintainable as it grows.
In this step, you will create the project folder, write the HTML skeleton with a dark-themed header, and build a base CSS color scheme that matches the trading terminal aesthetic.
In this step, get ready to:
- Create the project folder with three files: index.html, style.css, and script.js.
- Build a dark color theme using CSS custom properties.
- Add the page skeleton with placeholder sections for each major feature.
Create the project folder and files
Every file in this project has a single job. index.html holds the page structure, style.css handles the look and feel, and script.js will power the interactive features.
- In VS Code, open the integrated terminal by pressing Ctrl+` (the backtick key above Tab).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create the project folder and move into it by running these commands:
mkdir mql5-onboarding-hub
cd mql5-onboarding-hub
- Create the three project files by running this command:
macOS
touch index.html style.css script.js
Windows
New-Item -ItemType File -Path index.html, style.css, script.js
- Confirm the files exist by running:
ls
You should see index.html, script.js, and style.css in the output.
Don't see all three files?
Make sure you ran the touch or New-Item command from inside the mql5-onboarding-hub folder, not from the Desktop itself.
help me create files in my project folder
- Open the project folder in VS Code by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the mql5-onboarding-hub folder, and click Open.
You should see all three files listed in the Explorer sidebar on the left.
- Click index.html in the Explorer sidebar to open it in the editor.
- Set up the HTML boilerplate with a header by pasting this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MQL5 Developer Onboarding Hub</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="container">
<h1>MQL5 Developer Onboarding Hub</h1>
<p class="subtitle">Your launchpad for MQL5 development. Pick your role, explore resources, and track your setup progress.</p>
</div>
</header>
<script src="script.js"></script>
</body>
</html>
What does this code do?
- The <link> tag connects the stylesheet so the browser knows where to find styling rules.
- The <header> wraps the page title and subtitle in a container div that will center the content later.
- The <script> tag at the bottom loads JavaScript after the page structure is ready.
- Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Open index.html in your browser by running this in the terminal:
macOS
open index.html
Windows
start index.html
You should see an unstyled page with "MQL5 Developer Onboarding Hub" as a large heading and the subtitle below it. The page has a white background and default browser fonts for now.
Build the base dark theme
CSS custom properties let you define your color palette once and reuse the values everywhere. This keeps the dark theme consistent and easy to update later.
- Click style.css in the Explorer sidebar to open it.
- Define the color variables and base page styles by pasting this code:
/* Color scheme and design tokens */
:root {
--bg-primary: #0d1117;
--bg-secondary: #161b22;
--bg-card: #1c2128;
--border-color: #30363d;
--text-primary: #e6edf3;
--text-secondary: #8b949e;
--accent-blue: #58a6ff;
--accent-green: #3fb950;
--accent-purple: #bc8cff;
--accent-orange: #d29922;
--accent-red: #f85149;
--radius: 8px;
}
/* Reset default browser margins and padding */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Base page styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
}
What does this code do?
- The :root block defines 12 CSS custom properties (variables) for the color palette. Every style rule in the project references these variables instead of hardcoded hex values.
- The * selector resets browser defaults so elements start with zero margin, zero padding, and border-box sizing.
- The body rule sets the dark background, light text, and a system font stack that matches what most operating systems use natively.
- Save style.css and refresh your browser.
The page background is now dark (#0d1117) and the heading text is light. The trading terminal look is starting to take shape.
- Add the layout and header styles below your existing code in style.css:
/* Centered content container */
.container {
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
}
/* Header bar with darker background */
header {
background-color: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
padding: 40px 0;
text-align: center;
}
header h1 {
font-size: 2rem;
margin-bottom: 8px;
}
/* Muted subtitle text */
.subtitle {
color: var(--text-secondary);
font-size: 1.1rem;
}
What does this code do?
- The .container class centers content at a max width of 960px. Every major section of the page will use this wrapper.
- The header gets a slightly lighter dark background (--bg-secondary) with a subtle bottom border to separate it from the rest of the page.
- The .subtitle uses the muted --text-secondary color so it doesn't compete with the main heading.
- Save style.css and refresh your browser.
The header now has a centered title, a muted subtitle beneath it, and a slightly lighter background that separates it from the page body.
Header still looks unstyled?
Check that the <link> tag in index.html reads href="style.css" exactly. A typo in the filename breaks the connection between the HTML and CSS files.
Also confirm that style.css is saved and sits in the same folder as index.html.
help me get my CSS styles to show up
Add the page skeleton
The header looks good, but the hub needs distinct areas for its three main features: a role selector, a resource grid, and a setup checklist. Empty <section> elements act as labeled containers that JavaScript will populate later.
- Add the section spacing and responsive rules at the bottom of style.css:
/* Main content area */
main {
padding: 40px 0;
}
/* Space between sections */
section {
margin-bottom: 48px;
}
/* Responsive text sizing */
@media (max-width: 600px) {
header h1 {
font-size: 1.5rem;
}
}
What does this code do?
The main and section rules add breathing room between the content areas you are about to create. The @media query shrinks the heading on screens narrower than 600px so the title does not overflow on phones.
- Save style.css.
- Switch to index.html in the editor. Find the closing </header> tag.
- Add this code between </header> and the <script> tag:
<main class="container">
<section id="role-section"></section>
<section id="resources-section"></section>
<section id="checklist-section"></section>
</main>
<footer>
<div class="container">
<p>MQL5 Developer Onboarding Hub. Built for the team.</p>
</div>
</footer>
What does this code do?
- The three <section> elements are empty placeholders. Each has an id (role-section, resources-section, checklist-section) that JavaScript will use to inject content dynamically.
- The <footer> gives the page a clean bottom edge with a simple attribution line.
Before you refresh, what do you expect to see at the bottom of the page now that a footer element is in place?
- Save index.html and refresh your browser.
The dark-themed header still dominates the top. Just below it, you can see the footer text "MQL5 Developer Onboarding Hub. Built for the team." at the bottom. The three sections are invisible because they are empty, but they are ready for content.
Don't see the footer text?
Make sure the new code sits between the closing </header> tag and the <script> tag. If it is outside the <body> tags, the browser will not render it.
help me fix my page layout
✔️ Awesome, I've got everything!
Great. Double check that both files are saved before moving on.
ⓧ I'd like to double check the full code
Scroll up and confirm you have these code blocks in order, pasted into the correct files.
In index.html:
- The HTML boilerplate chunk (from "Create the project folder and files") containing the <!DOCTYPE html>, <head>, and <header> elements.
- The page skeleton chunk (from "Add the page skeleton") containing <main>, three <section> elements, and the <footer>, inserted between </header> and <script>.
In style.css:
- Chunk 1: :root variables, the * reset, and the body rule.
- Chunk 2: .container, header, header h1, and .subtitle styles.
- Chunk 3: main, section, and the @media responsive query.
Your project structure is solid and the dark theme is live. Next up, you will fill the hub with real MQL5 resource data and build the role selector that filters it.
Render Resource Cards with Role Filtering
Your role buttons highlight on click and the RESOURCES array holds 18 curated links. But clicking a button still shows nothing in the page. The #resource-grid section sits empty.
Dumping all 18 resources on every developer is overwhelming. The whole point of role selection is to surface only what matters for each track. In this step, you will connect the resource data to the page so clicking a role button dynamically renders a filtered grid of cards.
In this step, get ready to:
- Write a rendering function that filters resources by role and generates HTML cards.
- Connect the role buttons so clicking one instantly updates the card grid.
- Style the resource cards with a responsive grid layout, category badges, and hover effects.
Create the card rendering function
Each resource card needs a colored category badge (Getting Started, Code Base, Community, etc.). A small helper function maps each category name to its CSS class, and the main rendering function uses it to build card HTML on the fly.
- In script.js, scroll to the bottom of the file and find the document.querySelectorAll(".role-btn").forEach block.
- Delete the entire block, from document.querySelectorAll through its closing });.
- In its place, add the category badge helper and the resource rendering function by pasting this code:
// Maps each category name to a CSS badge class
function getCategoryBadgeClass(category) {
const map = {
"Getting Started": "badge-getting-started",
"Language Reference": "badge-language-reference",
"Code Base": "badge-code-base",
"Community": "badge-community",
"Marketplace": "badge-marketplace"
};
return map[category] || "";
}
// Filters the RESOURCES array by role and builds card HTML
function renderResources(role) {
const grid = document.getElementById("resource-grid");
const filtered = role === "all"
? RESOURCES
: RESOURCES.filter(r => r.roles.includes(role));
grid.innerHTML = filtered.map(resource => {
const isHighlyRelevant = role !== "all" && resource.roles.length === 1 && resource.roles.includes(role);
return `
<div class="resource-card">
<span class="category-badge ${getCategoryBadgeClass(resource.category)}">${resource.category}</span>
<h3><a href="${resource.url}" target="_blank" rel="noopener noreferrer">${resource.title}</a></h3>
<p>${resource.description}</p>
${isHighlyRelevant ? '<span class="relevance-tag">Recommended for your track</span>' : ""}
</div>
`;
}).join("");
}
What does this code do?
getCategoryBadgeClass takes a category string like "Getting Started" and returns the matching CSS class name. This keeps the badge-to-color mapping in one place.
renderResources does the heavy lifting. It filters the RESOURCES array based on the selected role, then maps each matching resource into a card's HTML. The isHighlyRelevant check tags resources that belong exclusively to one role with a "Recommended for your track" label.
- Save script.js.
- Refresh your browser and open the DevTools console by pressing F12 (or Cmd+Option+J on macOS).
- Type renderResources("ea") into the console and press Enter.
You should see unstyled text appear in the Resources section. The cards look plain right now, but the data is rendering into the DOM.
Nothing appeared in the grid?
- Check the console for red errors. A typo in the function name or a missing closing brace will prevent the script from loading.
- Make sure you deleted the old document.querySelectorAll block completely. Leftover braces can cause a syntax error.
- Confirm you saved the file before refreshing the browser.
Why aren't my resource cards rendering when I call renderResources in the console?
Connect filtering to the role buttons
The rendering function works when called manually, but the role buttons are disconnected. You need a controller function that highlights the active button and triggers a re-render, plus an initializer that wires everything up on page load.
- In script.js, add the following code below the renderResources function:
// Highlights the active button and re-renders the filtered resources
function setActiveRole(role) {
document.querySelectorAll(".role-btn").forEach(btn => {
btn.classList.toggle("active", btn.dataset.role === role);
});
renderResources(role);
}
// Wires up button listeners and renders the default view on load
function init() {
document.querySelectorAll(".role-btn").forEach(btn => {
btn.addEventListener("click", () => {
setActiveRole(btn.dataset.role);
});
});
// Show all resources on first page load
renderResources("all");
}
document.addEventListener("DOMContentLoaded", init);
What does this code do?
- setActiveRole is the single entry point for a role change. It toggles the active class on every button (removing it from the old selection and adding it to the new one), then calls renderResources with the chosen role.
- init attaches click listeners to every role button and renders the full resource grid on first load by calling renderResources("all").
- The DOMContentLoaded listener ensures init only runs after all HTML elements exist on the page.
- Save script.js and refresh your browser.
All 18 resources now appear as unstyled text blocks in the Resources section. Click EA Developer and the list shrinks to only EA-relevant resources. Click All Resources to bring them all back.
Cards not appearing on page load?
- Open the console and check for errors. A missing comma or bracket in the code you just added will stop the script entirely.
- Verify that document.addEventListener("DOMContentLoaded", init); is the very last line of your file. If it is inside another function by accident, init never gets called.
Why aren't my resource cards showing up when the page loads?
Want to double check your JavaScript so far?
✔️ Awesome, I've got everything!
Great. Make sure you have saved script.js before moving on.
ⓧ I'd like to double check the full code
The file is longer than a single screen, so scroll through script.js and confirm each section is present, in this order:
- The RESOURCES array (18 objects) from Step 2.
- The ROLE_LABELS and STORAGE_KEYS objects from Step 2.
- getCategoryBadgeClass(category) function (this step, chunk 1).
- renderResources(role) function (this step, chunk 1).
- setActiveRole(role) function (this step, chunk 2).
- init() function (this step, chunk 2).
- document.addEventListener("DOMContentLoaded", init); as the very last line.
If anything is missing, scroll back to the relevant chunk and copy it in.
Style the resource cards
The filtering logic works, but the cards are just raw text. A CSS Grid layout and card styles will turn the wall of text into a polished, browsable resource library.
- In style.css, find the .role-btn.active rule. Below it, add the grid layout and card base styles by pasting this code:
.resource-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.resource-card {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 20px;
transition: border-color 0.2s, transform 0.2s;
}
.resource-card:hover {
border-color: var(--accent-blue);
transform: translateY(-2px);
}
What does this code do?
- repeat(auto-fill, minmax(280px, 1fr)) creates a responsive grid that fits as many 280px-wide columns as possible, stretching each to fill remaining space.
- The transition on .resource-card makes the border color change and the hover lift (translateY(-2px)) animate smoothly instead of snapping.
- Save style.css and refresh your browser.
The resources now sit in a tidy grid of dark cards. Hover over one and you should see it lift slightly with a blue border highlight.
- Below the .resource-card:hover rule you just added, paste the badge and card text styles:
.resource-card .category-badge {
display: inline-block;
font-size: 0.75rem;
padding: 3px 10px;
border-radius: 12px;
margin-bottom: 10px;
font-weight: 600;
text-transform: uppercase;
}
.badge-getting-started { background-color: rgba(63, 185, 80, 0.15); color: var(--accent-green); }
.badge-language-reference { background-color: rgba(88, 166, 255, 0.15); color: var(--accent-blue); }
.badge-code-base { background-color: rgba(188, 140, 255, 0.15); color: var(--accent-purple); }
.badge-community { background-color: rgba(210, 153, 34, 0.15); color: var(--accent-orange); }
.badge-marketplace { background-color: rgba(248, 81, 73, 0.15); color: var(--accent-red); }
.resource-card h3 { font-size: 1.05rem; margin-bottom: 8px; }
.resource-card h3 a { color: var(--accent-blue); text-decoration: none; }
.resource-card h3 a:hover { text-decoration: underline; }
.resource-card p { color: var(--text-secondary); font-size: 0.9rem; }
.resource-card .relevance-tag { display: inline-block; margin-top: 10px; font-size: 0.75rem; color: var(--accent-green); font-weight: 600; }
What does this code do?
- Each badge class uses a semi-transparent background tinted to match the category's accent color. This keeps the badges visible without overpowering the dark theme.
- The .relevance-tag uses the green accent to draw the eye to resources that are exclusive to the selected role.
- Save style.css and refresh your browser.
Each card now shows a colored category badge at the top, a blue linked title, and muted description text. Cards tagged as exclusive to a role display a green "Recommended for your track" label.
One last detail. The grid should collapse to a single column on small screens so cards are not squeezed.
- In style.css, scroll to the @media (max-width: 600px) block at the bottom of the file.
- Find the existing rules inside the media query. It currently looks like this:
@media (max-width: 600px) {
header h1 {
font-size: 1.5rem;
}
.role-buttons {
flex-direction: column;
}
}
- Add the following rule inside the media query block, below the .role-buttons rule, so the result looks like this:
@media (max-width: 600px) {
header h1 {
font-size: 1.5rem;
}
.role-buttons {
flex-direction: column;
}
.resource-grid {
grid-template-columns: 1fr;
}
}
Cards not showing colored badges?
- Check that the badge class names in your CSS (.badge-getting-started, .badge-code-base, etc.) match the strings returned by getCategoryBadgeClass in your JavaScript. A single character difference means the class won't apply.
- Make sure the new CSS rules are outside the media query block, not accidentally pasted inside it.
My resource card styles are not applying correctly. What should I check?
Want to double check your stylesheet?
✔️ Awesome, I've got everything!
Great. Make sure you have saved style.css before testing the final flow.
ⓧ I'd like to double check the full code
The stylesheet is long, so scroll through style.css and confirm each section is present, in this order:
- :root custom properties, reset, body, and container styles (from Step 1).
- Header, subtitle, welcome-banner, sticky-nav, and nav-links styles (from Step 2).
- Main, section, section-desc, role-buttons, and role-btn styles (from Step 2).
- .resource-grid and .resource-card base styles with hover effect (this step, chunk 1).
- .category-badge, five badge color classes, card text styles, and .relevance-tag (this step, chunk 2).
- @media (max-width: 600px) block with header, role-buttons, and .resource-grid overrides.
If any section is missing, scroll back to the relevant chunk and copy it in.
Time for the final test. Before you click through the roles, predict: if you click Script Writer, roughly how many cards do you expect to see compared to All Resources?
- Save style.css and refresh your browser.
- Click EA Developer and watch the grid update to show only EA-relevant resource cards.
- Click Indicator Builder and see a different set of cards, with some marked "Recommended for your track" in green.
- Click All Resources to confirm all 18 cards reappear.
Each role surfaces a different subset of cards. Resources exclusive to a single role get the green "Recommended for your track" tag. The grid adapts to your window size automatically.
Your hub now shows the right resources to the right developers. Next up, you will add a setup checklist that saves each developer's onboarding progress across sessions using localStorage.
Build the Setup Checklist with localStorage
Your resource grid is live. Clicking any role button filters the cards instantly, giving each developer a focused view of what matters for their track.
There is still a gap. A developer browses the hub, closes the tab, and loses track of where they are in the onboarding process. A persistent checklist solves this by saving progress to localStorage so they can pick up exactly where they left off.
In this step, get ready to:
- Define the onboarding checklist data with 7 setup items.
- Add localStorage persistence so progress survives page refreshes.
- Add a reset button to clear progress with a confirmation prompt.
Define the checklist data and render it
The checklist section needs a progress bar, a completion counter, and a reset button before any JavaScript can populate it. You will add these structural elements to the HTML first, then style them, then define the checklist data.
- In index.html, find the #checklist-section. Replace the entire section with the following code:
<section id="checklist-section">
<h2>Setup Checklist</h2>
<p class="section-desc">Track your onboarding progress. Your checkmarks are saved automatically.</p>
<!-- Progress bar showing completion percentage -->
<div class="progress-bar-container">
<div id="progress-bar" class="progress-bar"></div>
</div>
<p id="progress-text" class="progress-text">0 of 7 completed</p>
<!-- JS will generate checklist items here -->
<div id="checklist" class="checklist"></div>
<button id="reset-btn" class="reset-btn">Reset Progress</button>
</section>
What did you add?
- progress-bar-container wraps a green bar that will visually track how many items are checked.
- progress-text displays a counter like "3 of 7 completed".
- The #checklist div stays empty for now. JavaScript will populate it with checkboxes.
- reset-btn gives developers a way to start over.
- Save index.html and refresh your browser.
- Scroll down to the Setup Checklist section. You should see "0 of 7 completed" text and a "Reset Progress" button below the empty checklist area.
Don't see the new elements?
- Make sure the new elements are inside the existing section tags. The opening and closing section tags should still wrap everything.
- Check that the progress bar container and reset button are siblings of the checklist div, not nested inside it.
Help me fix my checklist section HTML structure.
Now those elements need styling. The progress bar, checklist items, reset button, and footer all need dark-theme styles.
- In style.css, find the .resource-card .relevance-tag rule. Add the following styles below it, before the @media query:
/* Progress bar and footer styles */
.progress-bar-container {
background-color: var(--bg-card);
border-radius: 4px;
height: 8px;
overflow: hidden;
margin-bottom: 8px;
}
.progress-bar {
background-color: var(--accent-green);
height: 100%;
width: 0%;
transition: width 0.3s;
border-radius: 4px;
}
.progress-text {
color: var(--text-secondary);
font-size: 0.85rem;
margin-bottom: 16px;
}
footer {
background-color: var(--bg-secondary);
border-top: 1px solid var(--border-color);
padding: 24px 0;
text-align: center;
color: var(--text-secondary);
font-size: 0.85rem;
}
What do these styles do?
- The progress bar container is a thin 8px strip. The inner .progress-bar fills it with green as completion grows. The transition: width 0.3s makes the fill animate smoothly.
- The footer gets a top border and secondary background to cap the page cleanly.
- Continue adding styles directly below the footer rule, still above @media:
/* Checklist layout and item cards */
.checklist {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
}
.checklist-item {
display: flex;
align-items: center;
gap: 12px;
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 12px 16px;
cursor: pointer;
transition: border-color 0.2s;
}
.checklist-item:hover {
border-color: var(--accent-blue);
}
What do these styles do?
The .checklist container stacks items vertically with a small gap. Each .checklist-item is styled as a dark card with a hover effect that highlights the border in blue.
- Add the final batch of styles directly below .checklist-item:hover:
/* Checkbox, checked state, and reset button */
.checklist-item input[type="checkbox"] {
accent-color: var(--accent-green);
width: 18px;
height: 18px;
cursor: pointer;
}
.checklist-item.checked label {
text-decoration: line-through;
color: var(--text-secondary);
}
.checklist-item label {
cursor: pointer;
flex: 1;
}
.reset-btn {
background-color: transparent;
color: var(--accent-red);
border: 1px solid var(--accent-red);
border-radius: var(--radius);
padding: 8px 16px;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.2s;
}
.reset-btn:hover {
background-color: rgba(248, 81, 73, 0.15);
}
What do these styles do?
- Checkboxes get a green accent color to match the progress bar.
- When a checklist item has the checked class, its label gets a strikethrough to show completion.
- The reset button uses a red outline and a subtle red hover background to signal a destructive action.
- Save style.css and refresh your browser.
- Scroll to the bottom of the page. You should see a styled progress bar area, "0 of 7 completed" text, a red-outlined "Reset Progress" button, and a clean footer with a top border.
Reset button or footer not styled?
- Confirm all three CSS blocks are placed after the resource card styles and before the @media query at the bottom of the file.
- Check for missing closing braces. Each CSS rule must have a matching opening and closing curly brace.
Help me debug my checklist CSS styles.
The styling is ready. Now you need the data that defines the onboarding steps.
- In script.js, find the closing ]; of the RESOURCES array. Add the following CHECKLIST_ITEMS array below it, before ROLE_LABELS:
// Onboarding steps every new MQL5 developer should complete
const CHECKLIST_ITEMS = [
"Download and install MetaTrader 5",
"Open MetaEditor from the terminal (press F4)",
"Register an account on mql5.com",
"Configure MQL5 Storage (Tools > Settings > Community tab in MetaEditor)",
"Run the MQL5 Wizard (File > New) to create a Hello World script",
"Browse the Code Base for example programs in your track",
"Join the MQL5 Forum and introduce yourself"
];
What are these 7 items?
Each string maps to a real onboarding step: installing the platform, opening the IDE, registering on the community site, configuring version control, creating a first program, studying existing code, and joining the forum. These are the essentials every new developer in your group needs to complete.
Add localStorage persistence
The data array defines what the checklist contains, but it does not display anything yet. You need JavaScript functions that render the checkboxes, save their state to localStorage, and update the progress bar whenever a checkbox changes.
- In script.js, find the setActiveRole function. Add the following three helper functions below it:
// Read saved checklist progress from localStorage
function getProgress() {
const saved = localStorage.getItem(STORAGE_KEYS.progress);
return saved ? JSON.parse(saved) : {};
}
// Write current checklist state to localStorage
function saveProgress(progress) {
localStorage.setItem(STORAGE_KEYS.progress, JSON.stringify(progress));
}
// Update the visual progress bar and text counter
function updateProgressBar(progress) {
const completed = Object.values(progress).filter(Boolean).length;
const total = CHECKLIST_ITEMS.length;
const pct = Math.round((completed / total) * 100);
document.getElementById("progress-bar").style.width = pct + "%";
document.getElementById("progress-text").textContent = completed + " of " + total + " completed";
}
What do these helper functions do?
- getProgress() reads the saved state from localStorage. If nothing is saved yet, it returns an empty object.
- saveProgress() writes the current checked/unchecked state as a JSON string to localStorage under the key mql5-onboarding-progress.
- updateProgressBar() counts how many items are checked, calculates the percentage, and sets the bar width and counter text.
- Directly below updateProgressBar, add the renderChecklist function that generates the checkbox items and wires up persistence:
// Generate checklist items and restore saved state
function renderChecklist() {
const container = document.getElementById("checklist");
const progress = getProgress();
container.innerHTML = CHECKLIST_ITEMS.map(function(item, index) {
const checked = progress[index] || false;
return '<div class="checklist-item ' + (checked ? 'checked' : '') + '"><input type="checkbox" id="check-' + index + '" ' + (checked ? 'checked' : '') + '><label for="check-' + index + '">' + item + '</label></div>';
}).join("");
// Attach change listener to each checkbox
container.querySelectorAll("input[type='checkbox']").forEach(function(checkbox, index) {
checkbox.addEventListener("change", function() {
const progress = getProgress();
progress[index] = checkbox.checked;
saveProgress(progress);
checkbox.closest(".checklist-item").classList.toggle("checked", checkbox.checked);
updateProgressBar(progress);
});
});
updateProgressBar(progress);
}
What does renderChecklist do?
- It reads saved progress from localStorage, then generates a checkbox for each of the 7 checklist items. If an item was previously checked, it restores that state.
- Each checkbox gets a change listener. When you check or uncheck an item, the listener saves the new state to localStorage, toggles the strikethrough styling, and updates the progress bar.
- The final updateProgressBar(progress) call syncs the bar on first render so it reflects any previously saved state.
The function exists but nothing calls it yet. You need to add it to the init() function.
- In the init() function, find this line:
renderResources("all");
- Add renderChecklist(); on the next line, so it looks like this:
renderResources("all");
renderChecklist();
- Save script.js and refresh your browser.
- Scroll down to the Setup Checklist section. You should see 7 checklist items with checkboxes, the progress bar at 0%, and the text "0 of 7 completed".
- Check three items. The progress bar should grow and the counter should update to "3 of 7 completed". Checked items get a strikethrough.
Before you test persistence, what do you think will happen when you refresh the page? Will your three checked items still be checked, or will they reset?
- Refresh the page.
Your three items are still checked. The progress bar and counter both reflect the saved state. That is localStorage at work.
- Open your browser's developer console by pressing Cmd+Option+J (macOS) or Ctrl+Shift+J (Windows).
- Type the following into the console and press Enter:
localStorage.getItem('mql5-onboarding-progress')
You should see a JSON string like {"0":true,"1":true,"2":true} showing which items are checked by their index.
Checklist items not appearing?
- Open the browser console and check for errors. A common issue is a typo in CHECKLIST_ITEMS or STORAGE_KEYS.progress that causes a reference error.
- Confirm that renderChecklist(); is inside the init() function, not outside it.
- Make sure CHECKLIST_ITEMS is placed before ROLE_LABELS in the file. JavaScript reads top to bottom, so the array must be defined before any function that references it.
Help me debug my checklist rendering.
Add a reset button
The Reset Progress button is visible on the page but clicking it does nothing. You need a click handler that clears the saved state and re-renders the checklist.
- In the init() function, add the following code after the renderChecklist(); call:
// Reset button clears saved progress after confirmation
document.getElementById("reset-btn").addEventListener("click", () => {
if (confirm("Reset all checklist progress? This cannot be undone.")) {
localStorage.removeItem(STORAGE_KEYS.progress);
renderChecklist();
}
});
What does this handler do?
- confirm() opens a browser dialog asking the user to confirm. This prevents accidental resets.
- If the user confirms, localStorage.removeItem() deletes the saved progress. Then renderChecklist() re-renders the items from scratch, which finds no saved state and shows everything unchecked.
- Save script.js.
You still have checked items from earlier. What do you think will happen when you click the Reset Progress button and confirm the dialog?
- Refresh the browser and click the Reset Progress button.
- Click OK in the confirmation dialog.
All checkboxes are cleared. The progress bar drops to 0% and the counter resets to "0 of 7 completed".
Nothing happens when you click Reset Progress?
- Check the console for errors. The most common cause is the reset handler being placed outside the init() function body.
- Verify the button's ID in your HTML is exactly reset-btn. The JavaScript uses getElementById("reset-btn"), so a mismatch means the listener never attaches.
Help me fix my reset button handler.
✔️ Awesome, I've got everything!
Great. Double check you have saved all three files: index.html, style.css, and script.js.
ⓧ I'd like to double check the full code
Scroll up through this step and confirm each code block is present in your files, in order:
- index.html: The #checklist-section contains a progress-bar-container div, a #progress-text paragraph, the #checklist div, and a #reset-btn button.
- style.css: Three CSS blocks added before the @media query: progress bar and footer styles, checklist layout and item styles, then checkbox and reset button styles.
- script.js: CHECKLIST_ITEMS array after RESOURCES. Then getProgress, saveProgress, updateProgressBar, and renderChecklist functions after setActiveRole. Inside init(): renderChecklist() call and the reset button click handler.
Your onboarding hub now tracks progress across sessions. Developers can check off setup items, close the tab, and come back to find everything exactly where they left it. Next up, you will polish the whole page and connect the pieces together.
Polish and Connect Everything
Your checklist saves progress and your role buttons filter resources, but the experience resets every time a developer closes the tab. They pick "EA Developer," browse a few cards, close the browser, and come back to a blank slate with "All Resources" selected again.
In this step, you will persist the role selection to localStorage so it survives page refreshes, add a personalized welcome banner, and test the complete hub end to end.
In this step, get ready to:
- Persist the role selection to localStorage.
- Add a personalized welcome banner.
- Test the complete hub flow.
Persist the role selection
Right now, setActiveRole highlights the clicked button and renders filtered cards. But the selected role lives only in memory. A page refresh wipes it out and defaults back to all resources.
- In script.js, scroll to the setActiveRole function.
- Find this line:
renderResources(role);
- Add a new line directly below it so the function now ends like this:
renderResources(role);
localStorage.setItem(STORAGE_KEYS.role, role);
Now the role saves itself. Next, the init function needs to read that saved value on page load instead of always defaulting to "all".
- Scroll down to the init function.
- Find this line:
renderResources("all");
- Replace that line with these two lines:
const savedRole = localStorage.getItem(STORAGE_KEYS.role) || "all";
setActiveRole(savedRole);
What does this change do?
On page load, localStorage.getItem(STORAGE_KEYS.role) checks if a role was saved from a previous visit. If nothing is saved, the || "all" fallback keeps the default behavior.
Calling setActiveRole(savedRole) then highlights the correct button and renders the filtered cards in one shot. It replaces the old renderResources("all") call because setActiveRole already calls renderResources internally.
- Save script.js.
- Refresh your browser.
- Click the EA Developer button.
- Refresh the page again.
The EA Developer button should still be highlighted and only EA-relevant resources should appear. The role survived the refresh.
Role resets to All Resources on refresh?
- Make sure the localStorage.setItem line is inside setActiveRole, not outside it.
- Confirm you replaced renderResources("all") in init with the two new lines. If both the old and new lines exist, the old one runs first and overrides the saved role.
- Open the browser console and type localStorage.getItem("mql5-onboarding-role"). If it returns null, the save step is not firing.
Help me debug why my role selection doesn't persist after refreshing the page.
Add a personalized welcome banner
Your HTML already has a #welcome-banner div sitting between the header and the nav, hidden by default. You need a function that reveals it with a personalized greeting when a specific role is selected.
- In script.js, add the updateWelcomeBanner function directly below the setActiveRole function by pasting this code:
function updateWelcomeBanner(role) {
// Grab the banner element
const banner = document.getElementById("welcome-banner");
if (role === "all") {
// Hide the banner when no specific role is selected
banner.classList.add("hidden");
banner.textContent = "";
} else {
// Show a personalized greeting using the role label
banner.classList.remove("hidden");
banner.textContent = `Welcome, ${ROLE_LABELS[role]}! Resources are filtered for your track.`;
}
}
What does this function do?
- When the selected role is "all", the banner gets the hidden class, which sets display: none in your CSS.
- For a specific role like "ea", it removes hidden and sets the text to ROLE_LABELS[role], which resolves to the friendly name like "EA Developer".
Now you need to call this function every time the role changes. setActiveRole is the right place.
- In the setActiveRole function, find this line:
localStorage.setItem(STORAGE_KEYS.role, role);
- Add updateWelcomeBanner(role); on the line directly below it, so the end of the function looks like this:
renderResources(role);
localStorage.setItem(STORAGE_KEYS.role, role);
updateWelcomeBanner(role);
}
- Save script.js and refresh your browser.
- Click Indicator Builder.
A blue-bordered banner should appear below the header saying "Welcome, Indicator Builder! Resources are filtered for your track."
- Click All Resources.
The banner should disappear. The hidden class kicks in and removes it from the layout entirely.
Banner not appearing?
- Check that your updateWelcomeBanner function is defined outside of setActiveRole, not nested inside it.
- Verify that the call updateWelcomeBanner(role) is inside setActiveRole after the localStorage.setItem line.
- Confirm your HTML has a div with id="welcome-banner" between the <header> and <nav> elements.
Help me figure out why my welcome banner isn't showing up.
✔️ Awesome, I've got everything!
Great. Make sure you have saved script.js.
ⓧ I'd like to double check the full code
The full script.js is longer than what fits here. Scroll through your file and confirm these sections are present in order:
- RESOURCES array (18 resource objects).
- CHECKLIST_ITEMS array (7 strings).
- ROLE_LABELS and STORAGE_KEYS objects.
- getCategoryBadgeClass function.
- renderResources(role) function using template literals.
- setActiveRole(role) with renderResources(role), localStorage.setItem(STORAGE_KEYS.role, role), and updateWelcomeBanner(role).
- updateWelcomeBanner(role) function that shows/hides the banner.
- getProgress, saveProgress, updateProgressBar, and renderChecklist functions.
- init() reading savedRole from localStorage and calling setActiveRole(savedRole) instead of renderResources("all").
- DOMContentLoaded listener calling init.
Test the full flow
Your HTML already includes a sticky navigation bar with anchor links to each section, and your CSS makes the layout responsive on narrow screens. Everything is wired up. Time for an end-to-end test.
- Click Checklist in the sticky nav bar at the top.
The page should scroll down to the Setup Checklist section. The nav bar stays fixed at the top as you scroll.
Now clear all saved state so you can test the hub like a brand-new developer would experience it.
- Open your browser's developer console by pressing Cmd+Option+J (macOS) or Ctrl+Shift+J (Windows).
- Clear localStorage by running this in the console:
localStorage.clear()
- Refresh the page.
The page should load with All Resources selected, no welcome banner visible, and all checklist items unchecked. This is the fresh state.
Before you test persistence, predict what will happen. If you click Indicator Builder, check off three checklist items, and then refresh the page, what do you expect to see?
- Click Indicator Builder.
- Check off the first three items in the Setup Checklist.
- Refresh the page.
The Indicator Builder button should still be highlighted. The welcome banner should say "Welcome, Indicator Builder! Resources are filtered for your track." The resource grid should show only indicator-relevant cards. Your three checked items should still be checked, with the progress bar showing 3 of 7 completed.
- Click Script Writer and confirm the resource cards update to show script-relevant resources.
- Click EA Developer and confirm the cards update again with EA-specific resources.
- Click All Resources and confirm all 18 cards appear and the welcome banner disappears.
What localStorage keys does the hub use?
Your hub stores two keys. mql5-onboarding-role holds the selected role string (like "ea" or "indicator").
mql5-onboarding-progress holds a JSON object mapping checklist indices to true or false. Together, these two keys give the hub full session persistence with zero backend.
Checklist persists but role does not?
- Open the console and type localStorage.getItem("mql5-onboarding-role"). If it returns null, the localStorage.setItem call in setActiveRole is missing or not running.
- Make sure you replaced the old renderResources("all") line in init. If it still exists, it overrides the restored role.
Why does my checklist persist but my role selection resets on refresh?
Secret mission
Add Resource Search
Your resource library has 18 links and counting. What happens when a developer needs one specific article but does not want to scroll through every card? Add a live search bar that filters resources by keyword in real time, layered on top of the existing role filter.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs or cloud resources to worry about. Decide whether to keep your project files, customize them for your group, or delete everything.
Resources you used:
- The mql5-onboarding-hub folder on your Desktop containing index.html, style.css, and script.js.
- localStorage data saved in your browser under the keys mql5-onboarding-progress and mql5-onboarding-role.
Keep everything running
No action needed. Choose this if you plan to share the hub with your group or keep customizing it.
- Your mql5-onboarding-hub folder stays on your Desktop ready to open or host.
- Your checklist progress and role selection remain saved in the browser's localStorage.
- To share the hub, drop the folder on any static host like GitHub Pages, Netlify, or Vercel. You can also share the three files directly.
Pause - I'll come back to this later
Keep your project files but clear browser data to free up localStorage. You can restore your checklist progress by simply reopening the page and checking items off again.
- Your project folder stays on your Desktop. No changes needed there.
- To clear your saved progress and role selection, open the browser console and run the commands below.
localStorage.removeItem('mql5-onboarding-progress');
localStorage.removeItem('mql5-onboarding-role');
- Add your group's specific links, internal docs, or Discord/Telegram invite links to the RESOURCES array in script.js whenever you are ready to pick it back up.
Delete - I don't want to use this again
Remove all project files and browser data to start completely fresh.
- Delete the project folder by running the command for your operating system.
macOS
rm -rf ~/Desktop/mql5-onboarding-hub
Windows
rmdir /s /q %USERPROFILE%\Desktop\mql5-onboarding-hub
- Clear your saved localStorage data by opening the browser console on any page and running these commands:
localStorage.removeItem('mql5-onboarding-progress');
localStorage.removeItem('mql5-onboarding-role');
That covers everything. No cloud resources, accounts, or installed tools need to be removed.
Nice Work!
Nice Work!
Nice work! You just built a fully functional MQL5 Developer Onboarding Hub that any new developer can open, pick their role, and instantly find the resources they need.
You've learned how to:
- Built a data-driven resource library of 18 curated MQL5 links and a role-based filtering system that dynamically renders the right documentation, Code Base entries, and community links for EA Developers, Indicator Builders, and Script Writers.
- Implemented localStorage persistence to save onboarding checklist progress, role selection, and a personalized welcome banner across browser sessions.
- Connected every section with sticky navigation, a progress bar, and a responsive dark-themed layout built with CSS Grid that is ready to share with your group.
- Completed an optional Secret Mission to push your skills further.
Ready to quiz yourself?