Εισαγωγή στην JavaScript - Κάνοντας τις Σελίδες Διαδραστικές

Κεφάλαιο 1: Βασικές αρχές της JavaScript (Ο πυρήνας της γλώσσας)

Πριν αρχίσουμε να χειριζόμαστε ιστοσελίδες, πρέπει να μάθουμε τους θεμελιώδεις κανόνες και τα δομικά στοιχεία της ίδιας της γλώσσας. Θα τα δούμε μέσα από τη σκοπιά του πώς λειτουργούν στον browser.

1.1 Πώς προσθέτουμε JavaScript σε μια σελίδα;

Όπως και στο CSS, υπάρχουν τρεις τρόποι, αλλά και εδώ θα χρησιμοποιούμε σχεδόν αποκλειστικά τον έναν.

  1. Inline (Κακή πρακτική): ` `
  2. Internal (Εσωτερικά): Χρησιμοποιώντας το tag <script> μέσα στο HTML.
  3. External (Εξωτερικά): Η επαγγελματική μέθοδος. Γράφουμε τον κώδικά μας σε ένα ξεχωριστό αρχείο (π.χ., script.js) και το συνδέουμε με την HTML.

Γιατί εξωτερικά; Για τους ίδιους λόγους με το CSS: διαχωρισμός αρμοδιοτήτων και επαναχρησιμοποίηση.

Πού τοποθετούμε το <script> tag; Παραδοσιακά, το έβαζαν μέσα στο <head>. Η σύγχρονη και καλύτερη πρακτική είναι να τοποθετούμε το <script> tag ακριβώς πριν το κλείσιμο του </body> tag.

    ...
    <script src="script.js"></script>
</body>
</html>

Γιατί στο τέλος; Ο browser διαβάζει την HTML από πάνω προς τα κάτω. Αν το script είναι στο <head>, ο browser θα σταματήσει να φορτώνει την ορατή σελίδα, θα κατεβάσει και θα εκτελέσει το JavaScript, και μετά θα συνεχίσει. Αυτό κάνει τη σελίδα να φαίνεται αργή. Βάζοντάς το στο τέλος, εξασφαλίζουμε ότι όλη η HTML έχει φορτωθεί και είναι ορατή στον χρήστη πριν αρχίσει να εκτελείται το JS.

1.2 Η κονσόλα του Browser (Console): Ο καλύτερος σου φίλος Η καρτέλα Console στα Developer Tools (F12) είναι το μέρος όπου θα βλέπεις τα μηνύματα από τον κώδικά σου, τα λάθη (errors) και θα μπορείς να πειραματιστείς ζωντανά. Η εντολή console.log() είναι το πιο βασικό εργαλείο για debugging. Τυπώνει ό,τι του δώσεις στην κονσόλα.

console.log("Γεια σου κόσμε!");
console.log(5 + 3); // Θα τυπώσει 8

1.3 Μεταβλητές (Variables): Τα "Κουτιά" για τα δεδομένα σου

Μια μεταβλητή είναι ένα "δοχείο" με ένα όνομα, στο οποίο αποθηκεύουμε μια τιμή. Στη σύγχρονη JS, χρησιμοποιούμε δύο λέξεις-κλειδιά για να δηλώσουμε μεταβλητές: let και const.

  • let: Για μεταβλητές των οποίων η τιμή μπορεί να αλλάξει αργότερα.
    let age = 30;
    console.log(age); // 30
    age = 31; // Μπορούμε να αλλάξουμε την τιμή
    console.log(age); // 31
    
  • const: Για σταθερές (constants), δηλαδή μεταβλητές των οποίων η τιμή δεν θα αλλάξει ποτέ αφού οριστεί.
    const birthYear = 1990;
    // birthYear = 1991; // Αυτό θα προκαλέσει σφάλμα (Error)!
    

    Κανόνας: Προτίμησε να χρησιμοποιείς const από προεπιλογή. Χρησιμοποίησε let μόνο όταν ξέρεις σίγουρα ότι η τιμή της μεταβλητής χρειάζεται να αλλάξει.

1.4 Τύποι Δεδομένων (Data Types) Η JavaScript έχει διάφορους τύπους δεδομένων:

  • String (Συμβολοσειρά): Κείμενο. Πρέπει να είναι μέσα σε μονά ή διπλά εισαγωγικά ('' ή ""). const name = "Γιάννης";
  • Number (Αριθμός): Οποιοσδήποτε αριθμός, ακέραιος ή δεκαδικός. const price = 19.99;
  • Boolean (Λογική τιμή): Μπορεί να είναι μόνο true (αληθής) ή false (ψευδής). const isLoggedIn = true;
  • Array (Πίνακας): Μια ταξινομημένη λίστα από τιμές. Ορίζεται με αγκύλες []. const colors = ["red", "green", "blue"];
  • Object (Αντικείμενο): Μια συλλογή από ζεύγη "κλειδί-τιμή". Ορίζεται με άγκιστρα {}.
    const user = {
      firstName: "Μαρία",
      lastName: "Παπαδοπούλου",
      age: 25
    };
    console.log(user.firstName); // Τυπώνει "Μαρία"
    

1.5 Λογικοί τελεστές & συνθήκες (Operators & Conditionals)

  • Τελεστές σύγκρισης: > (μεγαλύτερο από), < (μικρότερο από), === (αυστηρά ίσο), !== (όχι ίσο).
  • Λογικοί τελεστές: && (AND - και), || (OR - ή), ! (NOT - όχι).

