jQuery Events: Σταμάτα την (κατα)χρήση του Return False

SHARE THE ❤️

SHARE THE ❤️

Profile picture for user Nick Galatis
Last Update: • Reading Time
Ετικέτες:

Όταν ξεκινάς να μαθαίνεις τα jQuery events , το πρώτο πράγμα που μαθαίνεις είναι το πως να ακυρώνεις την προκαθορισμένη συμπεριφορά του browser. Για παράδειγμα ένα πρώτο tutorial για αρχάριους μπορεί να είναι σαν αυτό:

$("a.toggle").click(function () {
    $("#mydiv").toggle();
    return false; // Prevent browser from visiting `#`
});

Η παραπάνω συνάρτηση εμφανίζει και κρύβει το στοιχείο #mydiv, και μετά ακυρώνει την προκαθορισμένη συμπεριφορά του browser, να επισκεφθεί το href του anchor tag.

Σε αυτά τα πρώτα tutorials γίνεται το κακό και αρχίζουμε και διαμορφώνουμε κακές συνήθειες. Έτσι πολλοί χρήστες συνηθίζουν και χρησιμοποιούν το return false όπου θέλουν να ακυρώσουν την αναμενόμενη συμπεριφορά του browser.
Σε αυτό το άρθρο θα αναλύσουμε 2 πολύ σημαντικά θέματα σχετικά με την ακύρωση των events που όλοι θέλουμε και χρησιμοποιούμε στον κώδικά μας.

  • Πως να χρησιμοποιήσεις την σωστή μέθοδο για κάθε δουλειά: return false vs preventDefault vs stopImmediatePropagation
  • Αρχή, τέλος ή κάπου στη μέση: σε ποιο σημείο είναι καλύτερο να ακυρώνεις την προκαθορισμένη συμπεριφορά του browser

Σημείωση: Στο άρθρο αναφερόμαστε και στο event bubbling, δηλαδή πως πολλά event θα χτυπήσουν το αρχικό DOM element και μετά θα χτυπήσουν και όλα τα προηγούμενα (parent) elements στο δέντρο του DOM. Τα events δεν κάνουν bubble σε συγγενικά elements ή στα επόμενα γνωστά και ως παιδιά τους (children elements). Όταν τα event κάνουν bubble προς τα κάτω (στα παιδιά) τότε αυτό ονομάζεται event capturing. Μπορείτε να διαβάσετε περισσότερα εδώ: https://medium.com/@vsvaibhav2016/event-bubbling-and-event-capturing-in-javascript-6ff38bec30e 

Χρησιμοποίησε το σωστό εργαλείο (μέθοδο), για την δουλειά

Ο κύριος λόγος που γίνεται όλη αυτή η κατάχρηση στο return false είναι γιατί με την πρώτη ματιά, φαίνεται να δουλεύει. Οι σύνδεσμοι (link callbacks) φαίνεται ότι δεν δουλεύουν, τα submit των φορμών δεν κάνουν submit τις φόρμες πλέον, άρα μια χαρά γιατί να μας χαλάει αυτό;

ΤΙ ΚΑΝΕΙ ΤΟ RETURN FALSE ΣΤΗΝ ΠΡΑΓΜΑΤΙΚΟΤΗΤΑ

Μόλις καλέσεις το return false κάνει 3 διαφορετικά πράγματα:

  1. event.preventDefault();
  2. event.stopPropagation();
  3. Σταματάει την εκτέλεση του callback και επιστρέφει άμεσα.

“Ώπα ώπα! Εγώ απλά ήθελα να ακυρώσω την αναμενόμενη συμπεριφορά του browser, δεν θέλω τα άλλα 2... νομίζω.”
Στην πραγματικότητα μόνο μια από τις 3 αυτές ενέργειες χρειάζεται για να ακυρωθεί η αναμενόμενη συμπεριφορά του browser, το preventDefault(). Αν δεν ήθελες συγκεκριμένα να σταματήσεις την διάδοση (propagation) του event (bubbling), τότε η χρήση του return false θα κάνει τον κώδικα σου πολύ εύθραυστο... Ας δούμε πως η κατάχρηση κάνει τη ζημιά σε ένα πιθανό σενάριο

