mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
43
lib/core/datatable/components/datatable/data-cell.event.ts
Normal file
43
lib/core/datatable/components/datatable/data-cell.event.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BaseEvent } from '../../../events';
|
||||
import { DataColumn } from '../../data/data-column.model';
|
||||
import { DataRow } from '../../data/data-row.model';
|
||||
|
||||
export class DataCellEventModel {
|
||||
|
||||
readonly row: DataRow;
|
||||
readonly col: DataColumn;
|
||||
actions: any[];
|
||||
|
||||
constructor(row: DataRow, col: DataColumn, actions: any[]) {
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
this.actions = actions || [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DataCellEvent extends BaseEvent<DataCellEventModel> {
|
||||
|
||||
constructor(row: DataRow, col: DataColumn, actions: any[]) {
|
||||
super();
|
||||
this.value = new DataCellEventModel(row, col, actions);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BaseEvent } from '../../../events';
|
||||
import { DataRow } from '../../data/data-row.model';
|
||||
|
||||
export class DataRowActionEvent extends BaseEvent<DataRowActionModel> {
|
||||
|
||||
// backwards compatibility with 1.2.0 and earlier
|
||||
get args(): DataRowActionModel {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
constructor(row: DataRow, action: any) {
|
||||
super();
|
||||
this.value = new DataRowActionModel(row, action);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DataRowActionModel {
|
||||
|
||||
row: DataRow;
|
||||
action: any;
|
||||
|
||||
constructor(row: DataRow, action: any) {
|
||||
this.row = row;
|
||||
this.action = action;
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { DataColumn } from '../../data/data-column.model';
|
||||
import { DataRow } from '../../data/data-row.model';
|
||||
import { DataTableAdapter } from '../../data/datatable-adapter';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-datatable-cell',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<ng-container>
|
||||
<span [title]="tooltip" class="adf-datatable-cell-value">{{value}}</span>
|
||||
</ng-container>`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-datatable-cell' }
|
||||
})
|
||||
export class DataTableCellComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
data: DataTableAdapter;
|
||||
|
||||
@Input()
|
||||
column: DataColumn;
|
||||
|
||||
@Input()
|
||||
row: DataRow;
|
||||
|
||||
@Input()
|
||||
value: any;
|
||||
|
||||
@Input()
|
||||
tooltip: string;
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.value && this.column && this.column.key && this.row && this.data) {
|
||||
this.value = this.data.getValue(this.row, this.column);
|
||||
|
||||
if (!this.tooltip) {
|
||||
this.tooltip = this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
187
lib/core/datatable/components/datatable/datatable.component.html
Normal file
187
lib/core/datatable/components/datatable/datatable.component.html
Normal file
@@ -0,0 +1,187 @@
|
||||
<table
|
||||
*ngIf="data"
|
||||
class="full-width adf-data-table">
|
||||
|
||||
<thead *ngIf="showHeader">
|
||||
<tr>
|
||||
<!-- Actions (left) -->
|
||||
<th *ngIf="actions && actionsPosition === 'left'" class="actions-column">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
<!-- Columns -->
|
||||
<th *ngIf="multiselect">
|
||||
<mat-checkbox [checked]="isSelectAllChecked" (change)="onSelectAllClick($event)"></mat-checkbox>
|
||||
</th>
|
||||
<th class="adf-data-table-cell--{{col.type || 'text'}} {{col.cssClass}}"
|
||||
*ngFor="let col of data.getColumns()"
|
||||
[class.sortable]="col.sortable"
|
||||
[attr.data-automation-id]="'auto_id_' + col.key"
|
||||
[class.adf-data-table__header--sorted-asc]="isColumnSorted(col, 'asc')"
|
||||
[class.adf-data-table__header--sorted-desc]="isColumnSorted(col, 'desc')"
|
||||
(click)="onColumnHeaderClick(col)"
|
||||
(keyup.enter)="onColumnHeaderClick(col)"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="{{ col.title | translate }}">
|
||||
<span *ngIf="col.srTitle" class="sr-only">{{ col.srTitle | translate }}</span>
|
||||
<span *ngIf="col.title">{{ col.title | translate}}</span>
|
||||
</th>
|
||||
<!-- Actions (right) -->
|
||||
<th *ngIf="actions && actionsPosition === 'right'" class="actions-column">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<ng-container *ngIf="!loading && !noPermission">
|
||||
<tr *ngFor="let row of data.getRows(); let idx = index"
|
||||
role="button"
|
||||
[class.is-selected]="row.isSelected"
|
||||
[adf-upload]="allowDropFiles && rowAllowsDrop(row)" [adf-upload-data]="row"
|
||||
[ngStyle]="rowStyle"
|
||||
[ngClass]="getRowStyle(row)"
|
||||
(keyup)="onRowKeyUp(row, $event)">
|
||||
|
||||
<!-- Actions (left) -->
|
||||
<td *ngIf="actions && actionsPosition === 'left'">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu"
|
||||
[attr.data-automation-id]="'action_menu_' + idx">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item *ngFor="let action of getRowActions(row)"
|
||||
[attr.data-automation-id]="action.title"
|
||||
[disabled]="action.disabled"
|
||||
(click)="onExecuteRowAction(row, action)">
|
||||
<mat-icon *ngIf="action.icon">{{ action.icon }}</mat-icon>
|
||||
<span>{{ action.title | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
|
||||
<td *ngIf="multiselect">
|
||||
<mat-checkbox
|
||||
[checked]="row.isSelected"
|
||||
(change)="onCheckboxChange(row, $event)">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
<td *ngFor="let col of data.getColumns()"
|
||||
class="adf-data-table-cell adf-data-table-cell--{{col.type || 'text'}} {{col.cssClass}}"
|
||||
tabindex="0"
|
||||
(click)="onRowClick(row, $event)"
|
||||
[context-menu]="getContextMenuActions(row, col)"
|
||||
[context-menu-enabled]="contextMenu">
|
||||
<div *ngIf="!col.template" class="cell-container">
|
||||
<ng-container [ngSwitch]="col.type">
|
||||
<div *ngSwitchCase="'image'" class="cell-value">
|
||||
<mat-icon *ngIf="isIconValue(row, col)">{{ asIconValue(row, col) }}</mat-icon>
|
||||
<mat-icon *ngIf="!isIconValue(row, col) && row.isSelected" svgIcon="selected" >
|
||||
</mat-icon>
|
||||
|
||||
<img *ngIf="!isIconValue(row, col) && !row.isSelected"
|
||||
alt="{{ iconAltTextKey(data.getValue(row, col)) | translate }}"
|
||||
src="{{ data.getValue(row, col) }}"
|
||||
(error)="onImageLoadingError($event)">
|
||||
</div>
|
||||
<div *ngSwitchCase="'icon'" class="cell-value">
|
||||
<span class="sr-only">{{ iconAltTextKey(data.getValue(row, col)) | translate }}</span>
|
||||
<mat-icon>{{ data.getValue(row, col) }}</mat-icon>
|
||||
</div>
|
||||
<div *ngSwitchCase="'date'" class="cell-value"
|
||||
[attr.data-automation-id]="'date_' + data.getValue(row, col)">
|
||||
<adf-date-cell
|
||||
[data]="data"
|
||||
[column]="col"
|
||||
[row]="row"
|
||||
[tooltip]="getCellTooltip(row, col)">
|
||||
</adf-date-cell>
|
||||
</div>
|
||||
<div *ngSwitchCase="'location'" class="cell-value"
|
||||
[attr.data-automation-id]="'location' + data.getValue(row, col)">
|
||||
<adf-location-cell
|
||||
[data]="data"
|
||||
[column]="col"
|
||||
[row]="row"
|
||||
[tooltip]="getCellTooltip(row, col)">
|
||||
</adf-location-cell>
|
||||
</div>
|
||||
<div *ngSwitchCase="'fileSize'" class="cell-value"
|
||||
[attr.data-automation-id]="'fileSize_' + data.getValue(row, col)">
|
||||
<adf-filesize-cell
|
||||
[data]="data"
|
||||
[column]="col"
|
||||
[row]="row"
|
||||
[tooltip]="getCellTooltip(row, col)">
|
||||
</adf-filesize-cell>
|
||||
</div>
|
||||
<div *ngSwitchCase="'text'" class="cell-value"
|
||||
[attr.data-automation-id]="'text_' + data.getValue(row, col)">
|
||||
<adf-datatable-cell
|
||||
[data]="data"
|
||||
[column]="col"
|
||||
[row]="row"
|
||||
[tooltip]="getCellTooltip(row, col)">
|
||||
</adf-datatable-cell>
|
||||
</div>
|
||||
<span *ngSwitchDefault class="cell-value">
|
||||
<!-- empty cell for unknown column type -->
|
||||
</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="col.template" class="cell-container">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="col.template"
|
||||
[ngTemplateOutletContext]="{ $implicit: { data: data, row: row, col: col }, value: data.getValue(row, col) }">
|
||||
</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Actions (right) -->
|
||||
<td *ngIf="actions && actionsPosition === 'right'" class="alfresco-datatable__actions-cell">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu"
|
||||
[attr.data-automation-id]="'action_menu_' + idx">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item *ngFor="let action of getRowActions(row)"
|
||||
[attr.data-automation-id]="action.title"
|
||||
[disabled]="action.disabled"
|
||||
(click)="onExecuteRowAction(row, action)">
|
||||
<mat-icon *ngIf="action.icon">{{ action.icon }}</mat-icon>
|
||||
<span>{{ action.title | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr *ngIf="data.getRows().length === 0">
|
||||
<td class="adf-no-content-container"
|
||||
[attr.colspan]="1 + data.getColumns().length">
|
||||
<ng-template *ngIf="noContentTemplate"
|
||||
ngFor [ngForOf]="[data]"
|
||||
[ngForTemplate]="noContentTemplate">
|
||||
</ng-template>
|
||||
<ng-content select="adf-empty-list"></ng-content>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<tr *ngIf="!loading && noPermission" class="adf-no-permission__row">
|
||||
<td class="adf-no-permission__cell">
|
||||
<ng-template *ngIf="noPermissionTemplate"
|
||||
ngFor [ngForOf]="[data]"
|
||||
[ngForTemplate]="noPermissionTemplate">
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="loading">
|
||||
<td class="adf-loading-content-container"
|
||||
[attr.colspan]="1 + data.getColumns().length">
|
||||
<ng-template *ngIf="loadingTemplate"
|
||||
ngFor [ngForOf]="[data]"
|
||||
[ngForTemplate]="loadingTemplate">
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
296
lib/core/datatable/components/datatable/datatable.component.scss
Normal file
296
lib/core/datatable/components/datatable/datatable.component.scss
Normal file
@@ -0,0 +1,296 @@
|
||||
|
||||
@mixin adf-datatable-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
$primary: map-get($theme, primary);
|
||||
|
||||
$data-table-font-size: 14px !default;
|
||||
$data-table-header-font-size: 12px !default;
|
||||
$data-table-header-sort-icon-size: 16px !default;
|
||||
$data-table-header-color: mat-color($foreground, text) !default;
|
||||
$data-table-header-sorted-color: mat-color($foreground, text) !default;
|
||||
$data-table-header-sorted-icon-hover-color: mat-color($foreground, disabled-text) !default;
|
||||
$data-table-divider-color: mat-color($foreground, text, .07) !default;
|
||||
$data-table-hover-color: mat-color($background, 'hover') !default;
|
||||
$data-table-selection-color: mat-color($background, 'selected-button') !default;
|
||||
$data-table-dividers: 1px solid $data-table-divider-color !default;
|
||||
$data-table-row-height: 56px !default;
|
||||
// $data-table-last-row-height: 56px !default;
|
||||
// $data-table-header-height: 56px !default;
|
||||
$data-table-column-spacing: 36px !default;
|
||||
$data-table-column-padding: $data-table-column-spacing / 2;
|
||||
// $data-table-card-header-height: 64px !default;
|
||||
// $data-table-card-title-top: 20px !default;
|
||||
$data-table-card-padding: 24px !default;
|
||||
// $data-table-button-padding-right: 16px !default;
|
||||
$data-table-cell-top: $data-table-card-padding / 2;
|
||||
$data-table-drag-border: 1px dashed rgb(68, 138, 255);
|
||||
|
||||
.adf-data-table {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border: $data-table-dividers;
|
||||
border-collapse: collapse;
|
||||
white-space: nowrap;
|
||||
font-size: $data-table-font-size;
|
||||
|
||||
/* Firefox fixes */
|
||||
border-collapse: unset;
|
||||
border-spacing: 0;
|
||||
|
||||
thead {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
height: $data-table-row-height;
|
||||
@include material-animation-default(0.28s);
|
||||
transition-property: background-color;
|
||||
|
||||
&:hover {
|
||||
background-color: $data-table-hover-color;
|
||||
}
|
||||
|
||||
&.is-selected, &.is-selected:hover {
|
||||
background-color: $data-table-selection-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-offset: -1px;
|
||||
outline-width: 1px;
|
||||
outline-color: rgb(68, 138, 255);
|
||||
outline-style: solid;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
& > td {
|
||||
border-bottom: $data-table-dividers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: 0 $data-table-column-padding 12px $data-table-column-padding;
|
||||
text-align: right;
|
||||
|
||||
&:first-of-type {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-offset: -1px;
|
||||
outline-width: 1px;
|
||||
outline-color: rgb(68, 138, 255);
|
||||
outline-style: solid;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
color: mat-color($foreground, text);
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
height: $data-table-row-height;
|
||||
border-top: $data-table-dividers;
|
||||
padding-top: $data-table-cell-top;
|
||||
box-sizing: border-box;
|
||||
|
||||
@include no-select;
|
||||
}
|
||||
|
||||
th {
|
||||
@include no-select;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
vertical-align: bottom;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
height: $data-table-row-height;
|
||||
font-size: $data-table-header-font-size;
|
||||
color: $data-table-header-color;
|
||||
padding-bottom: 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.sortable {
|
||||
@include no-select;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.adf-data-table__header--sorted-asc,
|
||||
&.adf-data-table__header--sorted-desc {
|
||||
color: $data-table-header-sorted-color;
|
||||
&:before {
|
||||
@include typo-icon;
|
||||
font-size: $data-table-header-sort-icon-size;
|
||||
content: "\e5d8";
|
||||
margin-right: 5px;
|
||||
vertical-align: sub;
|
||||
}
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
&:before {
|
||||
color: $data-table-header-sorted-icon-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.adf-data-table__header--sorted-desc:before {
|
||||
content: "\e5db";
|
||||
}
|
||||
}
|
||||
|
||||
.adf-data-table-cell {
|
||||
text-align: left;
|
||||
height: 100%;
|
||||
|
||||
&--text {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&--date {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&--number {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&--image {
|
||||
|
||||
.cell-value {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
text-align: left;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Empty folder */
|
||||
.adf-no-content-container {
|
||||
padding: 0 !important;
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading folder */
|
||||
.adf-loading-content-container {
|
||||
padding: 0 !important;
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-no-permission {
|
||||
&__row:hover {
|
||||
cursor: default;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
&__cell {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ellipsis-cell {
|
||||
.cell-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cell-container > * {
|
||||
display: block;
|
||||
position: absolute;
|
||||
max-width: calc(100% - 2em);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.12em;
|
||||
}
|
||||
|
||||
/* visible content */
|
||||
.cell-value {
|
||||
display: block;
|
||||
position: relative;
|
||||
max-width: calc(100% - 2em);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.12em;
|
||||
}
|
||||
|
||||
/* cell stretching content */
|
||||
& > div:after {
|
||||
content: attr(title);
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* [Accessibility] For screen reader only */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Utils */
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* mobile phone */
|
||||
@media all and (max-width: 768px) {
|
||||
.desktop-only {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-device-width: 768px) {
|
||||
.desktop-only {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-upload__dragging {
|
||||
& > td {
|
||||
border-top: $data-table-drag-border;
|
||||
border-bottom: $data-table-drag-border;
|
||||
|
||||
&:first-child {
|
||||
border-left: $data-table-drag-border;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right: $data-table-drag-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,650 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatCheckboxChange } from '@angular/material';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { DataTableModule } from '../../datatable.module';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import {
|
||||
DataColumn,
|
||||
DataRow,
|
||||
DataSorting,
|
||||
ObjectDataColumn,
|
||||
ObjectDataTableAdapter
|
||||
} from './../../data/index';
|
||||
import { DataTableComponent } from './datatable.component';
|
||||
|
||||
describe('DataTable', () => {
|
||||
|
||||
let fixture: ComponentFixture<DataTableComponent>;
|
||||
let dataTable: DataTableComponent;
|
||||
let element: any;
|
||||
let eventMock: any;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
DataTableModule,
|
||||
MaterialModule
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DataTableComponent);
|
||||
dataTable = fixture.componentInstance;
|
||||
element = fixture.debugElement.nativeElement;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
eventMock = {
|
||||
preventDefault: function () {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it('should change the rows on changing of the data', () => {
|
||||
let newData = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: 'TEST' },
|
||||
{ name: 'FAKE' }
|
||||
],
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: '1' },
|
||||
{ name: '2' }
|
||||
],
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
|
||||
dataTable.ngOnChanges({
|
||||
data: new SimpleChange(null, newData, false)
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(element.querySelector('[data-automation-id="text_TEST"]')).not.toBeNull();
|
||||
expect(element.querySelector('[data-automation-id="text_FAKE"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should reset selection on mode change', () => {
|
||||
spyOn(dataTable, 'resetSelection').and.callThrough();
|
||||
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: '1' },
|
||||
{ name: '2' }
|
||||
],
|
||||
[ new ObjectDataColumn({ key: 'name'}) ]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
rows[0].isSelected = true;
|
||||
rows[1].isSelected = true;
|
||||
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeTruthy();
|
||||
|
||||
dataTable.ngOnChanges({
|
||||
selectionMode: new SimpleChange(null, 'multiple', false)
|
||||
});
|
||||
|
||||
expect(dataTable.resetSelection).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should select only one row with [single] selection mode', () => {
|
||||
dataTable.selectionMode = 'single';
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: '1' },
|
||||
{ name: '2' }
|
||||
],
|
||||
[ new ObjectDataColumn({ key: 'name'}) ]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeFalsy();
|
||||
|
||||
dataTable.onRowClick(rows[1], null);
|
||||
expect(rows[0].isSelected).toBeFalsy();
|
||||
expect(rows[1].isSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not unselect the row with [single] selection mode', () => {
|
||||
dataTable.selectionMode = 'single';
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: '1' },
|
||||
{ name: '2' }
|
||||
],
|
||||
[ new ObjectDataColumn({ key: 'name'}) ]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeFalsy();
|
||||
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should unselect the row with [multiple] selection mode and modifier key', () => {
|
||||
dataTable.selectionMode = 'multiple';
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[ { name: '1' } ],
|
||||
[ new ObjectDataColumn({ key: 'name'}) ]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
|
||||
dataTable.onRowClick(rows[0], <any> { metaKey: true, preventDefault() {} });
|
||||
expect(rows[0].isSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should select multiple rows with [multiple] selection mode', () => {
|
||||
dataTable.selectionMode = 'multiple';
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: '1' },
|
||||
{ name: '2' }
|
||||
],
|
||||
[ new ObjectDataColumn({ key: 'name'}) ]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
const event = new MouseEvent('click', {
|
||||
metaKey: true
|
||||
});
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(rows[0], event);
|
||||
dataTable.onRowClick(rows[1], event);
|
||||
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should put actions menu to the right by default', () => {
|
||||
dataTable.data = new ObjectDataTableAdapter([], [
|
||||
<DataColumn> {},
|
||||
<DataColumn> {},
|
||||
<DataColumn> {}
|
||||
]);
|
||||
dataTable.actions = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let headers = element.querySelectorAll('th');
|
||||
expect(headers.length).toBe(4);
|
||||
expect(headers[headers.length - 1].classList.contains('actions-column')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should put actions menu to the left', () => {
|
||||
dataTable.data = new ObjectDataTableAdapter([], [
|
||||
<DataColumn> {},
|
||||
<DataColumn> {},
|
||||
<DataColumn> {}
|
||||
]);
|
||||
dataTable.actions = true;
|
||||
dataTable.actionsPosition = 'left';
|
||||
fixture.detectChanges();
|
||||
|
||||
let headers = element.querySelectorAll('th');
|
||||
expect(headers.length).toBe(4);
|
||||
expect(headers[0].classList.contains('actions-column')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should initialize default adapter', () => {
|
||||
let table = new DataTableComponent(null, null);
|
||||
expect(table.data).toBeUndefined();
|
||||
table.ngOnChanges({'data': new SimpleChange('123', {}, true)});
|
||||
expect(table.data).toEqual(jasmine.any(ObjectDataTableAdapter));
|
||||
});
|
||||
|
||||
it('should load data table on onChange', () => {
|
||||
let table = new DataTableComponent(null, null);
|
||||
let data = new ObjectDataTableAdapter([], []);
|
||||
|
||||
expect(table.data).toBeUndefined();
|
||||
table.ngOnChanges({'data': new SimpleChange('123', data, true)});
|
||||
expect(table.data).toEqual(data);
|
||||
});
|
||||
|
||||
it('should initialize with custom data', () => {
|
||||
let data = new ObjectDataTableAdapter([], []);
|
||||
dataTable.data = data;
|
||||
dataTable.ngAfterContentInit();
|
||||
expect(dataTable.data).toBe(data);
|
||||
});
|
||||
|
||||
it('should emit row click event', done => {
|
||||
let row = <DataRow> {};
|
||||
|
||||
dataTable.rowClick.subscribe(e => {
|
||||
expect(e.value).toBe(row);
|
||||
done();
|
||||
});
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(row, null);
|
||||
});
|
||||
|
||||
it('should emit double click if there are two single click in 250ms', (done) => {
|
||||
|
||||
let row = <DataRow> {};
|
||||
dataTable.ngOnChanges({});
|
||||
|
||||
dataTable.rowDblClick.subscribe( () => {
|
||||
done();
|
||||
});
|
||||
|
||||
dataTable.onRowClick(row, null);
|
||||
setTimeout(() => {
|
||||
dataTable.onRowClick(row, null);
|
||||
}
|
||||
, 240);
|
||||
|
||||
});
|
||||
|
||||
it('should emit double click if there are more than two single click in 250ms', (done) => {
|
||||
|
||||
let row = <DataRow> {};
|
||||
dataTable.ngOnChanges({});
|
||||
|
||||
dataTable.rowDblClick.subscribe( () => {
|
||||
done();
|
||||
});
|
||||
|
||||
dataTable.onRowClick(row, null);
|
||||
setTimeout(() => {
|
||||
|
||||
dataTable.onRowClick(row, null);
|
||||
dataTable.onRowClick(row, null);
|
||||
}
|
||||
, 240);
|
||||
|
||||
});
|
||||
|
||||
it('should emit single click if there are two single click in more than 250ms', (done) => {
|
||||
|
||||
let row = <DataRow> {};
|
||||
let clickCount = 0;
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
|
||||
dataTable.rowClick.subscribe( () => {
|
||||
clickCount += 1;
|
||||
if (clickCount === 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
dataTable.onRowClick(row, null);
|
||||
setTimeout(() => {
|
||||
dataTable.onRowClick(row, null);
|
||||
}
|
||||
, 260);
|
||||
});
|
||||
|
||||
it('should emit row-click dom event', (done) => {
|
||||
let row = <DataRow> {};
|
||||
|
||||
fixture.nativeElement.addEventListener('row-click', (e) => {
|
||||
expect(e.detail.value).toBe(row);
|
||||
done();
|
||||
});
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(row, null);
|
||||
});
|
||||
|
||||
it('should emit row-dblclick dom event', (done) => {
|
||||
let row = <DataRow> {};
|
||||
|
||||
fixture.nativeElement.addEventListener('row-dblclick', (e) => {
|
||||
expect(e.detail.value).toBe(row);
|
||||
done();
|
||||
});
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(row, null);
|
||||
dataTable.onRowClick(row, null);
|
||||
});
|
||||
|
||||
it('should prevent default behaviour on row click event', () => {
|
||||
let e = jasmine.createSpyObj('event', ['preventDefault']);
|
||||
dataTable.ngAfterContentInit();
|
||||
dataTable.onRowClick(null, e);
|
||||
expect(e.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should prevent default behaviour on row double-click event', () => {
|
||||
let e = jasmine.createSpyObj('event', ['preventDefault']);
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.ngAfterContentInit();
|
||||
dataTable.onRowDblClick(null, e);
|
||||
expect(e.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not sort if column is missing', () => {
|
||||
dataTable.ngOnChanges({'data': new SimpleChange('123', {}, true)});
|
||||
let adapter = dataTable.data;
|
||||
spyOn(adapter, 'setSorting').and.callThrough();
|
||||
dataTable.onColumnHeaderClick(null);
|
||||
expect(adapter.setSorting).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not sort upon clicking non-sortable column header', () => {
|
||||
dataTable.ngOnChanges({'data': new SimpleChange('123', {}, true)});
|
||||
let adapter = dataTable.data;
|
||||
spyOn(adapter, 'setSorting').and.callThrough();
|
||||
|
||||
let column = new ObjectDataColumn({
|
||||
key: 'column_1'
|
||||
});
|
||||
|
||||
dataTable.onColumnHeaderClick(column);
|
||||
expect(adapter.setSorting).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set sorting upon column header clicked', () => {
|
||||
dataTable.ngOnChanges({'data': new SimpleChange('123', {}, true)});
|
||||
let adapter = dataTable.data;
|
||||
spyOn(adapter, 'setSorting').and.callThrough();
|
||||
|
||||
let column = new ObjectDataColumn({
|
||||
key: 'column_1',
|
||||
sortable: true
|
||||
});
|
||||
|
||||
dataTable.onColumnHeaderClick(column);
|
||||
expect(adapter.setSorting).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
key: 'column_1',
|
||||
direction: 'asc'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should invert sorting upon column header clicked', () => {
|
||||
dataTable.ngOnChanges({'data': new SimpleChange('123', {}, true)});
|
||||
|
||||
let adapter = dataTable.data;
|
||||
let sorting = new DataSorting('column_1', 'asc');
|
||||
spyOn(adapter, 'setSorting').and.callThrough();
|
||||
spyOn(adapter, 'getSorting').and.returnValue(sorting);
|
||||
|
||||
let column = new ObjectDataColumn({
|
||||
key: 'column_1',
|
||||
sortable: true
|
||||
});
|
||||
|
||||
// check first click on the header
|
||||
dataTable.onColumnHeaderClick(column);
|
||||
expect(adapter.setSorting).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
key: 'column_1',
|
||||
direction: 'desc'
|
||||
})
|
||||
);
|
||||
|
||||
// check second click on the header
|
||||
sorting.direction = 'desc';
|
||||
dataTable.onColumnHeaderClick(column);
|
||||
expect(adapter.setSorting).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
key: 'column_1',
|
||||
direction: 'asc'
|
||||
})
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
it('should invert "select all" status', () => {
|
||||
expect(dataTable.isSelectAllChecked).toBeFalsy();
|
||||
dataTable.onSelectAllClick(<MatCheckboxChange> { checked: true });
|
||||
expect(dataTable.isSelectAllChecked).toBeTruthy();
|
||||
dataTable.onSelectAllClick(<MatCheckboxChange> { checked: false });
|
||||
expect(dataTable.isSelectAllChecked).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should update rows on "select all" click', () => {
|
||||
let data = new ObjectDataTableAdapter([{}, {}, {}], []);
|
||||
let rows = data.getRows();
|
||||
|
||||
dataTable.data = data;
|
||||
dataTable.multiselect = true;
|
||||
dataTable.ngAfterContentInit();
|
||||
|
||||
dataTable.onSelectAllClick(<MatCheckboxChange> { checked: true });
|
||||
expect(dataTable.isSelectAllChecked).toBe(true);
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
expect(rows[i].isSelected).toBe(true);
|
||||
}
|
||||
|
||||
dataTable.onSelectAllClick(<MatCheckboxChange> { checked: false });
|
||||
expect(dataTable.isSelectAllChecked).toBe(false);
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
expect(rows[i].isSelected).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow "select all" calls with no rows', () => {
|
||||
dataTable.multiselect = true;
|
||||
dataTable.ngOnChanges({'data': new SimpleChange('123', {}, true)});
|
||||
|
||||
dataTable.onSelectAllClick(<MatCheckboxChange> { checked: true });
|
||||
expect(dataTable.isSelectAllChecked).toBe(true);
|
||||
});
|
||||
|
||||
it('should require multiselect option to toggle row state', () => {
|
||||
let data = new ObjectDataTableAdapter([{}, {}, {}], []);
|
||||
let rows = data.getRows();
|
||||
|
||||
dataTable.data = data;
|
||||
dataTable.multiselect = false;
|
||||
dataTable.ngAfterContentInit();
|
||||
|
||||
dataTable.onSelectAllClick(<MatCheckboxChange> { checked: true });
|
||||
expect(dataTable.isSelectAllChecked).toBe(true);
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
expect(rows[i].isSelected).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should require row and column for icon value check', () => {
|
||||
expect(dataTable.isIconValue(null, null)).toBeFalsy();
|
||||
expect(dataTable.isIconValue(<DataRow> {}, null)).toBeFalsy();
|
||||
expect(dataTable.isIconValue(null, <DataColumn> {})).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should use special material url scheme', () => {
|
||||
let column = <DataColumn> {};
|
||||
|
||||
let row = {
|
||||
getValue: function (key: string) {
|
||||
return 'material-icons://android';
|
||||
}
|
||||
};
|
||||
|
||||
expect(dataTable.isIconValue(<DataRow> row, column)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not use special material url scheme', () => {
|
||||
let column = <DataColumn> {};
|
||||
|
||||
let row = {
|
||||
getValue: function (key: string) {
|
||||
return 'http://www.google.com';
|
||||
}
|
||||
};
|
||||
|
||||
expect(dataTable.isIconValue(<DataRow> row, column)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should parse icon value', () => {
|
||||
let column = <DataColumn> {};
|
||||
|
||||
let row = {
|
||||
getValue: function (key: string) {
|
||||
return 'material-icons://android';
|
||||
}
|
||||
};
|
||||
|
||||
expect(dataTable.asIconValue(<DataRow> row, column)).toBe('android');
|
||||
});
|
||||
|
||||
it('should not parse icon value', () => {
|
||||
let column = <DataColumn> {};
|
||||
|
||||
let row = {
|
||||
getValue: function (key: string) {
|
||||
return 'http://www.google.com';
|
||||
}
|
||||
};
|
||||
|
||||
expect(dataTable.asIconValue(<DataRow> row, column)).toBe(null);
|
||||
});
|
||||
|
||||
it('should parse icon values to a valid i18n key', () => {
|
||||
expect(dataTable.iconAltTextKey('custom')).toBe('ICONS.custom');
|
||||
expect(dataTable.iconAltTextKey('/path/to/custom')).toBe('ICONS.custom');
|
||||
expect(dataTable.iconAltTextKey('/path/to/custom.svg')).toBe('ICONS.custom');
|
||||
});
|
||||
|
||||
it('should require column and direction to evaluate sorting state', () => {
|
||||
expect(dataTable.isColumnSorted(null, null)).toBeFalsy();
|
||||
expect(dataTable.isColumnSorted(<DataColumn> {}, null)).toBeFalsy();
|
||||
expect(dataTable.isColumnSorted(null, 'asc')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should require adapter sorting to evaluate sorting state', () => {
|
||||
dataTable.ngOnChanges({'data': new SimpleChange('123', {}, true)});
|
||||
spyOn(dataTable.data, 'getSorting').and.returnValue(null);
|
||||
expect(dataTable.isColumnSorted(<DataColumn> {}, 'asc')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should evaluate column sorting state', () => {
|
||||
dataTable.ngOnChanges({'data': new SimpleChange('123', {}, true)});
|
||||
spyOn(dataTable.data, 'getSorting').and.returnValue(new DataSorting('column_1', 'asc'));
|
||||
expect(dataTable.isColumnSorted(<DataColumn> {key: 'column_1'}, 'asc')).toBeTruthy();
|
||||
expect(dataTable.isColumnSorted(<DataColumn> {key: 'column_2'}, 'desc')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should replace image source with fallback thumbnail on error', () => {
|
||||
let event = <any> {
|
||||
target: {
|
||||
src: 'missing-image'
|
||||
}
|
||||
};
|
||||
|
||||
dataTable.fallbackThumbnail = '<fallback>';
|
||||
dataTable.onImageLoadingError(event);
|
||||
expect(event.target.src).toBe(dataTable.fallbackThumbnail);
|
||||
});
|
||||
|
||||
it('should replace image source only when fallback available', () => {
|
||||
const originalSrc = 'missing-image';
|
||||
let event = <any> {
|
||||
target: {
|
||||
src: originalSrc
|
||||
}
|
||||
};
|
||||
|
||||
dataTable.fallbackThumbnail = null;
|
||||
dataTable.onImageLoadingError(event);
|
||||
expect(event.target.src).toBe(originalSrc);
|
||||
});
|
||||
|
||||
it('should not get cell tooltip when row is not provided', () => {
|
||||
const col = <DataColumn> { key: 'name', type: 'text' };
|
||||
expect(dataTable.getCellTooltip(null, col)).toBeNull();
|
||||
});
|
||||
|
||||
it('should not get cell tooltip when column is not provided', () => {
|
||||
const row = <DataRow> {};
|
||||
expect(dataTable.getCellTooltip(row, null)).toBeNull();
|
||||
});
|
||||
|
||||
it('should not get cell tooltip when formatter is not provided', () => {
|
||||
const col = <DataColumn> { key: 'name', type: 'text' };
|
||||
const row = <DataRow> {};
|
||||
expect(dataTable.getCellTooltip(row, col)).toBeNull();
|
||||
});
|
||||
|
||||
it('should use formatter function to generate tooltip', () => {
|
||||
const tooltip = 'tooltip value';
|
||||
const col = <DataColumn> {
|
||||
key: 'name',
|
||||
type: 'text',
|
||||
formatTooltip: () => tooltip
|
||||
};
|
||||
const row = <DataRow> {};
|
||||
expect(dataTable.getCellTooltip(row, col)).toBe(tooltip);
|
||||
});
|
||||
|
||||
it('should return null value from the tooltip formatter', () => {
|
||||
const col = <DataColumn> {
|
||||
key: 'name',
|
||||
type: 'text',
|
||||
formatTooltip: () => null
|
||||
};
|
||||
const row = <DataRow> {};
|
||||
expect(dataTable.getCellTooltip(row, col)).toBeNull();
|
||||
});
|
||||
|
||||
it('should cache the rows menu', () => {
|
||||
let emitted = 0;
|
||||
dataTable.showRowActionsMenu.subscribe(() => { emitted++; });
|
||||
|
||||
const column = <DataColumn> {};
|
||||
const row = <DataRow> { getValue: function (key: string) { return 'id'; } };
|
||||
|
||||
dataTable.getRowActions(row, column);
|
||||
dataTable.getRowActions(row, column);
|
||||
dataTable.getRowActions(row, column);
|
||||
|
||||
expect(emitted).toBe(1);
|
||||
});
|
||||
|
||||
it('should reset the menu cache after rows change', () => {
|
||||
let emitted = 0;
|
||||
dataTable.showRowActionsMenu.subscribe(() => { emitted++; });
|
||||
|
||||
const column = <DataColumn> {};
|
||||
const row = <DataRow> { getValue: function (key: string) { return 'id'; } };
|
||||
|
||||
dataTable.getRowActions(row, column);
|
||||
dataTable.ngOnChanges({'data': new SimpleChange('123', {}, true)});
|
||||
dataTable.getRowActions(row, column);
|
||||
|
||||
expect(emitted).toBe(2);
|
||||
});
|
||||
});
|
490
lib/core/datatable/components/datatable/datatable.component.ts
Normal file
490
lib/core/datatable/components/datatable/datatable.component.ts
Normal file
@@ -0,0 +1,490 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
AfterContentInit, Component, ContentChild, DoCheck, ElementRef, EventEmitter, Input,
|
||||
IterableDiffers, OnChanges, Output, SimpleChange, SimpleChanges, TemplateRef, ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { MatCheckboxChange } from '@angular/material';
|
||||
import { Observable, Observer, Subscription } from 'rxjs/Rx';
|
||||
import { DataColumnListComponent } from '../../../data-column';
|
||||
import { DataColumn } from '../../data/data-column.model';
|
||||
import { DataRowEvent } from '../../data/data-row-event.model';
|
||||
import { DataRow } from '../../data/data-row.model';
|
||||
import { DataSorting } from '../../data/data-sorting.model';
|
||||
import { DataTableAdapter } from '../../data/datatable-adapter';
|
||||
|
||||
import { ObjectDataRow, ObjectDataTableAdapter } from '../../data/object-datatable-adapter';
|
||||
import { DataCellEvent } from './data-cell.event';
|
||||
import { DataRowActionEvent } from './data-row-action.event';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-datatable',
|
||||
styleUrls: ['./datatable.component.scss'],
|
||||
templateUrl: './datatable.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck {
|
||||
|
||||
@ContentChild(DataColumnListComponent) columnList: DataColumnListComponent;
|
||||
|
||||
@Input()
|
||||
data: DataTableAdapter;
|
||||
|
||||
@Input()
|
||||
rows: any[] = [];
|
||||
|
||||
@Input()
|
||||
selectionMode: string = 'single'; // none|single|multiple
|
||||
|
||||
@Input()
|
||||
multiselect: boolean = false;
|
||||
|
||||
@Input()
|
||||
actions: boolean = false;
|
||||
|
||||
@Input()
|
||||
actionsPosition: string = 'right'; // left|right
|
||||
|
||||
@Input()
|
||||
fallbackThumbnail: string;
|
||||
|
||||
@Input()
|
||||
contextMenu: boolean = false;
|
||||
|
||||
@Input()
|
||||
allowDropFiles: boolean = false;
|
||||
|
||||
@Input()
|
||||
rowStyle: string;
|
||||
|
||||
@Input()
|
||||
rowStyleClass: string = '';
|
||||
|
||||
@Input()
|
||||
showHeader: boolean = true;
|
||||
|
||||
@Output()
|
||||
rowClick = new EventEmitter<DataRowEvent>();
|
||||
|
||||
@Output()
|
||||
rowDblClick = new EventEmitter<DataRowEvent>();
|
||||
|
||||
@Output()
|
||||
showRowContextMenu = new EventEmitter<DataCellEvent>();
|
||||
|
||||
@Output()
|
||||
showRowActionsMenu = new EventEmitter<DataCellEvent>();
|
||||
|
||||
@Output()
|
||||
executeRowAction = new EventEmitter<DataRowActionEvent>();
|
||||
|
||||
@Input()
|
||||
loading: boolean = false;
|
||||
|
||||
@Input()
|
||||
noPermission: boolean = false;
|
||||
|
||||
noContentTemplate: TemplateRef<any>;
|
||||
noPermissionTemplate: TemplateRef<any>;
|
||||
loadingTemplate: TemplateRef<any>;
|
||||
|
||||
isSelectAllChecked: boolean = false;
|
||||
selection = new Array<DataRow>();
|
||||
|
||||
private clickObserver: Observer<DataRowEvent>;
|
||||
private click$: Observable<DataRowEvent>;
|
||||
|
||||
private schema: DataColumn[] = [];
|
||||
|
||||
private differ: any;
|
||||
private rowMenuCache: object = {};
|
||||
|
||||
private singleClickStreamSub: Subscription;
|
||||
private multiClickStreamSub: Subscription;
|
||||
|
||||
constructor(private elementRef: ElementRef, differs: IterableDiffers) {
|
||||
if (differs) {
|
||||
this.differ = differs.find([]).create(null);
|
||||
}
|
||||
this.click$ = new Observable<DataRowEvent>(observer => this.clickObserver = observer).share();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.setTableSchema();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.initAndSubscribeClickStream();
|
||||
if (this.isPropertyChanged(changes['data'])) {
|
||||
if (this.isTableEmpty()) {
|
||||
this.initTable();
|
||||
} else {
|
||||
this.data = changes['data'].currentValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isPropertyChanged(changes['rows'])) {
|
||||
if (this.isTableEmpty()) {
|
||||
this.initTable();
|
||||
} else {
|
||||
this.setTableRows(changes['rows'].currentValue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.selectionMode && !changes.selectionMode.isFirstChange()) {
|
||||
this.resetSelection();
|
||||
this.emitRowSelectionEvent('row-unselect', null);
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck() {
|
||||
let changes = this.differ.diff(this.rows);
|
||||
if (changes) {
|
||||
this.setTableRows(this.rows);
|
||||
}
|
||||
}
|
||||
|
||||
isPropertyChanged(property: SimpleChange): boolean {
|
||||
return property && property.currentValue ? true : false;
|
||||
}
|
||||
|
||||
convertToRowsData(rows: any []): ObjectDataRow[] {
|
||||
return rows.map(row => new ObjectDataRow(row));
|
||||
}
|
||||
|
||||
private initAndSubscribeClickStream() {
|
||||
this.unsubscribeClickStream();
|
||||
let singleClickStream = this.click$
|
||||
.buffer(this.click$.debounceTime(250))
|
||||
.map(list => list)
|
||||
.filter(x => x.length === 1);
|
||||
|
||||
this.singleClickStreamSub = singleClickStream.subscribe((obj: DataRowEvent[]) => {
|
||||
let event: DataRowEvent = obj[0];
|
||||
this.rowClick.emit(event);
|
||||
if (!event.defaultPrevented) {
|
||||
this.elementRef.nativeElement.dispatchEvent(
|
||||
new CustomEvent('row-click', {
|
||||
detail: event,
|
||||
bubbles: true
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let multiClickStream = this.click$
|
||||
.buffer(this.click$.debounceTime(250))
|
||||
.map(list => list)
|
||||
.filter(x => x.length >= 2);
|
||||
|
||||
this.multiClickStreamSub = multiClickStream.subscribe((obj: DataRowEvent[]) => {
|
||||
let event: DataRowEvent = obj[0];
|
||||
this.rowDblClick.emit(event);
|
||||
if (!event.defaultPrevented) {
|
||||
this.elementRef.nativeElement.dispatchEvent(
|
||||
new CustomEvent('row-dblclick', {
|
||||
detail: event,
|
||||
bubbles: true
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private unsubscribeClickStream() {
|
||||
if (this.singleClickStreamSub) {
|
||||
this.singleClickStreamSub.unsubscribe();
|
||||
}
|
||||
if (this.multiClickStreamSub) {
|
||||
this.multiClickStreamSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
private initTable() {
|
||||
this.data = new ObjectDataTableAdapter(this.rows, this.schema);
|
||||
this.rowMenuCache = {};
|
||||
}
|
||||
|
||||
isTableEmpty() {
|
||||
return this.data === undefined || this.data === null;
|
||||
}
|
||||
|
||||
private setTableRows(rows) {
|
||||
if (this.data) {
|
||||
this.data.setRows(this.convertToRowsData(rows));
|
||||
}
|
||||
}
|
||||
|
||||
private setTableSchema() {
|
||||
if (this.columnList && this.columnList.columns) {
|
||||
this.schema = this.columnList.columns.map(c => <DataColumn> c);
|
||||
}
|
||||
|
||||
if (this.data && this.schema && this.schema.length > 0) {
|
||||
this.data.setColumns(this.schema);
|
||||
}
|
||||
}
|
||||
|
||||
onRowClick(row: DataRow, e: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (row) {
|
||||
if (this.data) {
|
||||
if (this.isSingleSelectionMode()) {
|
||||
this.resetSelection();
|
||||
this.selectRow(row, true);
|
||||
this.emitRowSelectionEvent('row-select', row);
|
||||
}
|
||||
|
||||
if (this.isMultiSelectionMode()) {
|
||||
const modifier = e && (e.metaKey || e.ctrlKey);
|
||||
const newValue = modifier ? !row.isSelected : true;
|
||||
const domEventName = newValue ? 'row-select' : 'row-unselect';
|
||||
|
||||
if (!modifier) {
|
||||
this.resetSelection();
|
||||
}
|
||||
this.selectRow(row, newValue);
|
||||
this.emitRowSelectionEvent(domEventName, row);
|
||||
}
|
||||
}
|
||||
|
||||
const dataRowEvent = new DataRowEvent(row, e, this);
|
||||
this.clickObserver.next(dataRowEvent);
|
||||
}
|
||||
}
|
||||
|
||||
resetSelection(): void {
|
||||
if (this.data) {
|
||||
const rows = this.data.getRows();
|
||||
if (rows && rows.length > 0) {
|
||||
rows.forEach(r => r.isSelected = false);
|
||||
}
|
||||
this.selection.splice(0);
|
||||
}
|
||||
this.isSelectAllChecked = false;
|
||||
}
|
||||
|
||||
onRowDblClick(row: DataRow, e?: Event) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
let dataRowEvent = new DataRowEvent(row, e, this);
|
||||
this.clickObserver.next(dataRowEvent);
|
||||
}
|
||||
|
||||
onRowKeyUp(row: DataRow, e: KeyboardEvent) {
|
||||
const event = new CustomEvent('row-keyup', {
|
||||
detail: {
|
||||
row: row,
|
||||
keyboardEvent: e,
|
||||
sender: this
|
||||
},
|
||||
bubbles: true
|
||||
});
|
||||
|
||||
this.elementRef.nativeElement.dispatchEvent(event);
|
||||
|
||||
if (event.defaultPrevented) {
|
||||
e.preventDefault();
|
||||
} else {
|
||||
if (e.key === 'Enter') {
|
||||
this.onKeyboardNavigate(row, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onKeyboardNavigate(row: DataRow, e: KeyboardEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
const event = new DataRowEvent(row, e, this);
|
||||
|
||||
this.rowDblClick.emit(event);
|
||||
this.elementRef.nativeElement.dispatchEvent(
|
||||
new CustomEvent('row-dblclick', {
|
||||
detail: event,
|
||||
bubbles: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
onColumnHeaderClick(column: DataColumn) {
|
||||
if (column && column.sortable) {
|
||||
let current = this.data.getSorting();
|
||||
let newDirection = 'asc';
|
||||
if (current && column.key === current.key) {
|
||||
newDirection = current.direction === 'asc' ? 'desc' : 'asc';
|
||||
}
|
||||
this.data.setSorting(new DataSorting(column.key, newDirection));
|
||||
}
|
||||
}
|
||||
|
||||
onSelectAllClick(e: MatCheckboxChange) {
|
||||
this.isSelectAllChecked = e.checked;
|
||||
|
||||
if (this.multiselect) {
|
||||
let rows = this.data.getRows();
|
||||
if (rows && rows.length > 0) {
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
this.selectRow(rows[i], e.checked);
|
||||
}
|
||||
}
|
||||
|
||||
const domEventName = e.checked ? 'row-select' : 'row-unselect';
|
||||
const row = this.selection.length > 0 ? this.selection[0] : null;
|
||||
|
||||
this.emitRowSelectionEvent(domEventName, row);
|
||||
}
|
||||
}
|
||||
|
||||
onCheckboxChange(row: DataRow, event: MatCheckboxChange) {
|
||||
const newValue = event.checked;
|
||||
|
||||
this.selectRow(row, newValue);
|
||||
|
||||
const domEventName = newValue ? 'row-select' : 'row-unselect';
|
||||
this.emitRowSelectionEvent(domEventName, row);
|
||||
}
|
||||
|
||||
onImageLoadingError(event: Event) {
|
||||
if (event && this.fallbackThumbnail) {
|
||||
let element = <any> event.target;
|
||||
element.src = this.fallbackThumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
isIconValue(row: DataRow, col: DataColumn): boolean {
|
||||
if (row && col) {
|
||||
let value = row.getValue(col.key);
|
||||
return value && value.startsWith('material-icons://');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
asIconValue(row: DataRow, col: DataColumn): string {
|
||||
if (this.isIconValue(row, col)) {
|
||||
let value = row.getValue(col.key) || '';
|
||||
return value.replace('material-icons://', '');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
iconAltTextKey(value: string): string {
|
||||
return value ? 'ICONS.' + value.substring(value.lastIndexOf('/') + 1).replace(/\.[a-z]+/, '') : '';
|
||||
}
|
||||
|
||||
isColumnSorted(col: DataColumn, direction: string): boolean {
|
||||
if (col && direction) {
|
||||
let sorting = this.data.getSorting();
|
||||
return sorting && sorting.key === col.key && sorting.direction === direction;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getContextMenuActions(row: DataRow, col: DataColumn): any[] {
|
||||
let event = new DataCellEvent(row, col, []);
|
||||
this.showRowContextMenu.emit(event);
|
||||
return event.value.actions;
|
||||
}
|
||||
|
||||
getRowActions(row: DataRow, col: DataColumn): any[] {
|
||||
const id = row.getValue('id');
|
||||
|
||||
if (!this.rowMenuCache[id]) {
|
||||
let event = new DataCellEvent(row, col, []);
|
||||
this.showRowActionsMenu.emit(event);
|
||||
this.rowMenuCache[id] = event.value.actions;
|
||||
}
|
||||
|
||||
return this.rowMenuCache[id];
|
||||
}
|
||||
|
||||
onExecuteRowAction(row: DataRow, action: any) {
|
||||
if (action.disabled || action.disabled) {
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
this.executeRowAction.emit(new DataRowActionEvent(row, action));
|
||||
}
|
||||
}
|
||||
|
||||
rowAllowsDrop(row: DataRow): boolean {
|
||||
return row.isDropTarget === true;
|
||||
}
|
||||
|
||||
hasSelectionMode(): boolean {
|
||||
return this.isSingleSelectionMode() || this.isMultiSelectionMode();
|
||||
}
|
||||
|
||||
isSingleSelectionMode(): boolean {
|
||||
return this.selectionMode && this.selectionMode.toLowerCase() === 'single';
|
||||
}
|
||||
|
||||
isMultiSelectionMode(): boolean {
|
||||
return this.selectionMode && this.selectionMode.toLowerCase() === 'multiple';
|
||||
}
|
||||
|
||||
getRowStyle(row: DataRow): string {
|
||||
row.cssClass = row.cssClass ? row.cssClass : '';
|
||||
this.rowStyleClass = this.rowStyleClass ? this.rowStyleClass : '';
|
||||
return `${row.cssClass} ${this.rowStyleClass}`;
|
||||
}
|
||||
|
||||
private selectRow(row: DataRow, value: boolean) {
|
||||
if (row) {
|
||||
row.isSelected = value;
|
||||
const idx = this.selection.indexOf(row);
|
||||
|
||||
if (value) {
|
||||
if (idx < 0) {
|
||||
this.selection.push(row);
|
||||
}
|
||||
} else {
|
||||
if (idx > -1) {
|
||||
this.selection.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCellTooltip(row: DataRow, col: DataColumn): string {
|
||||
if (row && col && col.formatTooltip) {
|
||||
const result: string = col.formatTooltip(row, col);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private emitRowSelectionEvent(name: string, row: DataRow) {
|
||||
const domEvent = new CustomEvent(name, {
|
||||
detail: {
|
||||
row: row,
|
||||
selection: this.selection
|
||||
},
|
||||
bubbles: true
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(domEvent);
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
|
||||
import { DataTableCellComponent } from './datatable-cell.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-date-cell',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<ng-container>
|
||||
<span [title]="tooltip">{{value}}</span>
|
||||
</ng-container>
|
||||
`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-date-cell' }
|
||||
})
|
||||
export class DateCellComponent extends DataTableCellComponent {}
|
@@ -0,0 +1,6 @@
|
||||
<div class="adf-empty-list_template">
|
||||
<ng-content select="[adf-empty-list-header]"></ng-content>
|
||||
<ng-content select="[adf-empty-list-body]"></ng-content>
|
||||
<ng-content select="[adf-empty-list-footer]"></ng-content>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
@@ -0,0 +1,5 @@
|
||||
.adf-empty-list_template {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { EmptyListComponent } from './empty-list.component';
|
||||
|
||||
describe('EmptyListComponentComponent', () => {
|
||||
let component: EmptyListComponent;
|
||||
let fixture: ComponentFixture<EmptyListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
EmptyListComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EmptyListComponent);
|
||||
component = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(component).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render the input values', async(() => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('.adf-empty-list_template')).toBeDefined();
|
||||
});
|
||||
}));
|
||||
});
|
@@ -0,0 +1,30 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Directive, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-empty-list',
|
||||
styleUrls: ['./empty-list.component.scss'],
|
||||
templateUrl: './empty-list.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class EmptyListComponent {}
|
||||
|
||||
@Directive({ selector: '[adf-empty-list-header]' }) export class EmptyListHeaderDirective {}
|
||||
@Directive({ selector: '[adf-empty-list-body]' }) export class EmptyListBodyDirective {}
|
||||
@Directive({ selector: '[adf-empty-list-footer]' }) export class EmptyListFooterDirective {}
|
@@ -0,0 +1,32 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
|
||||
import { DataTableCellComponent } from './datatable-cell.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-filesize-cell',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<ng-container>
|
||||
<span [title]="tooltip">{{ value | adfFileSize }}</span>
|
||||
</ng-container>
|
||||
`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-filesize-cell' }
|
||||
})
|
||||
export class FileSizeCellComponent extends DataTableCellComponent {}
|
@@ -0,0 +1,132 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import {
|
||||
ObjectDataColumn,
|
||||
ObjectDataTableAdapter
|
||||
} from './../../data/index';
|
||||
import { LocationCellComponent } from './location-cell.component';
|
||||
|
||||
describe('LocationCellComponent', () => {
|
||||
let component: LocationCellComponent;
|
||||
let fixture: ComponentFixture<LocationCellComponent>;
|
||||
let dataTableAdapter: ObjectDataTableAdapter;
|
||||
let rowData;
|
||||
let columnData;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
LocationCellComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LocationCellComponent);
|
||||
component = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
rowData = {
|
||||
name: '1',
|
||||
path: {
|
||||
elements: [
|
||||
{ id: '1', name: 'path' },
|
||||
{ id: '2', name: 'to' },
|
||||
{ id: '3', name: 'location' }
|
||||
],
|
||||
name: '/path/to/location'
|
||||
}
|
||||
};
|
||||
|
||||
columnData = { format: '/somewhere', type: 'location', key: 'path'};
|
||||
|
||||
dataTableAdapter = new ObjectDataTableAdapter(
|
||||
[rowData],
|
||||
[ new ObjectDataColumn(columnData) ]
|
||||
);
|
||||
|
||||
component.link = [];
|
||||
component.column = dataTableAdapter.getColumns()[0];
|
||||
component.data = dataTableAdapter;
|
||||
component.row = dataTableAdapter.getRows()[0];
|
||||
});
|
||||
|
||||
it('should set displayText', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.displayText).toBe('location');
|
||||
});
|
||||
|
||||
it('should set tooltip', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.tooltip).toEqual(rowData.path.name);
|
||||
});
|
||||
|
||||
it('should set router link', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.link).toEqual([ columnData.format , rowData.path.elements[2].id ]);
|
||||
});
|
||||
|
||||
it('should not setup cell when path has no data', () => {
|
||||
rowData.path = {};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.displayText).toBe('');
|
||||
expect(component.tooltip).toBeUndefined();
|
||||
expect(component.link).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not setup cell when path is missing required properties', () => {
|
||||
rowData.path = { someProp: '' };
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.displayText).toBe('');
|
||||
expect(component.tooltip).toBeUndefined();
|
||||
expect(component.link).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not setup cell when path data is missing one of the property', () => {
|
||||
rowData.path = {
|
||||
name: 'some-name'
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.displayText).toBe('');
|
||||
expect(component.tooltip).toBeUndefined();
|
||||
expect(component.link).toEqual([]);
|
||||
|
||||
rowData.path = {
|
||||
elements: []
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.displayText).toBe('');
|
||||
expect(component.tooltip).toBeUndefined();
|
||||
expect(component.link).toEqual([]);
|
||||
});
|
||||
});
|
@@ -0,0 +1,61 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { PathInfoEntity } from 'alfresco-js-api';
|
||||
import { DataTableCellComponent } from './datatable-cell.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-location-cell',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<ng-container>
|
||||
<a href="" [title]="tooltip" [routerLink]="link">
|
||||
{{ displayText }}
|
||||
</a>
|
||||
</ng-container>
|
||||
`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-location-cell' }
|
||||
})
|
||||
export class LocationCellComponent extends DataTableCellComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
link: any[];
|
||||
|
||||
@Input()
|
||||
displayText: string = '';
|
||||
|
||||
/** @override */
|
||||
ngOnInit() {
|
||||
if (!this.value && this.column && this.column.key && this.row && this.data) {
|
||||
const path: PathInfoEntity = this.data.getValue(this.row, this.column);
|
||||
|
||||
if (path && path.name && path.elements) {
|
||||
this.value = path;
|
||||
this.displayText = path.name.split('/').pop();
|
||||
|
||||
if (!this.tooltip) {
|
||||
this.tooltip = path.name;
|
||||
}
|
||||
|
||||
const parent = path.elements[path.elements.length - 1];
|
||||
this.link = [ this.column.format, parent.id ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user