Merge pull request #2591 from coreos-inc/cor-tabs
Change cor-tabs to be a TypeScript and Angular "neu" component
This commit is contained in:
commit
f228ebdc22
24 changed files with 832 additions and 588 deletions
|
@ -0,0 +1 @@
|
|||
<div class="co-tab-content tab-content col-md-11" ng-transclude></div>
|
|
@ -0,0 +1,15 @@
|
|||
import { Component } from 'ng-metadata/core';
|
||||
|
||||
|
||||
/**
|
||||
* A component that is placed under a cor-tabs to wrap tab content with additional styling.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'cor-tab-content',
|
||||
templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab-content.component.html',
|
||||
legacy: {
|
||||
transclude: true,
|
||||
replace: true,
|
||||
}
|
||||
})
|
||||
export class CorTabContentComponent {}
|
83
static/js/directives/ui/cor-tabs/cor-tab-handlers.ts
Normal file
83
static/js/directives/ui/cor-tabs/cor-tab-handlers.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { CorTabComponent } from './cor-tab.component';
|
||||
import { CorTabPanelComponent } from './cor-tab-panel.component';
|
||||
|
||||
/**
|
||||
* Defines an interface for reading and writing the current tab state.
|
||||
*/
|
||||
export interface CorTabCurrentHandler {
|
||||
getInitialTabId(): string
|
||||
|
||||
notifyTabChanged(tab: CorTabComponent, isDefaultTab: boolean)
|
||||
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
export function CorTabCurrentHandlerFactory(options?: any): CorTabCurrentHandler {
|
||||
switch (options.type) {
|
||||
case "cookie":
|
||||
return new CookieCurrentTabHandler(options.cookieService, options.cookieName);
|
||||
default:
|
||||
return new LocationCurrentTabHandler(options.panel, options.$location, options.$rootScope);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and writes the tab from the `tab` query parameter in the location.
|
||||
*/
|
||||
export class LocationCurrentTabHandler implements CorTabCurrentHandler {
|
||||
private cancelWatchHandle: Function;
|
||||
|
||||
constructor (private panel: CorTabPanelComponent,
|
||||
private $location: ng.ILocationService,
|
||||
private $rootScope: ng.IRootScopeService) {
|
||||
}
|
||||
|
||||
private checkLocation(): void {
|
||||
var specifiedTabId = this.$location.search()['tab'];
|
||||
var specifiedTab = this.panel.findTab(specifiedTabId);
|
||||
this.panel.setActiveTab(specifiedTab);
|
||||
}
|
||||
|
||||
public getInitialTabId(): string {
|
||||
if (!this.cancelWatchHandle) {
|
||||
this.cancelWatchHandle = this.$rootScope.$on('$routeUpdate', () => this.checkLocation());
|
||||
}
|
||||
return this.$location.search()['tab'];
|
||||
}
|
||||
|
||||
public notifyTabChanged(tab: CorTabComponent, isDefaultTab: boolean) {
|
||||
var newSearch = $.extend(this.$location.search(), {});
|
||||
if (isDefaultTab) {
|
||||
delete newSearch['tab'];
|
||||
} else {
|
||||
newSearch['tab'] = tab.tabId;
|
||||
}
|
||||
|
||||
this.$location.search(newSearch);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.cancelWatchHandle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and writes the tab from a cookie,.
|
||||
*/
|
||||
export class CookieCurrentTabHandler implements CorTabCurrentHandler {
|
||||
constructor (private CookieService: any, private cookieName: string) {}
|
||||
|
||||
public getInitialTabId(): string {
|
||||
return this.CookieService.get(this.cookieName);
|
||||
}
|
||||
|
||||
public notifyTabChanged(tab: CorTabComponent, isDefaultTab: boolean) {
|
||||
if (isDefaultTab) {
|
||||
this.CookieService.clear(this.cookieName);
|
||||
} else {
|
||||
this.CookieService.putPermanent(this.cookieName, tab.tabId);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<div class="co-tab-pane" ng-show="$ctrl.isActiveTab">
|
||||
<div ng-transclude />
|
||||
</div>
|
31
static/js/directives/ui/cor-tabs/cor-tab-pane.component.ts
Normal file
31
static/js/directives/ui/cor-tabs/cor-tab-pane.component.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { Component, Input, Inject, Host, OnInit } from 'ng-metadata/core';
|
||||
import { CorTabPanelComponent } from './cor-tab-panel.component';
|
||||
|
||||
|
||||
/**
|
||||
* A component that creates a single tab pane under a cor-tabs component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'cor-tab-pane',
|
||||
templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab-pane.component.html',
|
||||
legacy: {
|
||||
transclude: true,
|
||||
}
|
||||
})
|
||||
export class CorTabPaneComponent implements OnInit {
|
||||
@Input('@') public id: string;
|
||||
|
||||
// Whether this is the active tab.
|
||||
private isActiveTab: boolean = false;
|
||||
|
||||
constructor(@Host() @Inject(CorTabPanelComponent) private parent: CorTabPanelComponent) {
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.parent.addTabPane(this);
|
||||
}
|
||||
|
||||
public changeState(isActive: boolean): void {
|
||||
this.isActiveTab = isActive;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<div class="co-main-content-panel co-tab-panel co-fx-box-shadow-heavy">
|
||||
<div class="co-tab-container" ng-transclude></div>
|
||||
</div>
|
115
static/js/directives/ui/cor-tabs/cor-tab-panel.component.ts
Normal file
115
static/js/directives/ui/cor-tabs/cor-tab-panel.component.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
import { Component, Input, Inject, OnDestroy } from 'ng-metadata/core';
|
||||
import { CorTabComponent } from './cor-tab.component';
|
||||
import { CorTabPaneComponent } from './cor-tab-pane.component';
|
||||
import { CorTabCurrentHandler, LocationCurrentTabHandler, CookieCurrentTabHandler, CorTabCurrentHandlerFactory } from './cor-tab-handlers'
|
||||
|
||||
|
||||
/**
|
||||
* A component that contains a cor-tabs and handles all of its logic.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'cor-tab-panel',
|
||||
templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab-panel.component.html',
|
||||
legacy: {
|
||||
transclude: true,
|
||||
}
|
||||
})
|
||||
export class CorTabPanelComponent implements OnDestroy {
|
||||
// If 'true', the currently selected tab will be remembered via a cookie and not the page URL.
|
||||
@Input('@') public rememberCookie: string;
|
||||
|
||||
// The tabs under this tabs component.
|
||||
private tabs: CorTabComponent[] = [];
|
||||
|
||||
// The tab panes under the tabs component, indexed by the tab id.
|
||||
private tabPanes: {[id: string]: CorTabPaneComponent} = {};
|
||||
|
||||
// The currently active tab, if any.
|
||||
private activeTab: CorTabComponent = null;
|
||||
|
||||
// Whether the initial tab was set.
|
||||
private initialTabSet: boolean = false;
|
||||
|
||||
// The handler to use to read/write the current tab.
|
||||
private currentTabHandler: CorTabCurrentHandler = null;
|
||||
|
||||
constructor(@Inject('$location') private $location: ng.ILocationService,
|
||||
@Inject('$rootScope') private $rootScope: ng.IRootScopeService,
|
||||
@Inject('CookieService') private CookieService: any,
|
||||
@Inject('CorTabCurrentHandlerFactory') private CorTabCurrentHandlerFactory: (Object) => CorTabCurrentHandler) {
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.currentTabHandler.dispose();
|
||||
}
|
||||
|
||||
public tabClicked(tab: CorTabComponent): void {
|
||||
this.setActiveTab(tab);
|
||||
}
|
||||
|
||||
public findTab(tabId: string): CorTabComponent {
|
||||
if (!this.tabs.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var tab = this.tabs.find(function(current) {
|
||||
return current.tabId == tabId;
|
||||
}) || this.tabs[0];
|
||||
|
||||
if (!this.tabPanes[tab.tabId]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
public setActiveTab(tab: CorTabComponent): void {
|
||||
if (this.activeTab == tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeTab != null) {
|
||||
this.activeTab.changeState(false);
|
||||
this.tabPanes[this.activeTab.tabId].changeState(false);
|
||||
}
|
||||
|
||||
this.activeTab = tab;
|
||||
this.activeTab.changeState(true);
|
||||
this.tabPanes[this.activeTab.tabId].changeState(true);
|
||||
this.currentTabHandler.notifyTabChanged(tab, this.tabs[0] == tab);
|
||||
}
|
||||
|
||||
public addTab(tab: CorTabComponent): void {
|
||||
this.tabs.push(tab);
|
||||
this.checkInitialTab();
|
||||
}
|
||||
|
||||
public addTabPane(tabPane: CorTabPaneComponent): void {
|
||||
this.tabPanes[tabPane.id] = tabPane;
|
||||
this.checkInitialTab();
|
||||
}
|
||||
|
||||
private checkInitialTab(): void {
|
||||
if (this.tabs.length < 1 || this.initialTabSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentTabHandler = this.CorTabCurrentHandlerFactory({
|
||||
type: this.rememberCookie ? 'cookie' : 'location',
|
||||
cookieService: this.CookieService,
|
||||
cookeName: this.rememberCookie,
|
||||
panel: this,
|
||||
$location: this.$location,
|
||||
$rootScope: this.$rootScope,
|
||||
});
|
||||
|
||||
var tabId = this.currentTabHandler.getInitialTabId();
|
||||
var tab = this.findTab(tabId);
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.initialTabSet = true;
|
||||
this.setActiveTab(tab);
|
||||
}
|
||||
}
|
10
static/js/directives/ui/cor-tabs/cor-tab.component.html
Normal file
10
static/js/directives/ui/cor-tabs/cor-tab.component.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<li ng-class="{'active': $ctrl.isActive}">
|
||||
<a ng-click="$ctrl.tabClicked()">
|
||||
<span data-title="{{ ::$ctrl.tabTitle }}"
|
||||
data-placement="right"
|
||||
data-container="body"
|
||||
style="display: inline-block"
|
||||
bs-tooltip><span ng-transclude/></span><span class="visible-xs-inline xs-label">{{ ::$ctrl.tabTitle }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
46
static/js/directives/ui/cor-tabs/cor-tab.component.ts
Normal file
46
static/js/directives/ui/cor-tabs/cor-tab.component.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { Component, Input, Output, Inject, EventEmitter, Host, OnInit } from 'ng-metadata/core';
|
||||
import { CorTabPanelComponent } from './cor-tab-panel.component';
|
||||
|
||||
|
||||
/**
|
||||
* A component that creates a single tab under a cor-tabs component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'cor-tab',
|
||||
templateUrl: '/static/js/directives/ui/cor-tabs/cor-tab.component.html',
|
||||
legacy: {
|
||||
transclude: true,
|
||||
}
|
||||
})
|
||||
export class CorTabComponent implements OnInit {
|
||||
@Input('@') public tabId: string;
|
||||
@Input('@') public tabTitle: string;
|
||||
|
||||
@Output() public tabInit: EventEmitter<any> = new EventEmitter();
|
||||
@Output() public tabShown: EventEmitter<any> = new EventEmitter();
|
||||
@Output() public tabHidden: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
// Whether this is the active tab.
|
||||
private isActive: boolean = false;
|
||||
|
||||
constructor(@Host() @Inject(CorTabPanelComponent) private parent: CorTabPanelComponent) {
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.parent.addTab(this);
|
||||
}
|
||||
|
||||
public changeState(isActive: boolean): void {
|
||||
this.isActive = isActive;
|
||||
if (isActive) {
|
||||
this.tabInit.emit({});
|
||||
this.tabShown.emit({});
|
||||
} else {
|
||||
this.tabHidden.emit({});
|
||||
}
|
||||
}
|
||||
|
||||
private tabClicked(): void {
|
||||
this.parent.tabClicked(this);
|
||||
}
|
||||
}
|
4
static/js/directives/ui/cor-tabs/cor-tabs.component.html
Normal file
4
static/js/directives/ui/cor-tabs/cor-tabs.component.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<span class="co-tab-element" ng-class="$ctrl.isClosed ? 'closed' : 'open'">
|
||||
<span class="xs-toggle" ng-click="$ctrl.toggleClosed($event)"></span>
|
||||
<ul class="co-tabs col-md-1" ng-transclude></ul>
|
||||
</span>
|
22
static/js/directives/ui/cor-tabs/cor-tabs.component.ts
Normal file
22
static/js/directives/ui/cor-tabs/cor-tabs.component.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Component, Host, Inject } from 'ng-metadata/core';
|
||||
import { CorTabComponent } from './cor-tab.component';
|
||||
|
||||
|
||||
/**
|
||||
* A component that holds the actual tabs.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'cor-tabs',
|
||||
templateUrl: '/static/js/directives/ui/cor-tabs/cor-tabs.component.html',
|
||||
legacy: {
|
||||
transclude: true,
|
||||
}
|
||||
})
|
||||
export class CorTabsComponent {
|
||||
// If true, the tabs are in a closed state. Only applies in the mobile view.
|
||||
private isClosed: boolean = true;
|
||||
|
||||
private toggleClosed(e): void {
|
||||
this.isClosed = !this.isClosed;
|
||||
}
|
||||
}
|
Reference in a new issue