Προγραμματισμός

Αναδημοσίευση: Πειραματισμοί με CHECKBOXes και RADIOs

Τις προάλλες είχα μια ιδέα για το πως θα μπορούσα να φτιάξω custom checkbox και radio με CSS3. Έθεσα σαν προϋπόθεση να εξακολουθούν να λειτουργούν (προφανώς) αλλά και να παρουσιάζονται με αποδεκτό τρόπο σε χρήστες του Internet Explorer 7 και 8 (όπου δεν υποστηρίζονται CSS3) και στην πορεία έκανα μερικά πειράματα με Javascript. Για να δούμε πως πήγε…

(Η πρώτη δημοσίευση αυτού του άρθρου μου είναι στο wdf.gr)

Η βασική ιδέα ήταν να εκμεταλλευτώ το γεγονός ότι το label «μεταφέρει» το click στο input με το οποίο είναι συσχετισμένο. Ο συσχετισμός μπορεί να γίνει είτε με for attribute στο label και ομώνυμο id στο input, είτε βάζοντας το input μέσα στο label, όπως στο δικό μου παράδειγμα. Ορίστε και η υλοποίηση, ακολουθεί ο κώδικας HTML

<fieldset>
	<label>
		<input type="checkbox">
		<span>&nbsp;</span> Checkbox
	</label>
	<label>
		<input type="checkbox">
		<span>&nbsp;</span> Checkbox
	</label>
</fieldset>

<fieldset>
	<label>
		<input type="radio" name="group" checked>
		<span>&nbsp;</span> Radio
	</label>
	<label>
		<input type="radio" name="group">
		<span>&nbsp;</span> Radio
	</label>
	<label>
		<input type="radio" name="group">
		<span>&nbsp;</span> Radio
	</label>
	<label>
		<input type="radio" name="group">
		<span>&nbsp;</span> Radio
	</label>
</fieldset>

…τα σχετικά CSS…

label {
	display:inline-block;
	position:relative;
	cursor:pointer;
	outline:0 none;
	line-height:30px;
	padding:0 10px;
}
input {
	opacity:0;
	position:absolute;
}
span {
	position:absolute;
	left:0;
	top:0;
	width:100%;
	height:100%;
	z-index:-1;
	background-color:#ccc;
}
:checked + span {
	background-color:#f6bd0f;
}

…και τα conditional comments για Internet Explorer παλιότερο από 9.

<!--[if lt IE 9]>
<style>
input {
	position:relative;
}
</style>
<![endif]-->

Τα labels μου είναι μέσα σε fieldsets μόνο για ξεχωρίζουν. Το display θα πρέπει να είναι είτε inline-block είτε block, το position είναι απαραίτητο για να κρύψω το input και να τοποθετήσω το span, το οποίο είναι ένα άχρηστο element που απλώς κρατάει το χρώμα του background ή το γραφικό. Το input έχει position:absolute και opacity:0 για να μην πιάνει χώρο — όχι display:none, για να είναι «χρήσιμο» σε screen reader! Το span παίρνει τις διαστάσεις του label ενώ το z-index:-1 χρειάζεται για να μην καλύπτει το λεκτικό του label. Το μοναδικό CSS3 και αυτό που κάνει όλη τη δουλειά είναι το :checked που είναι ένα νέο pseudo-class και επιλέγει, τι άλλο, ότι είναι checked! Σε συνδυασμό με το γνωστό (ελπίζω) Adjacent Sibling Selector +, το span αλλάζει χρώμα ανάλογα με την κατάσταση του input. Η ενεργοποίηση των επιλογών με το πληκτρολόγιο δεν είναι εύκολη αφού δεν υπάρχει κάποια ένδειξη focus αλλά νομίζω θα μπορούσα να το λύσω με :focus CSS στο label. Τέλος, επειδή όλο αυτό δεν λειτουργεί σε Internet Explorer 8 και παλαιότερο, επέλεξα να χρησιμοποιήσω conditional comments για να ορίσω position:relative στο input ώστε να κάθεται σωστά, ενώ το opacity:0 έτσι κι αλλιώς αγνοείται. Αν αυτό είναι αποδεκτό fallback (ή graceful degradation αν προτιμάτε), μπορείτε να σταματήσετε να διαβάζετε!

Ας προσθέσουμε Javascript