Η δομή if...else μας επιτρέπει να εκτελούμε διαφορετικό κώδικα ανάλογα με το αν μια συνθήκη είναι αληθής ή ψευδής.

const userAge = 19;

if (userAge >= 18) {
  console.log("Μπορείς να μπεις στο club.");
} else {
  console.log("Είσαι πολύ μικρός/ή.");
}

1.6 Συναρτήσεις (Functions)

Μια συνάρτηση είναι ένα επαναχρησιμοποιήσιμο μπλοκ κώδικα που εκτελεί μια συγκεκριμένη εργασία. Μπορεί να δέχεται "εισόδους" (ορίσματα - arguments) και να επιστρέφει μια "έξοδο" (μια τιμή).

// Ορισμός της συνάρτησης
function greet(name) {
  const message = "Καλωσόρισες, " + name + "!";
  return message;
}

// Κλήση (χρήση) της συνάρτησης
const greetingForNikos = greet("Νίκο");
console.log(greetingForNikos); // Τυπώνει "Καλωσόρισες, Νίκο!"

Ώρα για πράξη: Το πρώτο σου Script

  1. Δημιούργησε το αρχείο: Στον φάκελο του project σου (my-first-git-project), δημιούργησε ένα νέο αρχείο με όνομα script.js.
  2. Σύνδεσέ το: Στο index.html, πήγαινε στο τέλος, ακριβώς πριν το </body> tag, και πρόσθεσε:
    <script src="script.js"></script>
    
  3. Γράψε κώδικα: Στο script.js αρχείο, γράψε μερικές εντολές για να πειραματιστείς:
    // Τυπώνουμε ένα μήνυμα για να επιβεβαιώσουμε ότι το script φορτώθηκε
    console.log("Το script.js φορτώθηκε με επιτυχία!");
    
    // Δημιουργούμε κάποιες μεταβλητές
    const siteName = "Το Βιογραφικό μου";
    let currentYear = 2024;
    const isProjectComplete = false;
    
    // Τις τυπώνουμε στην κονσόλα
    console.log("Όνομα Ιστοσελίδας:", siteName);
    console.log("Τρέχον Έτος:", currentYear);
    
    // Φτιάχνουμε μια απλή συνάρτηση
    function calculateAge(birthYear) {
      const age = currentYear - birthYear;
      return age;
    }
    
    const myAge = calculateAge(1995);
    console.log("Η ηλικία μου είναι περίπου:", myAge);
    
    // Ένας απλός έλεγχος if/else
    if (isProjectComplete) {
      console.log("Το project είναι έτοιμο!");
    } else {
      console.log("Το project είναι ακόμα υπό κατασκευή.");
    }
    
  4. Σώσε τα αρχεία και δες την κονσόλα: Ανανέωσε τη σελίδα του βιογραφικού σου στον browser και άνοιξε τα Developer Tools (F12) στην καρτέλα "Console". Θα πρέπει να δεις όλα τα μηνύματα που έγραψες!

τέλεια! Αφού είδαμε τις βασικές αρχές της γλώσσας, ήρθε η ώρα για το πιο συναρπαστικό κομμάτι. Πώς η JavaScript, που μέχρι τώρα "ζούσε" μόνο μέσα στην κονσόλα, μπορεί να βγει έξω και να αλληλεπιδράσει με την ίδια την ιστοσελίδα;

Η απάντηση βρίσκεται σε μια έννοια που ονομάζεται DOM (Document Object Model).


Κεφάλαιο 2: Η JavaScript στον Browser: Το DOM (Document Object Model)

2.1 Τι είναι το DOM;

Όταν ο browser φορτώνει μια HTML σελίδα, δεν τη βλέπει απλώς ως ένα αρχείο κειμένου. Τη μετατρέπει σε μια δενδρική δομή από αντικείμενα (objects) στη μνήμη του. Κάθε HTML element, κάθε attribute, κάθε κομμάτι κειμένου γίνεται ένα "αντικείμενο" (ένα "node") σε αυτό το δέντρο. Αυτή η αναπαράσταση της σελίδας ως δέντρο από αντικείμενα είναι το DOM.

Η κρίσιμη ιδέα: Η JavaScript δεν επεξεργάζεται απευθείας το HTML κείμενο. Επεξεργάζεται αυτό το ζωντανό δέντρο αντικειμένων (το DOM). Όταν η JavaScript αλλάζει κάτι σε ένα αντικείμενο του DOM (π.χ., το χρώμα του), ο browser αυτόματα ανανεώνει την οθόνη για να αντικατοπτρίσει αυτή την αλλαγή.

Το σημείο εκκίνησης για κάθε αλληλεπίδραση με το DOM είναι το global object document.

2.2 Επιλογή στοιχείων (Selecting Elements)

