Skip to content

Commit

Permalink
feat(tabs): removable tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
Panchenko Vyacheslav committed Feb 10, 2016
1 parent eec3cb4 commit c465610
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 8 deletions.
4 changes: 4 additions & 0 deletions components/tabs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ export class Tabset implements OnInit {
export class Tab implements OnInit, OnDestroy, DoCheck {
@Input() public heading:string;
@Input() public disabled:boolean;
@Input() public removable:boolean;

/** tab active state toogle */
@HostBinding('class.active')
@Input() public get active() {}

@Output() public select:EventEmitter<Tab> = new EventEmitter();
@Output() public deselect:EventEmitter<Tab> = new EventEmitter();
@Output() public removed:EventEmitter<Tab> = new EventEmitter();
}

// directive TabHeading
Expand All @@ -56,10 +58,12 @@ export const TAB_DIRECTIVES:Array<any> = [Tab, TabHeading, Tabset];
- `heading` (`string`) - tab header text
- `active` (`?boolean=false`) - if tab is active equals true, or set `true` to activate tab
- `disabled` (`?boolean=false`) - if `true` tab can not be activated
- `removable` (`?boolean=false`) - if `true` tab can be removable, additional button will appear

### Tab events
- `select` - fired when `tab` became active, `$event:Tab` equals to selected instance of `Tab` component
- `deselect` - fired when `tab` became inactive, `$event:Tab` equals to deselected instance of `Tab` component
- `removed` - fired before `tab` will be removed

### Tab heading
Should be used to mark `<template>` element as a template for tab heading
7 changes: 6 additions & 1 deletion components/tabs/tab.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {Tabset} from './tabset.component';
export class Tab implements OnDestroy {
@Input() public heading:string;
@Input() public disabled:boolean;
@Input() public removable:boolean;

/** tab active state toogle */
@HostBinding('class.active')
Expand All @@ -22,7 +23,7 @@ export class Tab implements OnDestroy {

@Output() public select:EventEmitter<Tab> = new EventEmitter();
@Output() public deselect:EventEmitter<Tab> = new EventEmitter();

@Output() public removed:EventEmitter<Tab> = new EventEmitter();

public set active(active) {
if (this.disabled && active || !active) {
Expand Down Expand Up @@ -52,6 +53,10 @@ export class Tab implements OnDestroy {
this.tabset.addTab(this);
}

ngOnInit() {
this.removable = !!this.removable;
}

ngOnDestroy() {
this.tabset.removeTab(this);
}
Expand Down
44 changes: 40 additions & 4 deletions components/tabs/tabset.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import {Tab} from './tab.directive';
[class.active]="tabz.active" [class.disabled]="tabz.disabled"
(click)="tabz.active = true">
<span [ngTransclude]="tabz.headingRef">{{tabz.heading}}</span>
<span [hidden]="!tabz.removable">
<span (click)="$event.preventDefault(); removeTab(tabz);" class="glyphicon glyphicon-remove-circle"></span>
</span>
</a>
</li>
</ul>
Expand Down Expand Up @@ -84,12 +87,45 @@ export class Tabset implements OnInit {
return;
}
// Select a new tab if the tab to be removed is selected and not destroyed
if (tab.active && this.tabs.length > 1) {
// If this is the last tab, select the previous tab. else, the next tab.
let newActiveIndex = index === this.tabs.length - 1 ? index - 1 : index + 1;
if (tab.active && this.hasAvailableTabs(index)) {
let newActiveIndex = this.getClosestTabIndex(index);
this.tabs[newActiveIndex].active = true;
}

this.tabs.slice(index, 1);
tab.removed.emit(tab);
this.tabs.splice(index, 1);
}

private getClosestTabIndex (index:number):number {
let tabsLength = this.tabs.length;
if (!tabsLength) {
return -1;
}

for (let step = 1; step <= tabsLength; step += 1) {
let prevIndex = index - step;
let nextIndex = index + step;
if (this.tabs[prevIndex] && !this.tabs[prevIndex].disabled) {
return prevIndex;
}
if (this.tabs[nextIndex] && !this.tabs[nextIndex].disabled) {
return nextIndex;
}
}
return -1;
}

private hasAvailableTabs (index:number) {
let tabsLength = this.tabs.length;
if (!tabsLength) {
return false;
}

for (let i = 0; i < tabsLength; i += 1) {
if (!this.tabs[i].disabled && i !== index) {
return true;
}
}
return false;
}
}
4 changes: 3 additions & 1 deletion demo/components/tabs/tabs-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
[active]="tabz.active"
(select)="tabz.active = true"
(deselect)="tabz.active = false"
[disabled]="tabz.disabled">
[disabled]="tabz.disabled"
[removable]="tabz.removable"
(removed)="removeTabHandler(tabz)">
{{tabz?.content}}
</tab>
<tab (select)="alertMe()">
Expand Down
9 changes: 7 additions & 2 deletions demo/components/tabs/tabs-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ let template = require('./tabs-demo.html');
export class TabsDemo {
public tabs:Array<any> = [
{title: 'Dynamic Title 1', content: 'Dynamic content 1'},
{title: 'Dynamic Title 2', content: 'Dynamic content 2', disabled: true}
{title: 'Dynamic Title 2', content: 'Dynamic content 2', disabled: true},
{title: 'Dynamic Title 3', content: 'Dynamic content 3', removable: true}
];

public alertMe() {
Expand All @@ -25,5 +26,9 @@ export class TabsDemo {

public setActiveTab(index:number) {
this.tabs[index].active = true;
}
};

public removeTabHandler(tab:any) {
console.log('Remove Tab handler');
};
}

0 comments on commit c465610

Please sign in to comment.