mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-31 17:38:48 +00:00
[ADF-112] Change task details feature (due date) (#2071)
* Add Assignee to readonly mode * Style adoption and first steps to editable mode * Switch between mode coverage * Rebase fix * Because of design and requirement changes, revert unnecessary parts * Small refactoring before the introduction of CardViewDateItem * Fix AdfCardView tests * Editable Card date item * Do not allow edit on task details after the task is completed. * Update task details request * Login footer switch fix * Login customisable copyright text * Card text item (first sketches) * Small fix for supported card items' template * Dynamic component loading for card view items * Test and linting fixes * Updating Readme.md * Update Readme.md * Fix Readme.md errors * CardViewTextItemComponent tests * Rebase fix
This commit is contained in:
committed by
Eugenio Romano
parent
8a1281475c
commit
f6c3fafe32
@@ -23,7 +23,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p class="toggle">
|
<p class="toggle">
|
||||||
<label for="switch4" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
<label for="switch4" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||||
<input type="checkbox" id="switch4" class="mdl-switch__input" [checked]="showFooter" (click)="showFooter = !showFooter">
|
<input type="checkbox" id="switch4" class="mdl-switch__input" [checked]="showFooter" (click)="toggleFooter()">
|
||||||
<span class="mdl-switch__label">Login footer</span>
|
<span class="mdl-switch__label">Login footer</span>
|
||||||
</label>
|
</label>
|
||||||
</p>
|
</p>
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
[disableCsrf]="disableCsrf"
|
[disableCsrf]="disableCsrf"
|
||||||
[showLoginActions]="showFooter"
|
[showLoginActions]="showFooter"
|
||||||
[showRememberMe]="showFooter"
|
[showRememberMe]="showFooter"
|
||||||
|
copyrightText="© 2016 Alfresco Software, Inc. All Rights Reserved. (customised text)"
|
||||||
(onSuccess)="onLogin($event)"
|
(onSuccess)="onLogin($event)"
|
||||||
(onError)="onError($event)">
|
(onError)="onError($event)">
|
||||||
<div class="mobile-settings">
|
<div class="mobile-settings">
|
||||||
@@ -67,7 +68,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="switch4-mobile" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
<label for="switch4-mobile" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||||
<input type="checkbox" id="switch4-mobile" class="mdl-switch__input" [checked]="showFooter" (click)="showFooter = !showFooter">
|
<input type="checkbox" id="switch4-mobile" class="mdl-switch__input" [checked]="showFooter" (click)="toggleFooter()">
|
||||||
<span class="mdl-switch__label">Login footer</span>
|
<span class="mdl-switch__label">Login footer</span>
|
||||||
</label>
|
</label>
|
||||||
</p>
|
</p>
|
||||||
|
@@ -95,6 +95,10 @@ export class LoginDemoComponent implements OnInit {
|
|||||||
this.disableCsrf = !this.disableCsrf;
|
this.disableCsrf = !this.disableCsrf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleFooter() {
|
||||||
|
this.showFooter = !this.showFooter;
|
||||||
|
}
|
||||||
|
|
||||||
updateProvider() {
|
updateProvider() {
|
||||||
if (this.isBPM && this.isECM) {
|
if (this.isBPM && this.isECM) {
|
||||||
this.providers = 'ALL';
|
this.providers = 'ALL';
|
||||||
|
@@ -65,8 +65,17 @@ module.exports = {
|
|||||||
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.component.scss$/,
|
test: /\.scss$/,
|
||||||
use: ['to-string-loader', 'raw-loader', 'sass-loader'],
|
use: [{
|
||||||
|
loader: "to-string-loader"
|
||||||
|
}, {
|
||||||
|
loader: "raw-loader"
|
||||||
|
}, {
|
||||||
|
loader: "sass-loader",
|
||||||
|
options: {
|
||||||
|
includePaths: [ path.resolve(__dirname, '../../ng2-alfresco-core/styles')]
|
||||||
|
}
|
||||||
|
}],
|
||||||
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -20,6 +20,7 @@ import { ModuleWithProviders, NgModule } from '@angular/core';
|
|||||||
import {
|
import {
|
||||||
MdAutocompleteModule,
|
MdAutocompleteModule,
|
||||||
MdButtonModule,
|
MdButtonModule,
|
||||||
|
MdCardModule,
|
||||||
MdDatepickerModule,
|
MdDatepickerModule,
|
||||||
MdGridListModule,
|
MdGridListModule,
|
||||||
MdIconModule,
|
MdIconModule,
|
||||||
@@ -91,6 +92,7 @@ export const ACTIVITI_TASKLIST_PROVIDERS: any[] = [
|
|||||||
MdIconModule,
|
MdIconModule,
|
||||||
MdButtonModule,
|
MdButtonModule,
|
||||||
MdInputModule,
|
MdInputModule,
|
||||||
|
MdCardModule,
|
||||||
MdProgressSpinnerModule,
|
MdProgressSpinnerModule,
|
||||||
MdDatepickerModule,
|
MdDatepickerModule,
|
||||||
MdNativeDateModule,
|
MdNativeDateModule,
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0;
|
margin: 0 0 8px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
|
@@ -27,7 +27,7 @@ import { Component,
|
|||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ContentLinkModel, FormModel, FormOutcomeEvent, FormService } from 'ng2-activiti-form';
|
import { ContentLinkModel, FormModel, FormOutcomeEvent, FormService } from 'ng2-activiti-form';
|
||||||
import { AlfrescoAuthenticationService, AlfrescoTranslationService, LogService } from 'ng2-alfresco-core';
|
import { AlfrescoAuthenticationService, AlfrescoTranslationService, CardViewUpdateService, LogService, UpdateNotification } from 'ng2-alfresco-core';
|
||||||
import { TaskQueryRequestRepresentationModel } from '../models/filter.model';
|
import { TaskQueryRequestRepresentationModel } from '../models/filter.model';
|
||||||
import { TaskDetailsModel } from '../models/task-details.model';
|
import { TaskDetailsModel } from '../models/task-details.model';
|
||||||
import { User } from '../models/user.model';
|
import { User } from '../models/user.model';
|
||||||
@@ -39,7 +39,10 @@ declare let dialogPolyfill: any;
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-task-details, activiti-task-details',
|
selector: 'adf-task-details, activiti-task-details',
|
||||||
templateUrl: './activiti-task-details.component.html',
|
templateUrl: './activiti-task-details.component.html',
|
||||||
styleUrls: ['./activiti-task-details.component.css']
|
styleUrls: ['./activiti-task-details.component.css'],
|
||||||
|
providers: [
|
||||||
|
CardViewUpdateService
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class ActivitiTaskDetailsComponent implements OnInit, OnChanges {
|
export class ActivitiTaskDetailsComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
@@ -132,17 +135,20 @@ export class ActivitiTaskDetailsComponent implements OnInit, OnChanges {
|
|||||||
noTaskDetailsTemplateComponent: TemplateRef<any>;
|
noTaskDetailsTemplateComponent: TemplateRef<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
*
|
||||||
* @param auth Authentication service
|
* @param translateService
|
||||||
* @param translate Translation service
|
* @param activitiForm
|
||||||
* @param activitiForm Form service
|
* @param activitiTaskList
|
||||||
* @param activitiTaskList Task service
|
* @param logService
|
||||||
|
* @param authService
|
||||||
*/
|
*/
|
||||||
constructor(private translateService: AlfrescoTranslationService,
|
constructor(private translateService: AlfrescoTranslationService,
|
||||||
private activitiForm: FormService,
|
private activitiForm: FormService,
|
||||||
private activitiTaskList: ActivitiTaskListService,
|
private activitiTaskList: ActivitiTaskListService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private authService: AlfrescoAuthenticationService) {
|
private authService: AlfrescoAuthenticationService,
|
||||||
|
private cardViewUpdateService: CardViewUpdateService
|
||||||
|
) {
|
||||||
|
|
||||||
if (translateService) {
|
if (translateService) {
|
||||||
translateService.addTranslationFolder('ng2-activiti-tasklist', 'assets/ng2-activiti-tasklist');
|
translateService.addTranslationFolder('ng2-activiti-tasklist', 'assets/ng2-activiti-tasklist');
|
||||||
@@ -153,6 +159,8 @@ export class ActivitiTaskDetailsComponent implements OnInit, OnChanges {
|
|||||||
if (this.taskId) {
|
if (this.taskId) {
|
||||||
this.loadDetails(this.taskId);
|
this.loadDetails(this.taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.cardViewUpdateService.itemUpdated$.subscribe(this.updateTaskDetails.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
@@ -186,6 +194,18 @@ export class ActivitiTaskDetailsComponent implements OnInit, OnChanges {
|
|||||||
return this.taskDetails && this.taskDetails.duration === null;
|
return this.taskDetails && this.taskDetails.duration === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a task detail and update it after a successful response
|
||||||
|
*
|
||||||
|
* @param updateNotification
|
||||||
|
*/
|
||||||
|
private updateTaskDetails(updateNotification: UpdateNotification) {
|
||||||
|
this.activitiTaskList.updateTask(this.taskId, updateNotification.changed)
|
||||||
|
.subscribe(
|
||||||
|
() => { this.loadDetails(this.taskId); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the activiti task details
|
* Load the activiti task details
|
||||||
* @param taskId
|
* @param taskId
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
:host {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activiti-task-header__label {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activiti-task-header__value {
|
|
||||||
color: rgb(68, 138, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
@@ -1,11 +1,13 @@
|
|||||||
<div *ngIf="taskDetails">
|
<md-card *ngIf="taskDetails">
|
||||||
<div class="mdl-grid mdl-shadow--2dp">
|
|
||||||
|
|
||||||
<adf-card-view [properties]="properties"></adf-card-view>
|
<md-card-content>
|
||||||
|
<adf-card-view [properties]="properties" [editable]="!isCompleted()"></adf-card-view>
|
||||||
|
</md-card-content>
|
||||||
|
|
||||||
<button *ngIf="!isAssignedToMe()" data-automation-id="header-claim-button" type="button" id="claim-task"
|
<md-card-actions class="adf-controls">
|
||||||
(click)="claimTask(taskDetails.id)" class="mdl-button">{{ 'TASK_DETAILS.BUTTON.CLAIM' | translate }}
|
<button md-button *ngIf="!isAssignedToMe()" data-automation-id="header-claim-button" id="claim-task"
|
||||||
|
(click)="claimTask(taskDetails.id)" class="adf-claim-controls">{{ 'TASK_DETAILS.BUTTON.CLAIM' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
</md-card-actions>
|
||||||
|
|
||||||
</div>
|
</md-card>
|
||||||
</div>
|
|
||||||
|
@@ -0,0 +1,24 @@
|
|||||||
|
@import 'theming';
|
||||||
|
|
||||||
|
.#{$ADF} {
|
||||||
|
&-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-edit-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-switch-to-edit-mode,
|
||||||
|
&-save-edit-mode {
|
||||||
|
color: rgb(255, 152, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-cancel-edit-mode,
|
||||||
|
&-claim-controls {
|
||||||
|
color: rgb(131, 131, 131);
|
||||||
|
}
|
||||||
|
}
|
@@ -15,9 +15,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
|
import { AlfrescoTranslationService, CardViewUpdateService, CoreModule } from 'ng2-alfresco-core';
|
||||||
import { Observable } from 'rxjs/Rx';
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
|
||||||
import { TaskDetailsModel } from '../models/task-details.model';
|
import { TaskDetailsModel } from '../models/task-details.model';
|
||||||
@@ -31,6 +32,7 @@ describe('ActivitiTaskHeaderComponent', () => {
|
|||||||
let componentHandler: any;
|
let componentHandler: any;
|
||||||
let component: ActivitiTaskHeaderComponent;
|
let component: ActivitiTaskHeaderComponent;
|
||||||
let fixture: ComponentFixture<ActivitiTaskHeaderComponent>;
|
let fixture: ComponentFixture<ActivitiTaskHeaderComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -41,7 +43,8 @@ describe('ActivitiTaskHeaderComponent', () => {
|
|||||||
ActivitiTaskHeaderComponent
|
ActivitiTaskHeaderComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ActivitiTaskListService
|
ActivitiTaskListService,
|
||||||
|
CardViewUpdateService
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
@@ -54,6 +57,7 @@ describe('ActivitiTaskHeaderComponent', () => {
|
|||||||
fixture = TestBed.createComponent(ActivitiTaskHeaderComponent);
|
fixture = TestBed.createComponent(ActivitiTaskHeaderComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
service = TestBed.get(ActivitiTaskListService);
|
service = TestBed.get(ActivitiTaskListService);
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
|
||||||
component.taskDetails = new TaskDetailsModel(taskDetailsMock);
|
component.taskDetails = new TaskDetailsModel(taskDetailsMock);
|
||||||
|
|
||||||
@@ -73,7 +77,7 @@ describe('ActivitiTaskHeaderComponent', () => {
|
|||||||
it('should display assignee', () => {
|
it('should display assignee', () => {
|
||||||
component.ngOnChanges({});
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-header__value'));
|
let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-property-value'));
|
||||||
expect(formNameEl.nativeElement.innerText).toBe('Wilbur Adams');
|
expect(formNameEl.nativeElement.innerText).toBe('Wilbur Adams');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,10 +85,43 @@ describe('ActivitiTaskHeaderComponent', () => {
|
|||||||
component.taskDetails.assignee = null;
|
component.taskDetails.assignee = null;
|
||||||
component.ngOnChanges({});
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-header__value'));
|
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-property-value'));
|
||||||
expect(valueEl.nativeElement.innerText).toBe('No assignee');
|
expect(valueEl.nativeElement.innerText).toBe('No assignee');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should display created-by', () => {
|
||||||
|
component.ngOnChanges({});
|
||||||
|
fixture.detectChanges();
|
||||||
|
let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-created-by"] .adf-property-value'));
|
||||||
|
expect(formNameEl.nativeElement.innerText).toBe('Wilbur Adams');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display placeholder if no created-by', () => {
|
||||||
|
component.taskDetails.assignee = null;
|
||||||
|
component.ngOnChanges({});
|
||||||
|
fixture.detectChanges();
|
||||||
|
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-created-by"] .adf-property-value'));
|
||||||
|
expect(valueEl.nativeElement.innerText).toBe('No assignee');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set editable to false if the task has already completed', () => {
|
||||||
|
component.taskDetails.endDate = '05/05/2002';
|
||||||
|
component.ngOnChanges({});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-dueDate"]`));
|
||||||
|
expect(datePicker).toBeNull('Datepicker should NOT be in DOM');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set editable to true if the task has not completed yet', () => {
|
||||||
|
component.taskDetails.endDate = undefined;
|
||||||
|
component.ngOnChanges({});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-dueDate"]`));
|
||||||
|
expect(datePicker).not.toBeNull('Datepicker should be in DOM');
|
||||||
|
});
|
||||||
|
|
||||||
it('should display the claim button if no assignee', () => {
|
it('should display the claim button if no assignee', () => {
|
||||||
component.taskDetails.assignee = null;
|
component.taskDetails.assignee = null;
|
||||||
component.ngOnChanges({});
|
component.ngOnChanges({});
|
||||||
@@ -97,30 +134,30 @@ describe('ActivitiTaskHeaderComponent', () => {
|
|||||||
component.taskDetails.dueDate = '2016-11-03';
|
component.taskDetails.dueDate = '2016-11-03';
|
||||||
component.ngOnChanges({});
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-header__value'));
|
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-property-value'));
|
||||||
expect(valueEl.nativeElement.innerText).toBe('Nov 03 2016');
|
expect(valueEl.nativeElement.innerText.trim()).toBe('Nov 03 2016');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display placeholder if no due date', () => {
|
it('should display placeholder if no due date', () => {
|
||||||
component.taskDetails.dueDate = null;
|
component.taskDetails.dueDate = null;
|
||||||
component.ngOnChanges({});
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-header__value'));
|
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-property-value'));
|
||||||
expect(valueEl.nativeElement.innerText).toBe('No date');
|
expect(valueEl.nativeElement.innerText.trim()).toBe('No date');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display form name', () => {
|
it('should display form name', () => {
|
||||||
component.formName = 'test form';
|
component.formName = 'test form';
|
||||||
component.ngOnChanges({});
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-formName"] .adf-header__value'));
|
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-formName"] .adf-property-value'));
|
||||||
expect(valueEl.nativeElement.innerText).toBe('test form');
|
expect(valueEl.nativeElement.innerText).toBe('test form');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not display form name if no form name provided', () => {
|
it('should not display form name if no form name provided', () => {
|
||||||
component.ngOnChanges({});
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-formName"] .adf-header__value'));
|
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-formName"] .adf-property-value'));
|
||||||
expect(valueEl.nativeElement.innerText).toBe('No form');
|
expect(valueEl.nativeElement.innerText).toBe('No form');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -16,14 +16,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||||
import { AlfrescoTranslationService, CardViewModel, LogService } from 'ng2-alfresco-core';
|
import { AlfrescoTranslationService, CardViewDateItemModel, CardViewItem, CardViewTextItemModel, LogService } from 'ng2-alfresco-core';
|
||||||
import { TaskDetailsModel } from '../models/index';
|
import { TaskDetailsModel } from '../models/index';
|
||||||
import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
|
import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-task-header, activiti-task-header',
|
selector: 'adf-task-header, activiti-task-header',
|
||||||
templateUrl: './activiti-task-header.component.html',
|
templateUrl: './activiti-task-header.component.html',
|
||||||
styleUrls: ['./activiti-task-header.component.css']
|
styleUrls: ['./activiti-task-header.component.scss']
|
||||||
})
|
})
|
||||||
export class ActivitiTaskHeaderComponent implements OnChanges {
|
export class ActivitiTaskHeaderComponent implements OnChanges {
|
||||||
|
|
||||||
@@ -36,7 +36,8 @@ export class ActivitiTaskHeaderComponent implements OnChanges {
|
|||||||
@Output()
|
@Output()
|
||||||
claim: EventEmitter<any> = new EventEmitter<any>();
|
claim: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
properties: CardViewModel [];
|
properties: CardViewItem [];
|
||||||
|
inEdit: boolean = false;
|
||||||
|
|
||||||
constructor(private translateService: AlfrescoTranslationService,
|
constructor(private translateService: AlfrescoTranslationService,
|
||||||
private activitiTaskService: ActivitiTaskListService,
|
private activitiTaskService: ActivitiTaskListService,
|
||||||
@@ -47,6 +48,7 @@ export class ActivitiTaskHeaderComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
console.log('change van:', changes, this.taskDetails);
|
||||||
this.refreshData();
|
this.refreshData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,20 +56,22 @@ export class ActivitiTaskHeaderComponent implements OnChanges {
|
|||||||
if (this.taskDetails) {
|
if (this.taskDetails) {
|
||||||
|
|
||||||
this.properties = [
|
this.properties = [
|
||||||
new CardViewModel({label: 'Status:', value: this.getTaskStatus(), key: 'status'}),
|
new CardViewTextItemModel({ label: 'Assignee', value: this.taskDetails.getFullName(), key: 'assignee', default: 'No assignee' } ),
|
||||||
new CardViewModel({label: 'Due Date:', value: this.taskDetails.dueDate, format: 'MMM DD YYYY', key: 'dueDate', default: 'No date'}),
|
new CardViewTextItemModel({ label: 'Status', value: this.getTaskStatus(), key: 'status' }),
|
||||||
new CardViewModel({label: 'Category:', value: this.taskDetails.category, key: 'category', default: 'No category'}),
|
new CardViewDateItemModel({ label: 'Due Date', value: this.taskDetails.dueDate, key: 'dueDate', default: 'No date', editable: true }),
|
||||||
new CardViewModel(
|
new CardViewTextItemModel({ label: 'Category', value: this.taskDetails.category, key: 'category', default: 'No category' }),
|
||||||
{
|
new CardViewTextItemModel({ label: 'Created By', value: this.taskDetails.getFullName(), key: 'created-by', default: 'No assignee' }),
|
||||||
label: 'Created By:',
|
new CardViewDateItemModel({ label: 'Created', value: this.taskDetails.created, key: 'created' }),
|
||||||
value: this.taskDetails.getFullName(),
|
new CardViewTextItemModel({ label: 'Id', value: this.taskDetails.id, key: 'id' }),
|
||||||
key: 'assignee',
|
new CardViewTextItemModel({
|
||||||
default: 'No assignee'
|
label: 'Description',
|
||||||
}),
|
value: this.taskDetails.description,
|
||||||
new CardViewModel({label: 'Created:', value: this.taskDetails.created, format: 'MMM DD YYYY', key: 'created'}),
|
key: 'description',
|
||||||
new CardViewModel({label: 'Id:', value: this.taskDetails.id, key: 'id'}),
|
default: 'No description',
|
||||||
new CardViewModel({label: 'Description:', value: this.taskDetails.description, key: 'description', default: 'No description'}),
|
multiline: true,
|
||||||
new CardViewModel({label: 'Form name:', value: this.formName, key: 'formName', default: 'No form'})
|
editable: true
|
||||||
|
}),
|
||||||
|
new CardViewTextItemModel({ label: 'Form name', value: this.formName, key: 'formName', default: 'No form' })
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +85,7 @@ export class ActivitiTaskHeaderComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTaskStatus(): string {
|
getTaskStatus(): string {
|
||||||
return this.taskDetails.endDate ? 'Completed' : 'Running';
|
return this.isCompleted() ? 'Completed' : 'Running';
|
||||||
}
|
}
|
||||||
|
|
||||||
claimTask(taskId: string) {
|
claimTask(taskId: string) {
|
||||||
@@ -91,4 +95,8 @@ export class ActivitiTaskHeaderComponent implements OnChanges {
|
|||||||
this.claim.emit(taskId);
|
this.claim.emit(taskId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCompleted() {
|
||||||
|
return !!this.taskDetails.endDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ describe('NoTaskDetailsTemplateDirective', () => {
|
|||||||
let detailsComponent: ActivitiTaskDetailsComponent;
|
let detailsComponent: ActivitiTaskDetailsComponent;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
detailsComponent = new ActivitiTaskDetailsComponent(null, null, null, null, null);
|
detailsComponent = new ActivitiTaskDetailsComponent(null, null, null, null, null, null);
|
||||||
component = new NoTaskDetailsTemplateDirective(detailsComponent);
|
component = new NoTaskDetailsTemplateDirective(detailsComponent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -562,6 +562,22 @@ describe('Activiti TaskList Service', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update a task', (done) => {
|
||||||
|
let taskId = '111';
|
||||||
|
|
||||||
|
service.updateTask(taskId, {property: 'value'}).subscribe(
|
||||||
|
(res: any) => {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||||
|
'status': 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
responseText: JSON.stringify({})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return the filter if it contains task id', (done) => {
|
it('should return the filter if it contains task id', (done) => {
|
||||||
let taskId = '1';
|
let taskId = '1';
|
||||||
let filterFake = new FilterRepresentationModel({
|
let filterFake = new FilterRepresentationModel({
|
||||||
|
@@ -375,6 +375,15 @@ export class ActivitiTaskListService {
|
|||||||
.catch(err => this.handleError(err));
|
.catch(err => this.handleError(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update due date
|
||||||
|
* @param dueDate - the new due date
|
||||||
|
*/
|
||||||
|
updateTask(taskId: any, updated): Observable<TaskDetailsModel> {
|
||||||
|
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.updateTask(taskId, updated))
|
||||||
|
.catch(err => this.handleError(err));
|
||||||
|
}
|
||||||
|
|
||||||
private callApiTasksFiltered(requestNode: TaskQueryRequestRepresentationModel) {
|
private callApiTasksFiltered(requestNode: TaskQueryRequestRepresentationModel) {
|
||||||
return this.apiService.getInstance().activiti.taskApi.listTasks(requestNode);
|
return this.apiService.getInstance().activiti.taskApi.listTasks(requestNode);
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,16 @@
|
|||||||
* [Events](#events-1)
|
* [Events](#events-1)
|
||||||
- [ADF Card View](#adf-card-view)
|
- [ADF Card View](#adf-card-view)
|
||||||
* [Properties](#properties-2)
|
* [Properties](#properties-2)
|
||||||
* [CardViewModel](#cardviewmodel)
|
* [Editing](#editing)
|
||||||
|
* [Defining properties](#defining-properties)
|
||||||
|
* [Card Text Item](#card-text-item)
|
||||||
|
+ [Options](#options)
|
||||||
|
* [Card Date Item](#card-date-item)
|
||||||
|
+ [Options](#options-1)
|
||||||
|
* [Defining your custom card Item](#defining-your-custom-card-item)
|
||||||
|
+ [1. Define the model for the custom type](#1-define-the-model-for-the-custom-type)
|
||||||
|
+ [2. Define the component for the custom type](#2-define-the-component-for-the-custom-type)
|
||||||
|
+ [3. Add you custom component to your module's entryComponents list](#3-add-you-custom-component-to-your-modules-entrycomponents-list)
|
||||||
- [AlfrescoTranslationService](#alfrescotranslationservice)
|
- [AlfrescoTranslationService](#alfrescotranslationservice)
|
||||||
- [Renditions Service](#renditions-service)
|
- [Renditions Service](#renditions-service)
|
||||||
- [Build from sources](#build-from-sources)
|
- [Build from sources](#build-from-sources)
|
||||||
@@ -593,40 +602,193 @@ export class AppComponent {
|
|||||||
|
|
||||||
## ADF Card View
|
## ADF Card View
|
||||||
|
|
||||||
The component shows the [CardViewModel](#cardviewmodel)} object.
|
The CardViewComponent is a configurable property list renderer. You define the property list, the CardViewComponent does the rest. Each property represents a card view item (a row) in the card view component. At the time of writing two different kind of card view item (property type) is supported out of the box ([text](#card-text-item) item and [date](#card-date-item) item) but you can define your own custom types as well.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<adf-card-view
|
<adf-card-view
|
||||||
[properties]="[{label: 'My Label', value: 'My value'}]">
|
[properties]="[{label: 'My Label', value: 'My value'}]"
|
||||||
|
[editable]="false">
|
||||||
</adf-card-view>
|
</adf-card-view>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| properties | {array[CardViewModel](#cardviewmodel)} | (**required**) The custom view to render |
|
| properties | [CardViewItem](#cardviewitem)[] | (**required**) The custom view to render |
|
||||||
|
| editable | boolean | If the component editable or not |
|
||||||
|
|
||||||
### CardViewModel
|
### Editing
|
||||||
|
|
||||||
```json
|
The card view can optionally allow its properties to be edited. You can control the editing of the properties in two level.
|
||||||
{
|
- **global level** - *via the editable paramter of the adf-card-view component*
|
||||||
"label": "string",
|
- **property level** - *in each property via the editable attribute*
|
||||||
"value": "any",
|
|
||||||
"format": "string",
|
If you set the global editable parameter to false, no properties can be edited regardless of what is set inside the property.
|
||||||
"default": "string"
|
|
||||||
|
### Defining properties
|
||||||
|
|
||||||
|
Properties is an array of models which one by one implements the CardViewItem interface.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export interface CardViewItem {
|
||||||
|
label: string;
|
||||||
|
value: any;
|
||||||
|
key: string;
|
||||||
|
default?: any;
|
||||||
|
type: string;
|
||||||
|
displayValue: string;
|
||||||
|
editable?: boolean;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Name | Type | Description |
|
At the moment two models are defined out of the box:
|
||||||
| --- | --- | --- |
|
|
||||||
| label | string | The label to render |
|
|
||||||
| value | string | The value to render |
|
|
||||||
| format | string | The format to use in case the value is a date |
|
|
||||||
| default | string | The default value to render in case the value is empty |
|
|
||||||
|
|
||||||

|
- **[CardViewTextItemModel](#card-text-item)** - *for text items*
|
||||||
|
- **[CardViewDateItemModel](#card-date-item)** - *for date items*
|
||||||
|
|
||||||
|
Each of them are extending the abstract CardViewBaseItemModel class, and each of them are adding some custom functionality to the basic behaviour.
|
||||||
|
|
||||||
|
```js
|
||||||
|
this.properties = [
|
||||||
|
new CardViewTextItemModel({
|
||||||
|
label: 'Name',
|
||||||
|
value: 'Spock',
|
||||||
|
key: 'name',
|
||||||
|
default: 'default bar' ,
|
||||||
|
multiline: false
|
||||||
|
}),
|
||||||
|
new CardViewDateItemModel({
|
||||||
|
label: 'Birth of date',
|
||||||
|
value: someDate,
|
||||||
|
key: 'birth-of-date',
|
||||||
|
default: new Date(),
|
||||||
|
format: '<any format that momentjs accepts>',
|
||||||
|
editable: true
|
||||||
|
}),
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Card Text Item
|
||||||
|
|
||||||
|
CardViewTextItemModel is a property type for text properties.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const textItemProperty = new CardViewTextItemModel(options);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| label* | string | --- | The label to render |
|
||||||
|
| value* | any | --- | The original value |
|
||||||
|
| key* | string | --- | the key of the property. Have an important role when editing the property. |
|
||||||
|
| default | any | --- | The default value to render in case the value is empty |
|
||||||
|
| displayValue* | string | --- | The value to render |
|
||||||
|
| editable | boolean | false | Whether the property editable or not |
|
||||||
|
| multiline | string | false | Single or multiline text |
|
||||||
|
|
||||||
|
### Card Date Item
|
||||||
|
|
||||||
|
CardViewDateItemModel is a property type for date properties.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const dateItemProperty = new CardViewDateItemModel(options);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| label* | string | --- | The label to render |
|
||||||
|
| value* | any | --- | The original value |
|
||||||
|
| key* | string | --- | the key of the property. Have an important role when editing the property. |
|
||||||
|
| default | any | --- | The default value to render in case the value is empty |
|
||||||
|
| displayValue* | string | --- | The value to render |
|
||||||
|
| editable | boolean | false | Whether the property editable or not |
|
||||||
|
| format | boolean | "MMM DD YYYY" | any format that momentjs accepts |
|
||||||
|
|
||||||
|
### Defining your custom card Item
|
||||||
|
|
||||||
|
Card item components are loaded dynamically, which makes you able to define your own custom component for the custom card item type.
|
||||||
|
|
||||||
|
Let's consider you want to have a **stardate** type to display Captain Picard's birthday (47457.1). For this, you need to do the following steps.
|
||||||
|
|
||||||
|
#### 1. Define the model for the custom type
|
||||||
|
|
||||||
|
Your model has to extend the CardViewBaseItemModel and implement the CardViewItem interface.
|
||||||
|
*(You can check how the CardViewTextItemModel is implemented for further guidance.)*
|
||||||
|
|
||||||
|
```js
|
||||||
|
export class CardViewStarDateItemModel extends CardViewBaseItemModel implements CardViewItem {
|
||||||
|
type: string = 'star-date';
|
||||||
|
|
||||||
|
get displayValue() {
|
||||||
|
return this.convertToStarDate(this.value) || this.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertToStarDate(starTimeStamp: number): string {
|
||||||
|
// Do the magic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The most important part of this model is the value of the **type** attribute. This is how the Card View component will be able to recognise which component is needed to render it dynamically.
|
||||||
|
|
||||||
|
The type is a **hyphen-separated-lowercase-words** string (just like how I wrote it). This will be converted to a PascalCase (or UpperCamelCase) string to find the right component. In our case the Card View component will look for the CardView**StarDate**ItemComponent.
|
||||||
|
|
||||||
|
#### 2. Define the component for the custom type
|
||||||
|
|
||||||
|
As discussed in the previous step the only important thing here is the naming of your component class ( **CardViewStarDateItemComponent**). Since the selector is not used in this case, you can give any selector name to it, but it make sense to follow the angular standards.
|
||||||
|
|
||||||
|
```js
|
||||||
|
@Component({
|
||||||
|
selector: 'card-view-stardateitem' // For example
|
||||||
|
...
|
||||||
|
})
|
||||||
|
export class CardViewStarDateItemComponent {
|
||||||
|
@Input()
|
||||||
|
property: CardViewStarDateItemModel;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
editable: boolean;
|
||||||
|
|
||||||
|
constructor(private cardViewUpdateService: CardViewUpdateService) {}
|
||||||
|
|
||||||
|
isEditble() {
|
||||||
|
return this.editable && this.property.editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
showStarDatePicker() {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
To make your component editable, you can have a look on either the CardViewTextItemComponent' or on the CardViewDateItemComponent's source.
|
||||||
|
|
||||||
|
#### 3. Add you custom component to your module's entryComponents list
|
||||||
|
|
||||||
|
For Angular to be able to load your custom component dynamically, you have to register your component in your modules entryComponents.
|
||||||
|
|
||||||
|
```js
|
||||||
|
@NgModule({
|
||||||
|
imports: [...],
|
||||||
|
declarations: [
|
||||||
|
CardViewStarDateItemComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
CardViewStarDateItemComponent
|
||||||
|
],
|
||||||
|
exports: [...]
|
||||||
|
})
|
||||||
|
export class MyModule {}
|
||||||
|
```
|
||||||
|
|
||||||
## AlfrescoTranslationService
|
## AlfrescoTranslationService
|
||||||
|
|
||||||
|
@@ -60,8 +60,17 @@ module.exports = {
|
|||||||
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.component.scss$/,
|
test: /\.scss$/,
|
||||||
use: ['to-string-loader', 'raw-loader', 'sass-loader'],
|
use: [{
|
||||||
|
loader: "to-string-loader"
|
||||||
|
}, {
|
||||||
|
loader: "raw-loader"
|
||||||
|
}, {
|
||||||
|
loader: "sass-loader",
|
||||||
|
options: {
|
||||||
|
includePaths: [ path.resolve(__dirname, '../styles')]
|
||||||
|
}
|
||||||
|
}],
|
||||||
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 74 KiB |
@@ -69,6 +69,7 @@ export { ContextMenuModule } from './src/components/context-menu/context-menu.mo
|
|||||||
export { CardViewModule } from './src/components/view/card-view.module';
|
export { CardViewModule } from './src/components/view/card-view.module';
|
||||||
export { CollapsableModule } from './src/components/collapsable/collapsable.module';
|
export { CollapsableModule } from './src/components/collapsable/collapsable.module';
|
||||||
export { UserPreferencesService } from './src/services/user-preferences.service';
|
export { UserPreferencesService } from './src/services/user-preferences.service';
|
||||||
|
export { CardViewItem } from './src/interface/card-view-item.interface';
|
||||||
export * from './src/services/index';
|
export * from './src/services/index';
|
||||||
export * from './src/components/data-column/data-column.component';
|
export * from './src/components/data-column/data-column.component';
|
||||||
export * from './src/components/data-column/data-column-list.component';
|
export * from './src/components/data-column/data-column-list.component';
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { Directive, ViewContainerRef } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[adf-card-view-content-proxy]'
|
||||||
|
})
|
||||||
|
export class AdfCardViewContentProxyDirective {
|
||||||
|
constructor(public viewContainerRef: ViewContainerRef) { }
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
<div class="adf-property-label">{{ property.label }}</div>
|
||||||
|
<div class="adf-property-value">
|
||||||
|
<span *ngIf="!isEditble()">
|
||||||
|
<span [attr.data-automation-id]="'card-dateitem-' + property.key">{{ property.displayValue }}</span>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="isEditble()" class="adf-dateitem-editable">
|
||||||
|
<input class="adf-invisible-date-input" [mdDatepicker]="picker" value="{{property.value}}"><!--
|
||||||
|
--><span
|
||||||
|
class="adf-datepicker-toggle"
|
||||||
|
[attr.data-automation-id]="'datepicker-label-toggle-' + property.key"
|
||||||
|
(click)="showDatePicker($event)">{{ property.displayValue }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
[attr.data-automation-id]="'datepickertoggle-' + property.key"
|
||||||
|
[mdDatepickerToggle]="picker">
|
||||||
|
</button>
|
||||||
|
<md-datepicker #picker
|
||||||
|
[attr.data-automation-id]="'datepicker-' + property.key"
|
||||||
|
(selectedChanged)="dateChanged($event)"
|
||||||
|
[startAt]="property.value">
|
||||||
|
</md-datepicker>
|
||||||
|
</span>
|
||||||
|
</div>
|
@@ -0,0 +1,28 @@
|
|||||||
|
@import 'theming';
|
||||||
|
|
||||||
|
.#{$ADF} {
|
||||||
|
&-invisible-date-input {
|
||||||
|
height: 24px;
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-dateitem-editable {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover button {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,151 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { MdDatepickerModule, MdInputModule, MdNativeDateModule } from '@angular/material';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { CardViewDateItemModel } from '../../models/card-view-dateitem.model';
|
||||||
|
import { CardViewUpdateService } from '../../services/adf-card-view-update.service';
|
||||||
|
import { CardViewDateItemComponent } from './adf-card-view-dateitem.component';
|
||||||
|
|
||||||
|
describe('CardViewDateItemComponent', () => {
|
||||||
|
|
||||||
|
let fixture: ComponentFixture<CardViewDateItemComponent>;
|
||||||
|
let component: CardViewDateItemComponent;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
MdDatepickerModule,
|
||||||
|
MdInputModule,
|
||||||
|
MdNativeDateModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CardViewDateItemComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
CardViewUpdateService
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CardViewDateItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.property = <CardViewDateItemModel>{
|
||||||
|
type: 'date',
|
||||||
|
label: 'Date label',
|
||||||
|
value: new Date('07/10/2017'),
|
||||||
|
key: 'datekey',
|
||||||
|
default: '',
|
||||||
|
format: '',
|
||||||
|
editable: false,
|
||||||
|
get displayValue(): string {
|
||||||
|
return 'Jul 10 2017';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the label and value', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||||
|
expect(labelValue).not.toBeNull();
|
||||||
|
expect(labelValue.nativeElement.innerText).toBe('Date label');
|
||||||
|
|
||||||
|
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||||
|
expect(value).not.toBeNull();
|
||||||
|
expect(value.nativeElement.innerText.trim()).toBe('Jul 10 2017');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render value when editable:true', () => {
|
||||||
|
component.editable = true;
|
||||||
|
component.property.editable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||||
|
expect(value).not.toBeNull();
|
||||||
|
expect(value.nativeElement.innerText.trim()).toBe('Jul 10 2017');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the picker and toggle in case of editable:true', () => {
|
||||||
|
component.editable = true;
|
||||||
|
component.property.editable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-${component.property.key}"]`));
|
||||||
|
let datePickerToggle = fixture.debugElement.query(By.css(`[data-automation-id="datepickertoggle-${component.property.key}"]`));
|
||||||
|
expect(datePicker).not.toBeNull('Datepicker should be in DOM');
|
||||||
|
expect(datePickerToggle).not.toBeNull('Datepicker toggle should be shown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT render the picker and toggle in case of editable:false', () => {
|
||||||
|
component.property.editable = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-${component.property.key}"]`));
|
||||||
|
let datePickerToggle = fixture.debugElement.query(By.css(`[data-automation-id="datepickertoggle-${component.property.key}"]`));
|
||||||
|
expect(datePicker).toBeNull('Datepicker should NOT be in DOM');
|
||||||
|
expect(datePickerToggle).toBeNull('Datepicker toggle should NOT be shown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT render the picker and toggle in case of editable:true but (general) editable:false', () => {
|
||||||
|
component.editable = false;
|
||||||
|
component.property.editable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-${component.property.key}"]`));
|
||||||
|
let datePickerToggle = fixture.debugElement.query(By.css(`[data-automation-id="datepickertoggle-${component.property.key}"]`));
|
||||||
|
expect(datePicker).toBeNull('Datepicker should NOT be in DOM');
|
||||||
|
expect(datePickerToggle).toBeNull('Datepicker toggle should NOT be shown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open the datetimepicker when clicking on the label', () => {
|
||||||
|
component.editable = true;
|
||||||
|
component.property.editable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
spyOn(component.datepicker, 'open');
|
||||||
|
|
||||||
|
let datePickerLabelToggle = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-label-toggle-${component.property.key}"]`));
|
||||||
|
datePickerLabelToggle.triggerEventHandler('click', {});
|
||||||
|
|
||||||
|
expect(component.datepicker.open).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger an update event on the CardViewUpdateService', (done) => {
|
||||||
|
component.editable = true;
|
||||||
|
component.property.editable = true;
|
||||||
|
const cardViewUpdateService = TestBed.get(CardViewUpdateService);
|
||||||
|
const expectedDate = new Date('11/11/2017');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
cardViewUpdateService.itemUpdated$.subscribe(
|
||||||
|
(updateNotification) => {
|
||||||
|
expect(updateNotification.target).toBe(component.property);
|
||||||
|
expect(updateNotification.changed).toEqual({ datekey: expectedDate });
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
component.datepicker.selectedChanged.next(expectedDate);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,51 @@
|
|||||||
|
/*!
|
||||||
|
* @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, Input, ViewChild } from '@angular/core';
|
||||||
|
import { MdDatepicker } from '@angular/material';
|
||||||
|
import { CardViewDateItemModel } from '../../models/card-view-dateitem.model';
|
||||||
|
import { CardViewUpdateService } from '../../services/adf-card-view-update.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-card-view-dateitem',
|
||||||
|
templateUrl: './adf-card-view-dateitem.component.html',
|
||||||
|
styleUrls: ['./adf-card-view-dateitem.component.scss']
|
||||||
|
})
|
||||||
|
export class CardViewDateItemComponent {
|
||||||
|
@Input()
|
||||||
|
property: CardViewDateItemModel;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
editable: boolean;
|
||||||
|
|
||||||
|
@ViewChild(MdDatepicker)
|
||||||
|
public datepicker: MdDatepicker<any>;
|
||||||
|
|
||||||
|
constructor(private cardViewUpdateService: CardViewUpdateService) {}
|
||||||
|
|
||||||
|
isEditble() {
|
||||||
|
return this.editable && this.property.editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
showDatePicker() {
|
||||||
|
this.datepicker.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
dateChanged(changed) {
|
||||||
|
this.cardViewUpdateService.update(this.property, { [this.property.key]: changed });
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,155 @@
|
|||||||
|
/*!
|
||||||
|
* @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, Input } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
|
||||||
|
import { CardViewItem } from '../../interface/card-view-item.interface';
|
||||||
|
import { AdfCardViewContentProxyDirective } from './adf-card-view-content-proxy.directive';
|
||||||
|
import { CardViewItemDispatcherComponent } from './adf-card-view-item-dispatcher.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'whatever-you-want-to-have',
|
||||||
|
template: '<div data-automation-id="found-me">Hey I am shiny!</div>'
|
||||||
|
})
|
||||||
|
export class CardViewShinyCustomElementItemComponent {
|
||||||
|
@Input() property: CardViewItem;
|
||||||
|
@Input() editable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CardViewItemDispatcherComponent', () => {
|
||||||
|
|
||||||
|
let fixture: ComponentFixture<CardViewItemDispatcherComponent>;
|
||||||
|
let component: CardViewItemDispatcherComponent;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [
|
||||||
|
CardViewItemDispatcherComponent,
|
||||||
|
CardViewShinyCustomElementItemComponent,
|
||||||
|
AdfCardViewContentProxyDirective
|
||||||
|
],
|
||||||
|
providers: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// entryComponents are not supported yet on TestBed, that is why this ugly workaround:
|
||||||
|
// https://github.com/angular/angular/issues/10760
|
||||||
|
TestBed.overrideModule(BrowserDynamicTestingModule, {
|
||||||
|
set: { entryComponents: [ CardViewShinyCustomElementItemComponent ] }
|
||||||
|
});
|
||||||
|
|
||||||
|
TestBed.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CardViewItemDispatcherComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.property = <CardViewItem>{
|
||||||
|
type: 'shiny-custom-element',
|
||||||
|
label: 'Shiny custom element',
|
||||||
|
value: null,
|
||||||
|
key: 'customkey',
|
||||||
|
default: '',
|
||||||
|
editable: false,
|
||||||
|
get displayValue(): string {
|
||||||
|
return 'custom value displayed';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
component.editable = true;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.ngOnChanges(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Subcompomnent creation', () => {
|
||||||
|
|
||||||
|
it('should load the CardViewShinyCustomElementItemComponent', () => {
|
||||||
|
const innerElement = fixture.debugElement.query(By.css('[data-automation-id="found-me"]'));
|
||||||
|
expect(innerElement).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load the CardViewShinyCustomElementItemComponent only ONCE', () => {
|
||||||
|
component.ngOnChanges();
|
||||||
|
component.ngOnChanges();
|
||||||
|
component.ngOnChanges();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const shinyCustomElementItemComponent = fixture.debugElement.queryAll(By.css('whatever-you-want-to-have'));
|
||||||
|
|
||||||
|
expect(shinyCustomElementItemComponent.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass through the property and editable parameters', () => {
|
||||||
|
const shinyCustomElementItemComponent = fixture.debugElement.query(By.css('whatever-you-want-to-have')).componentInstance;
|
||||||
|
|
||||||
|
expect(shinyCustomElementItemComponent.property).toBe(component.property);
|
||||||
|
expect(shinyCustomElementItemComponent.editable).toBe(component.editable);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Angular lifecycle methods', () => {
|
||||||
|
|
||||||
|
let shinyCustomElementItemComponent;
|
||||||
|
|
||||||
|
const lifeCycleMethods = [
|
||||||
|
'ngOnChanges',
|
||||||
|
'ngOnInit',
|
||||||
|
'ngDoCheck',
|
||||||
|
'ngAfterContentInit',
|
||||||
|
'ngAfterContentChecked',
|
||||||
|
'ngAfterViewInit',
|
||||||
|
'ngAfterViewChecked',
|
||||||
|
'ngOnDestroy'
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
shinyCustomElementItemComponent = fixture.debugElement.query(By.css('whatever-you-want-to-have')).componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call through the lifecycle methods', () => {
|
||||||
|
lifeCycleMethods.forEach((lifeCycleMethod) => {
|
||||||
|
shinyCustomElementItemComponent[lifeCycleMethod] = () => {};
|
||||||
|
spyOn(shinyCustomElementItemComponent, lifeCycleMethod);
|
||||||
|
const param1 = {};
|
||||||
|
const param2 = {};
|
||||||
|
|
||||||
|
component[lifeCycleMethod].call(component, param1, param2);
|
||||||
|
|
||||||
|
expect(shinyCustomElementItemComponent[lifeCycleMethod]).toHaveBeenCalledWith(param1, param2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT call through the lifecycle methods if the method does not exist (no error should be thrown)', () => {
|
||||||
|
lifeCycleMethods.forEach((lifeCycleMethod) => {
|
||||||
|
shinyCustomElementItemComponent[lifeCycleMethod] = undefined;
|
||||||
|
|
||||||
|
const execution = () => {
|
||||||
|
component[lifeCycleMethod].call(component);
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(execution).not.toThrowError();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,97 @@
|
|||||||
|
/*!
|
||||||
|
* @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,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
Type,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { CardViewItem } from '../../interface/card-view-item.interface';
|
||||||
|
import { AdfCardViewContentProxyDirective } from './adf-card-view-content-proxy.directive';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-card-view-item-dispatcher',
|
||||||
|
template: '<ng-template adf-card-view-content-proxy></ng-template>'
|
||||||
|
})
|
||||||
|
export class CardViewItemDispatcherComponent implements OnChanges {
|
||||||
|
@Input()
|
||||||
|
property: CardViewItem;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
editable: boolean;
|
||||||
|
|
||||||
|
@ViewChild(AdfCardViewContentProxyDirective)
|
||||||
|
private content: AdfCardViewContentProxyDirective;
|
||||||
|
|
||||||
|
private loaded: boolean = false;
|
||||||
|
private componentReference: any = null;
|
||||||
|
|
||||||
|
public ngOnInit;
|
||||||
|
public ngDoCheck;
|
||||||
|
|
||||||
|
constructor(private resolver: ComponentFactoryResolver) {
|
||||||
|
const dynamicLifecycleMethods = [
|
||||||
|
'ngOnInit',
|
||||||
|
'ngDoCheck',
|
||||||
|
'ngAfterContentInit',
|
||||||
|
'ngAfterContentChecked',
|
||||||
|
'ngAfterViewInit',
|
||||||
|
'ngAfterViewChecked',
|
||||||
|
'ngOnDestroy'
|
||||||
|
];
|
||||||
|
|
||||||
|
dynamicLifecycleMethods.forEach((dynamicLifecycleMethod) => {
|
||||||
|
this[dynamicLifecycleMethod] = this.proxy.bind(this, dynamicLifecycleMethod);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(...args) {
|
||||||
|
if (!this.loaded) {
|
||||||
|
this.loadComponent();
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.proxy('ngOnChanges', ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadComponent() {
|
||||||
|
const upperCamelCasedType = this.getUpperCamelCase(this.property.type),
|
||||||
|
className = `CardView${upperCamelCasedType}ItemComponent`;
|
||||||
|
|
||||||
|
const factories = Array.from(this.resolver['_factories'].keys());
|
||||||
|
const factoryClass = <Type<any>>factories.find((x: any) => x.name === className);
|
||||||
|
const factory = this.resolver.resolveComponentFactory(factoryClass);
|
||||||
|
this.componentReference = this.content.viewContainerRef.createComponent(factory);
|
||||||
|
|
||||||
|
this.componentReference.instance.editable = this.editable;
|
||||||
|
this.componentReference.instance.property = this.property;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUpperCamelCase(type: string): string {
|
||||||
|
const camelCasedType = type.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
|
||||||
|
return camelCasedType[0].toUpperCase() + camelCasedType.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private proxy(methodName, ...args) {
|
||||||
|
if (this.componentReference.instance[methodName]) {
|
||||||
|
this.componentReference.instance[methodName].apply(this.componentReference.instance, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
<div class="adf-property-label">{{ property.label }}</div>
|
||||||
|
<div class="adf-property-value">
|
||||||
|
<span *ngIf="!isEditble()">
|
||||||
|
<span [attr.data-automation-id]="'card-textitem-value-' + property.key">{{ property.displayValue }}</span>
|
||||||
|
</span>
|
||||||
|
<span *ngIf="isEditble()">
|
||||||
|
<div *ngIf="!inEdit" (click)="setEditMode(true)" class="adf-textitem-readonly" [attr.data-automation-id]="'card-textitem-edit-toggle-' + property.key">
|
||||||
|
<span [attr.data-automation-id]="'card-textitem-value-' + property.key">{{ property.displayValue }}</span>
|
||||||
|
<md-icon [attr.data-automation-id]="'card-textitem-edit-icon-' + property.key" class="adf-textitem-icon">create</md-icon>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="inEdit" class="adf-textitem-editable">
|
||||||
|
<md-input-container floatPlaceholder="never" class="adf-input-container">
|
||||||
|
<input *ngIf="!property.multiline" #editorInput
|
||||||
|
mdInput
|
||||||
|
class="adf-input"
|
||||||
|
[placeholder]="property.default"
|
||||||
|
[(ngModel)]="editedValue"
|
||||||
|
[attr.data-automation-id]="'card-textitem-editinput-' + property.key">
|
||||||
|
<textarea *ngIf="property.multiline" #editorInput
|
||||||
|
mdInput
|
||||||
|
mdTextareaAutosize
|
||||||
|
mdAutosizeMaxRows="1"
|
||||||
|
mdAutosizeMaxRows="5"
|
||||||
|
class="adf-textarea"
|
||||||
|
[placeholder]="property.default"
|
||||||
|
[(ngModel)]="editedValue"
|
||||||
|
[attr.data-automation-id]="'card-textitem-edittextarea-' + property.key"></textarea>
|
||||||
|
</md-input-container>
|
||||||
|
<md-icon
|
||||||
|
class="adf-textitem-icon adf-update-icon"
|
||||||
|
(click)="update()"
|
||||||
|
[attr.data-automation-id]="'card-textitem-update-' + property.key">done</md-icon>
|
||||||
|
<md-icon
|
||||||
|
class="adf-textitem-icon adf-reset-icon"
|
||||||
|
(click)="reset()"
|
||||||
|
[attr.data-automation-id]="'card-textitem-reset-' + property.key">clear</md-icon>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
@@ -0,0 +1,80 @@
|
|||||||
|
@import 'theming';
|
||||||
|
|
||||||
|
.#{$ADF} {
|
||||||
|
&-textitem-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
top: 3px;
|
||||||
|
padding-left: 8px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-update-icon {
|
||||||
|
padding-left: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-readonly {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover md-icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-editable {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
md-icon:hover {
|
||||||
|
opacity: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
md-input-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
textarea:focus {
|
||||||
|
border: 1px solid #EEE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-editable /deep/ .mat-input-wrapper {
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-editable /deep/ .mat-input-underline {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-editable /deep/ .mat-input-placeholder-wrapper {
|
||||||
|
padding-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-editable /deep/ .mat-input-placeholder {
|
||||||
|
top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-editable /deep/ .mat-input-element {
|
||||||
|
font-family: inherit;
|
||||||
|
position: relative;
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-editable /deep/ .mat-input-element:focus {
|
||||||
|
padding: 5px;
|
||||||
|
left: -6px;
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-editable /deep/ input.mat-input-element {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-textitem-editable /deep/ input.mat-input-element:focus {
|
||||||
|
margin-bottom: -7px;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,147 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { FormsModule } from '@angular/forms';
|
||||||
|
import { MdDatepickerModule, MdIconModule, MdInputModule, MdNativeDateModule } from '@angular/material';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
|
||||||
|
import { CardViewUpdateService } from '../../services/adf-card-view-update.service';
|
||||||
|
import { CardViewTextItemComponent } from './adf-card-view-textitem.component';
|
||||||
|
|
||||||
|
describe('CardViewTextItemComponent', () => {
|
||||||
|
|
||||||
|
let fixture: ComponentFixture<CardViewTextItemComponent>;
|
||||||
|
let component: CardViewTextItemComponent;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
NoopAnimationsModule,
|
||||||
|
MdDatepickerModule,
|
||||||
|
MdIconModule,
|
||||||
|
MdInputModule,
|
||||||
|
MdNativeDateModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CardViewTextItemComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
CardViewUpdateService
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CardViewTextItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.property = <CardViewTextItemModel>{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Text label',
|
||||||
|
value: 'Lorem ipsum',
|
||||||
|
key: 'textkey',
|
||||||
|
default: '',
|
||||||
|
editable: false,
|
||||||
|
get displayValue(): string {
|
||||||
|
return 'Lorem ipsum';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the label and value', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||||
|
expect(labelValue).not.toBeNull();
|
||||||
|
expect(labelValue.nativeElement.innerText).toBe('Text label');
|
||||||
|
|
||||||
|
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||||
|
expect(value).not.toBeNull();
|
||||||
|
expect(value.nativeElement.innerText.trim()).toBe('Lorem ipsum');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render value when editable:true', () => {
|
||||||
|
component.editable = true;
|
||||||
|
component.property.editable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||||
|
expect(value).not.toBeNull();
|
||||||
|
expect(value.nativeElement.innerText.trim()).toBe('Lorem ipsum');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the edit icon in case of editable:true', () => {
|
||||||
|
component.editable = true;
|
||||||
|
component.property.editable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`));
|
||||||
|
expect(editIcon).not.toBeNull('Edit icon should be shown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT render the edit icon in case of editable:false', () => {
|
||||||
|
component.editable = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`));
|
||||||
|
expect(editIcon).toBeNull('Edit icon should NOT be shown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT render the picker and toggle in case of editable:true but (general) editable:false', () => {
|
||||||
|
component.editable = false;
|
||||||
|
component.property.editable = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`));
|
||||||
|
expect(editIcon).toBeNull('Edit icon should NOT be shown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger an update event on the CardViewUpdateService', (done) => {
|
||||||
|
component.editable = true;
|
||||||
|
component.property.editable = true;
|
||||||
|
const cardViewUpdateService = TestBed.get(CardViewUpdateService);
|
||||||
|
const expectedText = 'changed text';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
cardViewUpdateService.itemUpdated$.subscribe(
|
||||||
|
(updateNotification) => {
|
||||||
|
expect(updateNotification.target).toBe(component.property);
|
||||||
|
expect(updateNotification.changed).toEqual({ textkey: expectedText });
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-toggle-${component.property.key}"]`));
|
||||||
|
editIcon.triggerEventHandler('click', null);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let editInput = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-editinput-${component.property.key}"]`));
|
||||||
|
editInput.nativeElement.value = expectedText;
|
||||||
|
editInput.nativeElement.dispatchEvent(new Event('input'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let updateInput = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-update-${component.property.key}"]`));
|
||||||
|
updateInput.triggerEventHandler('click', null);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,65 @@
|
|||||||
|
/*!
|
||||||
|
* @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, Input, OnChanges, ViewChild } from '@angular/core';
|
||||||
|
import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
|
||||||
|
import { CardViewUpdateService } from '../../services/adf-card-view-update.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-card-view-textitem',
|
||||||
|
templateUrl: './adf-card-view-textitem.component.html',
|
||||||
|
styleUrls: ['./adf-card-view-textitem.component.scss']
|
||||||
|
})
|
||||||
|
export class CardViewTextItemComponent implements OnChanges {
|
||||||
|
@Input()
|
||||||
|
property: CardViewTextItemModel;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
editable: boolean;
|
||||||
|
|
||||||
|
@ViewChild('editorInput')
|
||||||
|
private editorInput: any;
|
||||||
|
|
||||||
|
inEdit: boolean = false;
|
||||||
|
editedValue: string;
|
||||||
|
|
||||||
|
constructor(private cardViewUpdateService: CardViewUpdateService) {}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
this.editedValue = this.property.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEditble() {
|
||||||
|
return this.editable && this.property.editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditMode(editStatus: boolean): void {
|
||||||
|
this.inEdit = editStatus;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.editorInput.nativeElement.click();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.editedValue = this.property.value;
|
||||||
|
this.setEditMode(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(): void {
|
||||||
|
this.cardViewUpdateService.update(this.property, { [this.property.key]: this.editedValue });
|
||||||
|
}
|
||||||
|
}
|
@@ -1,18 +0,0 @@
|
|||||||
:host {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.adf-header__label {
|
|
||||||
font-weight: bold;
|
|
||||||
float: left;
|
|
||||||
width: 50%;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.adf-header__value {
|
|
||||||
color: rgb(68, 138, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
@@ -1,6 +1,7 @@
|
|||||||
<div class="mdl-cell mdl-cell--12-col" *ngFor="let property of properties; let idx = index">
|
<div class="adf-property-list">
|
||||||
<div [attr.data-automation-id]="'header-' + property.key">
|
<ng-container *ngFor="let property of properties">
|
||||||
<div class="adf-header__label">{{ property.label }}</div>
|
<div [attr.data-automation-id]="'header-'+property.key" class="adf-property">
|
||||||
<div class="adf-header__value">{{ getPropertyValue(property) }}</div>
|
<adf-card-view-item-dispatcher [property]="property" [editable]="editable"></adf-card-view-item-dispatcher>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,32 @@
|
|||||||
|
@import 'theming';
|
||||||
|
|
||||||
|
.#{$ADF} {
|
||||||
|
&-property-list {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-property {
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-property /deep/ &-property-label {
|
||||||
|
display: table-cell;
|
||||||
|
min-width: 100px;
|
||||||
|
padding-right: 30px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
color: rgb(186, 186, 186);
|
||||||
|
vertical-align: top;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-property /deep/ &-property-value {
|
||||||
|
width: 100%;
|
||||||
|
display: table-cell;
|
||||||
|
color: rgb(101, 101, 101);
|
||||||
|
vertical-align: top;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
@@ -16,8 +16,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MdDatepickerModule, MdIconModule, MdInputModule, MdNativeDateModule } from '@angular/material';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { CardViewModel } from '../../models/card-view.model';
|
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
|
||||||
|
import { CardViewDateItemModel } from '../../models/card-view-dateitem.model';
|
||||||
|
import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
|
||||||
|
import { CardViewUpdateService } from '../../services/adf-card-view-update.service';
|
||||||
|
import { AdfCardViewContentProxyDirective } from './adf-card-view-content-proxy.directive';
|
||||||
|
import { CardViewDateItemComponent } from './adf-card-view-dateitem.component';
|
||||||
|
import { CardViewItemDispatcherComponent } from './adf-card-view-item-dispatcher.component';
|
||||||
|
import { CardViewTextItemComponent } from './adf-card-view-textitem.component';
|
||||||
import { CardViewComponent } from './adf-card-view.component';
|
import { CardViewComponent } from './adf-card-view.component';
|
||||||
|
|
||||||
describe('AdfCardView', () => {
|
describe('AdfCardView', () => {
|
||||||
@@ -27,12 +36,32 @@ describe('AdfCardView', () => {
|
|||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
MdDatepickerModule,
|
||||||
|
MdIconModule,
|
||||||
|
MdInputModule,
|
||||||
|
MdNativeDateModule,
|
||||||
|
FormsModule
|
||||||
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CardViewComponent
|
CardViewComponent,
|
||||||
|
CardViewItemDispatcherComponent,
|
||||||
|
AdfCardViewContentProxyDirective,
|
||||||
|
CardViewTextItemComponent,
|
||||||
|
CardViewDateItemComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
CardViewUpdateService
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
});
|
||||||
|
|
||||||
|
// entryComponents are not supported yet on TestBed, that is why this ugly workaround:
|
||||||
|
// https://github.com/angular/angular/issues/10760
|
||||||
|
TestBed.overrideModule(BrowserDynamicTestingModule, {
|
||||||
|
set: { entryComponents: [ CardViewTextItemComponent, CardViewDateItemComponent ] }
|
||||||
|
});
|
||||||
|
|
||||||
|
TestBed.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -41,59 +70,73 @@ describe('AdfCardView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render the label and value', async(() => {
|
it('should render the label and value', async(() => {
|
||||||
component.properties = [new CardViewModel({label: 'My label', value: 'My value'})];
|
component.properties = [new CardViewTextItemModel({label: 'My label', value: 'My value', key: 'some key'})];
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
let labelValue = fixture.debugElement.query(By.css('.adf-header__label'));
|
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||||
expect(labelValue).not.toBeNull();
|
expect(labelValue).not.toBeNull();
|
||||||
expect(labelValue.nativeElement.innerText).toBe('My label');
|
expect(labelValue.nativeElement.innerText).toBe('My label');
|
||||||
|
|
||||||
let value = fixture.debugElement.query(By.css('.adf-header__value'));
|
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||||
expect(value).not.toBeNull();
|
expect(value).not.toBeNull();
|
||||||
expect(value.nativeElement.innerText).toBe('My value');
|
expect(value.nativeElement.innerText).toBe('My value');
|
||||||
});
|
});
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should pass through editable property to the items', () => {
|
||||||
|
component.editable = true;
|
||||||
|
component.properties = [new CardViewDateItemModel({
|
||||||
|
label: 'My date label',
|
||||||
|
value: '2017-06-14',
|
||||||
|
key: 'some-key',
|
||||||
|
editable: true
|
||||||
|
})];
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-some-key"]`));
|
||||||
|
expect(datePicker).not.toBeNull('Datepicker should be in DOM');
|
||||||
|
});
|
||||||
|
|
||||||
it('should render the date in the correct format', async(() => {
|
it('should render the date in the correct format', async(() => {
|
||||||
component.properties = [new CardViewModel({
|
component.properties = [new CardViewDateItemModel({
|
||||||
label: 'My date label', value: '2017-06-14',
|
label: 'My date label', value: '2017-06-14', key: 'some key',
|
||||||
format: 'MMM DD YYYY'
|
format: 'MMM DD YYYY'
|
||||||
})];
|
})];
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
let labelValue = fixture.debugElement.query(By.css('.adf-header__label'));
|
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||||
expect(labelValue).not.toBeNull();
|
expect(labelValue).not.toBeNull();
|
||||||
expect(labelValue.nativeElement.innerText).toBe('My date label');
|
expect(labelValue.nativeElement.innerText).toBe('My date label');
|
||||||
|
|
||||||
let value = fixture.debugElement.query(By.css('.adf-header__value'));
|
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||||
expect(value).not.toBeNull();
|
expect(value).not.toBeNull();
|
||||||
expect(value.nativeElement.innerText).toBe('Jun 14 2017');
|
expect(value.nativeElement.innerText).toBe('Jun 14 2017');
|
||||||
});
|
});
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should render the default value if the value is empty', async(() => {
|
it('should render the default value if the value is empty', async(() => {
|
||||||
component.properties = [new CardViewModel({
|
component.properties = [new CardViewTextItemModel({
|
||||||
label: 'My default label',
|
label: 'My default label',
|
||||||
default: 'default value'
|
value: null,
|
||||||
|
default: 'default value',
|
||||||
|
key: 'some key'
|
||||||
})];
|
})];
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
let labelValue = fixture.debugElement.query(By.css('.adf-header__label'));
|
let labelValue = fixture.debugElement.query(By.css('.adf-property-label'));
|
||||||
expect(labelValue).not.toBeNull();
|
expect(labelValue).not.toBeNull();
|
||||||
expect(labelValue.nativeElement.innerText).toBe('My default label');
|
expect(labelValue.nativeElement.innerText).toBe('My default label');
|
||||||
|
|
||||||
let value = fixture.debugElement.query(By.css('.adf-header__value'));
|
let value = fixture.debugElement.query(By.css('.adf-property-value'));
|
||||||
expect(value).not.toBeNull();
|
expect(value).not.toBeNull();
|
||||||
expect(value.nativeElement.innerText).toBe('default value');
|
expect(value.nativeElement.innerText).toBe('default value');
|
||||||
});
|
});
|
||||||
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@@ -16,26 +16,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import * as moment from 'moment';
|
import { CardViewItem } from '../../interface/card-view-item.interface';
|
||||||
import { CardViewModel } from '../../models/card-view.model';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-card-view',
|
selector: 'adf-card-view',
|
||||||
templateUrl: './adf-card-view.component.html',
|
templateUrl: './adf-card-view.component.html',
|
||||||
styleUrls: ['./adf-card-view.component.css']
|
styleUrls: ['./adf-card-view.component.scss']
|
||||||
})
|
})
|
||||||
export class CardViewComponent {
|
export class CardViewComponent {
|
||||||
|
@Input()
|
||||||
|
properties: CardViewItem [];
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
properties: CardViewModel [];
|
editable: boolean;
|
||||||
|
|
||||||
getPropertyValue(property: CardViewModel): string {
|
|
||||||
if (!property.value) {
|
|
||||||
return property.default;
|
|
||||||
} else if (property.format) {
|
|
||||||
return moment(property.value).format(property.format);
|
|
||||||
}
|
|
||||||
return property.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,14 +17,34 @@
|
|||||||
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MdButtonModule, MdDatepickerModule, MdIconModule, MdInputModule, MdNativeDateModule } from '@angular/material';
|
||||||
|
import { AdfCardViewContentProxyDirective } from './adf-card-view-content-proxy.directive';
|
||||||
|
import { CardViewDateItemComponent } from './adf-card-view-dateitem.component';
|
||||||
|
import { CardViewItemDispatcherComponent } from './adf-card-view-item-dispatcher.component';
|
||||||
|
import { CardViewTextItemComponent } from './adf-card-view-textitem.component';
|
||||||
import { CardViewComponent } from './adf-card-view.component';
|
import { CardViewComponent } from './adf-card-view.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule
|
CommonModule,
|
||||||
|
MdDatepickerModule,
|
||||||
|
MdNativeDateModule,
|
||||||
|
MdInputModule,
|
||||||
|
MdIconModule,
|
||||||
|
MdButtonModule,
|
||||||
|
FormsModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CardViewComponent
|
CardViewComponent,
|
||||||
|
CardViewItemDispatcherComponent,
|
||||||
|
AdfCardViewContentProxyDirective,
|
||||||
|
CardViewTextItemComponent,
|
||||||
|
CardViewDateItemComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
CardViewTextItemComponent,
|
||||||
|
CardViewDateItemComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CardViewComponent
|
CardViewComponent
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
/*!
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CardViewItem {
|
||||||
|
label: string;
|
||||||
|
value: any;
|
||||||
|
key: string;
|
||||||
|
default?: any;
|
||||||
|
type: string;
|
||||||
|
displayValue: string;
|
||||||
|
editable?: boolean;
|
||||||
|
}
|
@@ -20,21 +20,29 @@
|
|||||||
* This object represent the basic structure of a card view.
|
* This object represent the basic structure of a card view.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @returns {CardViewModel} .
|
* @returns {CardViewBaseItemModel} .
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class CardViewModel {
|
export interface CardViewItemProperties {
|
||||||
label: string;
|
label: string;
|
||||||
value: any;
|
value: any;
|
||||||
key: any;
|
key: any;
|
||||||
format: string;
|
default?: string;
|
||||||
default: string;
|
editable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(obj?: any) {
|
export abstract class CardViewBaseItemModel {
|
||||||
|
label: string;
|
||||||
|
value: any;
|
||||||
|
key: any;
|
||||||
|
default: string;
|
||||||
|
editable: boolean;
|
||||||
|
|
||||||
|
constructor(obj: CardViewItemProperties) {
|
||||||
this.label = obj.label || '';
|
this.label = obj.label || '';
|
||||||
this.value = obj.value;
|
this.value = obj.value;
|
||||||
this.key = obj.key;
|
this.key = obj.key;
|
||||||
this.format = obj.format;
|
|
||||||
this.default = obj.default;
|
this.default = obj.default;
|
||||||
|
this.editable = obj.editable || false;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
/*!
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* This object represent the basic structure of a card view.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns {CardViewDateItemModel} .
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { CardViewItem } from '../interface/card-view-item.interface';
|
||||||
|
import { CardViewBaseItemModel, CardViewItemProperties } from './card-view-baseitem.model';
|
||||||
|
|
||||||
|
export interface CardViewDateItemProperties extends CardViewItemProperties {
|
||||||
|
format?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CardViewDateItemModel extends CardViewBaseItemModel implements CardViewItem {
|
||||||
|
type: string = 'date';
|
||||||
|
format: string;
|
||||||
|
|
||||||
|
constructor(obj: CardViewDateItemProperties) {
|
||||||
|
super(obj);
|
||||||
|
this.format = obj.format || 'MMM DD YYYY';
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayValue() {
|
||||||
|
if (!this.value) {
|
||||||
|
return this.default;
|
||||||
|
} else {
|
||||||
|
return moment(this.value).format(this.format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* This object represent the basic structure of a card view.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns {CardViewTextItemModel} .
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CardViewItem } from '../interface/card-view-item.interface';
|
||||||
|
import { CardViewBaseItemModel, CardViewItemProperties } from './card-view-baseitem.model';
|
||||||
|
|
||||||
|
export interface CardViewTextItemProperties extends CardViewItemProperties {
|
||||||
|
multiline?: boolean;
|
||||||
|
}
|
||||||
|
export class CardViewTextItemModel extends CardViewBaseItemModel implements CardViewItem {
|
||||||
|
type: string = 'text';
|
||||||
|
multiline: boolean;
|
||||||
|
|
||||||
|
constructor(obj: CardViewTextItemProperties) {
|
||||||
|
super(obj);
|
||||||
|
this.multiline = obj.multiline || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayValue() {
|
||||||
|
return this.value || this.default;
|
||||||
|
}
|
||||||
|
}
|
@@ -15,5 +15,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './card-view.model';
|
export * from './card-view-textitem.model';
|
||||||
|
export * from './card-view-dateitem.model';
|
||||||
export * from './file.model';
|
export * from './file.model';
|
||||||
|
@@ -0,0 +1,42 @@
|
|||||||
|
/*!
|
||||||
|
* @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 { Injectable } from '@angular/core';
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
import { CardViewBaseItemModel } from '../models/card-view-baseitem.model';
|
||||||
|
|
||||||
|
export interface UpdateNotification {
|
||||||
|
target: any;
|
||||||
|
changed: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CardViewUpdateService {
|
||||||
|
|
||||||
|
// Observable sources
|
||||||
|
private itemUpdatedSource = new Subject<UpdateNotification>();
|
||||||
|
|
||||||
|
// Observable streams
|
||||||
|
public itemUpdated$ = this.itemUpdatedSource.asObservable();
|
||||||
|
|
||||||
|
update(property: CardViewBaseItemModel, changed: any) {
|
||||||
|
this.itemUpdatedSource.next({
|
||||||
|
target: property,
|
||||||
|
changed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -33,3 +33,4 @@ export * from './alfresco-translate-loader.service';
|
|||||||
export * from './app-config.service';
|
export * from './app-config.service';
|
||||||
export * from './thumbnail.service';
|
export * from './thumbnail.service';
|
||||||
export * from './upload.service';
|
export * from './upload.service';
|
||||||
|
export * from './adf-card-view-update.service';
|
||||||
|
@@ -77,6 +77,7 @@ export class AppComponent {
|
|||||||
| needHelpLink | string | | It will change the url of the NEED HELP link in the footer |
|
| needHelpLink | string | | It will change the url of the NEED HELP link in the footer |
|
||||||
| registerLink | string | | It will change the url of the REGISTER link in the footer |
|
| registerLink | string | | It will change the url of the REGISTER link in the footer |
|
||||||
| logoImageUrl | string | Alfresco logo image | To change the logo image with a customised image |
|
| logoImageUrl | string | Alfresco logo image | To change the logo image with a customised image |
|
||||||
|
| copyrightText | string | © 2016 Alfresco Software, Inc. All Rights Reserved. | The copyright text below the login box |
|
||||||
| backgroundImageUrl | string | Alfresco background image | To change the background image with a customised image |
|
| backgroundImageUrl | string | Alfresco background image | To change the background image with a customised image |
|
||||||
| fieldsValidation | { [key: string]: any; }, extra?: { [key: string]: any; } | | Use it to customise the validation rules of the login form |
|
| fieldsValidation | { [key: string]: any; }, extra?: { [key: string]: any; } | | Use it to customise the validation rules of the login form |
|
||||||
| showRememberMe | boolean | false | Toggle `Remember me` checkbox visibility |
|
| showRememberMe | boolean | false | Toggle `Remember me` checkbox visibility |
|
||||||
|
@@ -63,8 +63,17 @@ module.exports = {
|
|||||||
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.component.scss$/,
|
test: /\.scss$/,
|
||||||
use: ['to-string-loader', 'raw-loader', 'sass-loader'],
|
use: [{
|
||||||
|
loader: "to-string-loader"
|
||||||
|
}, {
|
||||||
|
loader: "raw-loader"
|
||||||
|
}, {
|
||||||
|
loader: "sass-loader",
|
||||||
|
options: {
|
||||||
|
includePaths: [ path.resolve(__dirname, '../../ng2-alfresco-core/styles')]
|
||||||
|
}
|
||||||
|
}],
|
||||||
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -107,7 +107,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="copyright">
|
<div class="copyright" data-automation-id="login-copyright">
|
||||||
© 2016 Alfresco Software, Inc. All Rights Reserved.
|
{{ copyrightText }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -157,6 +157,22 @@ describe('AlfrescoLogin', () => {
|
|||||||
expect(element.querySelector('#login-action-register').innerText).toEqual('LOGIN.ACTION.REGISTER');
|
expect(element.querySelector('#login-action-register').innerText).toEqual('LOGIN.ACTION.REGISTER');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Copyright text', () => {
|
||||||
|
|
||||||
|
it('should render the default copyright text', () => {
|
||||||
|
expect(element.querySelector('[data-automation-id="login-copyright"]')).toBeDefined();
|
||||||
|
expect(element.querySelector('[data-automation-id="login-copyright"]').innerText).toEqual('© 2016 Alfresco Software, Inc. All Rights Reserved.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the customised copyright text', () => {
|
||||||
|
component.copyrightText = 'customised';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(element.querySelector('[data-automation-id="login-copyright"]')).toBeDefined();
|
||||||
|
expect(element.querySelector('[data-automation-id="login-copyright"]').innerText).toEqual('customised');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should render user and password input fields with default values', () => {
|
it('should render user and password input fields with default values', () => {
|
||||||
expect(element.querySelector('form')).toBeDefined();
|
expect(element.querySelector('form')).toBeDefined();
|
||||||
expect(element.querySelector('input[type="password"]')).toBeDefined();
|
expect(element.querySelector('input[type="password"]')).toBeDefined();
|
||||||
|
@@ -63,6 +63,9 @@ export class AlfrescoLoginComponent implements OnInit {
|
|||||||
@Input()
|
@Input()
|
||||||
backgroundImageUrl: string = require('../assets/images/background.svg');
|
backgroundImageUrl: string = require('../assets/images/background.svg');
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
copyrightText: string = '© 2016 Alfresco Software, Inc. All Rights Reserved.';
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
providers: string;
|
providers: string;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user