CristianVillafaneBlog
Rogue Spaceship with JavaScript by Cristian Villafane
NAVE DESTRUIDA
Naves Destruidas:
Daño Total Causado:
Tiempo de Supervivencia:
ROGUE SPACESHIP
Objetivo: ¡Sobrevive y destruye naves enemigas para conseguir chatarra!
Controles:
- Disparar: Mantén presionado el botón rojo de DISPARAR.
- Mejoras: Usa la Chatarra para comprar mejoras en el panel inferior.
- Pantalla Completa: Para una mejor experiencia, ¡usa el botón de pantalla completa!
¡Buena suerte, piloto!
MultiTools with JavaScript by Cristian Villafane
Multi Tools with Javascript
by Cristian Villafane
Utilities
Currency Converter with Javascript
Weather App with Javascript
Web Calculator with Javascript
English Dictionary with Javascript
Security
Password Generator with Javascript
Passphrase Generator with Javascript
Password Strength Checker
Idle Zombie Survivor with JavaScript by Cristian Villafane
Zombie Idle Survivors
COMBAT UPGRADES
BASE & RESOURCES
Passphrase Generator with JavaScript by Cristian Villafane
Passphrase Generator with JavaScript by Cristian Villafane
Evolving from passwords to passphrases: A guide to building a stronger, more memorable credential generator with JavaScript.
In the quest for better online security, we’re moving beyond complex, hard-to-remember passwords. Enter the passphrase: a sequence of random words that is both significantly harder for computers to crack and surprisingly easy for humans to remember. A passphrase like Correct-Horse-Battery-Staple is far more secure and memorable than P@ssw0rd1!. This project will guide you through building your own passphrase generator, leveling up your skills in HTML, CSS, and modern JavaScript logic, for free! only on JavaScript by Cristian Villafane
Phase 1: The Blueprint – Structuring the HTML
The foundation of our tool is the HTML structure. We need to provide users with clear, intuitive controls. Instead of character types, we’ll focus on the components of a passphrase: the number of words, separators, and optional additions like capitalization and numbers.
Crafting the User Controls
Our interface will have a main container for all the settings. The key elements are:
- Word Count: An
<input type="range">slider is perfect for allowing the user to choose the number of words in their passphrase. - Customization Options: We’ll use
<input type="checkbox">for enabling capitalization of each word and for adding a number to the end. - Separators: A group of
<button>elements will let the user select the character that joins the words, such as a hyphen, dot, or space.
<!-- Word Count Slider -->
<div class="option">
<label for="word-count-slider">Words: <span id="word-count-value">4</span></label>
<input type="range" id="word-count-slider" min="3" max="8" value="4">
</div>
<!-- Separator Control -->
<div class="option">
<span>Separator</span>
<div class="separator-buttons">
<button class="separator-btn active" data-separator="-">-</button>
<button class="separator-btn" data-separator=".">.</button>
</div>
</div>
Phase 2: The Core Logic – Generating Passphrases with JavaScript
This is where our application comes to life. The JavaScript logic will handle fetching a list of words, randomly selecting from it based on user settings, and assembling the final passphrase.
Building the Generation Engine
The process starts with a word list. For this tutorial, we’ll embed a curated array of words directly in our script. Our main function, generatePassphrase(), will read the user’s choices: number of words, capitalization, number inclusion, and the selected separator. It will then loop, picking a random word from our list for each iteration, and apply any requested transformations.
The Selection Loop
With our word list and user options defined, a simple for loop is used to build an array of random words. For each word, we check if capitalization is enabled. After the loop, these words are joined together using the chosen separator, and a final random number might be appended. This method produces a highly random and customizable result.
const words = [];
for (let i = 0; i < wordCount; i++) {
let randomWord = wordList[Math.floor(Math.random() * wordList.length)];
if (useCapitals) {
randomWord = randomWord.charAt(0).toUpperCase() + randomWord.slice(1);
}
words.push(randomWord);
}
let passphrase = words.join(separator);
Phase 3: Making It Interactive
A great tool responds instantly. We’ll attach event listeners to all our controls. Any change—sliding the word count, ticking a box, or choosing a new separator—will immediately trigger the generatePassphrase() function, providing a fluid and seamless user experience. The same principles of asynchronous font loading and efficient DOM manipulation apply here to keep the tool fast and responsive.
Passphrase Generator in Action
Here is the final, functional passphrase generator. Experiment with the settings to see how different options create unique and strong passphrases. This is the result of combining our structured HTML with dynamic JavaScript.
The Complete Generator Code
For easy implementation, here is the complete, self-contained code for the passphrase generator. You can copy and paste this directly into an HTML file to see it in action.
Building a passphrase generator is a fantastic way to engage with modern security concepts while honing your JavaScript skills. I hope this Javascript by Cristian Villafane tutorial has been insightful. Use this project as a launchpad to explore further enhancements. The journey of learning is limitless! 🔐
Password Generator with JavaScript by Cristian Villafane
Password Generator: A Javascript Tutorial by Cristian Villafane
A practical guide to creating an essential security tool, mastering random generation logic and DOM manipulation.
In today’s digital world, password security is more crucial than ever. Creating strong, unique passwords for each service is a cornerstone of good security practice. And what better way to understand how it’s done than by building your own generator? This project is an excellent exercise to solidify your skills in HTML, CSS, and most importantly, JavaScript. In this guide on Javascript by Cristian Villafane, we will walk you through creating a functional and aesthetically pleasing tool, step by step.
Phase 1: The Structure – HTML for the Options
Every web application needs a skeleton. For our generator, the HTML structure will define all the options the user can control. We’ll need a place to display the generated password, a slider for length, and checkboxes for character types.
Designing the User Interface
We start with a main container. Inside, the most important element is the password display area. A <div> or <span> is perfect for this. Next to it, a “copy” button is essential for usability. Then, we create a section for the options:
- Password Length: An
<input type="range">is the most intuitive choice. We’ll give it anid, andminandmaxattributes to define the length range. - Character Types: We’ll use
<input type="checkbox">to allow the user to include or exclude uppercase letters, numbers, and special symbols. Each checkbox will be associated with a<label>for better accessibility.
<!-- Length Option -->
<div class="option">
<label>Length: <span id="length-value">16</span></label>
<input type="range" id="length" min="8" max="32" value="16">
</div>
<!-- Numbers Option -->
<div class="option">
<label>
Include Numbers
<input type="checkbox" id="numbers" checked>
</label>
</div>
Phase 2: The Logic – Real-Time Generation with JavaScript
This is where the magic happens. Our JavaScript script will be responsible for taking the user’s options, building a set of allowed characters, and then randomly selecting from that set to form the password in real-time.
Gathering Inputs and Defining Character Sets
First, we define the possible character sets as strings: lowercase letters (which we’ll include by default), uppercase, numbers, and symbols. Then, we create a main function, let’s say generatePassword(). Inside this function, the first thing is to get the current values from our HTML elements: the length from the slider and the checked state of each checkbox.
Based on the checked boxes, we build a master string called characterPool that contains all allowed characters. If the “Include Numbers” box is checked, we add the numbers string to our characterPool. We repeat this for uppercase and symbols. This dynamic approach is key in Javascript by Cristian Villafane for creating flexible tools.
The Generation Loop
With our characterPool ready and the desired length known, the next step is a for loop that will run as many times as the password length. In each iteration, we generate a random index corresponding to a position within the characterPool string. We use Math.random() to get a decimal between 0 and 1, multiply it by the pool’s length, and use Math.floor() to get an integer index. The character at that position is then appended to our password variable.
let password = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characterPool.length);
password += characterPool[randomIndex];
}
An important refinement is to ensure the final password contains at least one character of each selected type to guarantee its strength. This can be achieved by pre-loading the password with one random character from each required set and then filling the rest.
Phase 3: Interactivity and Performance Optimization
A functional tool is good, but a fast and intuitive one is better. We’ll connect our logic to user events and ensure the experience is smooth by addressing common performance issues like render-blocking requests and layout shifts.
Event Listeners and Asynchronous Loading
We add event listeners to the slider and checkboxes. Whenever their value changes, our password generation logic is triggered, creating a real-time experience. A new randomize button also allows for generating a new password with the same settings. For performance, external resources like fonts are loaded asynchronously. This prevents them from blocking the initial page render, leading to a faster perceived load time for the user.
Password Generator in Action
Below, you can try out the optimized password generator. Play with the options and see the functionality for yourself. This is the final result of applying the concepts from this tutorial.
The Complete Generator Code
Here is the self-contained, performance-optimized code for the password generator component. You can copy and paste this into your own project.
<!-- HTML Structure -->
<div class="generator-container">
<div class="generator-header">
<span id="password-display" class="password-display">Generate your password</span>
<div class="header-buttons">
<button id="randomize-btn" class="icon-btn" title="Randomize password">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>
</button>
<button id="toggle-visibility-btn" class="icon-btn" title="Toggle visibility">
<svg id="eye-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
<svg id="eye-slash-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-4 .69l2.22 2.22c.55-.24 1.15-.38 1.79-.38zm-2.06 11.94L12 17c2.76 0 5-2.24 5-5 0-.64-.13-1.25-.36-1.82l-2.93 2.93c-.01.01 0 .01 0 0zm2.06-9.94c-1.66 0-3 1.34-3 3 0 .65.21 1.25.56 1.76l1.43 1.43c.51-.35 1.11-.56 1.76-.56.18 0 .36.02.53.05l1.4-1.4c-.58-.23-1.2-.35-1.85-.35zM2.39 4.22 1.11 5.5l2.07 2.07C2.12 8.79.79 10.38 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l2.11 2.11 1.27-1.27L2.39 4.22z"/></svg>
</button>
<button id="copy-btn" class="icon-btn" title="Copy to clipboard">
<svg id="copy-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
<svg id="check-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
</button>
</div>
</div>
<div class="generator-options">
<div class="option">
<label for="length-slider">Length: <span id="length-value" class="length-display">16</span></label>
<input type="range" id="length-slider" min="8" max="32" value="16">
</div>
<div class="option">
<label>
<span>Include Uppercase</span>
<input type="checkbox" id="uppercase-check" checked>
</label>
</div>
<div class="option">
<label>
<span>Include Numbers</span>
<input type="checkbox" id="numbers-check" checked>
</label>
</div>
<div class="option">
<label>
<span>Include Symbols</span>
<input type="checkbox" id="symbols-check" checked>
</label>
</div>
</div>
<div class="strength-indicator">
<div class="strength-item">
<span class="strength-label">Password Strength:</span>
<span id="strength-text" class="strength-value"></span>
</div>
<div class="strength-item">
<span class="strength-label">Time to crack:</span>
<span id="crack-time" class="strength-value"></span>
</div>
</div>
</div>
<!-- JavaScript Logic -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const passwordDisplay = document.getElementById('password-display');
const lengthSlider = document.getElementById('length-slider');
const lengthValue = document.getElementById('length-value');
const uppercaseCheck = document.getElementById('uppercase-check');
const numbersCheck = document.getElementById('numbers-check');
const symbolsCheck = document.getElementById('symbols-check');
const randomizeBtn = document.getElementById('randomize-btn');
const copyBtn = document.getElementById('copy-btn');
const copyIcon = document.getElementById('copy-icon');
const checkIcon = document.getElementById('check-icon');
const toggleBtn = document.getElementById('toggle-visibility-btn');
const eyeIcon = document.getElementById('eye-icon');
const eyeSlashIcon = document.getElementById('eye-slash-icon');
const strengthText = document.getElementById('strength-text');
const crackTimeText = document.getElementById('crack-time');
let actualPassword = '';
let isPasswordVisible = true;
const lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz';
const upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const numberChars = '0123456789';
const symbolChars = '!@#$%^&*()_+~`|}{[]:;?><,./-=';
const timeToCrackData = {
8: ['1 hour', '2 weeks', '2 months', '5 months'],
9: ['2 days', '2 years', '10 years', '31 years'],
10: ['1 month', '112 years', '651 years', '2k years'],
11: ['3 years', '5k years', '40k years', '153k years'],
12: ['74 years', '303k years', '2m years', '10m years'],
13: ['1k years', '15m years', '155m years', '751m years'],
14: ['50k years', '819m years', '9bn years', '52bn years'],
15: ['1m years', '42bn years', '596bn years', '3tn years'],
16: ['33m years', '2tn years', '36tn years', '257tn years'],
17: ['879m years', '115tn years', '2qd years', '18qd years'],
18: ['22bn years', '5qd years', '142qd years', '1qn years']
};
const getStrength = (length, hasUpper, hasNums, hasSymbols) => {
let complexityIndex = 0;
if (hasUpper && hasNums && hasSymbols) complexityIndex = 3;
else if (hasUpper && hasNums) complexityIndex = 2;
else if (hasUpper) complexityIndex = 1;
if (length < 8) return { time: 'Instantly', strength: 'Very Weak', className: 'strength-very-weak' };
const time = timeToCrackData[length] ? timeToCrackData[length][complexityIndex] : 'A very, very long time';
let strength = 'Medium';
let className = 'strength-medium';
if (time.includes('qd') || time.includes('tn') || time.includes('qn') || time.includes('bn')) {
strength = 'Very Strong';
className = 'strength-very-strong';
} else if (time.includes('m years') || time.includes('k years')) {
strength = 'Strong';
className = 'strength-strong';
} else if (time.includes('years')) {
strength = 'Medium';
className = 'strength-medium';
} else {
strength = 'Weak';
className = 'strength-weak';
}
if (length > 18) {
strength = 'Very Strong';
className = 'strength-very-strong';
}
return { time, strength, className };
};
const generatePassword = () => {
const length = parseInt(lengthSlider.value);
lengthValue.textContent = length; // Update length display
const hasUpper = uppercaseCheck.checked;
const hasNums = numbersCheck.checked;
const hasSymbols = symbolsCheck.checked;
let characterPool = lowerCaseChars;
let password = '';
const guaranteedChars = [];
if (hasUpper) {
characterPool += upperCaseChars;
guaranteedChars.push(upperCaseChars[Math.floor(Math.random() * upperCaseChars.length)]);
}
if (hasNums) {
characterPool += numberChars;
guaranteedChars.push(numberChars[Math.floor(Math.random() * numberChars.length)]);
}
if (hasSymbols) {
characterPool += symbolChars;
guaranteedChars.push(symbolChars[Math.floor(Math.random() * symbolChars.length)]);
}
guaranteedChars.push(lowerCaseChars[Math.floor(Math.random() * lowerCaseChars.length)]);
const remainingLength = length > guaranteedChars.length ? length - guaranteedChars.length : 0;
for (let i = 0; i < remainingLength; i++) {
const randomIndex = Math.floor(Math.random() * characterPool.length);
password += characterPool[randomIndex];
}
actualPassword = (password + guaranteedChars.join('')).split('').sort(() => 0.5 - Math.random()).join('').slice(0, length);
updatePasswordDisplay();
const strengthInfo = getStrength(length, hasUpper, hasNums, hasSymbols);
strengthText.textContent = strengthInfo.strength;
crackTimeText.textContent = strengthInfo.time;
strengthText.className = 'strength-value ' + strengthInfo.className;
crackTimeText.className = 'strength-value ' + strengthInfo.className;
};
const updatePasswordDisplay = () => {
if(isPasswordVisible) {
passwordDisplay.textContent = actualPassword;
eyeIcon.style.display = 'block';
eyeSlashIcon.style.display = 'none';
} else {
passwordDisplay.textContent = '•'.repeat(actualPassword.length);
eyeIcon.style.display = 'none';
eyeSlashIcon.style.display = 'block';
}
};
const toggleVisibility = () => {
isPasswordVisible = !isPasswordVisible;
updatePasswordDisplay();
};
const copyPassword = () => {
if (actualPassword) {
const textarea = document.createElement('textarea');
textarea.value = actualPassword;
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
textarea.style.top = '0';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
copyIcon.style.display = 'none';
checkIcon.style.display = 'inline-block';
setTimeout(() => {
copyIcon.style.display = 'inline-block';
checkIcon.style.display = 'none';
}, 2000);
} catch (err) {
console.error('Failed to copy password: ', err);
}
document.body.removeChild(textarea);
}
};
// Event Listeners for real-time generation
lengthSlider.addEventListener('input', generatePassword);
uppercaseCheck.addEventListener('change', generatePassword);
numbersCheck.addEventListener('change', generatePassword);
symbolsCheck.addEventListener('change', generatePassword);
randomizeBtn.addEventListener('click', generatePassword);
copyBtn.addEventListener('click', copyPassword);
toggleBtn.addEventListener('click', toggleVisibility);
// Initial password generation on load
generatePassword();
});
</script>
I hope you found this tutorial on Javascript by Cristian Villafane useful. Building a password generator not only reinforces your knowledge of JavaScript, but also introduces you to fundamental security concepts. Use this project as a foundation to explore and add new features. The only limit is your curiosity! 🔐
Idle Space Miner with Javascript by Cristian Villafane
Juego de Minería Espacial
¡Haz clic en el planeta para minar y comprar mejoras!
Currency Converter with JavaScript by Cristian Villafane
Currency Converter: A Javascript Tutorial by Cristian Villafane
A practical guide to building a financial tool by fetching real-time data from the FxRatesAPI.
In our interconnected world, knowing the real-time value of different currencies is essential for travel, business, and investment. A currency converter is a perfect project to practice asynchronous JavaScript and API interaction. This guide on Javascript by Cristian Villafane will walk you through creating a sleek, functional currency converter using the free and simple FxRatesAPI. We’ll focus on fetching data, updating the DOM dynamically, and providing a clean user experience.
Phase 1: The Structure – HTML for the Converter
The foundation of our application is the HTML structure. We need input fields for the user to enter the amount and select the currencies they wish to convert between. A clear and intuitive layout is key.
Designing the User Interface
We’ll use a main container for our tool. Inside, we’ll have a prominent display area for the result. The core of the interface will be the options section:
- Amount: An
<input type="number">allows the user to enter the value they want to convert. - From & To Currencies: Two
<select>dropdowns will hold the list of available currencies. We’ll populate these dynamically using JavaScript. - Swap Button: A simple button to quickly swap the “From” and “To” currencies is a great usability feature.
<!-- Amount Input -->
<div class="option">
<label for="amount-input">Amount</label>
<input type="number" id="amount-input" value="1" min="0">
</div>
<!-- "From" Currency Dropdown -->
<div class="option">
<label for="from-currency">From</label>
<select id="from-currency"></select>
</div>
Phase 2: The Logic – Real-Time Conversion with JavaScript
This is where our application comes to life. The JavaScript will fetch the list of currencies, populate our dropdowns, and perform the conversion calculation whenever the user changes an input.
Fetching Data with the `fetch` API
On page load, we make a single call to the FxRatesAPI endpoint https://api.fxratesapi.com/latest. This returns all available exchange rates relative to a base currency (USD). We store these rates and use their keys to populate our currency selection dropdowns. This single-fetch approach is highly efficient.
Client-Side Conversion
Once we have all the rates, we don’t need to call the API again for every conversion. The logic is handled directly in the browser. To convert from currency A to currency B, we use the stored rates and a simple formula: (amount / rate of A) * rate of B. This makes the converter feel instantaneous. This approach is central in Javascript by Cristian Villafane for building fast, data-driven applications.
// Store rates from initial fetch
let exchangeRates = {};
function convert() {
// No new API call needed here
const amount = amountInput.value;
const fromRate = exchangeRates[fromSelect.value];
const toRate = exchangeRates[toSelect.value];
const convertedAmount = (amount / fromRate) * toRate;
resultDisplay.textContent = `${convertedAmount.toFixed(2)} ${toSelect.value}`;
}
Currency Converter in Action
Below, you can try out the live currency converter. Enter an amount, select your currencies, and see the real-time result. This is the final product of applying the concepts from this tutorial.
The Complete Converter Code
Here is the self-contained, performance-optimized code for the currency converter component. You can copy and paste this into your own project.
<!-- HTML Structure (Unchanged) -->
<!-- NEW JAVASCRIPT LOGIC FOR FxRatesAPI -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const API_URL = 'https://api.fxratesapi.com/latest';
const amountInput = document.getElementById('amount-input');
const fromCurrencySelect = document.getElementById('from-currency');
const toCurrencySelect = document.getElementById('to-currency');
const resultDisplay = document.getElementById('result-display');
const swapBtn = document.getElementById('swap-btn');
let exchangeRates = {};
let baseCurrency = 'USD';
function convertCurrency() {
const amount = parseFloat(amountInput.value);
const fromCurrency = fromCurrencySelect.value;
const toCurrency = toCurrencySelect.value;
if (Object.keys(exchangeRates).length === 0) {
resultDisplay.textContent = 'Rates not loaded.';
return;
}
if (isNaN(amount) || amount < 0) {
resultDisplay.textContent = 'Enter a valid amount';
return;
}
const fromRate = exchangeRates[fromCurrency];
const toRate = exchangeRates[toCurrency];
// The formula is (amount / fromRate) * toRate because all rates are relative to the base currency.
const convertedAmount = (amount / fromRate) * toRate;
resultDisplay.textContent = `${convertedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ${toCurrency}`;
}
// --- INITIALIZATION ---
fetch(API_URL)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (!data.success) {
throw new Error('API did not return a successful response.');
}
// Store the rates and base currency
exchangeRates = data.rates;
baseCurrency = data.base;
// Add the base currency to the rates object since it's not included by default
exchangeRates[baseCurrency] = 1;
// Populate the dropdowns
const fragment = document.createDocumentFragment();
const sortedCurrencies = Object.keys(exchangeRates).sort();
sortedCurrencies.forEach(code => {
const option = document.createElement('option');
option.value = code;
option.textContent = code;
fragment.appendChild(option);
});
fromCurrencySelect.appendChild(fragment.cloneNode(true));
toCurrencySelect.appendChild(fragment);
// Set default values
fromCurrencySelect.value = 'USD';
toCurrencySelect.value = 'EUR';
// Perform the first conversion
convertCurrency();
// Add event listeners now that everything is ready
amountInput.addEventListener('input', convertCurrency);
fromCurrencySelect.addEventListener('change', convertCurrency);
toCurrencySelect.addEventListener('change', convertCurrency);
swapBtn.addEventListener('click', () => {
const temp = fromCurrencySelect.value;
fromCurrencySelect.value = toCurrencySelect.value;
toCurrencySelect.value = temp;
convertCurrency();
});
})
.catch(error => {
console.error('Initialization Error:', error);
resultDisplay.textContent = 'Could not load rates.';
});
});
</script>
I hope you found this tutorial on Javascript by Cristian Villafane useful. Building a currency converter is an excellent way to practice working with external APIs and handling asynchronous operations in JavaScript. Use this project as a foundation to explore and add new features, such as historical data charts or a portfolio tracker. The only limit is your curiosity! 💹
Web Calculator with JavaScript by Cristian Villafane
Web Calculator: A Tutorial on Javascript by Cristian Villafane
A complete, step-by-step guide to mastering DOM manipulation and event logic in your first major interactive Javascript project.
Every developer remembers that first project that made them feel like they were “making magic” in the browser. For many, that project was a simple calculator. Far from being just an academic exercise, building a calculator from scratch is a rite of passage that solidifies our understanding of the pillars of web development: structure with HTML, style with CSS, and most importantly, interactivity and logic. In this tutorial on Javascript by Cristian Villafane, we will get ready to put your skills into practice and create something tangible.
Phase 1: The Foundation – HTML & Javascript User Input
Before we can even think about performing calculations, we need to build the user interface and ensure our code can effectively listen for and interpret user interactions. We’ll start with the Document Object Model (DOM) manipulation, which is central to how Javascript interacts with a web page.
Crafting the Interface with HTML
The first step is to create the calculator’s body using HTML. This typically involves a container div, a div to act as our display screen (often an <input> element with the readonly attribute is a good choice here to prevent keyboard input), and a series of <button> elements for the numbers (0-9), the decimal point, the core arithmetic operators (+, -, *, /), a ‘clear’ button (C or AC), and the all-important ‘equals’ button (=). Each of these buttons should be given appropriate id or class attributes, and for the numbers and operators, data-* attributes can be incredibly useful for storing their respective values.
<!-- Example button for the number 7 -->
<button data-number="7">7</button>
<!-- Example button for the addition operator -->
<button data-operator="+">+</button>
This semantic approach makes our Javascript code cleaner and more maintainable.
Listening for Clicks with JavaScript
With the HTML structure in place, we pivot to Javascript. Our primary goal is to attach event listeners to our buttons. We’ll use the DOMContentLoaded event to ensure our script only runs after the entire HTML document has been loaded and parsed. Inside this event listener, we’ll select all our buttons. A common and efficient way to do this is using document.querySelectorAll() to grab all elements with a specific class, say .calculator-button. We then iterate over this collection of buttons, often with a forEach loop, and attach a click event listener to each one.
Inside the callback function for this event listener, we have access to the event object. The event.target property is our golden ticket, as it gives us a reference to the specific button that was clicked. From this target, we can then access the dataset property to retrieve the values we stored in our data-* attributes. The logic then becomes a series of conditional statements. This is a core concept in web development, and this guide to Javascript by Cristian Villafane aims to make it clear.
Phase 2: State Management & Calculation Logic in Javascript
So, we have user input flowing in. The critical next step is to create a state machine, albeit a simple one. In Javascript, this “state” is just a set of variables that hold the crucial pieces of information at any given moment. We need to know the first number in our equation (firstOperand), the mathematical operator the user has selected, and the second number (secondOperand). We also need a way to know if the display should be cleared before the next number is entered, for instance, right after an operator has been pressed. Let’s call this a shouldResetDisplay flag. These variables are the memory of our calculator.
Handling Operations and the Equals Button
When a user clicks an operator button (+, -, *, /), our logic needs to kick in. First, we check if a firstOperand and an operator have already been set. If they have, it means the user is chaining operations (e.g., 5 + 10 -). In this scenario, we should perform the pending calculation first before storing the new operator. If not, we capture the current value shown on the display and store it in our firstOperand variable. It’s important to convert this value from a string, which is what we get from the display, into a number using parseFloat() to handle decimals.
The ‘equals’ button is the grand finale of our logic loop. When clicked, it triggers the final calculation. We’ll grab the current number on the display as our secondOperand. Now, with firstOperand, operator, and secondOperand all in hand, we pass them to a dedicated calculate() function. A switch statement is a perfect tool here. It will check the value of the operator variable and execute the corresponding arithmetic. A crucial edge case to handle here is division by zero. If the operator is ‘/’ and secondOperand is 0, we should return an error message like “Error” instead of Infinity, which is Javascript‘s default. After the calculation, we must reset our state. The result becomes the new firstOperand for potential subsequent calculations.
The Reset Switch
The ‘Clear’ button (‘C’ or ‘AC’) has a simple but vital job: it’s our reset switch. A click on ‘clear’ should call a resetCalculator() function. This function will restore all our state variables to their initial values and clear the content of our display. This brings the calculator back to its starting state, ready for a fresh calculation.
Phase 3: Polishing the Experience (UX/UI)
So far, our calculator works, but it’s rigid. To elevate this project, our first order of business is implementing keyboard support. Users instinctively expect to be able to use their physical keyboard for an application like this.
- Keyboard Support: We can achieve this by adding a single
keydownevent listener to the globalwindowobject. Inside this listener’s callback function, theevent.keyproperty tells us exactly which key was pressed. We can then map theevent.keyvalue to our calculator’s functions, for example, mapping the ‘Enter’ key to the equals button. - Input Validation: A simple calculator can easily break if we don’t handle edge cases. What happens if a user tries to type
5..5? It’s invalid input. Inside our number-handling logic, before appending a decimal point, we must first check if the current display string already includes(‘.’). If it does, we simply return and do nothing. - Visual Feedback: Visual feedback is another cornerstone of good UX. When a button is clicked, we can add a CSS class, like
.active. Then, usingsetTimeout(), we remove that class after a short delay. This creates a quick flash that gives the user an unambiguous confirmation that their input was registered. - Backspace Function: Forcing the user to hit ‘Clear’ for a single typo is poor design. We can add a ‘DEL’ button. Its logic is straightforward: when clicked, it should take the current string on the display and use the
slice(0, -1)method. This returns a new string with the last character removed.
Final Code: Putting It All Together
Here is the complete, unified code from this tutorial on Javascript by Cristian Villafane for you to copy, paste, and watch the magic happen in your own browser.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calculator</title>
<style>
body { display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f0f0; }
.calculator { border-radius: 10px; box-shadow: 0 0 25px rgba(0,0,0,0.15); overflow: hidden; max-width: 400px; }
.calculator-display { background-color: #222; color: #fff; font-size: 2.5rem; padding: 20px; text-align: right; }
.calculator-keys { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 1px; }
button { font-size: 1.5rem; border: none; padding: 25px; background: #e0e0e0; cursor: pointer; transition: background .2s; }
button:hover { background: #d5d5d5; }
button:active { box-shadow: inset 0px 0px 5px #c1c1c1; }
.key--operator { background: #f5a623; color: white; }
.key--operator:hover { background: #e09414; }
.key--equal { background: #4a90e2; color: white; grid-column: -2; grid-row: 2 / span 4; }
.key--equal:hover { background: #3a80d2; }
</style>
</head>
<body>
<div class="calculator">
<div class="calculator-display">0</div>
<div class="calculator-keys">
<button class="key--operator" data-action="add">+</button>
<button class="key--operator" data-action="subtract">-</button>
<button class="key--operator" data-action="multiply">×</button>
<button class="key--operator" data-action="divide">÷</button>
<button>7</button>
<button>8</button>
<button>9</button>
<button>4</button>
<button>5</button>
<button>6</button>
<button>1</button>
<button>2</button>
<button>3</button>
<button>0</button>
<button data-action="decimal">.</button>
<button data-action="clear">AC</button>
<button class="key--equal" data-action="calculate">=</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const calculator = document.querySelector('.calculator');
const keys = calculator.querySelector('.calculator-keys');
const display = calculator.querySelector('.calculator-display');
keys.addEventListener('click', e => {
if (!e.target.matches('button')) return;
const key = e.target;
const action = key.dataset.action;
const keyContent = key.textContent;
const displayedNum = display.textContent;
const previousKeyType = calculator.dataset.previousKeyType;
if (!action) {
if (displayedNum === '0' || previousKeyType === 'operator' || previousKeyType === 'calculate') {
display.textContent = keyContent;
} else {
display.textContent = displayedNum + keyContent;
}
calculator.dataset.previousKeyType = 'number';
}
if (action === 'decimal') {
if (!displayedNum.includes('.')) {
display.textContent = displayedNum + '.';
} else if (previousKeyType === 'operator' || previousKeyType === 'calculate') {
display.textContent = '0.';
}
calculator.dataset.previousKeyType = 'decimal';
}
if (action === 'add' || action === 'subtract' || action === 'multiply' || action === 'divide') {
const firstValue = calculator.dataset.firstValue;
const operator = calculator.dataset.operator;
const secondValue = displayedNum;
if (firstValue && operator && previousKeyType !== 'operator' && previousKeyType !== 'calculate') {
const calcValue = calculate(firstValue, operator, secondValue);
display.textContent = calcValue;
calculator.dataset.firstValue = calcValue;
} else {
calculator.dataset.firstValue = displayedNum;
}
key.classList.add('is-depressed');
calculator.dataset.previousKeyType = 'operator';
calculator.dataset.operator = action;
}
Array.from(key.parentNode.children).forEach(k => k.classList.remove('is-depressed'));
if (action === 'clear') {
display.textContent = '0';
delete calculator.dataset.firstValue;
delete calculator.dataset.operator;
calculator.dataset.previousKeyType = 'clear';
}
if (action === 'calculate') {
let firstValue = calculator.dataset.firstValue;
const operator = calculator.dataset.operator;
let secondValue = displayedNum;
if (firstValue) {
if (previousKeyType === 'calculate') {
firstValue = displayedNum;
secondValue = calculator.dataset.modValue;
}
display.textContent = calculate(firstValue, operator, secondValue);
}
calculator.dataset.modValue = secondValue;
calculator.dataset.previousKeyType = 'calculate';
}
});
const calculate = (n1, operator, n2) => {
const firstNum = parseFloat(n1);
const secondNum = parseFloat(n2);
if (operator === 'add') return firstNum + secondNum;
if (operator === 'subtract') return firstNum - secondNum;
if (operator === 'multiply') return firstNum * secondNum;
if (operator === 'divide') return firstNum / secondNum;
};
});
</script>
</body>
</html>
I hope you found this tutorial on Javascript by Cristian Villafane useful. The concepts you’ve applied here, DOM manipulation, event handling, and state management, are the bedrock of modern web development. Use this project as a stepping stone. Keep experimenting, keep building, and never stop learning. The web is your canvas. 🚀