Το πρώτο βήμα για να κάνεις οτιδήποτε είναι να "πιάσεις" το HTML element που σε ενδιαφέρει και να το αποθηκεύσεις σε μια μεταβλητή. Η JavaScript μας δίνει διάφορες μεθόδους για να το κάνουμε, οι οποίες μοιάζουν πολύ με τους CSS selectors.

  • document.getElementById('id-name') Επιλέγει το ένα και μοναδικό στοιχείο που έχει το συγκεκριμένο id. Είναι η ταχύτερη μέθοδος επιλογής.
    // Στο HTML: <header id="main-header">...</header>
    const mainHeader = document.getElementById('main-header');
    console.log(mainHeader); // Θα τυπώσει το HTML element του header στην κονσόλα
    
  • document.querySelector('css-selector') Η πιο ευέλικτη και σύγχρονη μέθοδος. Επιλέγει το πρώτο στοιχείο που ταιριάζει με οποιονδήποτε έγκυρο CSS selector του δώσεις.
    // Επιλογή του πρώτου <p>
    const firstParagraph = document.querySelector('p');
    
    // Επιλογή του στοιχείου με την κλάση 'highlight'
    const highlightedText = document.querySelector('.highlight');
    
    // Επιλογή ενός link μέσα σε ένα nav
    const navLink = document.querySelector('nav a');
    
  • document.querySelectorAll('css-selector') Όπως η querySelector, αλλά επιστρέφει όλα τα στοιχεία που ταιριάζουν με τον selector, μέσα σε μια ειδική λίστα που λέγεται NodeList.
    // Επιλογή ΟΛΩΝ των li μέσα στην ενότητα #skills
    const allSkillItems = document.querySelectorAll('#skills li');
    console.log(allSkillItems); // Θα τυπώσει μια λίστα με όλα τα li
    
    // Μπορούμε να κάνουμε loop σε αυτή τη λίστα
    allSkillItems.forEach(skill => {
        console.log(skill.textContent); // Τυπώνει το κείμενο κάθε δεξιότητας
    });
    

    Κανόνας: Προτίμησε querySelector και querySelectorAll γιατί είναι πιο ευέλικτες, ακριβώς όπως οι CSS selectors που ήδη ξέρεις. Χρησιμοποίησε getElementById όταν έχεις ένα συγκεκριμένο id και θέλεις την απόλυτη ταχύτητα.

2.3 Χειρισμός στοιχείων (Manipulating Elements)

Αφού "πιάσουμε" ένα στοιχείο και το βάλουμε σε μια μεταβλητή, μπορούμε να του κάνουμε σχεδόν τα πάντα!

  • Αλλαγή περιεχομένου:
    • .textContent: Παίρνει ή αλλάζει μόνο το κείμενο μέσα σε ένα στοιχείο, αγνοώντας οποιοδήποτε HTML. Είναι ασφαλές και γρήγορο.
      const pageTitle = document.querySelector('h1');
      pageTitle.textContent = "Καλώς ήρθατε στο Νέο μου Site!";
      
    • .innerHTML: Παίρνει ή αλλάζει ολόκληρο το HTML περιεχόμενο ενός στοιχείου. Είναι πανίσχυρο αλλά πρέπει να χρησιμοποιείται με προσοχή (ειδικά αν βάζεις κείμενο που προέρχεται από χρήστη, για λόγους ασφαλείας).
      const contactSection = document.querySelector('#contact');
      contactSection.innerHTML = '<h2>Η φόρμα έκλεισε!</h2><p>Ευχαριστούμε.</p>';
      
  • Αλλαγή στυλ: Μπορούμε να αλλάξουμε οποιαδήποτε CSS ιδιότητα μέσω της ιδιότητας .style. Οι CSS ιδιότητες που έχουν παύλα (π.χ., background-color) γράφονται σε camelCase (π.χ., backgroundColor).
    const mainHeader = document.getElementById('main-header'); // Το είχαμε πιάσει και πριν
    mainHeader.style.backgroundColor = '#1abc9c'; // Αλλάζει το χρώμα του φόντου
    mainHeader.style.padding = '50px'; // Αυξάνει το padding
    
  • Αλλαγή Attributes: Μπορούμε να αλλάξουμε τα HTML attributes όπως href, src, alt, κ.λπ.
    const profileImage = document.querySelector('#about img');
    profileImage.src = 'images/new-photo.jpg'; // Αλλάζει την εικόνα
    profileImage.alt = 'Η νέα μου φωτογραφία';
    
  • Προσθήκη και αφαίρεση κλάσεων (Classes): Αυτός είναι ο καλύτερος και προτιμώμενος τρόπος για να αλλάζεις το στυλ ενός στοιχείου. Αντί να αλλάζεις πολλές .style ιδιότητες με τη JS, είναι καλύτερα να έχεις προκαθορισμένες κλάσεις στο CSS σου και απλά να τις προσθέτεις/αφαιρείς με JS.
    /* Στο style.css */
    .highlighted {
      background-color: yellow;
      border: 2px solid orange;
    }
    
    // Στο script.js
    const skillsSection = document.querySelector('#skills');
    skillsSection.classList.add('highlighted');    // Προσθέτει την κλάση
    skillsSection.classList.remove('highlighted'); // Αφαιρεί την κλάση
    skillsSection.classList.toggle('highlighted'); // Προσθέτει την κλάση αν δεν υπάρχει, την αφαιρεί αν υπάρχει
    

Ώρα για πράξη: Ζωντανεύουμε το βιογραφικό

Πάμε στο script.js και ας κάνουμε μερικές αλλαγές στη σελίδα μας όταν αυτή φορτώνει.

// Σβήνουμε τον παλιό κώδικα ή τον κάνουμε comment out
console.log("DOM manipulation script loaded!");

// 1. Επιλέγουμε τον κύριο τίτλο (το όνομά μας)
const myNameElement = document.querySelector('header h1');

