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

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

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

Φίλτρο και λειτουργία group

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

id categoryId name
1 1 Apple
2 1 Μπανάνα
3 2 PHP
4 3 Πράσινο
5 3 Κόκκινο
6 3 Μπλε

Μια απλή λίστα με όλα τα στοιχεία χρησιμοποιώντας ένα πρότυπο 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

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

categoryId name
1 Φρούτα
2 Γλώσσες
3 Χρώματα

Φορτώνουμε δεδομένα από τον πίνακα items χρησιμοποιώντας την εντολή $items = $db->table('items') του Nette Database Explorer. Κατά την επανάληψη αυτών των δεδομένων, έχουμε τη δυνατότητα όχι μόνο να έχουμε πρόσβαση σε χαρακτηριστικά όπως τα $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} σηματοδοτεί το εσωτερικό μέρος, δηλαδή τα μεμονωμένα στοιχεία. Η συνθήκη στην ετικέτα end λέει ότι η επανάληψη θα συνεχιστεί εφόσον το τρέχον και το επόμενο στοιχείο ανήκουν στην ίδια κατηγορία ($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