Η HTML του πιθανού μας σεναρίου:

<div class="post">
    <h2><a href="/path/to/page">My Page</a></h2>
    <div class="content">
        Teaser text...
    </div>
</div>
<div class="post">
    <h2><a href="/path/to/other_page">My Other Page</a></h2>
    <div class="content">
        Teaser text...
    </div>
</div>

Ας υποθέσουμε τώρα ότι θέλουμε το άρθρο να φορτώνει μέσα στο αντίστοιχο div.content όταν ο χρήστης πατάει στον αντίστοιχο τίτλο:

jQuery(document).ready(function ($) {
   $("div.post h2 a").click(function () {
      var a    = $(this),
          href = a.attr('href'), // Let jQuery normalize `href`,
          content  = a.parent().next();
      content.load(href + " #content");
      return false; // "cancel" the default behavior of following the link
   });
});

Όλα καλά (μέχρι εδώ) και η δυναμική σελίδα μας είναι έτοιμη. Αργότερα αποφασίζουμε ότι θέλουμε να προσθέσουμε και μια κλάση "active" στο div.post που πατήθηκε (ή όταν ένα παιδικό element του, πατήθηκε) πιο πρόσφατα. Οπότε προσθέτουμε τον παρακάτω κώδικα:

// Inside Document Ready:
var posts = $("div.post");
posts.click(function () {
    // Remove active from all div.post
    posts.removeClass("active");
    // Add it back to this one
    $(this).addClass("active");
});

Άρα θα πρέπει να δουλέψει όταν πατήσουμε στον τίτλο; ΟΧΙ! Ο λόγος που δεν θα δουλέψει είναι γιατί χρησιμοποιήσαμε return false στο click event αντί να χρησιμοποιήσουμε αυτό που πραγματικά χρειαζόμασταν! Γιατί το return false που χρησιμοποιήσαμε σημαίνει όπως είπαμε παραπάνω event.preventDefault(); event.stopPropagation(); κι έτσι το click event δεν πέρασε ποτέ στα προηγούμενα (parent) elements (bubbling), δηλαδή το div.post, κι έτσι το νέο μας event δεν καλέστηκε ποτέ.

Το πρόβλημα γίνεται ακόμα μεγαλύτερο όταν μπερδέψεις κανονικά events με live ή delegate events:

$("a").click(function () {
    // do something
    return false;
});
 
$("a").live("click", function () {
    // THIS WON'T FIRE
});

ΤΙ ΑΚΡΙΒΩΣ ΘΕΣ ΝΑ ΚΑΝΕΙΣ ΤΕΛΙΚΑ;

preventDefault()
Στην συντριπτική πλειοψηφία των περιπτώσεων που θα χρησιμοποιούσες return false αυτό που πραγματικά θες να χρησιμοποιήσεις είναι το e.preventDefault(). Χρησιμοποιώντας το preventDefault επιβάλλεται να βάλεις την παράμετρο του event να καλεστεί στο callback σου (στο παράδειγμα, χρησιμοποιώ το e):


$("a").click(function (e) {
     // e == our event data
     e.preventDefault();
 });
Thumbnail

Το συγκεκριμένο κάνει όλα όσα θέλεις χωρίς να απαγορεύει στα γονεϊκά event να παίρνουν αυτά τα events κι αυτά. Όσο λιγότερους περιορισμούς βάζεις στον κώδικα σου τόσο πιο ευλύγιστος θα είναι και φυσικά ποιο εύκολος να τον κάνεις maintain.

stopPropagation()
Κάποιες φορές μπορεί να θες να σταματήσεις και το propagation. Πάρε το παρακάτω παράδειγμα:

<div class="post">
    Normal text and then a <a href="/path">link</a> and then more text.
</div>

Ας υποθέσουμε ότι θες να γίνεται κάτι όταν πατάς οπουδήποτε πάνω στο div εκτός από το link, το οποίο θες να δουλεύει κανονικά όταν το πατάει ο χρήστης. (Ξέρουμε ότι είναι ατυχές το παράδειγμα για την χρηστικότητα, μην μας την πέσετε οι UX τύποι που διαβάζετε, είναι απλά ένα παράδειγμα. Πιθανότατα δεν θες να γίνεται κάτι διαφορετικό αν για κάποιο λόγο ο χρήστης πατήσει κατά λάθος λίγο παραδίπλα από το link)

$("div.post").click(function () {
   // Do the first thing;
});
 
$("div.post a").click(function (e) {
    // Don't cancel the browser's default action
    // and don't bubble this event!
    e.stopPropagation();
});

Εδώ αν χρησιμοποιούσαμε return false ούτε το click event του div θα έπαιζε, ούτε το link θα δούλευε σωστά.

stopImmediatePropagation()
Αυτή η μέθοδος σταματάει την οποιαδήποτε περαιτέρω εκτέλεση του event ακόμα και σε άλλους handlers που πιάνονται στο ίδιο αντικείμενο. Όλα τα event που πιάνουν σε ένα συγκεκριμένο αντικείμενο θα χτυπηθούν με την σειρά που γράφτηκαν. Δες το παρακάτω παράδειγμα:

$("div a").click(function () {
   // Do something
});
 
$("div a").click(function (e) {
   // Do something else
   e.stopImmediatePropagation();
});
 
$("div a").click(function () {
   // THIS NEVER FIRES
});
 
$("div").click(function () {
   // THIS NEVER FIRES
});

Ναι, εντάξει, το παραπάνω παράδειγμα είναι στημένο. Παρόλα αυτά η κατάσταση είναι αληθινή και μπορεί να συμβεί. Όσο γράφεις περισσότερο κώδικα και πιο abstract κώδικα, διαφορετικά widget και plugin μπορεί να τοποθετούν events στα ίδια αντικείμενα που δουλεύεις κι εσύ στον δικό σου κώδικα. Άρα πρέπει να συμφωνήσουμε ότι είναι σημαντικό να καταλάβεις πως χρησιμοποιείται και τι κάνει το stopImmediatePropagation γιατί όταν θα χρειαστείς αυτή τη λειτουργικότητα θα με θυμηθείς!

return false
Γενικά δεν υπάρχει λόγος να χρησιμοποιείς το return false, γιατί όπως είδαμε παραπάνω υπάρχουν πιο σωστές εναλλακτικές. Χρησιμοποίησε το μόνο στην περίπτωση που θες και το preventDefault() και το stopPropagation() και ξέρεις τι κάνεις και τι θες ο κώδικας σου να κάνει, διαφορετικά είναι άλλη μια κακιά συνήθεια. Σε όλους τους υπόλοιπους ειδικά τους νέους προγραμματιστές συνιστούμε την χρήση των παραπάνω εναλλακτικών.

Πάνω, Κάτω ή Κάπου στη Μέση