// 2. Αλλάζουμε το κείμενό του
myNameElement.textContent = "ΙΩΑΝΝΗΣ ΠΑΠΑΔΟΠΟΥΛΟΣ";
myNameElement.style.color = "#ecf0f1"; // Ένα πιο ανοιχτό λευκό

// 3. Ας κάνουμε την εικόνα προφίλ μας στρογγυλή
const profilePic = document.querySelector('#about img');
if (profilePic) { // Καλό είναι να ελέγχουμε αν το στοιχείο βρέθηκε
    profilePic.style.borderRadius = '50%';
    profilePic.style.border = '5px solid #0056b3';
}

// 4. Ας δώσουμε μια ειδική κλάση σε όλες τις κάρτες εμπειρίας
const experienceCards = document.querySelectorAll('#experience article');
experienceCards.forEach(card => {
    // Θα μπορούσαμε να έχουμε μια κλάση στο CSS π.χ. .card-highlight
    // και εδώ να κάνουμε card.classList.add('card-highlight');
    // Προς το παρόν, ας τους αλλάξουμε απλά το background.
    card.style.backgroundColor = '#eaf4ff'; // Ένα πολύ απαλό μπλε
});

Σώσε το script.js και ανανέωσε τη σελίδα. Θα δεις τις αλλαγές να εφαρμόζονται αμέσως μόλις η σελίδα φορτώσει! Ο τίτλος, η εικόνα και οι κάρτες άλλαξαν, όχι από το CSS, αλλά από τις εντολές που τους έδωσε η JavaScript.


Κεφάλαιο 3: Events (Γεγονότα) - Η καρδιά της διαδραστικότητας

Ένα event είναι ένα σήμα που εκπέμπεται από τον browser για να μας πει ότι "κάτι συνέβη". Η JavaScript μπορεί να στήσει "ακροατές" (event listeners) σε οποιοδήποτε HTML element, οι οποίοι περιμένουν να συμβεί ένα συγκεκριμένο γεγονός και, όταν συμβεί, εκτελούν μια συνάρτηση που τους έχουμε ορίσει.

Η Μέθοδος-Κλειδί: addEventListener() Αυτή είναι η σύγχρονη και σωστή μέθοδος για να χειριστούμε events. Έχει την εξής σύνταξη: element.addEventListener('eventType', functionToExecute);

  • element: Το HTML element που έχουμε επιλέξει (π.χ., ένα κουμπί).
  • 'eventType': Το όνομα του γεγονότος που θέλουμε να "ακούσουμε", ως string (π.χ., 'click', 'mouseover').
  • functionToExecute: Η συνάρτηση που θα κληθεί όταν συμβεί το γεγονός. Αυτή η συνάρτηση ονομάζεται callback function.

Ένα απλό παράδειγμα:

<button id="myButton">Πάτα με!</button>
// 1. Επιλέγουμε το στοιχείο
const btn = document.getElementById('myButton');

// 2. Ορίζουμε τη συνάρτηση που θέλουμε να εκτελεστεί
function showAlert() {
  alert('Μπράβο! Πάτησες το κουμπί!');
}

// 3. Προσθέτουμε τον event listener
btn.addEventListener('click', showAlert);

Στο παραπάνω παράδειγμα, κάθε φορά που ο χρήστης κάνει κλικ στο κουμπί, η συνάρτηση showAlert θα εκτελείται.

Συχνά, για πιο απλές περιπτώσεις, γράφουμε τη συνάρτηση απευθείας μέσα στο addEventListener ως ανώνυμη συνάρτηση (anonymous function) ή ως arrow function:

// Χρήση ανώνυμης συνάρτησης
btn.addEventListener('click', function() {
  console.log('Το κουμπί πατήθηκε!');
});

// Χρήση arrow function (πιο μοντέρνα σύνταξη)
btn.addEventListener('click', () => {
  console.log('Το κουμπί πατήθηκε ξανά!');
});

Τα πιο συνηθισμένα Events

  • Mouse Events:
    • 'click': Όταν ο χρήστης κάνει κλικ.
    • 'mouseover': Όταν το ποντίκι μπαίνει πάνω στο στοιχείο.
    • 'mouseout': Όταν το ποντίκι φεύγει από το στοιχείο.
  • Keyboard Events:
    • 'keydown': Όταν ένα πλήκτρο πατιέται.
    • 'keyup': Όταν ένα πλήκτρο αφήνεται.
  • Form Events:
    • 'submit': Όταν υποβάλλεται μια φόρμα. Είναι σημαντικό να το "ακούμε" στο ίδιο το <form> element, όχι στο κουμπί submit.
    • 'input': Όταν η τιμή ενός <input> ή <textarea> αλλάζει.

Το "Маγικό" Object event Η συνάρτηση που εκτελείται από έναν event listener δέχεται αυτόματα ως πρώτο της όρισμα ένα ειδικό object, το οποίο συνήθως ονομάζουμε evente για συντομία). Αυτό το object περιέχει χρήσιμες πληροφορίες για το γεγονός που συνέβη.

Μια από τις πιο χρήσιμες μεθόδους του είναι η event.preventDefault(). Αυτή η μέθοδος ακυρώνει την προεπιλεγμένη συμπεριφορά του browser. Για παράδειγμα:

  • Η προεπιλεγμένη συμπεριφορά ενός link είναι να μας πάει σε νέα σελίδα.
  • Η προεπιλεγμένη συμπεριφορά της υποβολής μιας φόρμας είναι να ανανεώσει τη σελίδα.

