Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(accordion): add output for isOpen state changes #2619

Merged
merged 10 commits into from
Oct 23, 2017
8 changes: 4 additions & 4 deletions demo/src/app/components/+accordion/demos/basic/basic.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
</div>
This is just some content to illustrate fancy headings.
</accordion-group>
<accordion-group heading="Content 1">
<p>Content 1</p>
<accordion-group heading="Group with isOpenChange event listener" (isOpenChange)="log($event)">
<p>Some content</p>
</accordion-group>
<accordion-group heading="Content 2">
<p>Content 2</p>
<accordion-group heading="Another group">
<p>Some content</p>
</accordion-group>
</accordion>
6 changes: 5 additions & 1 deletion demo/src/app/components/+accordion/demos/basic/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ import { Component } from '@angular/core';
selector: 'demo-accordion-basic',
templateUrl: './basic.html'
})
export class DemoAccordionBasicComponent {}
export class DemoAccordionBasicComponent {
log(event: boolean) {
console.log(`Accordion has been ${event ? 'opened' : 'closed'}`);
}
}
9 changes: 7 additions & 2 deletions demo/src/ng-api-doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@ export const ngdoc: any = {
{
"name": "isOpen",
"type": "boolean",
"description": "<p>Is accordion group open or closed </p>\n"
"description": "<p>Is accordion group open or closed. This property supports two-way binding </p>\n"
},
{
"name": "panelClass",
"type": "string",
"description": "<p>Provides an ability to use Bootstrap&#39;s contextual panel classes\n(<code>panel-primary</code>, <code>panel-success</code>, <code>panel-info</code>, etc...).\nList of all available classes [available here]\n(<a href=\"http://getbootstrap.com/components/#panels-alternatives\" target=\"_blank\" title=\"null\">http://getbootstrap.com/components/#panels-alternatives</a>)</p>\n"
}
],
"outputs": [],
"outputs": [
{
"name": "isOpenChange",
"description": "<p>Emits when the opened state changes </p>\n"
}
],
"properties": [],
"methods": []
},
Expand Down
19 changes: 13 additions & 6 deletions src/accordion/accordion-group.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
Component, HostBinding, Inject, Input, OnDestroy, OnInit
Component, HostBinding, Inject, Input, OnDestroy, OnInit, Output, EventEmitter
} from '@angular/core';
import { isBs3 } from '../utils/theme-provider';
import { AccordionComponent } from './accordion.component';
Expand Down Expand Up @@ -29,27 +29,34 @@ export class AccordionPanelComponent implements OnInit, OnDestroy {
@Input() panelClass: string;
/** if <code>true</code> — disables accordion group */
@Input() isDisabled: boolean;
/** Emits when the opened state changes */
@Output() isOpenChange: EventEmitter<boolean> = new EventEmitter();

// Questionable, maybe .panel-open should be on child div.panel element?
/** Is accordion group open or closed */
/** Is accordion group open or closed. This property supports two-way binding */
@HostBinding('class.panel-open')
@Input()
get isOpen(): boolean {
return this._isOpen;
}

set isOpen(value: boolean) {
this._isOpen = value;
if (value) {
this.accordion.closeOtherPanels(this);
if (value !== this.isOpen) {
if (value) {
this.accordion.closeOtherPanels(this);
}
this._isOpen = value;
Promise.resolve(null).then(() => {
this.isOpenChange.emit(value);
});
}
}

get isBs3(): boolean {
return isBs3();
}

protected _isOpen: boolean;
protected _isOpen = false;
protected accordion: AccordionComponent;

constructor(@Inject(AccordionComponent) accordion: AccordionComponent) {
Expand Down
36 changes: 32 additions & 4 deletions src/spec/accordion.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { AccordionConfig } from '../accordion/accordion.config';

import { AccordionModule } from '../accordion/accordion.module';
Expand All @@ -25,19 +25,19 @@ const html = `
<accordion [closeOthers]="oneAtATime">

<accordion-group heading="Panel 1"
[isOpen]="panels[0].isOpen"
[(isOpen)]="panels[0].isOpen"
[isDisabled]="panels[0].isDisabled">
Content of panel 1
</accordion-group>

<accordion-group heading="Panel 2"
[isOpen]="panels[1].isOpen"
[(isOpen)]="panels[1].isOpen"
[isDisabled]="panels[1].isDisabled">
Content of panel 2
</accordion-group>

<accordion-group heading="Panel 3"
[isOpen]="panels[2].isOpen"
[(isOpen)]="panels[2].isOpen"
[isDisabled]="panels[2].isDisabled">
Content of panel 3
</accordion-group>
Expand Down Expand Up @@ -166,4 +166,32 @@ describe('Component: Accordion', () => {
fixture.detectChanges();
expectOpenPanels(element, [false, false, false]);
});

it('should modify the parent isOpen state when changed internally (2 way binding)', fakeAsync(() => {
const headingLinks = element.querySelectorAll('.accordion-toggle');

// Clicking (internal state modified)
headingLinks[0].click();
tick();
fixture.detectChanges();
expect(context.panels[0].isOpen).toBe(true);
expect(context.panels[1].isOpen).toBe(false);
expect(context.panels[2].isOpen).toBe(false);

// State modified by parent component
headingLinks[2].click();
tick();
fixture.detectChanges();
expect(context.panels[0].isOpen).toBe(false);
expect(context.panels[1].isOpen).toBe(false);
expect(context.panels[2].isOpen).toBe(true);

// Modified by binding
context.panels[1].isOpen = true;
fixture.detectChanges();
tick();
expect(context.panels[0].isOpen).toBe(false);
expect(context.panels[1].isOpen).toBe(true);
expect(context.panels[2].isOpen).toBe(false);
}));
});