Όλα όσα θέλατε ποτέ να μάθετε για την ομαδοποίηση

Όταν εργάζεστε με δεδομένα σε πρότυπα, μπορεί συχνά να συναντήσετε την ανάγκη να τα ομαδοποιήσετε ή να τα εμφανίσετε συγκεκριμένα σύμφωνα με ορισμένα κριτήρια. Το Latte προσφέρει πολλά ισχυρά εργαλεία για αυτόν τον σκοπό.

Το φίλτρο και η συνάρτηση |group επιτρέπουν την αποτελεσματική ομαδοποίηση δεδομένων σύμφωνα με ένα καθορισμένο κριτήριο, το φίλτρο |batch διευκολύνει τη διαίρεση δεδομένων σε σταθερές παρτίδες, και η ετικέτα {iterateWhile} παρέχει τη δυνατότητα πιο σύνθετου ελέγχου της ροής των βρόχων με συνθήκες. Κάθε μία από αυτές τις ετικέτες προσφέρει συγκεκριμένες δυνατότητες για εργασία με δεδομένα, καθιστώντας τα απαραίτητα εργαλεία για δυναμική και δομημένη εμφάνιση πληροφοριών στα πρότυπα του Latte.

Φίλτρο και συνάρτηση group

Φανταστείτε έναν πίνακα βάσης δεδομένων items με στοιχεία χωρισμένα σε κατηγορίες:

id categoryId name
1 1 Apple
2 1 Banana
3 2 PHP
4 3 Green
5 3 Red
6 3 Blue

Μια απλή λίστα όλων των στοιχείων χρησιμοποιώντας ένα πρότυπο Latte θα έμοιαζε ως εξής:

<ul>
{foreach $items as $item}
	<li>{$item->name}</li>
{/foreach}
</ul>

Ωστόσο, αν θέλαμε τα στοιχεία να οργανωθούν σε ομάδες ανά κατηγορία, θα χρειαζόταν να τα χωρίσουμε έτσι ώστε κάθε κατηγορία να έχει τη δική της λίστα. Το αποτέλεσμα θα έπρεπε τότε να μοιάζει ως εξής:

<ul>
	<li>Apple</li>
	<li>Banana</li>
</ul>

<ul>
	<li>PHP</li>
</ul>

<ul>
	<li>Green</li>
	<li>Red</li>
	<li>Blue</li>
</ul>

Η εργασία μπορεί να λυθεί εύκολα και κομψά χρησιμοποιώντας το |group. Ως παράμετρο, καθορίζουμε το categoryId, που σημαίνει ότι τα στοιχεία θα χωριστούν σε μικρότερους πίνακες με βάση την τιμή του $item->categoryId (αν το $item ήταν πίνακας, θα χρησιμοποιούνταν το $item['categoryId']):

