הרעיון של פונקציה או רכיב ברקורסיה אומר במילים אחרות שעל פי תנאי מסויים הוא קורא לעצמו שוב ושוב עד שהתנאי לא מתקיים יותר.
נכון שאפשר לבצע רקורסיה ללא תנאי, אבל אז הרקורסיה תהיה אין-סופית וזה לא משהו שאנחנו רוצים בו.
בפוסט הזה נראה איך ליצר רכיב אקורדיון שעל פי המידע שמגיע אליו, הוא ידע לייצר עוד ועוד אקורדיון אחד בתוך השני.
הקוד:
<div class="recursive-accordion"> <ul class="main-list"> <li *ngFor="let accDataItem of accordionData;let idx=index" [ngClass]="{'active':onActiveAccBtn(idx)}"> <h3 class="acc-item-title-h3"> <button [attr.aria-expanded]="onActiveAccBtn(idx)" class="acc-item-title-btn shadow-outline" (click)="onToggleContentFn($event,idx)">{{accDataItem.headerText}} <img src="assets/icons/Arrow_Right.svg" alt="arrow right" aria-hidden="true" class="img-arrow-indicator"> </button> </h3> <div role="region" class="acc-content-wrapper"> <fieldset> <p [innerHTML]="accDataItem.content"></p> <app-recursive-accordion [accordionData]="accDataItem.innerAccordion" [openMathod]="openMathod"></app-recursive-accordion> </fieldset> </div> </li> </ul> </div>
.recursive-accordion .main-list { margin: 0; padding: 0; list-style-type: none; } .recursive-accordion .main-list .acc-item-title-h3 { margin: 0; } .recursive-accordion .main-list .acc-item-title-btn { display: block; width: 100%; padding: 10px 40px 10px 5px; text-align: left; background-color: rgb(255, 255, 255); border: 1px solid rgb(190, 190, 190); border-radius: 4px; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; box-sizing: border-box; cursor: pointer; position: relative; } .recursive-accordion .main-list .acc-item-title-btn .img-arrow-indicator { position: absolute; top: 0; bottom: 0; right: 0; height: 100%; transform: rotate(0deg); -webkit-transform: rotate(0deg); -moz-transform: rotate(0deg); -ms-transform: rotate(0deg); -o-transform: rotate(0deg); transition: all 0.5s; -webkit-transition: all 0.5s; -moz-transition: all 0.5s; -ms-transition: all 0.5s; -o-transition: all 0.5s; } .recursive-accordion .main-list li.active>h3 .acc-item-title-btn .img-arrow-indicator { transform: rotate(90deg); -webkit-transform: rotate(90deg); -moz-transform: rotate(90deg); -ms-transform: rotate(90deg); -o-transform: rotate(90deg); } .recursive-accordion .main-list .acc-item-title-btn:hover, .recursive-accordion .main-list .acc-item-title-btn:focus { background-color: rgb(241, 241, 241); } .recursive-accordion .main-list .acc-content-wrapper { display: none; } .recursive-accordion .main-list .acc-content-wrapper fieldset { border: 1px solid rgb(190, 190, 190); } .recursive-accordion .main-list li.active>.acc-item-title-btn { font-weight: bold; background-color: whitesmoke; } .recursive-accordion .main-list li.active>.acc-content-wrapper { display: block; }
import { Component, OnInit, Input } from '@angular/core'; import { IRecursiveAccordion } from './recursive-accordion.interface'; @Component({ selector: 'app-recursive-accordion', templateUrl: './recursive-accordion.component.html', styleUrls: ['./recursive-accordion.component.css'] }) export class RecursiveAccordionComponent implements OnInit { @Input() accordionData: IRecursiveAccordion[] = []; @Input() openMathod: 'single' | 'multi' = "multi"; openAccArr: Array<string> = []; constructor() { } ngOnInit() { } onToggleContentFn(event, indxBtn) { if (this.openMathod === "single") { if (this.openAccArr[0] === indxBtn) { this.openAccArr = []; } else { this.openAccArr = [indxBtn]; } } else { if (this.openAccArr.includes(indxBtn)) { this.openAccArr.splice(this.openAccArr.findIndex(item => item === indxBtn), 1); } else { this.openAccArr.push(indxBtn); } } } onActiveAccBtn(indxBtn) { return this.openAccArr.includes(indxBtn); } }
export interface IRecursiveAccordion { headerText: string, content: string, isOpen: boolean, id?: string innerAccordion?: IRecursiveAccordion[] }
כמו שניתן לראות, לא מדובר בקוד ארוך מידי, אפשר כמובן להוסיף ולסבך אותו.
TS:
שורה 10 :
מקבלת את המידע לרכיב מבחוץ, אפשר במקום זה לחבר את הרכיב לשירות (service) לקבלת המידע.
שורה 11:
משתנה שאחראי על אופן התפקוד של הרכיב –
single – אומר שכל פעם שפותחים אקורדיון אחד אז יסגר הקודם
multi – אומר שניתן לפתוח מספר אקורדיונים באותו זמן
שורה 12:
מערך ששומר זיהוי (index) של כל אקורדיון פתוח, כמובן שאם מדובר על תצורה של single אז יהיה שימוש רק בתא 0 במערך.
שורה 17:
פונקציה שכל תפקידה הוא לקבל את הזיהוי בעת לחיצה על אקורדיון, לבדוק על איזה תצורה מדובר ולעדכן את המערך בהתאם.
הפונקציה מקבלת גם event למרות שאין צורך ושימוש בו (ניתן למחוק אותו), השארתי אותו למקרה יש צורך לבצע לוגיקה נוספת בהתאם ל-event.
שורה 36:
פונקציה שמקבלת זיהוי, בודקת אם הוא קיים במערך ומחזירה true/fulse בהתאם
INTERFACE:
יש דבר אחד שאני רוצה להתעכב עליו.
שורה 6:
זו השורה שמצביע לנו על רקורסיה, מה שהיא אומרת זה שהמידע יכול לקבל מערך של אוביקטים שהם בעלי אותם מאפיינים כמו הוא עצמו. סימן השאלה נמצא כדי להראות שלא חייב להיות עוד אקורדיון בתוך האקורדיון הקיים.
HTML:
שורה 3:
יצירה של הזיהוי ושליחה שלו לבדיקה במערך שמכיל את כל הזיהויים שאמורים להיות פתוחים, על פי התשובה יופעל ה-css class – active.
חשוב לציין, שאם אנחנו מבצעים בדיקה מול פונקציה ולא משתנה, אנגולר יבצע בדיקה בכל הרכיבים כל פעם מחדש כשמשהו השתנה, מהסיבה שהוא לא יודע מה תהיה התוצאה של הפונקציה.
שורה 11:
מקבלת את המידע כ-HTML על מנת שיבוצע parse למידע במידה שמדובר ב-html, אם אין צורך אז אפשר לשנות את זה לטקסט בלבד.
שורה 12:
כמו שה-interface שלנו יכול להכיל את עצמו כך גם ה-HTML, הרכיב יכול להכיל את עצמו אין סוף פעמים וכך אנחנו יצרנו רקורסיה 🤩.
זה הכל, די פשוט יחסית לרכיב רקורסיה 🤯