Αν πρέπει οπωσδήποτε το site να φαίνεται το ίδιο και σε IE 7,8 (καθόλου πρωτοφανές, μου συμβαίνει κάθε μέρα), τότε θα πρέπει να χρησιμοποιήσετε Javascript. Όχι jQuery ούτε mootools ούτε όποια άλλη βιβλιοθήκη της αρεσκείας σας. Λίγες γραμμές DOM scripting αρκούν! Προφανώς είναι ευκολότερο με jQuery αλλά δε νομίζω να χρειάζεστε 90 kilobytes Javascript για 20 γραμμές κώδικα (περίπου)… Το παρακάτω κομμάτι κώδικα πρέπει να μπει αμέσως πριν το </body> και θα εκτελεστεί από Internet Explorer 8 και πίσω…

<!--[if lt IE 9]>
<script>
// βρες όλα τα INPUTs
var inputs=document.getElementsByTagName("input");
// για κάθε ένα από αυτά
for (var i=0; i<inputs.length; i++) {
	// αν είναι CHECKBOX ή RADIO
	if (inputs[i].type=="checkbox" || inputs[i].type=="checkbox") {
		// όταν κάνω click
		inputs[i].onclick=function(){
			// αν είναι RADIO
			if (this.type == "radio") {
				// βρες το NAME
				var groupName=this.name;
				// έλενξε όλα τα INPUTs
				for (var j=0; j<inputs.length; j++) {
					// για το ίδιο NAME
					if (inputs[j].name == groupName){
						// και αφαίρεσε το CLASS από το SPAN που είναι παιδί του ίδιου parent
						// δεν χρησιμοποιώ nextSibling διότι, ενδέχεται, κάποιο whitespace να υπολογιστεί σαν element
						inputs[j].parentNode.getElementsByTagName("span")[0].className="";
					}
				}
			}
			// αν «άναψε»
			if (this.checked) {
				// βάλε το class στο SPAN
				this.parentNode.getElementsByTagName("span")[0].className="checked";
			} else {
				// αλλιώς καθάρισέ το
				this.parentNode.getElementsByTagName("span")[0].className="";
			}
		}
	}
}
</script>
<![endif]-->

…ενώ θα πρέπει να αλλάξουμε και τα CSS των IE.

<!--[if lt IE 9]>
<style>
input {
	left:-999em;
}
span.checked {
	background-color:#f6bd0f;
}
</style>
<![endif]-->

