eventsprimengaccordionevent-bubblingevent-capturing

How to control opening primeng accordion content by a button placed on the accordion


I want to open the primeng accordion using a button that is placed in its custom header and i dont want the accordion to open if i click anywhere else. i am using primeng 11-lts version and this is my code for the header of accordion-

                                <ng-template pTemplate="header">
                                    <span class="flex w-full justify-content-between">
                                        <div class="flex align-items-center">
                                            <p-avatar image="{{ profileimage }}" [size]="showDetails ? 'xlarge' : 'large'" class="ml-5"> </p-avatar>
                                            <div class="ml-5 flex flex-column">
                                                <span class="font-normal white-space-nowrap text-2xl"
                                                    >{{ farmerdetail != null ? (farmerdetail.custom_id && farmerdetail.custom_id.id_number ? farmerdetail.custom_id.id_number : farmerdetail.unique_id) : '---' }}
                                                </span>
                                                <span class="font-semibold white-space-nowrap text-3xl">{{ farmerdetail != null ? farmerdetail.firstname : '---' }} {{ farmerdetail != null ? farmerdetail.lastname : '---' }}</span>
                                            </div>
                                        </div>
                                        <div class="flex align-items-center">
                                            <button pButton type="button" class="p-button-outlined" [icon]="showDetails ? 'pi pi-eye-slash' : 'pi pi-eye'" iconPos="left" [label]="showDetails ? 'Hide Details' : 'View Details'"></button>
                                        </div>
                                    </span>
                                </ng-template>

i tried using (click)="$event.stopPrapagation()" on <span class="flex w-full justify-content-between">

but then the accordion is never opening even if i click on the view details button


Solution

  • To solve this issue, you first need to understand how event propagation happens through the document -

    when you click on an element, the click goes from the document to all the way to the lower most element (where the click happened) which is the target element , this is called event capturing, from here the event again goes back to the top , this is called event bubbling. in the event bubbling phase the event handlers(that you put in html or in script using false as third argument of addEventListener) are triggered.

    Therefore , knowing the above knowledge we can think of two ways -

    1. first would be to stop the propagation of the event in event capturing phase so that it does not reach the target element and therefore event bubbling will also not happen and the anchor tag which opens the accordion panel will not open the panel and also we can put our logic to open the accordion by only allowing event to propagate if the area clicked is our desired button.
    2. second would be to stop the propagation in event bubbling phase so that the target element is hit and it does the behaviour we desire but while event bubbling the anchor element is not clicked and the accordion panel is not opened/closed.

    so using the first solution we can do -

    angular + js -

    @ViewChild('accordionTab', { static: false }) accordionTab;
    
    @ViewChild('viewDetails', { static: false }) viewDetails;
    
    ngAfterViewInit() {
        this.accordionTab.accordion.el.nativeElement.childNodes[0].childNodes[0].childNodes[0].childNodes[0].addEventListener(
            'click',
            (e) => {
                if (e.target == this.viewDetails.nativeElement || e.target == this.viewDetails.nativeElement.children[0] || e.target == this.viewDetails.nativeElement.children[1]) {
                    this.showDetails = !this.showDetails;
                } else {
                    e.stopPropagation();
                }
            },
            true
        );
    }
    

    html -

    <p-accordionTab #accordionTab>
    <button pButton type="button" class="p-button-outlined" [icon]="showDetails ? 'pi pi-eye-slash' : 'pi pi-eye'" iconPos="left" [label]="showDetails ? 'Hide Details' : 'View Details'" #viewDetails></button>
    

    here , in the above code you will have to mindful if you are using *ngFor to create multiple accordions, then you will have to see how to adjust the logic. Also in the above code we are going to 4th child of accordion tab as the 4th child is the header element in primeng accordion, if you don't put the event listener on the fourth child but any child before the fourth child or the accordion tab itself, then the buttons in accordion content part will not trigger.

    the second solution is to put a event.stopPropagation() on the topmost element of your header template so that the click does not reach the anchor element of primeng accordion during event bubbling phase and put a function at the click handler on your desired button which has logic to click the parent of your topmost element (which would be the anchor tag). html -

    <span class="flex w-full justify-content-between" (click)="$event.stopPropagation()">
    <button
                                                pButton
                                                type="button"
                                                class="p-button-outlined"
                                                [icon]="showDetails ? 'pi pi-eye-slash' : 'pi pi-eye'"
                                                iconPos="left"
                                                [label]="showDetails ? 'Hide Details' : 'View Details'"
                                                (click)="this.accordionTab.accordion.el.nativeElement.childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].click()"
                                            ></button>
    

    in the above code i am accessing the anchor tag from the accordionTab element by putting #accordionTab on p-accordion-tab and using viewchild to take the reference. in this case make sure to remove the padding of the anchor element in the accordion so that user is not able to directly click the anchor element in the accordion header.

    in both the above cases you can repeat the same steps to add more buttons and other logic in your accordion header