Αν θέλουμε να χειριστούμε αυτά τα γεγονότα με JavaScript χωρίς να γίνει η προεπιλεγμένη ενέργεια, πρέπει να καλέσουμε την event.preventDefault().


Ώρα για πράξη: Διαδραστικότητα στο βιογραφικό

1. Κουμπί "Toggle" για την Εμφάνιση των Δεξιοτήτων Θέλουμε ένα κουμπί που όταν το πατάμε, να εμφανίζει ή να κρύβει τη λίστα με τις δεξιότητες.

  • HTML: Πάμε στην ενότητα skills στο index.html και προσθέτουμε ένα κουμπί πάνω από τη λίστα <ul>:
    <section id="skills">
        <h2>Δεξιότητες <button id="toggle-skills-btn">Εμφάνιση/Απόκρυψη</button></h2>
        <ul id="skills-list"> <!-- Δίνουμε ένα id και στη λίστα -->
            ...
        </ul>
    </section>
    
  • CSS: Προσθέτουμε μια κλάση στο style.css που θα κρύβει τα στοιχεία.
    .hidden {
      display: none;
    }
    
  • JavaScript: Στο script.js:
    // Επιλέγουμε το κουμπί και τη λίστα
    const toggleBtn = document.getElementById('toggle-skills-btn');
    const skillsList = document.getElementById('skills-list');
    
    // Προσθέτουμε τον listener στο κουμπί
    toggleBtn.addEventListener('click', () => {
      // Χρησιμοποιούμε το toggle που είναι ιδανικό για αυτή τη δουλειά
      skillsList.classList.toggle('hidden');
    });
    

    Τώρα, κάθε φορά που πατάτε το κουμπί, η κλάση hidden προστίθεται ή αφαιρείται, κρύβοντας ή εμφανίζοντας τη λίστα!

2. Αποτροπή ανανέωσης της φόρμας Θέλουμε, όταν ο χρήστης πατάει "Αποστολή" στη φόρμα επικοινωνίας, η σελίδα να μην ανανεώνεται. Αντιθέτως, θέλουμε να παίρνουμε τα δεδομένα και να τα τυπώνουμε στην κονσόλα.

  • HTML: Βεβαιωνόμαστε ότι η φόρμα μας έχει ένα id.
    <form action="#" method="post" id="contact-form">
      ...
    </form>
    
  • JavaScript: Στο script.js:
    // Επιλέγουμε τη φόρμα
    const contactForm = document.getElementById('contact-form');
    
    contactForm.addEventListener('submit', (event) => {
      // 1. ΑΚΥΡΩΝΟΥΜΕ την προεπιλεγμένη συμπεριφορά (την ανανέωση της σελίδας)
      event.preventDefault();
          
      console.log("Η φόρμα υποβλήθηκε χωρίς ανανέωση!");
    
      // 2. Παίρνουμε τις τιμές από τα πεδία
      const nameInput = document.getElementById('name');
      const emailInput = document.getElementById('email');
    
      const nameValue = nameInput.value;
      const emailValue = emailInput.value;
    
      // 3. Εμφανίζουμε τις τιμές
      console.log(`Όνομα: ${nameValue}, Email: ${emailValue}`);
          
      // Bonus: Εμφανίζουμε ένα μήνυμα επιτυχίας
      alert(`Ευχαριστούμε, ${nameValue}! Το μήνυμά σας έχει (υποθετικά) σταλεί.`);
    
      // Bonus 2: Καθαρίζουμε τη φόρμα
      contactForm.reset();
    });
    

    Τώρα, μπορείτε να συμπληρώσετε τη φόρμα, να πατήσετε "Αποστολή" και η σελίδα δεν θα ανανεωθεί. Κοιτάξτε στην κονσόλα για να δείτε τα δεδομένα που "συλλέξατε"!


Κεφάλαιο 4: Ασύγχρονη JavaScript (Asynchronous JS) & Fetch API

4.1 Το Πρόβλημα: Συγχρονισμός vs Ασύγχρονη εκτέλεση

Η JavaScript από τη φύση της είναι single-threaded (μονού νήματος). Αυτό σημαίνει ότι μπορεί να κάνει μόνο ένα πράγμα τη φορά. Φανταστείτε τον παρακάτω (ψευδο)κώδικα:

console.log("Πρώτη εντολή");
const data = get_data_from_server(); // Αυτό μπορεί να πάρει 5 δευτερόλεπτα
console.log("Τρίτη εντολή");

Αν η JavaScript ήταν αυστηρά σύγχρονη, ο browser θα "πάγωνε" για 5 δευτερόλεπτα περιμένοντας τα δεδομένα. Ο χρήστης δεν θα μπορούσε να κάνει κλικ, να σκρολάρει, τίποτα. Αυτό θα ήταν μια τραγική εμπειρία.

Για να λυθεί αυτό, η JavaScript χρησιμοποιεί έναν ασύγχρονο μηχανισμό. Λέει στον browser: "Εσύ ξεκίνα αυτό το αίτημα προς τον server, και όταν με το καλό έρθει η απάντηση, ειδοποίησέ με για να κάνω κάτι με αυτήν. Στο μεταξύ, εγώ θα συνεχίσω να εκτελώ τον υπόλοιπο κώδικα."