{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
	<ul>
		{foreach $categoryItems as $item}
			<li>{$item->name}</li>
		{/foreach}
	</ul>
{/foreach}

Το φίλτρο μπορεί επίσης να χρησιμοποιηθεί ως συνάρτηση στο Latte, δίνοντάς μας μια εναλλακτική σύνταξη: {foreach group($items, categoryId) ...}.

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

{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
	...
{/foreach}

Είναι σημαντικό να σημειωθεί ότι το $categoryItems δεν είναι ένας συνηθισμένος πίνακας, αλλά ένα αντικείμενο που συμπεριφέρεται σαν επαναλήπτης. Για πρόσβαση στο πρώτο στοιχείο της ομάδας, μπορείτε να χρησιμοποιήσετε τη συνάρτηση first().

Αυτή η ευελιξία στην ομαδοποίηση δεδομένων καθιστά το group ένα εξαιρετικά χρήσιμο εργαλείο για την παρουσίαση δεδομένων στα πρότυπα του Latte.

Ένθετοι Βρόχοι

Ας φανταστούμε ότι έχουμε έναν πίνακα βάσης δεδομένων με μια επιπλέον στήλη subcategoryId, η οποία ορίζει τις υποκατηγορίες των μεμονωμένων στοιχείων. Θέλουμε να εμφανίσουμε κάθε κύρια κατηγορία σε μια ξεχωριστή λίστα <ul> και κάθε υποκατηγορία σε μια ξεχωριστή ένθετη λίστα <ol>:

{foreach ($items|group: categoryId) as $categoryItems}
	<ul>
		{foreach ($categoryItems|group: subcategoryId) as $subcategoryItems}
			<ol>
				{foreach $subcategoryItems as $item}
					<li>{$item->name}
				{/foreach}
			</ol>
		{/foreach}
	</ul>
{/foreach}

Σύνδεση με τη Nette Database

Ας δείξουμε πώς να χρησιμοποιήσετε αποτελεσματικά την ομαδοποίηση δεδομένων σε συνδυασμό με τη Nette Database. Ας υποθέσουμε ότι εργαζόμαστε με τον πίνακα items από το εισαγωγικό παράδειγμα, ο οποίος συνδέεται μέσω της στήλης categoryId με αυτόν τον πίνακα categories:

categoryId name
1 Fruits
2 Languages
3 Colors

Φορτώνουμε τα δεδομένα από τον πίνακα items χρησιμοποιώντας τον Nette Database Explorer με την εντολή $items = $db->table('items'). Κατά την επανάληψη πάνω σε αυτά τα δεδομένα, έχουμε τη δυνατότητα να αποκτήσουμε πρόσβαση όχι μόνο σε χαρακτηριστικά όπως $item->name και $item->categoryId, αλλά, χάρη στη σύνδεση με τον πίνακα categories, και στη σχετική γραμμή σε αυτόν μέσω του $item->category. Αυτή η σύνδεση μπορεί να χρησιμοποιηθεί για να επιδείξει μια ενδιαφέρουσα χρήση:

{foreach ($items|group: category) as $category => $categoryItems}
	<h1>{$category->name}</h1>
	<ul>
		{foreach $categoryItems as $item}
			<li>{$item->name}</li>
		{/foreach}
	</ul>
{/foreach}

Σε αυτή την περίπτωση, χρησιμοποιούμε το φίλτρο |group για ομαδοποίηση με βάση τη συνδεδεμένη γραμμή $item->category, όχι μόνο με βάση τη στήλη categoryId. Χάρη σε αυτό, η μεταβλητή κλειδί περιέχει απευθείας το ActiveRow της δεδομένης κατηγορίας, επιτρέποντάς μας να εκτυπώσουμε απευθείας το όνομά της χρησιμοποιώντας {$category->name}. Αυτό είναι ένα πρακτικό παράδειγμα του πώς η ομαδοποίηση μπορεί να κάνει τα πρότυπα πιο σαφή και να διευκολύνει την εργασία με δεδομένα.

Φίλτρο |batch

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

Ας φανταστούμε ότι έχουμε μια λίστα στοιχείων και θέλουμε να τα εμφανίσουμε σε λίστες, όπου κάθε μία περιέχει το πολύ τρία στοιχεία. Η χρήση του φίλτρου |batch είναι πολύ πρακτική σε μια τέτοια περίπτωση:

<ul>
{foreach ($items|batch: 3) as $batch}
	{foreach $batch as $item}
		<li>{$item->name}</li>
	{/foreach}
{/foreach}
</ul>

Σε αυτό το παράδειγμα, η λίστα $items χωρίζεται σε μικρότερες ομάδες, όπου κάθε ομάδα ($batch) περιέχει έως και τρία στοιχεία. Κάθε ομάδα εμφανίζεται στη συνέχεια σε μια ξεχωριστή λίστα <ul>.

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

{foreach ($items|batch: 3, '—') as $batch}
	...
{/foreach}

Ετικέτα {iterateWhile}

Θα δείξουμε τις ίδιες εργασίες που λύσαμε με το φίλτρο |group χρησιμοποιώντας την ετικέτα {iterateWhile}. Η κύρια διαφορά μεταξύ των δύο προσεγγίσεων είναι ότι το group επεξεργάζεται και ομαδοποιεί πρώτα όλα τα δεδομένα εισόδου, ενώ το {iterateWhile} ελέγχει τη ροή των βρόχων με συνθήκες, οπότε η επανάληψη γίνεται σταδιακά.

Πρώτα, θα αποδώσουμε τον πίνακα με τις κατηγορίες χρησιμοποιώντας το iterateWhile:

{foreach $items as $item}
	<ul>
		{iterateWhile}
			<li>{$item->name}</li>
		{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
	</ul>
{/foreach}

Ενώ το {foreach} σηματοδοτεί το εξωτερικό μέρος του βρόχου, δηλαδή την απόδοση των λιστών για κάθε κατηγορία, η ετικέτα {iterateWhile} σηματοδοτεί το εσωτερικό μέρος, δηλαδή τα μεμονωμένα στοιχεία. Η συνθήκη στην τελική ετικέτα λέει ότι η επανάληψη θα συνεχιστεί εφόσον το τρέχον και το επόμενο στοιχείο ανήκουν στην ίδια κατηγορία ($iterator->nextValue είναι το επόμενο στοιχείο).

Αν η συνθήκη ίσχυε πάντα, τότε όλα τα στοιχεία θα αποδίδονταν στον εσωτερικό βρόχο:

{foreach $items as $item}
	<ul>
		{iterateWhile}
			<li>{$item->name}
		{/iterateWhile true}
	</ul>
{/foreach}

Το αποτέλεσμα θα μοιάζει ως εξής:

<ul>
	<li>Apple</li>
	<li>Banana</li>
	<li>PHP</li>
	<li>Green</li>
	<li>Red</li>
	<li>Blue</li>
</ul>

Ποιο είναι το όφελος από μια τέτοια χρήση του iterateWhile; Όταν ο πίνακας είναι κενός και δεν περιέχει στοιχεία, το κενό <ul></ul> δεν θα εκτυπωθεί.

Αν καθορίσουμε μια συνθήκη στην αρχική ετικέτα {iterateWhile}, η συμπεριφορά αλλάζει: η συνθήκη (και η μετάβαση στο επόμενο στοιχείο) εκτελείται στην αρχή του εσωτερικού βρόχου, όχι στο τέλος. Έτσι, ενώ πάντα εισερχόμαστε στο {iterateWhile} χωρίς συνθήκη, εισερχόμαστε στο {iterateWhile $cond} μόνο εάν η συνθήκη $cond πληρούται. Και ταυτόχρονα, το επόμενο στοιχείο γράφεται στο $item.

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

<h1>Apple</h1>
<ul>
	<li>Banana</li>
</ul>

<h1>PHP</h1>
<ul>
</ul>

<h1>Green</h1>
<ul>
	<li>Red</li>
	<li>Blue</li>
</ul>

Θα τροποποιήσουμε τον αρχικό κώδικα έτσι ώστε να αποδώσουμε πρώτα το πρώτο στοιχείο και στη συνέχεια, στον εσωτερικό βρόχο {iterateWhile}, να αποδώσουμε τα υπόλοιπα στοιχεία από την ίδια κατηγορία:

{foreach $items as $item}
	<h1>{$item->name}</h1>
	<ul>
		{iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
			<li>{$item->name}</li>
		{/iterateWhile}
	</ul>
{/foreach}

Μέσα σε έναν μόνο βρόχο, μπορούμε να δημιουργήσουμε πολλαπλούς εσωτερικούς βρόχους και ακόμη και να τους ενθέσουμε. Με αυτόν τον τρόπο, θα μπορούσαμε να ομαδοποιήσουμε υποκατηγορίες, κ.λπ.

Ας υποθέσουμε ότι υπάρχει μια άλλη στήλη subcategoryId στον πίνακα και εκτός από το ότι κάθε κατηγορία θα βρίσκεται σε ξεχωριστό <ul>, κάθε υποκατηγορία θα βρίσκεται σε ξεχωριστό <ol>:

{foreach $items as $item}
	<ul>
		{iterateWhile}
			<ol>
				{iterateWhile}
					<li>{$item->name}
				{/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId}
			</ol>
		{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
	</ul>
{/foreach}
έκδοση: 3.0