Ο Javascript κώδικας έχει σχόλιο σε κάθε γραμμή για να καταλάβετε τι ακριβώς συμβαίνει. Επισημαίνω πως βρίσκεται στο τέλος της σελίδας ώστε για να εκτελεστεί αφού θα έχουν φορτωθεί τα inputs, μέσα σε conditional comment που απευθύνεται σε IE μικρότερο (παλαιότερο) από version 9. Οι αλλαγές στο CSS αφορούν στη θέση του input, που φεύγει από την οθόνη με αρνητικό αριστερό άκρο για να μην φαίνεται το outline του σε IE 7, ενώ προστίθεται ένας κανόνας που δίνει στα span.checked τα στυλ που θα έπαιρναν αν υποστηρίζονταν τα CSS3 (:checked + span { background-color:#f6bd0f; }). Το ίδιο το class .checked προσθαφαιρείται με Javascript. Για μισό λεπτάκι όμως…

…διότι, τελικά, έχουμε γράψει παραπάνω κώδικα απ' όσο έπρεπε ενώ χρησιμοποιούμε και ένα άχρηστο tag για κάθε input που έχει η σελίδα — not good! Γιατί να μην το κάνουμε εξ ολοκλήρου με Javascript, σύμφωνα με την αρχή Progressive enhancement? θα έχουμε λιγότερη HTML, λιγότερα CSS, καθόλου conditional comments ενώ η σελίδα μας θα φαίνεται το ίδιο σε κάθε (desktop) browser και -το σημαντικότερο- θα δουλεύει παντού! Μπορούμε είτε να χρησιμοποιήσουμε DOM scripting αν δεν θέλουμε πολύ όγκο (με μια-δυο επιπλέον ρουτίνες για να γίνει πιο ευέλικτο το script) είτε να χρησιμοποιήσουμε κάποια βιβλιοθήκη που ναι μεν προσθέτει πολλά (πολλά!) kilobytes αλλά μπορεί να μας λύσει τα χέρια.

mootools

window.addEvent('domready', function() {
	// βρες όλα τα INPUTs
	var inputs=$$('input[type="checkbox"], input[type="radio"]');
	// για κάθε ένα από αυτά
	inputs.each(function(el){
		// εδώ που φτάσαμε, κρύψε όλα τα INPUTs
		el.addClass('hidden');
		// αν είναι ήδη ενεργοποιημένα
		if (el.getProperty('checked')) {
			// πρόσθεσε το class checked στο parent LABEL
			el.getParent('label').addClass('checked');
		}
		// όταν κάνω click
		el.addEvent('click', function(e){
			// αν είναι RADIO
			if (el.getProperty('type')=='radio') {
				// φιλτράρισε από όλα τα INPUTs εκείνα που ανήκουν στην ίδια ομάδα
				inputs.filter('input[name="'+ el.getProperty('name') +'"]').each(function(sameGroup){
					// αφαίρεσε το .checked από το parent LABEL
					sameGroup.getParent('label').removeClass('checked');
				});
			}
			// αν με το click «άναψε»
			if (el.getProperty('checked')) {
				// πρόσθεσε το class checked στο parent LABEL
				el.getParent('label').addClass('checked');
			} else {
				// αλλιώς αφαίρεσέ το
				el.getParent('label').removeClass('checked');
			}
		});
	});
});

Η ίδια σελίδα με mootools…

jQuery

$(document).ready(function() {
	// βρες όλα τα INPUTs
	var inputs=$('input[type="checkbox"], input[type="radio"]');
	// κρύψε όλα τα INPUTs
	$(inputs).addClass('hidden');
	// για κάθε ένα από αυτά
	$(inputs).each(function(){
		// αν είναι ήδη ενεργοποιημένα
		if ($(this).is(":checked")) {
			// πρόσθεσε το class checked στο parent LABEL
			$(this).parent('label').addClass('checked');
		}
		// όταν κάνω click
		$(this).click(function(){
			// αν είναι RADIO
			if ($(this).attr('type')=='radio') {
				// φιλτράρισε από όλα τα INPUTs εκείνα που ανήκουν στην ίδια ομάδα και αφαίρεσε το .checked από το parent LABEL
				$(inputs).filter('input[name="'+ $(this).attr('name') +'"]').parent().removeClass('checked');
			}
			// αν με το click «άναψε»
			if (this.checked) {
				// πρόσθεσε το class checked στο parent LABEL
				$(this).parent('label').addClass('checked');
			} else {
				// αλλιώς αφαίρεσέ το
				$(this).parent('label').removeClass('checked');
			}
		});
	});
});

και με jQuery.

Συγκρίνοντας τις διαφορετικές υλοποιήσεις Javascript (DOM scripting, mootools, jQuery) παρατηρούμε πως ο κώδικας δεν διαφέρει ιδιαίτερα, ούτε σε όγκο ούτε στις ενέργειες που κάνουμε σε κάθε βήμα. Οι βιβλιοθήκες Javascript γενικά μας επιτρέπουν να γράφουμε πιο συγκεκριμένο, στιβαρό κώδικα, έχοντας ενσωματωμένες λειτουργίες όπως η addEvent και η addClass και πιο έξυπνους / ευέλικτους τρόπους να βρίσκουμε τα tags που θέλουμε. Το «κόστος» είναι στον όγκο της ίδιας της βιλιοθήκης που ίσως να αξίζει τον κόπο, ίσως όχι. Η minified jQuery 1.6 «ζυγίζει» 89 kilobytes ενώ ένα custom build της mootools 1.3.2 που κατέβασα για την περίσταση είναι 55 kilobytes. Αμελητέα τα μεγέθη από μόνα τους, αλλά λίγο-λίγο προστίθονται. Εννοείται πως κάθε ρουτίνα από αυτές που έγραψα μπορεί να γραφτεί συντομότερα, κομψότερα ή/και πιο «έξυπνα» αλλά σκοπός του άρθρου δεν ήταν να μάθουμε Javascript. Εκείνο που ήθελα να πω είναι πως κάθε πρόβλημα που θα αντιμετωπίσετε κατά το development πιθανότατα έχει παραπάνω από μία λύσεις και πρέπει να διαλέγετε την καταλληλότερη!

Σχολιάστε

Η ηλ. διεύθυνσή σας δεν κοινοποιείται. Τα υποχρεωτικά πεδία σημειώνονται με *

Επιτρέπονται τα εξής στοιχεία και ιδιότητες HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>