4.2 Promises: Η "Υπόσχεση" για μια μελλοντική τιμή Ένα Promise είναι ένα ειδικό αντικείμενο στη JavaScript που αντιπροσωπεύει την τελική επιτυχία ή αποτυχία μιας ασύγχρονης λειτουργίας. Είναι κυριολεκτικά μια "υπόσχεση" από τον browser προς τον κώδικά σου.

Ένα Promise μπορεί να βρίσκεται σε μία από τις τρεις καταστάσεις:

  1. Pending (Σε εκκρεμότητα): Η αρχική κατάσταση. Η λειτουργία δεν έχει ολοκληρωθεί ακόμα.
  2. Fulfilled (Εκπληρώθηκε): Η λειτουργία ολοκληρώθηκε με επιτυχία. Το Promise έχει τώρα μια τιμή (τα δεδομένα που ζητήσαμε).
  3. Rejected (Απορρίφθηκε): Η λειτουργία απέτυχε (π.χ., ο server δεν απάντησε). Το Promise έχει τώρα έναν λόγο αποτυχίας (ένα error object).

Για να "χειριστούμε" το αποτέλεσμα ενός Promise, χρησιμοποιούμε τη μέθοδο .then() και .catch():

  • .then(data => { ... }): Το μπλοκ κώδικα που εκτελείται αν το Promise είναι fulfilled. Το data είναι η τιμή που επέστρεψε η υπόσχεση.
  • .catch(error => { ... }): Το μπλοק κώδικα που εκτελείται αν το Promise είναι rejected.

4.3 Το Fetch API: Η σύγχρονη μέθοδος για HTTP Requests Το Fetch API είναι η ενσωματωμένη, μοντέρνα λειτουργία του browser για να κάνουμε δίκτυακά αιτήματα (HTTP requests). Είναι βασισμένο σε Promises.

Η Βασική του χρήση:

fetch('https://api.example.com/data') // Το fetch επιστρέφει ένα Promise
  .then(response => {
    // Αυτό το .then εκτελείται όταν ο server απαντήσει.
    // Η απάντηση (response) δεν είναι ακόμα τα δεδομένα,
    // αλλά ένα object που περιγράφει την απάντηση.
    // Πρέπει να το "μεταφράσουμε" σε JSON.
    return response.json(); // Το .json() επιστρέφει επίσης ένα Promise!
  })
  .then(data => {
    // Αυτό το δεύτερο .then εκτελείται όταν η μετάφραση σε JSON ολοκληρωθεί.
    // Τώρα έχουμε τα πραγματικά δεδομένα!
    console.log("Τα δεδομένα είναι εδώ:", data);
  })
  .catch(error => {
    // Αυτό το .catch εκτελείται αν υπάρξει οποιοδήποτε πρόβλημα στο δίκτυο.
    console.error("Υπήρξε ένα σφάλμα:", error);
  });

4.4 Async/Await: Ο "όμορφος" τρόπος Η αλυσιδωτή κλήση των .then() μπορεί να γίνει δυσανάγνωστη. Η ES2017 εισήγαγε μια νέα σύνταξη, τα async/await, που μας επιτρέπει να γράφουμε ασύγχρονο κώδικα που μοιάζει με σύγχρονο. Είναι απλώς "συντακτική ζάχαρη" (syntactic sugar) πάνω από τα Promises.

Κανόνες:

  1. Μπορείς να χρησιμοποιήσεις τη λέξη-κλειδί await μόνο μέσα σε μια συνάρτηση που έχει δηλωθεί με τη λέξη-κλειδί async.
  2. Το await "παγώνει" την εκτέλεση της συνάρτησης μόνο μέχρι το Promise να επιλυθεί, χωρίς να μπλοκάρει τον υπόλοιπο browser.

Το ίδιο παράδειγμα με async/await:

// Ορίζουμε τη συνάρτηση ως async
async function getData() {
  try {
    const response = await fetch('https://api.example.com/data'); // Περίμενε την απάντηση
    const data = await response.json(); // Περίμενε τη μετάφραση
    console.log("Τα δεδομένα είναι εδώ (από async/await):", data);
  } catch (error) {
    console.error("Υπήρξε ένα σφάλμα:", error);
  }
}

// Καλούμε τη συνάρτηση
getData();

Είναι πολύ πιο καθαρό και ευανάγνωστο! Θα χρησιμοποιούμε κυρίως αυτή την προσέγγιση.


Ώρα για πράξη: Φέρνουμε αποφθέγματα στο βιογραφικό μας

