Current directory: /home/klas4s23/domains/585455.klas4s23.mid-ica.nl/public_html/Gastenboek/uploads
/**
* English Vocabulary Learning Application
* JavaScript + AJAX Implementation
*/
// Application State
const App = {
sessionId: null,
currentUser: null,
words: [],
currentIndex: 0,
correctCount: 0,
incorrectCount: 0,
categories: [],
translationDirection: 'en-nl', // 'en-nl' or 'nl-en'
userLists: [],
init() {
this.checkUserAuth();
this.sessionId = this.generateSessionId();
this.setupEventListeners();
this.loadCategories();
this.loadStats();
},
checkUserAuth() {
const userStr = localStorage.getItem('user');
if (userStr) {
try {
this.currentUser = JSON.parse(userStr);
this.showUserMenu();
} catch (e) {
localStorage.removeItem('user');
}
}
},
showUserMenu() {
if (this.currentUser) {
document.getElementById('username-display').textContent = '👤 ' + this.currentUser.username;
document.getElementById('user-menu').classList.remove('hidden');
document.getElementById('login-link').classList.add('hidden');
// Show word lists link for logged in users
const wordListsLink = document.getElementById('word-lists-link');
if (wordListsLink) {
wordListsLink.classList.remove('hidden');
}
// Enable custom list selection
const sourceSelect = document.getElementById('source-select');
const listOption = sourceSelect.querySelector('option[value="list"]');
if (listOption) {
listOption.disabled = false;
listOption.textContent = 'Custom Lists';
}
// Load user's lists
this.loadUserLists();
}
},
async logout() {
try {
await fetch('php/logout.php', { method: 'POST' });
} catch (error) {
console.error('Logout error:', error);
}
localStorage.removeItem('user');
localStorage.removeItem('session_id');
window.location.href = 'login.html';
},
generateSessionId() {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
},
setupEventListeners() {
// Source selection change
document.getElementById('source-select').addEventListener('change', (e) => {
const listSelectGroup = document.getElementById('list-select-group');
const categorySelectGroup = document.getElementById('category-select-group');
if (e.target.value === 'list') {
listSelectGroup.style.display = 'block';
categorySelectGroup.style.display = 'none';
} else {
listSelectGroup.style.display = 'none';
categorySelectGroup.style.display = 'block';
}
});
// Start practice button
document.getElementById('start-practice').addEventListener('click', () => {
this.startPractice();
});
// Check answer button
document.getElementById('check-answer').addEventListener('click', () => {
this.checkAnswer();
});
// Skip word button
document.getElementById('skip-word').addEventListener('click', () => {
this.nextWord();
});
// Restart practice button
document.getElementById('restart-practice').addEventListener('click', () => {
this.restartPractice();
});
// Logout button
const logoutBtn = document.getElementById('logout-btn');
if (logoutBtn) {
logoutBtn.addEventListener('click', () => {
this.logout();
});
}
// Enter key to check answer
document.getElementById('answer-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.checkAnswer();
}
});
// Smooth scrolling
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
}
});
});
},
async loadCategories() {
try {
const response = await fetch('php/get_categories.php');
const data = await response.json();
if (data.success) {
this.categories = data.data;
this.populateCategorySelect();
}
} catch (error) {
console.error('Error loading categories:', error);
this.showNotification('Failed to load categories', 'error');
}
},
populateCategorySelect() {
const select = document.getElementById('category-select');
select.innerHTML = '<option value="">All Categories</option>';
this.categories.forEach(category => {
const option = document.createElement('option');
option.value = category.category_id;
option.textContent = `${category.category_name} (${category.word_count} words)`;
select.appendChild(option);
});
},
async loadUserLists() {
try {
const response = await fetch('php/get_lists.php');
const data = await response.json();
if (data.success) {
// Filter to get only user's own lists
this.userLists = data.lists.filter(list => list.is_owner == 1);
this.populateListSelect();
}
} catch (error) {
console.error('Error loading user lists:', error);
}
},
populateListSelect() {
const select = document.getElementById('list-select');
select.innerHTML = '<option value="">Choose a list...</option>';
this.userLists.forEach(list => {
const option = document.createElement('option');
option.value = list.list_id;
option.textContent = `${list.list_name} (${list.word_count} words)`;
select.appendChild(option);
});
},
async startPractice() {
// Get translation direction
const directionRadio = document.querySelector('input[name="direction"]:checked');
this.translationDirection = directionRadio ? directionRadio.value : 'en-nl';
const source = document.getElementById('source-select').value;
const difficulty = document.getElementById('difficulty-select').value;
const limit = document.getElementById('word-count').value;
// Build query string
let query = `limit=${limit}`;
query += `&source=${source}`;
if (difficulty) query += `&difficulty=${difficulty}`;
if (source === 'list') {
const listId = document.getElementById('list-select').value;
if (!listId) {
this.showNotification('Please select a word list', 'warning');
return;
}
query += `&list_id=${listId}`;
} else {
const category = document.getElementById('category-select').value;
if (category) query += `&category=${category}`;
}
try {
const response = await fetch(`php/get_practice_words.php?${query}`);
const data = await response.json();
if (data.success && data.words && data.words.length > 0) {
this.words = data.words;
this.currentIndex = 0;
this.correctCount = 0;
this.incorrectCount = 0;
// Show practice area, hide results
document.querySelector('.settings-panel').classList.add('hidden');
document.getElementById('practice-area').classList.remove('hidden');
document.getElementById('results-section').classList.add('hidden');
this.displayWord();
this.updateStats();
} else {
this.showNotification(data.error || 'No words found with selected criteria', 'warning');
}
} catch (error) {
console.error('Error loading words:', error);
this.showNotification('Failed to load words. Check your connection.', 'error');
}
},
displayWord() {
if (this.currentIndex >= this.words.length) {
this.showResults();
return;
}
const word = this.words[this.currentIndex];
// Set question and prompt based on translation direction
const questionWordEl = document.getElementById('question-word');
const promptEl = document.getElementById('translation-prompt');
const inputEl = document.getElementById('answer-input');
if (this.translationDirection === 'nl-en') {
// Dutch to English
questionWordEl.textContent = word.dutch_translation;
promptEl.textContent = 'Translate this word to English:';
inputEl.placeholder = 'Type your answer in English...';
} else {
// English to Dutch (default)
questionWordEl.textContent = word.english_word;
promptEl.textContent = 'Translate this word to Dutch:';
inputEl.placeholder = 'Type your answer in Dutch...';
}
document.getElementById('word-category').textContent = `📚 ${word.category_name || 'Custom List'}`;
document.getElementById('word-difficulty').textContent = this.getDifficultyBadge(word.difficulty_level);
inputEl.value = '';
document.getElementById('feedback').classList.add('hidden');
inputEl.focus();
this.updateProgressBar();
},
getDifficultyBadge(level) {
const badges = {
'beginner': '🟢 Beginner',
'intermediate': '🟡 Intermediate',
'advanced': '🔴 Advanced'
};
return badges[level] || level;
},
updateProgressBar() {
const progress = ((this.currentIndex) / this.words.length) * 100;
document.getElementById('progress-fill').style.width = progress + '%';
document.getElementById('progress-text').textContent = `${this.currentIndex} / ${this.words.length}`;
},
async checkAnswer() {
const answerInput = document.getElementById('answer-input');
const answer = answerInput.value.trim();
if (!answer) {
this.showNotification('Please enter an answer', 'warning');
return;
}
const word = this.words[this.currentIndex];
const feedbackEl = document.getElementById('feedback');
// Disable input during check
answerInput.disabled = true;
document.getElementById('check-answer').disabled = true;
// Determine correct answer based on translation direction
const correctAnswer = this.translationDirection === 'nl-en'
? word.english_word
: word.dutch_translation;
// Simple comparison (case-insensitive)
const isCorrect = answer.toLowerCase() === correctAnswer.toLowerCase();
try {
// Only send to server if using default words (for progress tracking)
if (word.word_id && document.getElementById('source-select').value === 'default') {
const response = await fetch('php/check_answer.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
word_id: word.word_id,
answer: answer,
session_id: this.sessionId
})
});
const data = await response.json();
if (data.success && data.correct) {
this.correctCount++;
feedbackEl.className = 'feedback correct';
feedbackEl.innerHTML = `
<span class="feedback-icon">✅</span>
<div class="feedback-text">
<strong>Correct!</strong>
<p>${word.english_word} = ${word.dutch_translation}</p>
</div>
`;
} else {
this.incorrectCount++;
feedbackEl.className = 'feedback incorrect';
feedbackEl.innerHTML = `
<span class="feedback-icon">❌</span>
<div class="feedback-text">
<strong>Incorrect</strong>
<p>The correct answer is: <strong>${correctAnswer}</strong></p>
</div>
`;
}
} else {
// For custom lists, just check locally
if (isCorrect) {
this.correctCount++;
feedbackEl.className = 'feedback correct';
feedbackEl.innerHTML = `
<span class="feedback-icon">✅</span>
<div class="feedback-text">
<strong>Correct!</strong>
<p>${word.english_word} = ${word.dutch_translation}</p>
</div>
`;
} else {
this.incorrectCount++;
feedbackEl.className = 'feedback incorrect';
feedbackEl.innerHTML = `
<span class="feedback-icon">❌</span>
<div class="feedback-text">
<strong>Incorrect</strong>
<p>The correct answer is: <strong>${correctAnswer}</strong></p>
</div>
`;
}
}
feedbackEl.classList.remove('hidden');
this.updateStats();
// Auto advance after 2 seconds
setTimeout(() => {
this.nextWord();
}, 2000);
} catch (error) {
console.error('Error checking answer:', error);
this.showNotification('Failed to check answer', 'error');
answerInput.disabled = false;
document.getElementById('check-answer').disabled = false;
}
},
nextWord() {
// Re-enable input
document.getElementById('answer-input').disabled = false;
document.getElementById('check-answer').disabled = false;
this.currentIndex++;
this.displayWord();
},
updateStats() {
document.getElementById('correct-count').textContent = this.correctCount;
document.getElementById('incorrect-count').textContent = this.incorrectCount;
const total = this.correctCount + this.incorrectCount;
const accuracy = total > 0 ? Math.round((this.correctCount / total) * 100) : 0;
document.getElementById('accuracy').textContent = accuracy + '%';
},
showResults() {
document.getElementById('practice-area').classList.add('hidden');
document.getElementById('results-section').classList.remove('hidden');
const total = this.correctCount + this.incorrectCount;
const score = total > 0 ? Math.round((this.correctCount / total) * 100) : 0;
document.getElementById('final-correct').textContent = this.correctCount;
document.getElementById('final-total').textContent = total;
document.getElementById('final-score').textContent = score + '%';
// Scroll to results
document.getElementById('results-section').scrollIntoView({ behavior: 'smooth' });
// Reload stats
this.loadStats();
},
restartPractice() {
document.getElementById('results-section').classList.add('hidden');
document.querySelector('.settings-panel').classList.remove('hidden');
// Scroll to practice section
document.getElementById('practice').scrollIntoView({ behavior: 'smooth' });
},
async loadStats() {
try {
const response = await fetch(`php/get_stats.php?session_id=${this.sessionId}`);
const data = await response.json();
if (data.success && data.stats) {
this.displayStats(data.stats, data.recent_words);
}
} catch (error) {
console.error('Error loading stats:', error);
}
},
displayStats(stats, recentWords) {
const statsContent = document.getElementById('stats-content');
if (stats.total_attempts == 0) {
statsContent.innerHTML = '<p class="stats-message">Start practicing to see your statistics!</p>';
return;
}
let html = `
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number">${stats.total_attempts || 0}</div>
<div class="stat-title">Words Practiced</div>
</div>
<div class="stat-card">
<div class="stat-number">${stats.total_correct || 0}</div>
<div class="stat-title">Correct Answers</div>
</div>
<div class="stat-card">
<div class="stat-number">${stats.mastered_words || 0}</div>
<div class="stat-title">Mastered Words</div>
</div>
<div class="stat-card">
<div class="stat-number">${stats.success_rate || 0}%</div>
<div class="stat-title">Success Rate</div>
</div>
</div>
`;
if (recentWords && recentWords.length > 0) {
html += '<h3>Recent Practice</h3><div class="recent-words">';
recentWords.forEach(word => {
const accuracy = word.attempts > 0 ? Math.round((word.correct / word.attempts) * 100) : 0;
const masteredBadge = word.is_mastered ? '<span class="mastered-badge">✓ Mastered</span>' : '';
html += `
<div class="recent-word-item">
<div class="word-info">
<strong>${word.english_word}</strong> - ${word.dutch_translation}
<span class="category-tag">${word.category_name}</span>
${masteredBadge}
</div>
<div class="word-progress">
${word.correct}/${word.attempts} correct (${accuracy}%)
</div>
</div>
`;
});
html += '</div>';
}
statsContent.innerHTML = html;
},
showNotification(message, type = 'info') {
// Simple notification (you can enhance this with a toast library)
alert(message);
}
};
// Initialize app when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
App.init();
});