Πριν διαβάσεις αυτό, που έκανες κατάχρηση στο return false, έπρεπε να το βάζεις πάντα στο τέλος της συνάρτησης ή στο τέλος της λογικής του κώδικα σου όπου ήθελες να σταματήσεις περαιτέρω εκτέλεση του κώδικα. Με το e.preventDefault έχουμε περισσότερες επιλογές. Μπορεί να καλεστεί οποιαδήποτε στιγμή μέσα σε ένα function με ελαφρώς διαφορετικά αποτελέσματα. Ας δούμε που πρέπει να το τοποθετείς.

  1. Κατά το development, σχεδόν πάντα πρέπει να είναι η πρώτη σειρά. Το τελευταίο που θέλεις είναι ψάχνεσαι γιατί η φόρμα που προσπαθείς να καταχωρήσεις με AJAX να κάνει submit σε άλλη σελίδα.

  2. Στο production, αν ακολουθείς τεχνική progressive enhancement, βάζε το στο τέλος του callback, ή στην αρχή του execution flow. Αν βελτιώνεις μια κανονική σελίδα τότε τα event σου έχουν fallbacks από την πλευρά του back end server για browsers χωρίς Javascript. Τα οφέλη εδώ είναι περισσότερα όταν ο browser έχει Javascript και πετάει κάποιο error. Δες το παράδειγμα και θα καταλάβεις:

    var data = {};
    
    	$("a").click(function (e) {
    
    	    e.preventDefault(); // cancel default behavior
    
    	    // Throws an error because `my` is undefined
    
    	    $("body").append(data.my.link);
    
    	    // The original link doesn't work AND the "cool"
    
    	    // JavaScript has broken. The user is left with NOTHING!
    
    	});

    Τώρα δες πως γίνεται αν βάλεις το preventDefault στο τέλος: 

    var data = {};
    
    	$("a").click(function (e) {
    
    	    // Throws an error because `my` is undefined
    
    	    $("body").append(data.my.link);
    
    	    // This line is never reached, and your website
    
    	    // falls back to using the `href` instead of this
    
    	    // "cool" broken JavaScript!
    
    	    e.preventDefault(); // cancel default behavior
    
    	});

    Το ίδιο εφαρμόζεται και σε form submit event, εφόσον έχεις ένα σωστό fallback στημένο στο backend. Ποτέ μην στηρίζεσαι στο ότι ο κώδικας σου θα δουλεύει πάντα. Πάντα να κρατάς μια πισινή με ένα καλό fallback. Καλύτερα να έχεις μια εναλλακτική από το να πιστεύεις ότι ο κώδικας σου δε θα βγάλει ποτέ σφάλμα!

  3. Στο production, αν η λειτουργικότητα είναι μόνο JavaScript, κράτα τη κλήση στην πρώτη γραμμή. Βασικά δεν είναι απαραίτητο να είναι η πρώτη γραμμή της συνάρτησης αλλά θα πρέπει να έρχεται όσο πιο νωρίς ταιριάζει στην λογική του προγράμματος σου.
    Το concept είναι: Αν το συγκεκριμένο κομμάτι λειτουργικότητας προστέθηκε με JavaScript, τότε δεν είναι απαραίτητο το fallback. Σε αυτή τη περίπτωση το να το έχεις στην αρχή θα σε γλιτώσει από τυχαίους # χαρακτήρες στο url και πηδήματα πάνω κάτω της σελίδας. Προφανώς φρόντισε όσο καλύτερα μπορείς τα σφάλματα για να είσαι σίγουρος ότι οι χρήστες σου δεν μένουν με άδεια χέρια στις προσπάθειες τους!

Κλείνοντας

Ελπίζω αυτό το άρθρο να έριξε λίγο φως στο return false και στο να πάρεις την σωστή απόφαση όταν ακυρώνεις events.
Να θυμάσαι να χρησιμοποιείς το return false μόνο όταν το χρειάζεσαι πραγματικά και να προσέχεις να ακυρώνεις τις προεπιλεγμένες λειτουργίες του browser στις σωστές χρονικές στιγμές μέσα στον κώδικα σου. Πάντα να δουλεύεις ώστε να έχεις όσο πιο ευέλικτο και ποιοτικό κώδικα μπορείς και σταμάτα επιτέλους το return false! ?

Πηγή: http://bit.ly/2LlfjRp

If you found the article useful, help us spread the word! (just click, it's free!)
SHARE THE ❤️


This article has: ... comments. View them and add yours! Open Comments