Θα προσθέσουμε μια νέα ενότητα στη σελίδα μας που θα τραβάει ένα τυχαίο απόφθεγμα από ένα δωρεάν, public API κάθε φορά που πατάμε ένα κουμπί.

  • HTML: Στο index.html, ίσως μέσα στο <main> ή στο <footer>, προσθέτουμε μια νέα ενότητα.
    <section id="quotes">
      <h2>Απόφθεγμα της Ημέρας</h2>
      <blockquote id="quote-text">
        <!-- Εδώ θα μπει το κείμενο του αποφθέγματος -->
      </blockquote>
      <p id="quote-author"></p>
      <button id="new-quote-btn">Νέο Απόφθεγμα</button>
    </section>
    
  • JavaScript (script.js):
    // 1. Επιλέγουμε τα στοιχεία μας
    const quoteTextElement = document.getElementById('quote-text');
    const quoteAuthorElement = document.getElementById('quote-author');
    const newQuoteBtn = document.getElementById('new-quote-btn');
    
    // 2. Δημιουργούμε την async function για να φέρει τα δεδομένα
    async function getQuote() {
      console.log("Fetching a new quote...");
      const apiUrl = 'https://api.quotable.io/random'; // Ένα δωρεάν API για αποφθέγματα
          
      try {
        const response = await fetch(apiUrl);
        if (!response.ok) { // Έλεγχος για σφάλματα από τον server (π.χ. 404)
           throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
    
        // 3. Ενημερώνουμε το DOM με τα νέα δεδομένα
        quoteTextElement.textContent = `"${data.content}"`;
        quoteAuthorElement.textContent = `- ${data.author}`;
      } catch (error) {
        quoteTextElement.textContent = "Δυστυχώς, δεν μπορέσαμε να φέρουμε νέο απόφθεγμα.";
        quoteAuthorElement.textContent = "";
        console.error("Σφάλμα κατά τη λήψη του αποφθέγματος:", error);
      }
    }
    
    // 4. Προσθέτουμε τον event listener στο κουμπί
    newQuoteBtn.addEventListener('click', getQuote);
    
    // 5. Φέρνουμε ένα απόφθεγμα και κατά τη φόρτωση της σελίδας
    getQuote();
    

Σώσε και τρέξε τη σελίδα. Θα δεις να εμφανίζεται ένα απόφθεγμα αμέσως, και κάθε φορά που πατάς το κουμπί, η σελίδα θα επικοινωνεί με τον server του quotable.io και θα εμφανίζει ένα νέο!

Μόλις έκανες τη σελίδα σου να αλληλεπιδρά όχι μόνο με τον χρήστη, αλλά και με ολόκληρο το διαδίκτυο. Αυτή η δεξιότητα είναι το κλειδί για τη δημιουργία σύγχρονων, πλούσιων σε δεδομένα web εφαρμογών.


Κεφάλαιο 5: Mini Project - Προσθήκη διαδραστικότητας στο βιογραφικό

Ο στόχος: Να πάρουμε τη στατική αλλά όμορφα σχεδιασμένη σελίδα του βιογραφικού και να της δώσουμε "εγκέφαλο". Θα προσθέσουμε τρεις διαφορετικές, πρακτικές λειτουργίες που η κάθε μία θα καλύπτει και ένα διαφορετικό κομμάτι από αυτά που μάθαμε στη JavaScript: χειρισμό DOM, events, client-side validation και ασύγχρονες κλήσεις.


Λειτουργία 1: Κουμπί "Dark/Light Mode"

Αυτή είναι μια κλασική και πολύ δημοφιλής λειτουργία. Θα επιτρέπει στον χρήστη να αλλάζει το χρωματικό θέμα της σελίδας.

Βήμα 1: CSS - Ορισμός του Dark Theme Ο καλύτερος τρόπος για να το κάνουμε είναι να ορίσουμε τα χρώματά μας ως CSS variables και μετά να τα αλλάζουμε με μια κλάση. Στην κορυφή του style.css σου, πρόσθεσε:

:root {
  --bg-color: #f4f4f4;
  --text-color: #333333;
  --card-bg: #ffffff;
  --primary-color: #0056b3;
}

body.dark-mode {
  --bg-color: #121212;
  --text-color: #e0e0e0;
  --card-bg: #1e1e1e;
  --primary-color: #64b5f6; /* Ένα πιο ανοιχτό μπλε για αντίθεση */
}

/* Τώρα, αντικατάστησε τα παλιά χρώματα με τις μεταβλητές */
body {
  background-color: var(--bg-color);
  color: var(--text-color);
  /* ...άλλες ιδιότητες... */
}

#experience article {
  background-color: var(--card-bg);
  /* ... */
}
/* ... κάνε το ίδιο για όλους τους κανόνες που έχουν χρώματα ... */

Βήμα 2: HTML - Το κουμπί Στο index.html, ας βάλουμε το κουμπί στο header, δίπλα στο όνομά μας.

<header>
    <div> <!-- Πρόσθεσε ένα div για ομαδοποίηση -->
      <h1>Το Όνομά σου</h1>
      <p>Web Developer...</p>
    </div>
    <button id="theme-toggle-btn">Toggle Theme</button> <!-- ΤΟ ΝΕΟ ΚΟΥΜΠΙ -->
    <nav>...</nav>
</header>

(Ίσως χρειαστεί να προσαρμόσεις λίγο το Flexbox στο CSS του header για να παίξει σωστά)

Βήμα 3: JavaScript - Η λογική Στο script.js:

const themeToggleButton = document.getElementById('theme-toggle-btn');

themeToggleButton.addEventListener('click', () => {
  // Απλά προσθέτουμε/αφαιρούμε την κλάση 'dark-mode' στο body
  document.body.classList.toggle('dark-mode');
});

Αυτό είναι! Με το πάτημα του κουμπιού, η σελίδα αλλάζει θέμα.


Λειτουργία 2: Client-Side Form Validation

Θα βελτιώσουμε τη φόρμα επικοινωνίας ώστε να κάνει έλεγχο των πεδίων με JavaScript πριν την (υποθετική) αποστολή, δίνοντας στον χρήστη σαφή μηνύματα λάθους.

Βήμα 1: HTML - Χώρος για τα λάθη Στο index.html, ακριβώς κάτω από το <form> tag, πρόσθεσε ένα στοιχείο για να εμφανίζουμε τα λάθη.

<form id="contact-form" ...>
  <div id="form-error-msg" class="error-text"></div> <!-- ΝΕΟ ΣΤΟΙΧΕΙΟ -->
  <!-- ...τα υπόλοιπα πεδία... -->
</form>

Βήμα 2: CSS - Στυλ για τα λάθη Στο style.css:

.error-text {
  color: #d32f2f; /* Ένα έντονο κόκκινο */
  font-weight: bold;
  margin-bottom: 10px;
}

Βήμα 3: JavaScript - Ο κώδικας ελέγχου Αντικατάστησε τον παλιό listener της φόρμας στο script.js με αυτόν:

const contactForm = document.getElementById('contact-form');
const formErrorMsg = document.getElementById('form-error-msg');

contactForm.addEventListener('submit', (event) => {
    event.preventDefault(); // Πάντα το πρώτο βήμα!

    // Παίρνουμε τις τιμές και τις "καθαρίζουμε" από κενά
    const name = document.getElementById('name').value.trim();
    const email = document.getElementById('email').value.trim();
    const message = document.getElementById('message').value.trim();

    // Έλεγχος
    if (name === '' || email === '' || message === '') {
        formErrorMsg.textContent = 'Όλα τα πεδία είναι υποχρεωτικά!';
        return; // Σταμάτα την εκτέλεση της συνάρτησης εδώ
    }
    
    // Απλός έλεγχος για το format του email
    if (!email.includes('@') || !email.includes('.')) {
        formErrorMsg.textContent = 'Παρακαλώ εισάγετε μια έγκυρη διεύθυνση email.';
        return;
    }

    // Αν όλα είναι ΟΚ
    formErrorMsg.textContent = ''; // Καθάρισε τα παλιά λάθη
    alert(`Ευχαριστούμε, ${name}! Το μήνυμά σας έχει ληφθεί.`);
    contactForm.reset();
});

Τώρα η φόρμα σου παρέχει άμεση και σαφή ανατροφοδότηση στον χρήστη.


Λειτουργία 3: Δυναμική γκαλερί εικόνων

Θα προσθέσουμε μια νέα ενότητα "Portfolio" και θα τη γεμίσουμε δυναμικά με εικόνες από ένα δωρεάν, ψεύτικο API.

Βήμα 1: HTML - Η Νέα Ενότητα Στο index.html, μέσα στο <main>, πρόσθεσε μια νέα ενότητα:

<section id="portfolio">
    <h2>Portfolio</h2>
    <div id="gallery-container">
        <!-- Οι εικόνες θα προστεθούν εδώ από την JS -->
    </div>
</section>

Βήμα 2: CSS - Styling του Πλέγματος (Grid) Θα χρησιμοποιήσουμε CSS Grid για ένα όμορφο, responsive πλέγμα. Στο style.css:

#gallery-container {
    display: grid;
    /* Αυτό είναι μαγικό: φτιάξε όσες στήλες χωράνε, με min πλάτος 150px */
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 15px;
}

#gallery-container img {
    width: 100%;
    height: 100%;
    object-fit: cover; /* Κάνει τις εικόνες να γεμίζουν το κουτί τους χωρίς να παραμορφώνονται */
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

Βήμα 3: JavaScript - Φόρτωση από το API Στο script.js, προσθέτουμε τη λογική για το API call:

const galleryContainer = document.getElementById('gallery-container');

async function loadPortfolioImages() {
    const apiUrl = 'https://jsonplaceholder.typicode.com/photos?_limit=9'; // Παίρνουμε 9 φωτογραφίες

    try {
        const response = await fetch(apiUrl);
        const images = await response.json();

        // Καθαρίζουμε τον container από παλιές εικόνες (αν υπάρχουν)
        galleryContainer.innerHTML = ''; 

        images.forEach(image => {
            // Δημιουργούμε ένα νέο img element για κάθε αντικείμενο από το API
            const imgElement = document.createElement('img');
            imgElement.src = image.thumbnailUrl; // Χρησιμοποιούμε το thumbnail
            imgElement.alt = image.title;

            // Το προσθέτουμε στο DOM
            galleryContainer.appendChild(imgElement);
        });
    } catch (error) {
        galleryContainer.textContent = 'Αποτυχία φόρτωσης των εικόνων.';
        console.error('Portfolio Error:', error);
    }
}

// Καλουμε τη συνάρτηση για να τρέξει όταν φορτώσει η σελίδα
loadPortfolioImages();

Αποθήκευση της Προόδου: Μην ξεχάσεις, μετά την ολοκλήρωση αυτών των λειτουργιών, να κάνεις ένα καλό commit στο Git!

git add .
git commit -m "Add dark mode, form validation, and dynamic gallery"
git push

Με την ολοκλήρωση αυτού του κεφαλαίου, έχεις μετατρέψει μια απλή στατική σελίδα σε μια πραγματική, μικρή web εφαρμογή. Έχεις αποδείξει ότι μπορείς να χειριστείς τα πιο συνηθισμένα tasks ενός front-end developer.

Τώρα είσαι απόλυτα έτοιμος/η να πάρεις όλες αυτές τις δεξιότητες και να τις εφαρμόσεις σε ένα project εντελώς από την αρχή, όπως η Εφαρμογή Καιρού του τελικού μας κεφαλαίου.