[ADF-852] Form style Material 2 (#2151)

* mdl2 transition part form 1

* hyperlink

* radio buttons

* label

* people component

* [ADF-852] moved textarea to new angular material

* number widget

* change error multiline

* [ADF-852] added md desgin for dropdown

* [ADF-852] removed unused css file

* functional widget

* error dropdown

* [ADF-852] - changed to new md date

* remove md-date-time-picker dependency in ng2-alfresco-from

* [ADF-852] conversion dynamic table phase 1

* container widget

* remove test unused

* validation change

* [ADF-852] convert dynamic table phase 2

* [ADF-852] improving style and fixing bugs

* move custom style for form in form.scss

* error footer refactor

* fix models and test

* [ADF-852]- fixed minor twitch on dynamic table

* align fields and fix tests dropdown

* disabling button in readonly
clean mdl form start process form

* align dropdown

* [ADF-1048] Upload widget can manage multiple files. (#2134)

* [ADF-1048] improving upload widget

* [ADF-1048] added ability to upload multiple file on upload widget

* [ADF-1048] added multiple upload elements on upload widget

* [ADF-1048] - show all the files on the completed form

* [ADF-1048] fixed wrong selecion on displya upload

* [ADF-1048] removed fdescribe from upload widget

* date validation and custom moment data adapter

* move content widget in the widget folder

* add style fields and theming

* color primary radio and checkbox

* fix amount widget and colors

* change ViewEncapsulation and fix date style button issue

* empty form customization 1736

* focus label style

* [ADF-224] fix the rendering of custom stencils when form is opened in readonly state. (#2161)

* [ADF-224] Fixed rendering of custom stencil in readonly mode

* [ADF-224] improved variable name

* test fix

* container filter in form model creation

* show display value correctly

* fix change date and test

* fix date editor and add some test coverage for date

* style minor issue

* fix new unused local import rule

* fix test date

* strict date check

* fix analytics failing test

* restore null as default in model

* unify model diagrams and analytics
This commit is contained in:
Eugenio Romano 2017-08-07 13:15:29 +01:00 committed by Mario Romano
parent 47ea517ffb
commit 083c9da0d4
197 changed files with 10201 additions and 4774 deletions

View File

@ -4,5 +4,7 @@
.adf-table-version {
width: 60%;
border: 0;
border-spacing: 0;
text-align: center;
}

View File

@ -1,20 +1,21 @@
<div class="about-container">
<h3>Server settings</h3>
<md-list>
<small>The values below are taken from the AppConfigService and loaded from the '{{ configFile }}' file.</small>
<div>
Alfresco Process Services URL: <strong>{{ bpmHost }}</strong>
</div>
<div>
Alfresco Content Services URL: <strong>{{ ecmHost }}</strong>
</div>
<md-list-item>
<h4 md-line> Alfresco Process Services URL: {{ bpmHost }}</h4>
</md-list-item>
<md-divider></md-divider>
<md-list-item>
<h4 md-line>Alfresco Content Services URL: {{ ecmHost }}</h4>
</md-list-item>
</md-list>
<h3>Product Versions</h3>
<div *ngIf="bpmVersion">
<h3>BPM</h3>
<label> Edition </label>
<p> {{ bpmVersion.edition }}</p>
<label> Edition </label> {{ bpmVersion.edition }}
<p></p>
<table border="2" class="adf-table-version">
<tr>
<th>Major Version</th>
@ -32,10 +33,10 @@
</div>
<div *ngIf="ecmVersion">
<h3>ECM</h3>
<label> Edition </label>
<p> {{ ecmVersion.edition }}</p>
<label> Version </label>
<p> {{ ecmVersion.version.display }}</p>
<label> Edition </label> {{ ecmVersion.edition }}
<p></p>
<label> Version </label> {{ ecmVersion.version.display }}
<p></p>
<h4>License</h4>
<table border="2" class="adf-table-version">
<tr>
@ -105,7 +106,6 @@
</div>
</div>
<h3>Packages</h3>
<small>Current project is using the following ADF libraries:</small>
<alfresco-datatable [data]="data"></alfresco-datatable>

View File

@ -37,9 +37,6 @@ import 'ng2-alfresco-webscript';
require('script-loader!dialog-polyfill/dialog-polyfill');
import 'dialog-polyfill/dialog-polyfill.css';
// Load the Angular Material 2 stylesheet
import '@angular/material/prebuilt-themes/indigo-pink.css';
// Google Material Design Lite
import 'material-design-icons/iconfont/material-icons.css';
import 'material-design-lite/dist/material.orange-blue.min.css';

View File

@ -46,6 +46,7 @@ module.exports = {
},
{
test: /\.html$/,
include: [helpers.root('app'), helpers.root('../ng2-components')],
loader: 'html-loader',
exclude: [/node_modules/, /public/, /resources/, /dist/]
},
@ -63,7 +64,8 @@ module.exports = {
loader: 'raw-loader'
},
{
test: /\.component.scss$/,
test: /\.scss$/,
include: [helpers.root('app'), helpers.root('../ng2-components')],
use: [{
loader: "to-string-loader"
}, {
@ -73,7 +75,8 @@ module.exports = {
options: {
includePaths: [path.resolve(__dirname, '../../ng2-components/ng2-alfresco-core/styles')]
}
}]
}],
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -103,7 +103,7 @@ module.exports = {
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
test: /\.component.scss$/,
test: /\.scss$/,
use: [{
loader: "to-string-loader"
}, {

View File

@ -37,9 +37,6 @@ export * from './src/components/analytics-report-parameters.component';
export * from './src/services/analytics.service';
export * from './src/components/widgets/index';
// exporting models
export * from './src/models/index';
export const ANALYTICS_DIRECTIVES: any[] = [
AnalyticsComponent,
AnalyticsReportListComponent,

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { ReportParameterDetailsModel } from '../models/report.model';
import { ReportParameterDetailsModel } from 'ng2-activiti-diagrams';
export let reportDefParamStatus = {
'id': 2005,

View File

@ -23,14 +23,14 @@ import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
import { ChartsModule } from 'ng2-charts';
import { Observable } from 'rxjs/Rx';
import { Chart } from 'ng2-activiti-diagrams';
import { ReportQuery } from 'ng2-activiti-diagrams';
import * as analyticMock from '../assets/analyticsComponent.mock';
import { AnalyticsGeneratorComponent } from '../components/analytics-generator.component';
import { AnalyticsReportHeatMapComponent } from '../components/analytics-report-heat-map.component';
import { AnalyticsReportListComponent } from '../components/analytics-report-list.component';
import { AnalyticsReportParametersComponent } from '../components/analytics-report-parameters.component';
import { WIDGET_DIRECTIVES } from '../components/widgets/index';
import { Chart } from '../models/chart.model';
import { ReportQuery } from '../models/report.model';
import { AnalyticsService } from '../services/analytics.service';
export const ANALYTICS_DIRECTIVES: any[] = [

View File

@ -16,9 +16,9 @@
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { ReportQuery } from 'ng2-activiti-diagrams';
import { Chart } from 'ng2-activiti-diagrams';
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
import { Chart } from '../models/chart.model';
import { ReportQuery } from '../models/report.model';
import { AnalyticsService } from '../services/analytics.service';
@Component({

View File

@ -17,10 +17,10 @@
import { DebugElement } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ReportParametersModel } from 'ng2-activiti-diagrams';
import { AlfrescoTranslationService, AppConfigModule, CoreModule } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { AnalyticsReportListComponent } from '../components/analytics-report-list.component';
import { ReportParametersModel } from '../models/report.model';
import { AnalyticsService } from '../services/analytics.service';
declare let jasmine: any;

View File

@ -16,8 +16,8 @@
*/
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ReportParametersModel } from 'ng2-activiti-diagrams';
import { Observable, Observer } from 'rxjs/Rx';
import { ReportParametersModel } from '../models/report.model';
import { AnalyticsService } from '../services/analytics.service';
@Component({

View File

@ -22,10 +22,10 @@ import * as moment from 'moment';
import { AlfrescoTranslationService, AppConfigModule, CoreModule } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { ReportParametersModel } from 'ng2-activiti-diagrams';
import * as analyticParamsMock from '../assets/analyticsParamsReportComponent.mock';
import { AnalyticsReportParametersComponent } from '../components/analytics-report-parameters.component';
import { WIDGET_DIRECTIVES } from '../components/widgets/index';
import { ReportParametersModel } from '../models/report.model';
import { AnalyticsService } from '../services/analytics.service';
declare let jasmine: any;
@ -210,6 +210,7 @@ describe('AnalyticsReportParametersComponent', () => {
typeFiltering: true
}
};
component.submit(values);
});

View File

@ -30,13 +30,13 @@ import {
} from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import * as moment from 'moment';
import { AlfrescoTranslationService, ContentService, LogService } from 'ng2-alfresco-core';
import {
ParameterValueModel,
ReportParameterDetailsModel,
ReportParametersModel,
ReportQuery
} from '../models/report.model';
} from 'ng2-activiti-diagrams';
import { AlfrescoTranslationService, ContentService, LogService } from 'ng2-alfresco-core';
import { AnalyticsService } from '../services/analytics.service';
declare var componentHandler;

View File

@ -16,8 +16,8 @@
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { ReportQuery } from 'ng2-activiti-diagrams';
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
import { ReportQuery } from '../models/report.model';
import { AnalyticsGeneratorComponent } from './analytics-generator.component';
@Component({

View File

@ -20,7 +20,7 @@
/* tslint:disable::no-access-missing-member */
import { Component, ElementRef, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ParameterValueModel, ReportParameterDetailsModel } from './../../../models/report.model';
import { ParameterValueModel, ReportParameterDetailsModel } from 'ng2-activiti-diagrams';
import { NumberWidgetComponent } from './../number/number.widget';
@Component({

View File

@ -1,307 +0,0 @@
/*!
* @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 * as moment from 'moment';
export class Chart {
id: string;
type: string;
icon: string;
constructor(obj?: any) {
this.id = obj && obj.id || null;
if (obj && obj.type) {
this.type = this.convertType(obj.type);
this.icon = this.getIconType(this.type);
}
}
private convertType(type: string) {
let chartType = '';
switch (type) {
case 'pieChart':
chartType = 'pie';
break;
case 'table':
chartType = 'table';
break;
case 'line':
chartType = 'line';
break;
case 'barChart':
chartType = 'bar';
break;
case 'multiBarChart':
chartType = 'multiBar';
break;
case 'processDefinitionHeatMap':
chartType = 'HeatMap';
break;
case 'masterDetailTable':
chartType = 'masterDetailTable';
break;
default:
chartType = 'table';
break;
}
return chartType;
}
private getIconType(type: string): string {
let typeIcon: string = '';
switch (type) {
case 'pie':
typeIcon = 'pie_chart';
break;
case 'table':
typeIcon = 'web';
break;
case 'line':
typeIcon = 'show_chart';
break;
case 'bar':
typeIcon = 'equalizer';
break;
case 'multiBar':
typeIcon = 'poll';
break;
case 'HeatMap':
typeIcon = 'share';
break;
case 'masterDetailTable':
typeIcon = 'subtitles';
break;
default:
typeIcon = 'web';
break;
}
return typeIcon;
}
}
export class LineChart extends Chart {
title: string;
titleKey: string;
labels: string[] = [];
datasets: any[] = [];
constructor(obj?: any) {
super(obj);
this.title = obj && obj.title || null;
this.titleKey = obj && obj.titleKey || null;
this.labels = obj && obj.columnNames.slice(1, obj.columnNames.length);
obj.rows.forEach((value: any) => {
this.datasets.push({data: value.slice(1, value.length), label: value[0]});
});
}
}
export class BarChart extends Chart {
title: string;
titleKey: string;
labels: any = [];
datasets: any[] = [];
data: any[] = [];
xAxisType: string;
yAxisType: string;
options: any = {
responsive: true,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
stepSize: 1
}
}],
xAxes: [{
ticks: {
},
stacked: false
}]
}
};
constructor(obj?: any) {
super(obj);
this.title = obj && obj.title || null;
this.titleKey = obj && obj.titleKey || null;
this.xAxisType = obj && obj.xAxisType || null;
this.yAxisType = obj && obj.yAxisType || null;
this.options.scales.xAxes[0].ticks.callback = this.xAxisTickFormatFunction(this.xAxisType);
this.options.scales.yAxes[0].ticks.callback = this.yAxisTickFormatFunction(this.yAxisType);
if (obj.values) {
obj.values.forEach((params: any) => {
let dataValue = [];
params.values.forEach((info: any) => {
info.forEach((value: any, index: any) => {
if (index % 2 === 0) {
if (!this.labels.includes(value)) {
this.labels.push(value);
}
} else {
dataValue.push(value);
}
});
});
if (dataValue && dataValue.length > 0) {
this.datasets.push({data: dataValue, label: params.key});
}
});
}
}
xAxisTickFormatFunction = function (xAxisType) {
return function (value) {
if (xAxisType !== null && xAxisType !== undefined) {
if ('date_day' === xAxisType) {
return moment(new Date(value)).format('DD');
} else if ('date_month' === xAxisType) {
return moment(new Date(value)).format('MMMM');
} else if ('date_year' === xAxisType) {
return moment(new Date(value)).format('YYYY');
}
}
return value;
};
};
yAxisTickFormatFunction = function (yAxisType) {
return function (value) {
if (yAxisType !== null && yAxisType !== undefined) {
if ('count' === yAxisType) {
let label = '' + value;
if (label.indexOf('.') !== -1) {
return '';
}
}
}
return value;
};
};
hasDatasets() {
return this.datasets && this.datasets.length > 0 ? true : false;
}
}
export class MultiBarChart extends BarChart {
constructor(obj?: any) {
super(obj);
}
}
export class TableChart extends Chart {
title: string;
titleKey: string;
labels: string[] = [];
datasets: any[] = [];
constructor(obj?: any) {
super(obj);
this.title = obj && obj.title || null;
this.titleKey = obj && obj.titleKey || null;
this.labels = obj && obj.columnNames;
if (obj.rows) {
this.datasets = obj && obj.rows;
}
}
hasDatasets() {
return this.datasets && this.datasets.length > 0 ? true : false;
}
}
export class DetailsTableChart extends TableChart {
detailsTable: any;
showDetails: boolean = false;
constructor(obj?: any) {
super(obj);
if (obj.detailTables) {
this.detailsTable = new TableChart(obj.detailTables[0]);
}
}
hasDetailsTable() {
return this.detailsTable ? true : false;
}
}
export class HeatMapChart extends Chart {
avgTimePercentages: string;
avgTimeValues: string;
processDefinitionId: string;
titleKey: string;
totalCountValues: string;
totalCountsPercentages: string;
totalTimePercentages: string;
totalTimeValues: string;
constructor(obj?: any) {
super(obj);
this.avgTimePercentages = obj && obj.avgTimePercentages || null;
this.avgTimeValues = obj && obj.avgTimeValues || null;
this.processDefinitionId = obj && obj.processDefinitionId || null;
this.totalCountValues = obj && obj.totalCountValues || null;
this.titleKey = obj && obj.titleKey || null;
this.totalCountsPercentages = obj && obj.totalCountsPercentages || null;
this.totalTimePercentages = obj && obj.totalTimePercentages || null;
this.totalTimeValues = obj && obj.totalTimeValues || null;
}
}
export class PieChart extends Chart {
title: string;
titleKey: string;
labels: string[] = [];
data: string[] = [];
constructor(obj?: any) {
super(obj);
this.title = obj && obj.title || null;
this.titleKey = obj && obj.titleKey || null;
if (obj.values) {
obj.values.forEach((value: any) => {
this.add(value.key, value.y);
});
}
}
add(label: string, data: string) {
this.labels.push(label);
this.data.push(data);
}
hasData(): boolean {
return this.data && this.data.length > 0 ? true : false;
}
hasZeroValues(): boolean {
let isZeroValues: boolean = false;
if (this.hasData()) {
isZeroValues = true;
this.data.forEach((value) => {
if (value.toString() !== '0') {
isZeroValues = false;
}
});
}
return isZeroValues;
}
}

View File

@ -1,144 +0,0 @@
/*!
* @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 report definition.
*
*
* @returns {ReportParametersModel} .
*/
export class ReportParametersModel {
id: number;
name: string;
definition: ReportDefinitionModel;
created: string;
constructor(obj?: any) {
this.id = obj && obj.id;
this.name = obj && obj.name || null;
if (obj && obj.definition) {
this.definition = new ReportDefinitionModel(JSON.parse(obj.definition));
}
this.created = obj && obj.created || null;
}
hasParameters() {
return (this.definition && this.definition.parameters && this.definition.parameters.length > 0) ? true : false;
}
}
export class ReportDefinitionModel {
parameters: ReportParameterDetailsModel[] = [];
constructor(obj?: any) {
obj.parameters.forEach((params: any) => {
let reportParamsModel = new ReportParameterDetailsModel(params);
this.parameters.push(reportParamsModel);
});
}
findParam(name: string): ReportParameterDetailsModel {
this.parameters.forEach((param) => {
return param.type === name ? param : null;
});
return null;
}
}
/**
*
* This object represent the report parameter definition.
*
*
* @returns {ReportParameterDetailsModel} .
*/
export class ReportParameterDetailsModel {
id: string;
name: string;
nameKey: string;
type: string;
value: any;
options: ParameterValueModel[];
dependsOn: string;
constructor(obj?: any) {
this.id = obj && obj.id;
this.name = obj && obj.name || null;
this.nameKey = obj && obj.nameKey || null;
this.type = obj && obj.type || null;
this.value = obj && obj.value || null;
this.options = obj && obj.options || null;
this.dependsOn = obj && obj.dependsOn || null;
}
}
export class ParameterValueModel {
id: string;
name: string;
version: string;
value: string;
constructor(obj?: any) {
this.id = obj && obj.id;
this.name = obj && obj.name || null;
this.value = obj && obj.value || null;
this.version = obj && obj.version || null;
}
get label () {
return this.version ? `${this.name} (v ${this.version}) ` : this.name;
}
}
export class ReportQuery {
reportName: string;
processDefinitionId: string;
status: string;
taskName: string;
typeFiltering: boolean;
dateRange: ReportDateRange;
dateRangeInterval: string;
slowProcessInstanceInteger: number;
duration: number;
constructor(obj?: any) {
this.reportName = obj && obj.reportName || null;
this.processDefinitionId = obj && obj.processDefinitionId || null;
this.status = obj && obj.status || null;
this.taskName = obj && obj.taskName || null;
this.dateRangeInterval = obj && obj.dateRangeInterval || null;
this.typeFiltering = obj && obj.typeFiltering || true;
this.slowProcessInstanceInteger = obj && obj.slowProcessInstanceInteger || 0;
this.duration = obj && obj.duration || 0;
this.dateRange = new ReportDateRange(obj);
}
}
export class ReportDateRange {
startDate: string;
endDate: string;
rangeId: string;
constructor(obj?: any) {
this.startDate = obj && obj.startDate || null;
this.endDate = obj && obj.endDate || null;
this.rangeId = obj && obj.rangeId || null;
}
}

View File

@ -17,8 +17,7 @@
import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { AlfrescoApiService, LogService } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { ParameterValueModel, ReportParametersModel } from 'ng2-activiti-diagrams';
import {
BarChart,
Chart,
@ -27,8 +26,9 @@ import {
MultiBarChart,
PieChart,
TableChart
} from '../models/chart.model';
import { ParameterValueModel, ReportParametersModel } from '../models/report.model';
} from 'ng2-activiti-diagrams';
import { AlfrescoApiService, LogService } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class AnalyticsService {

View File

@ -26,6 +26,7 @@ import { RAPHAEL_PROVIDERS } from './src/components/raphael/index';
// primitives
export * from './src/components/index';
export * from './src/components/raphael/index';
export * from './src/models/index';
@NgModule({
imports: [

View File

@ -15,14 +15,18 @@
* limitations under the License.
*/
import * as moment from 'moment';
export class Chart {
id: string;
type: string;
icon: string;
constructor(obj?: any) {
this.id = obj && obj.id || null;
if (obj && obj.type) {
this.type = this.convertType(obj.type);
this.icon = this.getIconType(this.type);
}
}
@ -41,15 +45,52 @@ export class Chart {
case 'barChart':
chartType = 'bar';
break;
case 'multiBarChart':
chartType = 'multiBar';
break;
case 'processDefinitionHeatMap':
chartType = 'HeatMap';
break;
case 'masterDetailTable':
chartType = 'masterDetailTable';
break;
default:
chartType = 'table';
break;
}
return chartType;
}
private getIconType(type: string): string {
let typeIcon: string = '';
switch (type) {
case 'pie':
typeIcon = 'pie_chart';
break;
case 'table':
typeIcon = 'web';
break;
case 'line':
typeIcon = 'show_chart';
break;
case 'bar':
typeIcon = 'equalizer';
break;
case 'multiBar':
typeIcon = 'poll';
break;
case 'HeatMap':
typeIcon = 'share';
break;
case 'masterDetailTable':
typeIcon = 'subtitles';
break;
default:
typeIcon = 'web';
break;
}
return typeIcon;
}
}
export class LineChart extends Chart {
@ -73,16 +114,24 @@ export class LineChart extends Chart {
export class BarChart extends Chart {
title: string;
titleKey: string;
labels: string[] = [];
labels: any = [];
datasets: any[] = [];
data: any[] = [];
xAxisType: string;
yAxisType: string;
options: any = {
responsive: true,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
stepSize: 1
}
}],
xAxes: [{
ticks: {
},
stacked: false
}]
}
};
@ -91,12 +140,19 @@ export class BarChart extends Chart {
super(obj);
this.title = obj && obj.title || null;
this.titleKey = obj && obj.titleKey || null;
this.xAxisType = obj && obj.xAxisType || null;
this.yAxisType = obj && obj.yAxisType || null;
this.options.scales.xAxes[0].ticks.callback = this.xAxisTickFormatFunction(this.xAxisType);
this.options.scales.yAxes[0].ticks.callback = this.yAxisTickFormatFunction(this.yAxisType);
if (obj.values) {
obj.values.forEach((params: any) => {
let dataValue = [];
params.values.forEach((info: any) => {
info.forEach((value: any, index: any) => {
if (index % 2 === 0) {
if (!this.labels.includes(value)) {
this.labels.push(value);
}
} else {
dataValue.push(value);
}
@ -107,12 +163,49 @@ export class BarChart extends Chart {
}
});
}
}
xAxisTickFormatFunction = function (xAxisType) {
return function (value) {
if (xAxisType !== null && xAxisType !== undefined) {
if ('date_day' === xAxisType) {
return moment(new Date(value)).format('DD');
} else if ('date_month' === xAxisType) {
return moment(new Date(value)).format('MMMM');
} else if ('date_year' === xAxisType) {
return moment(new Date(value)).format('YYYY');
}
}
return value;
};
};
yAxisTickFormatFunction = function (yAxisType) {
return function (value) {
if (yAxisType !== null && yAxisType !== undefined) {
if ('count' === yAxisType) {
let label = '' + value;
if (label.indexOf('.') !== -1) {
return '';
}
}
}
return value;
};
};
hasDatasets() {
return this.datasets && this.datasets.length > 0 ? true : false;
}
}
export class MultiBarChart extends BarChart {
constructor(obj?: any) {
super(obj);
}
}
export class TableChart extends Chart {
title: string;
titleKey: string;
@ -134,6 +227,22 @@ export class TableChart extends Chart {
}
}
export class DetailsTableChart extends TableChart {
detailsTable: any;
showDetails: boolean = false;
constructor(obj?: any) {
super(obj);
if (obj.detailTables) {
this.detailsTable = new TableChart(obj.detailTables[0]);
}
}
hasDetailsTable() {
return this.detailsTable ? true : false;
}
}
export class HeatMapChart extends Chart {
avgTimePercentages: string;
avgTimeValues: string;
@ -179,7 +288,20 @@ export class PieChart extends Chart {
this.data.push(data);
}
hasData() {
hasData(): boolean {
return this.data && this.data.length > 0 ? true : false;
}
hasZeroValues(): boolean {
let isZeroValues: boolean = false;
if (this.hasData()) {
isZeroValues = true;
this.data.forEach((value) => {
if (value.toString() !== '0') {
isZeroValues = false;
}
});
}
return isZeroValues;
}
}

View File

@ -67,8 +67,8 @@ export class DiagramElementModel {
constructor(obj?: any) {
if (obj) {
this.completed = obj.completed || false;
this.current = obj.current || false;
this.completed = !!obj.completed;
this.current = !!obj.current;
this.height = obj.height || '';
this.id = obj.id || '';
this.name = obj.name || '';
@ -117,8 +117,8 @@ export class DiagramFlowElementModel {
constructor(obj?: any) {
if (obj) {
this.completed = obj.completed || false;
this.current = obj.current || false;
this.completed = !!obj.completed;
this.current = !!obj.current;
this.id = obj.id;
this.properties = obj.properties;
this.sourceRef = obj.sourceRef;

View File

@ -38,7 +38,7 @@ export class ReportParametersModel {
}
hasParameters() {
return (this.definition && this.definition.parameters) ? true : false;
return (this.definition && this.definition.parameters && this.definition.parameters.length > 0) ? true : false;
}
}
@ -72,7 +72,7 @@ export class ReportParameterDetailsModel {
name: string;
nameKey: string;
type: string;
value: string;
value: any;
options: ParameterValueModel[];
dependsOn: string;
@ -106,6 +106,7 @@ export class ParameterValueModel {
}
export class ReportQuery {
reportName: string;
processDefinitionId: string;
status: string;
taskName: string;
@ -116,24 +117,28 @@ export class ReportQuery {
duration: number;
constructor(obj?: any) {
this.reportName = obj && obj.reportName || null;
this.processDefinitionId = obj && obj.processDefinitionId || null;
this.status = obj && obj.status || null;
this.taskName = obj && obj.taskName || null;
this.dateRangeInterval = obj && obj.dateRangeInterval || null;
this.typeFiltering = obj && obj.typeFiltering || true;
this.typeFiltering = obj && (typeof obj.typeFiltering !== 'undefined') ? obj.typeFiltering : true;
this.slowProcessInstanceInteger = obj && obj.slowProcessInstanceInteger || 0;
this.duration = obj && obj.duration || 0;
this.dateRange = new ReportDateRange(obj);
}
}
export class ReportDateRange {
startDate: string;
endDate: string;
rangeId: string;
constructor(obj?: any) {
this.startDate = obj && obj.startDate || null;
this.endDate = obj && obj.endDate || null;
this.rangeId = obj && obj.rangeId || null;
}
}

View File

@ -9,10 +9,12 @@
* [Services](#services)
- [Prerequisites](#prerequisites)
- [Install](#install)
- [ActivitiForm Component](#activitiform-component)
- [Form Component](#form-component)
* [Properties](#properties)
+ [Form Field Validators](#form-field-validators)
* [Advanced properties](#advanced-properties)
* [Events](#events)
* [Custom empty form template](#custom-empty-form-template)
* [Controlling outcome execution behaviour](#controlling-outcome-execution-behaviour)
- [Activiti Content Component](#activiti-content-component)
* [Properties](#properties-1)
@ -226,6 +228,21 @@ onFormSaved(form: FormModel) {
}
```
### Custom empty form template
You can add a template that will be show if no form definition has been found
```html
<adf-form .... >
<div empty-form >
<h2>Empty form</h2>
</div>
</adf-form>
```
### Controlling outcome execution behaviour
If absolutely needed it is possible taking full control over form outcome execution by means of `executeOutcome` event.

View File

@ -17,14 +17,16 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { MdButtonModule, MdCardModule, MdCheckboxModule, MdIconModule, MdInputModule, MdSlideToggleModule, MdTabsModule } from '@angular/material';
import { MdAutocompleteModule, MdButtonModule, MdCardModule, MdCheckboxModule,
MdDatepickerModule, MdGridListModule, MdIconModule, MdInputModule, MdListModule,
MdOptionModule, MdRadioModule, MdSelectModule, MdSlideToggleModule, MdTableModule, MdTabsModule } from '@angular/material';
import { CoreModule } from 'ng2-alfresco-core';
import { DataTableModule } from 'ng2-alfresco-datatable';
import { ActivitiContentComponent } from './src/components/activiti-content.component';
import { FormFieldComponent } from './src/components/form-field/form-field.component';
import { FormListComponent } from './src/components/form-list.component';
import { FormComponent } from './src/components/form.component';
import { StartFormComponent } from './src/components/start-form.component';
import { ContentWidgetComponent } from './src/components/widgets/content/content.widget';
import { MASK_DIRECTIVE, WIDGET_DIRECTIVES } from './src/components/widgets/index';
import { ActivitiAlfrescoContentService } from './src/services/activiti-alfresco.service';
import { ActivitiContentService } from './src/services/activiti-content-service';
@ -36,7 +38,7 @@ import { WidgetVisibilityService } from './src/services/widget-visibility.servic
export * from './src/components/form.component';
export * from './src/components/form-list.component';
export * from './src/components/activiti-content.component';
export * from './src/components/widgets/content/content.widget';
export * from './src/components/start-form.component';
export * from './src/services/form.service';
export * from './src/services/activiti-content-service';
@ -47,22 +49,23 @@ export * from './src/services/form-rendering.service';
export * from './src/events/index';
// Old deprecated import
import {ActivitiContentComponent as ActivitiContent } from './src/components/activiti-content.component';
import {FormComponent as ActivitiForm } from './src/components/form.component';
import {StartFormComponent as ActivitiStartForm } from './src/components/start-form.component';
import {ContentWidgetComponent as ActivitiContent } from './src/components/widgets/content/content.widget';
export {FormComponent as ActivitiForm} from './src/components/form.component';
export {ActivitiContentComponent as ActivitiContent} from './src/components/activiti-content.component';
export {ContentWidgetComponent as ActivitiContent} from './src/components/widgets/content/content.widget';
export {StartFormComponent as ActivitiStartForm} from './src/components/start-form.component';
export const ACTIVITI_FORM_DIRECTIVES: any[] = [
FormComponent,
FormListComponent,
ActivitiContentComponent,
ContentWidgetComponent,
StartFormComponent,
FormFieldComponent,
...WIDGET_DIRECTIVES,
...WIDGET_DIRECTIVES
];
// Old Deprecated export
export const DEPRECATED_FORM_DIRECTIVES: any[] = [
ActivitiForm,
ActivitiContent,
ActivitiStartForm
@ -78,21 +81,22 @@ export const ACTIVITI_FORM_PROVIDERS: any[] = [
FormRenderingService
];
export const MATERIAL_MODULE: any[] = [
MdAutocompleteModule, MdButtonModule, MdCardModule, MdCheckboxModule,
MdDatepickerModule, MdGridListModule, MdIconModule, MdInputModule, MdRadioModule,
MdSelectModule, MdSlideToggleModule, MdTableModule, MdTabsModule, MdOptionModule, MdListModule
];
@NgModule({
imports: [
CoreModule,
DataTableModule,
HttpModule,
MdCheckboxModule,
MdTabsModule,
MdCardModule,
MdButtonModule,
MdIconModule,
MdSlideToggleModule,
MdInputModule
...MATERIAL_MODULE
],
declarations: [
...ACTIVITI_FORM_DIRECTIVES,
...DEPRECATED_FORM_DIRECTIVES,
...MASK_DIRECTIVE
],
entryComponents: [
@ -103,13 +107,8 @@ export const ACTIVITI_FORM_PROVIDERS: any[] = [
],
exports: [
...ACTIVITI_FORM_DIRECTIVES,
MdCheckboxModule,
MdTabsModule,
MdCardModule,
MdButtonModule,
MdIconModule,
MdSlideToggleModule,
MdInputModule
...DEPRECATED_FORM_DIRECTIVES,
...MATERIAL_MODULE
]
})
export class ActivitiFormModule {

View File

@ -13,10 +13,8 @@ module.exports = function (config) {
//diagrams
'./node_modules/alfresco-js-api/dist/alfresco-js-api.js',
'./node_modules/moment/min/moment.min.js',
'./node_modules/md-date-time-picker/dist/js/mdDateTimePicker.js',
{pattern: './node_modules/ng2-translate/**/*.js', included: false, watched: false},
{pattern: './node_modules/md-date-time-picker/**/*.js', included: false, served: true, watched: false},
{pattern: './node_modules/moment/**/*.js', included: false, served: true, watched: false},
{pattern: 'karma-test-shim.js', watched: false},

View File

@ -50,7 +50,6 @@
"alfresco-js-api": "1.7.0",
"core-js": "2.4.1",
"hammerjs": "2.0.8",
"md-date-time-picker": "2.2.0",
"moment": "2.15.1",
"ng2-alfresco-core": "1.7.0",
"reflect-metadata": "0.1.10",

View File

@ -1,46 +0,0 @@
.upload-widget {
width: 100%;
word-break: break-all;
}
.upload-widget__content {
min-height: auto;
}
.upload-widget__icon {
float: left;
color: rgba(0, 0, 0, .26);
}
.upload-widget__file {
float: left;
margin-top: 4px;
color: rgba(0, 0, 0, .26);
}
.upload-widget__label {
color: rgba(0, 0, 0, .26);
}
.img-upload-widget {
width: 100%;
height: 100%;
border: 1px solid rgba(117, 117, 117, 0.57);
box-shadow: 1px 1px 2px #dddddd;
background-color: #ffffff;
}
.nothing-to-see {
box-shadow: 1px 1px 2px #dddddd;
background-color: #ffffff;
width: 100%;
height: 100%;
padding: 50px 0 50px 0;
text-align: center;
}
.previewTxt {
word-wrap: break-word;
word-break: break-all;
text-align: center;
}

View File

@ -1,23 +0,0 @@
<div class="upload-widget" *ngIf="content">
<div class="mdl-card mdl-shadow--2dp upload-widget__content">
<div *ngIf="showDocumentContent" class="mdl-card__title mdl-card--expand upload-widget__content-thumbnail">
<div *ngIf="content.isThumbnailSupported()">
<img id="thumbnailPreview" class="img-upload-widget" [src]="content.thumbnailUrl">
</div>
<div *ngIf="!content.isThumbnailSupported()">
<i class="material-icons">image</i>
<div id="unsupported-thumbnail" class="previewTxt">{{ 'FORM.PREVIEW.IMAGE_NOT_AVAILABLE' | translate }}</div>
</div>
</div>
<div class="mdl-card__supporting-text upload-widget__content-text">{{content.name}}</div>
<div class="mdl-card__actions mdl-card--border upload-widget__content-actions">
<button id="view" (click)="openViewer(content)" class="mdl-button mdl-js-button mdl-button--icon">
<i class="material-icons">zoom_in</i>
</button>
<button id="download" (click)="download(content)" class="mdl-button mdl-js-button mdl-button--icon">
<i class="material-icons">file_download</i>
</button>
</div>
</div>
</div>

View File

@ -16,7 +16,9 @@
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MdInputModule } from '@angular/material';
import { CoreModule } from 'ng2-alfresco-core';
import { ErrorWidgetComponent } from '../widgets/error/error.component';
import { FormRenderingService } from './../../services/form-rendering.service';
import { WidgetVisibilityService } from './../../services/widget-visibility.service';
import { CheckboxWidgetComponent } from './../widgets/checkbox/checkbox.widget';
@ -29,15 +31,21 @@ describe('FormFieldComponent', () => {
let fixture: ComponentFixture<FormFieldComponent>;
let component: FormFieldComponent;
let componentHandler: any;
let form: FormModel;
let formRenderingService: FormRenderingService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [FormFieldComponent, TextWidgetComponent, CheckboxWidgetComponent, InputMaskDirective],
imports: [CoreModule,
MdInputModule
],
declarations: [
FormFieldComponent,
TextWidgetComponent,
CheckboxWidgetComponent,
InputMaskDirective,
ErrorWidgetComponent],
providers: [
FormRenderingService,
WidgetVisibilityService
@ -47,12 +55,6 @@ describe('FormFieldComponent', () => {
}));
beforeEach(() => {
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered',
'upgradeElement'
]);
window['componentHandler'] = componentHandler;
fixture = TestBed.createComponent(FormFieldComponent);
component = fixture.componentInstance;
formRenderingService = fixture.debugElement.injector.get(FormRenderingService);
@ -84,13 +86,6 @@ describe('FormFieldComponent', () => {
expect(component.componentRef.componentType).toBe(CheckboxWidgetComponent);
});
it('should require field to create component', () => {
component.field = null;
fixture.detectChanges();
expect(component.componentRef).toBeUndefined();
});
it('should require component type to be resolved', () => {
let field = new FormFieldModel(form, {
type: FormFieldTypes.TEXT

View File

@ -26,7 +26,8 @@ import {
OnDestroy,
OnInit,
ViewChild,
ViewContainerRef
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import { CoreModule } from 'ng2-alfresco-core';
@ -41,10 +42,14 @@ declare var adf: any;
@Component({
selector: 'adf-form-field, form-field',
template: `
<div [hidden]="!field?.isVisible">
<div [hidden]="!field?.isVisible"
[class.adf-focus]="focus"
(focusin)="focusToggle()"
(focusout)="focusToggle()">
<div #container></div>
</div>
`
`,
encapsulation: ViewEncapsulation.None
})
export class FormFieldComponent implements OnInit, OnDestroy {
@ -56,33 +61,35 @@ export class FormFieldComponent implements OnInit, OnDestroy {
componentRef: ComponentRef<{}>;
constructor(
private formRenderingService: FormRenderingService,
focus: boolean = false;
constructor(private formRenderingService: FormRenderingService,
private componentFactoryResolver: ComponentFactoryResolver,
private visibilityService: WidgetVisibilityService,
private compiler: Compiler) {
}
ngOnInit() {
if (this.field) {
let customTemplate = this.field.form.customFieldTemplates[this.field.type];
if (customTemplate && this.hasController(this.field.type)) {
let factory = this.getComponentFactorySync(this.field.type, customTemplate);
let originalField = this.getField();
if (originalField) {
let customTemplate = this.field.form.customFieldTemplates[originalField.type];
if (customTemplate && this.hasController(originalField.type)) {
let factory = this.getComponentFactorySync(originalField.type, customTemplate);
this.componentRef = this.container.createComponent(factory);
let instance: any = this.componentRef.instance;
if (instance) {
instance.field = this.field;
instance.field = originalField;
}
} else {
let componentType = this.formRenderingService.resolveComponentType(this.field);
let componentType = this.formRenderingService.resolveComponentType(originalField);
if (componentType) {
let factory = this.componentFactoryResolver.resolveComponentFactory(componentType);
this.componentRef = this.container.createComponent(factory);
let instance = <WidgetComponent> this.componentRef.instance;
instance.field = this.field;
instance.fieldChanged.subscribe(field => {
if (field && field.form) {
this.visibilityService.refreshVisibility(field.form);
if (field && this.field.form) {
this.visibilityService.refreshVisibility(this.field.form);
}
});
}
@ -97,6 +104,10 @@ export class FormFieldComponent implements OnInit, OnDestroy {
}
}
private getField() {
return (this.field.params && this.field.params.field) ? this.field.params.field : this.field;
}
private hasController(type: string): boolean {
return (adf && adf.components && adf.components[type]);
}
@ -119,14 +130,19 @@ export class FormFieldComponent implements OnInit, OnDestroy {
}
private createComponentFactorySync(compiler: Compiler, metadata: Component, componentClass: any): ComponentFactory<any> {
const cmpClass = componentClass || class RuntimeComponent { };
const cmpClass = componentClass || class RuntimeComponent {
};
const decoratedCmp = Component(metadata)(cmpClass);
@NgModule({ imports: [CoreModule], declarations: [decoratedCmp] })
class RuntimeComponentModule { }
class RuntimeComponentModule {
}
let module: ModuleWithComponentFactories<any> = compiler.compileModuleAndAllComponentsSync(RuntimeComponentModule);
return module.componentFactories.find(x => x.componentType === decoratedCmp);
}
focusToggle() {
this.focus = !this.focus;
}
}

View File

@ -20,24 +20,23 @@ import { By } from '@angular/platform-browser';
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
import { DataTableModule } from 'ng2-alfresco-datatable';
import { Observable } from 'rxjs/Rx';
import { MATERIAL_MODULE } from '../../index';
import { EcmModelService } from '../services/ecm-model.service';
import { FormService } from '../services/form.service';
import { FormListComponent } from './form-list.component';
declare let jasmine: any;
describe('TaskAttachmentList', () => {
let component: FormListComponent;
let fixture: ComponentFixture<FormListComponent>;
let service: FormService;
let componentHandler: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot(),
DataTableModule
DataTableModule,
MATERIAL_MODULE
],
declarations: [
FormListComponent
@ -53,12 +52,6 @@ describe('TaskAttachmentList', () => {
spyOn(translateService, 'get').and.callFake((key) => {
return Observable.of(key);
});
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered',
'upgradeElement'
]);
window['componentHandler'] = componentHandler;
}));
beforeEach(async(() => {

View File

@ -15,13 +15,14 @@
* limitations under the License.
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { FormService } from './../services/form.service';
@Component({
selector: 'adf-form-list',
templateUrl: './form-list.component.html',
styleUrls: ['./form-list.component.css']
styleUrls: ['./form-list.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FormListComponent implements OnChanges {

View File

@ -1,36 +0,0 @@
.activiti-form-container {
width: 100%;
min-height: 100px;
overflow: visible;
}
.activiti-form-container > .mdl-card__media {
background-color: #fff;
}
.activiti-form-debug-container {
padding: 10px;
}
.activiti-form-debug-container .debug-toggle-text {
padding-left: 15px;
cursor: pointer;
}
.activiti-form-debug-container .debug-toggle-text:hover {
font-weight: bold;
}
.activiti-form-reload-button {
position: absolute;
right: 0;
top: 0;
}
.activiti-form-hide-button {
display: none;
}
.activiti-task-title {
text-align: center
}

View File

@ -1,13 +1,13 @@
<div>
<div *ngIf="!hasForm()">
<h3 class="activiti-task-title">Please select a Task</h3>
<ng-content select="[empty-form]">
</ng-content>
</div>
<div *ngIf="hasForm()" class="{{form.className}}">
<md-card>
<md-card-header>
<md-card-title>
<h4 *ngIf="isTitleEnabled()">
<div *ngIf="showRefreshButton" class="activiti-form-reload-button">
<div *ngIf="showRefreshButton" class="adf-form-reload-button">
<button md-icon-button (click)="onRefreshClicked()">
<md-icon>refresh</md-icon>
</button>
@ -34,19 +34,18 @@
<button *ngFor="let outcome of form.outcomes"
md-button
[disabled]="!isOutcomeButtonEnabled(outcome)"
[class.activiti-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
(click)="onOutcomeClicked(outcome, $event)">
{{outcome.name | uppercase}}
</button>
</md-card-actions>
</md-card>
</div>
</div>
<!--
For debugging and data visualisation purposes,
will be removed during future revisions
-->
<div *ngIf="showDebugButton" class="activiti-form-debug-container">
<div *ngIf="showDebugButton" class="adf-form-debug-container">
<md-slide-toggle [(ngModel)]="debugMode">Debug mode</md-slide-toggle>
<div *ngIf="debugMode && hasForm()">
<h4>Values</h4>

View File

@ -0,0 +1,38 @@
.adf {
&-form-container {
width: 100%;
min-height: 100px;
overflow: visible;
}
&-form-debug-container {
padding: 10px;
}
&-form-debug-container .debug-toggle-text {
padding-left: 15px;
cursor: pointer;
}
&-form-debug-container .debug-toggle-text:hover {
font-weight: bold;
}
&-form-reload-button {
position: absolute;
right: 0;
top: 0;
}
&-form-hide-button {
display: none;
}
&-task-title {
text-align: center
}
}
form-field {
width: 100%;
}

View File

@ -27,7 +27,6 @@ import { FormFieldModel, FormFieldTypes, FormModel, FormOutcomeEvent, FormOutcom
describe('FormComponent', () => {
let componentHandler: any;
let formService: FormService;
let formComponent: FormComponent;
let visibilityService: WidgetVisibilityService;
@ -35,11 +34,6 @@ describe('FormComponent', () => {
let logService: LogService;
beforeEach(() => {
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
window['componentHandler'] = componentHandler;
logService = new LogService();
visibilityService = new WidgetVisibilityService(null, logService);
spyOn(visibilityService, 'refreshVisibility').and.stub();
@ -48,18 +42,6 @@ describe('FormComponent', () => {
formComponent = new FormComponent(formService, visibilityService, null, nodeService);
});
it('should upgrade MDL content on view checked', () => {
formComponent.ngAfterViewChecked();
expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled();
});
it('should setup MDL content only if component handler available', () => {
expect(formComponent.setupMaterialComponents()).toBeTruthy();
window['componentHandler'] = null;
expect(formComponent.setupMaterialComponents()).toBeFalsy();
});
it('should check form', () => {
expect(formComponent.hasForm()).toBeFalsy();
formComponent.form = new FormModel();
@ -650,19 +632,6 @@ describe('FormComponent', () => {
expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form);
});
/*
it('should update the visibility when the container raise the change event', (valueChanged) => {
spyOn(formComponent, 'checkVisibility').and.callThrough();
let widget = new ContainerWidgetComponent();
let fakeForm = new FormModel();
let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'});
widget.formValueChanged.subscribe(field => { valueChanged(); });
widget.fieldChanged(fakeField);
expect(formComponent.checkVisibility).toHaveBeenCalledWith(fakeField);
});
*/
it('should prevent default outcome execution', () => {
let outcome = new FormOutcomeModel(new FormModel(), {

View File

@ -16,7 +16,7 @@
*/
/* tslint:disable */
import { AfterViewChecked, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { FormErrorEvent, FormEvent } from './../events/index';
import { EcmModelService } from './../services/ecm-model.service';
import { FormService } from './../services/form.service';
@ -26,14 +26,13 @@ import { FormFieldModel, FormModel, FormOutcomeEvent, FormOutcomeModel, FormValu
import { WidgetVisibilityService } from './../services/widget-visibility.service';
declare var componentHandler: any;
@Component({
selector: 'adf-form, activiti-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css']
styleUrls: ['./form.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FormComponent implements OnInit, AfterViewChecked, OnChanges {
export class FormComponent implements OnInit, OnChanges {
static SAVE_OUTCOME_ID: string = '$save';
static COMPLETE_OUTCOME_ID: string = '$complete';
@ -176,10 +175,6 @@ export class FormComponent implements OnInit, AfterViewChecked, OnChanges {
});
}
ngAfterViewChecked() {
this.setupMaterialComponents();
}
ngOnChanges(changes: SimpleChanges) {
let taskId = changes['taskId'];
if (taskId && taskId.currentValue) {
@ -306,15 +301,6 @@ export class FormComponent implements OnInit, AfterViewChecked, OnChanges {
return taskRepresentation.processDefinitionId && taskRepresentation.processDefinitionDeploymentId !== 'null';
}
setupMaterialComponents(): boolean {
// workaround for MDL issues with dynamic components
if (componentHandler) {
componentHandler.upgradeAllRegistered();
return true;
}
return false;
}
getFormByTaskId(taskId: string): Promise<FormModel> {
return new Promise<FormModel>((resolve, reject) => {
this.loadFormProcessVariables(this.taskId).then(_ => {

View File

@ -1,11 +1,12 @@
<div>
<div *ngIf="hasForm()">
<div class="mdl-card mdl-shadow--2dp activiti-form-container">
<div class="mdl-card__title">
<i class="material-icons">{{ form.isValid ? 'event_available' : 'event_busy' }}</i>
<md-card>
<md-card-header>
<md-card-title>
<md-icon>{{ form.isValid ? 'event_available' : 'event_busy' }}</md-icon>
<h2 *ngIf="isTitleEnabled()" class="mdl-card__title-text">{{form.taskName}}</h2>
</div>
<div class="mdl-card__media">
</md-card-title>
</md-card-header>
<md-card-content>
<div *ngIf="form.hasTabs()">
<tabs-widget [tabs]="form.tabs" (formTabChanged)="checkVisibility($event);"></tabs-widget>
</div>
@ -15,23 +16,22 @@
<form-field [field]="field.field"></form-field>
</div>
</div>
</div>
<div *ngIf="showOutcomeButtons && form.hasOutcomes()" class="mdl-card__actions mdl-card--border" #outcomesContainer>
</md-card-content>
<md-card-content *ngIf="showOutcomeButtons && form.hasOutcomes()" #outcomesContainer>
<button *ngFor="let outcome of form.outcomes"
md-button
[disabled]="!isOutcomeButtonEnabled(outcome)"
[class.mdl-button--colored]="!outcome.isSystem"
[class.activiti-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
(click)="onOutcomeClicked(outcome, $event)">
{{outcome.name}}
</button>
</div>
<div *ngIf="showRefreshButton" class="mdl-card__menu" >
<button (click)="onRefreshClicked()"
class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect">
<i class="material-icons">refresh</i>
</md-card-content>
<md-card-actions *ngIf="showRefreshButton">
<button md-button
(click)="onRefreshClicked()">
<md-icon>refresh</md-icon>
</button>
</div>
</div>
</div>
</md-card-actions>
</md-card>
</div>

View File

@ -17,24 +17,21 @@
import { SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MdTabsModule } from '@angular/material';
import { Observable } from 'rxjs/Rx';
import { MdInputModule } from '@angular/material';
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { MATERIAL_MODULE } from '../../index';
import { TranslationMock } from './../assets/translation.service.mock';
import { EcmModelService } from './../services/ecm-model.service';
import { FormService } from './../services/form.service';
import { WidgetVisibilityService } from './../services/widget-visibility.service';
import { ActivitiContentComponent } from './activiti-content.component';
import { FormFieldComponent } from './form-field/form-field.component';
import { StartFormComponent } from './start-form.component';
import { ContentWidgetComponent } from './widgets/content/content.widget';
import { MASK_DIRECTIVE } from './widgets/index';
import { WIDGET_DIRECTIVES } from './widgets/index';
describe('ActivitiStartForm', () => {
let componentHandler: any;
let formService: FormService;
let component: StartFormComponent;
let fixture: ComponentFixture<StartFormComponent>;
@ -46,13 +43,12 @@ describe('ActivitiStartForm', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MdTabsModule,
MdInputModule,
...MATERIAL_MODULE,
CoreModule.forRoot()],
declarations: [
StartFormComponent,
FormFieldComponent,
ActivitiContentComponent,
ContentWidgetComponent,
...WIDGET_DIRECTIVES,
...MASK_DIRECTIVE
],
@ -75,11 +71,6 @@ describe('ActivitiStartForm', () => {
processDefinitionName: 'my:process'
}));
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered',
'upgradeElement'
]);
window['componentHandler'] = componentHandler;
});
it('should load start form on change if processDefinitionId defined', () => {

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import { AlfrescoTranslationService, LogService } from 'ng2-alfresco-core';
import { FormService } from './../services/form.service';
import { WidgetVisibilityService } from './../services/widget-visibility.service';
@ -43,7 +43,8 @@ import { FormOutcomeModel } from './widgets/core/index';
@Component({
selector: 'adf-start-form, activiti-start-form',
templateUrl: './start-form.component.html',
styleUrls: ['./form.component.css']
styleUrls: ['./form.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class StartFormComponent extends FormComponent implements OnChanges, OnInit {

View File

@ -1,66 +0,0 @@
.amount-widget {
width: 100%;
}
.amount-widget__container {
position: relative;
font-size: 16px;
display: inline-block;
box-sizing: border-box;
width: 300px;
max-width: 100%;
margin: 0;
padding: 20px 0;
}
.adf-amount-widget {
vertical-align: baseline !important;
}
.adf-amount-widget__prefix-spacing {
padding-right: 5px;
}
:host() .amount-widget__input {
width: 100%;
}
.amount-widget__input .mat-focused {
transition: none;
}
.amount-widget__error_message {
color: #d50000;
position: absolute;
font-size: 12px;
margin-top: 3px;
display: block;
padding-top: 3px;
}
md-input-container >>> .mat-input-wrapper {
margin-top: 0px;
padding-top: 4px;
}
md-input-container >>> .mat-input-ripple {
transition: none;
visibility: hidden;
}
md-input-container.mat-focused >>> .mat-input-underline .mat-input-ripple {
visibility: hidden;
transition: none;
}
.amount-widget__invalid .mdl-textfield__input {
border-color: #d50000;
}
.amount-widget__invalid .mdl-textfield__label {
color: #d50000;
}
.amount-widget__invalid .mdl-textfield__label:after {
background-color: #d50000;
}

View File

@ -1,21 +1,18 @@
<div class="amount-widget__container amount-widget {{field.className}}"
[class.amount-widget__invalid]="!field.isValid">
<div>
<label>{{field.name}} <span *ngIf="isRequired()">*</span></label>
</div>
<md-input-container floatPlaceholder="never" class="amount-widget__input">
<div class="adf-amount-widget__container adf-amount-widget {{field.className}}" [class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly">
<md-input-container floatPlaceholder="never" class="adf-amount-widget__input">
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<span mdPrefix class="adf-amount-widget__prefix-spacing"> {{currency }}</span>
<input mdInput
class="adf-amount-widget"
type="text"
[attr.id]="field.id"
[attr.required]="isRequired()"
[id]="field.id"
[required]="isRequired()"
[value]="field.value"
[(ngModel)]="field.value"
(ngModelChange)="checkVisibility(field)"
[disabled]="field.readOnly"
placeholder="{{field.placeholder}}">
<span *ngIf="field.validationSummary" class="amount-widget__error_message">{{field.validationSummary}}</span>
</md-input-container>
<error-widget [error]="field.validationSummary" ></error-widget>
</div>

View File

@ -0,0 +1,28 @@
@import 'theming';
@import '../form';
.adf {
&-amount-widget {
width: 100%;
vertical-align: baseline !important;
padding-left: 14px;
}
&-amount-widget__container {
max-width: 100%;
}
&-amount-widget__input .mat-focused {
transition: none;
}
&-amount-widget__prefix-spacing {
padding-right: 5px;
}
}
.mat-input-prefix {
position: absolute;
margin-top: 42px;
}

View File

@ -19,6 +19,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MdInputModule } from '@angular/material';
import { CoreModule } from 'ng2-alfresco-core';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { ErrorWidgetComponent } from '../error/error.component';
import { EcmModelService } from './../../../services/ecm-model.service';
import { FormService } from './../../../services/form.service';
import { FormFieldModel } from './../core/form-field.model';
@ -36,7 +37,8 @@ describe('AmountWidgetComponent', () => {
MdInputModule
],
declarations: [
AmountWidgetComponent
AmountWidgetComponent,
ErrorWidgetComponent
],
providers: [
FormService,

View File

@ -17,15 +17,16 @@
/* tslint:disable:component-selector */
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { FormService } from './../../../services/form.service';
import { baseHost , WidgetComponent } from './../widget.component';
@Component({
selector: 'amount-widget',
templateUrl: './amount.widget.html',
styleUrls: ['./amount.widget.css'],
host: baseHost
styleUrls: ['./amount.widget.scss'],
host: baseHost,
encapsulation: ViewEncapsulation.None
})
export class AmountWidgetComponent extends WidgetComponent implements OnInit {

View File

@ -2,7 +2,7 @@
<label [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<div>
<span *ngIf="hasFile()" class="attach-widget__file mdl-chip"><span class="mdl-chip__text">{{getLinkedFileName()}}</span></span>
<button #browseFile [disabled]="field.readOnly" (click)="showDialog();" class="mdl-button mdl-js-button mdl-js-ripple-effect attach-widget__browser">
<button #browseFile [disabled]="field.readOnly" (click)="showDialog();" class="mdl-button mdl-jsm-button mdl-js-ripple-effect attach-widget__browser">
<i class="material-icons">image</i>
Browse {{selectedFolderSiteName}}
</button>

View File

@ -18,10 +18,12 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { MATERIAL_MODULE } from '../../../../index';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { ExternalContent } from '../core/external-content';
import { ExternalContentLink } from '../core/external-content-link';
import { FormFieldTypes } from '../core/form-field-types';
import { ErrorWidgetComponent } from '../error/error.component';
import { EcmModelService } from './../../../services/ecm-model.service';
import { FormService } from './../../../services/form.service';
import { FormFieldModel } from './../core/form-field.model';
@ -39,10 +41,12 @@ describe('AttachWidgetComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
CoreModule.forRoot(),
...MATERIAL_MODULE
],
declarations: [
AttachWidgetComponent
AttachWidgetComponent,
ErrorWidgetComponent
],
providers: [
FormService,

View File

@ -17,7 +17,7 @@
/* tslint:disable:component-selector */
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { Component, EventEmitter, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { ExternalContent } from '../core/external-content';
import { ExternalContentLink } from '../core/external-content-link';
@ -30,7 +30,9 @@ declare let dialogPolyfill: any;
@Component({
selector: 'attach-widget',
templateUrl: './attach.widget.html',
styleUrls: ['./attach.widget.css'], host: baseHost
styleUrls: ['./attach.widget.css'],
host: baseHost,
encapsulation: ViewEncapsulation.None
})
export class AttachWidgetComponent extends WidgetComponent implements OnInit {
@ -42,9 +44,6 @@ export class AttachWidgetComponent extends WidgetComponent implements OnInit {
selectedFolderNodes: [ExternalContent];
selectedFile: ExternalContent;
@Input()
field: FormFieldModel;
@Output()
fieldChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>();

View File

@ -1,8 +1,9 @@
<div [ngClass]="field.className">
<md-checkbox
[id]="field.id"
color="primary"
[required]="field.required"
[disabled]="field.readOnly"
[disabled]="field.readOnly || readOnly"
[(ngModel)]="field.value"
(change)="onChange()">
{{field.name}}

View File

@ -17,7 +17,7 @@
/* tslint:disable:component-selector */
import { Component } from '@angular/core';
import { Component, ViewEncapsulation } from '@angular/core';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
import { FormService } from './../../../services/form.service';
import { baseHost , WidgetComponent } from './../widget.component';
@ -25,7 +25,8 @@ import { baseHost , WidgetComponent } from './../widget.component';
@Component({
selector: 'checkbox-widget',
templateUrl: './checkbox.widget.html',
host: baseHost
host: baseHost,
encapsulation: ViewEncapsulation.None
})
export class CheckboxWidgetComponent extends WidgetComponent {

View File

@ -1,24 +0,0 @@
.container-widget {}
.container-widget__header {}
.container-widget__header-text {
border-bottom: 1px solid rgba(0,0,0,.87);
margin-left: 10px;
margin-right: 10px;
cursor: default;
user-select: none;
-webkit-user-select: none; /* Chrome/Safari/Opera */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
-webkit-touch-callout: none; /* iOS Safari */
}
.container-widget__header-text.collapsible {
cursor: pointer;
}
.container-widget__mdl-grid {
align-items: center;
}

View File

@ -1,4 +1,3 @@
<div class="container-widget">
<div [ngClass]="{'hidden':!(content?.isGroup() && content?.isVisible)}" class="container-widget__header">
<h4 class="container-widget__header-text" id="container-header"
[class.collapsible]="content?.isCollapsible()">
@ -11,15 +10,16 @@
<span (click)="onExpanderClicked()" id="container-header-label">{{content.name}}</span>
</h4>
</div>
<div class="mdl-grid" [ngClass]="{'hidden':!(content?.isVisible && content?.isExpanded)}">
<div *ngFor="let col of content.columns" class="mdl-cell mdl-cell--{{col.size}}-col">
<div class="mdl-grid container-widget__mdl-grid" *ngIf="col.hasFields()">
<div *ngFor="let field of col.fields" class="mdl-cell mdl-cell--12-col">
<div class="mdl-layout-spacer"></div>
<md-grid-list rowHeight="100px" [cols]="content.field.numberOfColumns"
[ngClass]="{'hidden':!(content?.isVisible && content?.isExpanded)}">
<md-grid-tile *ngFor="let col of content.columns"
[colspan]="content.colspan"
[rowspan]="content.rowspan">
<ul class="adf-field-list">
<li *ngFor="let field of col.fields">
<form-field [field]="field"></form-field>
<div class="mdl-layout-spacer"></div>
</div>
</div>
</div>
</div>
</div>
</li>
</ul>
</md-grid-tile>
</md-grid-list>

View File

@ -34,48 +34,6 @@ describe('ContainerWidgetComponentModel', () => {
expect(container.isExpanded).toBeTruthy();
});
it('should wrap fields into columns on setup', () => {
let form = new FormModel();
let json = {
fieldType: '<type>',
id: '<id>',
name: '<name>',
type: '<type>',
tab: '<tab>',
numberOfColumns: 3,
params: {},
visibilityCondition: {},
fields: {
'1': [
{ id: 'field-1' },
{ id: 'field-3' }
],
'2': [
{ id: 'field-2' }
],
'3': null
}
};
let field = new FormFieldModel(form, json);
let container = new ContainerWidgetComponentModel(field);
expect(container.columns.length).toBe(3);
let col1 = container.columns[0];
expect(col1.fields.length).toBe(2);
expect(col1.fields[0].id).toBe('field-1');
expect(col1.fields[1].id).toBe('field-3');
let col2 = container.columns[1];
expect(col2.fields.length).toBe(1);
expect(col2.fields[0].id).toBe('field-2');
let col3 = container.columns[2];
expect(col3.fields.length).toBe(0);
});
it('should allow collapsing only when of a group type', () => {
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.CONTAINER,

View File

@ -26,6 +26,8 @@ export class ContainerWidgetComponentModel extends ContainerModel {
columns: ContainerColumnModel[] = [];
isExpanded: boolean = true;
rowspan: number = 1;
colspan: number = 1;
isGroup(): boolean {
return this.type === FormFieldTypes.GROUP;
@ -57,6 +59,8 @@ export class ContainerWidgetComponentModel extends ContainerModel {
if (this.field) {
this.columns = this.field.columns || [];
this.isExpanded = !this.isCollapsedByDefault();
this.colspan = field.colspan;
this.rowspan = field.rowspan;
}
}
}

View File

@ -0,0 +1,57 @@
@import 'theming';
.adf {
&-field-list {
padding: 0;
list-style-type: none;
width: 100%;
height: 100%;
}
.container-widget__header-text {
border-bottom: 1px solid rgba(0, 0, 0, 0.87);
margin-left: 10px;
margin-right: 10px;
cursor: default;
user-select: none;
-webkit-user-select: none;
/* Chrome/Safari/Opera */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* IE/Edge */
-webkit-touch-callout: none;
/* iOS Safari */
&.collapsible {
cursor: pointer;
}
}
}
container-widget{
md-grid-list {
height:100px;
}
md-input-container {
width: 95%;
}
.mat-focused {
label {
transform: scaleX(1);
transition: transform 150ms linear,
background-color $swift-ease-in-duration $swift-ease-in-timing-function;
color: mat-color($primary);
}
.mat-input-prefix {
color: mat-color($primary);
}
}
.mat-grid-tile {
overflow: visible;
}
}

View File

@ -16,16 +16,16 @@
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MdInputModule, MdTabsModule } from '@angular/material';
import { CoreModule } from 'ng2-alfresco-core';
import { MATERIAL_MODULE } from '../../../../index';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { fakeFormJson } from '../../../services/assets/widget-visibility.service.mock';
import { WIDGET_DIRECTIVES } from '../index';
import { MASK_DIRECTIVE } from '../index';
import { EcmModelService } from './../../../services/ecm-model.service';
import { FormService } from './../../../services/form.service';
import { ActivitiContentComponent } from './../../activiti-content.component';
import { FormFieldComponent } from './../../form-field/form-field.component';
import { ContentWidgetComponent } from './../content/content.widget';
import { FormFieldTypes } from './../core/form-field-types';
import { FormFieldModel } from './../core/form-field.model';
import { FormModel } from './../core/form.model';
@ -38,17 +38,15 @@ describe('ContainerWidgetComponent', () => {
let fixture: ComponentFixture<ContainerWidgetComponent>;
let element: HTMLElement;
let contentService: ActivitiAlfrescoContentService;
let componentHandler;
let dialogPolyfill;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot(),
MdTabsModule,
MdInputModule
...MATERIAL_MODULE
],
declarations: [FormFieldComponent, ActivitiContentComponent, WIDGET_DIRECTIVES, MASK_DIRECTIVE],
declarations: [FormFieldComponent, ContentWidgetComponent, WIDGET_DIRECTIVES, MASK_DIRECTIVE],
providers: [
FormService,
EcmModelService,
@ -70,11 +68,6 @@ describe('ContainerWidgetComponent', () => {
};
}
};
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
window['componentHandler'] = componentHandler;
});
it('should wrap field with model instance', () => {
@ -85,18 +78,6 @@ describe('ContainerWidgetComponent', () => {
expect(widget.content.field).toBe(field);
});
it('should upgrade MDL content on view init', () => {
widget.ngAfterViewInit();
expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled();
});
it('should setup MDL content only if component handler available', () => {
expect(widget.setupMaterialComponents()).toBeTruthy();
window['componentHandler'] = null;
expect(widget.setupMaterialComponents()).toBeFalsy();
});
it('should toggle underlying group container', () => {
let container = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.GROUP,
@ -160,8 +141,6 @@ describe('ContainerWidgetComponent', () => {
let fakeContainerInvisible;
beforeEach(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
fakeContainerVisible = new ContainerWidgetComponentModel(new FormFieldModel(new FormModel(fakeFormJson), {
fieldType: FormFieldTypes.GROUP,
id: 'fake-cont-id-1',

View File

@ -17,18 +17,17 @@
/* tslint:disable:component-selector */
import { AfterViewInit, Component, OnInit } from '@angular/core';
import { AfterViewInit, Component, OnInit, ViewEncapsulation } from '@angular/core';
import { FormService } from './../../../services/form.service';
import { baseHost , WidgetComponent } from './../widget.component';
import { ContainerWidgetComponentModel } from './container.widget.model';
declare var componentHandler: any;
@Component({
selector: 'container-widget',
templateUrl: './container.widget.html',
styleUrls: ['./container.widget.css'],
host: baseHost
styleUrls: ['./container.widget.scss'],
host: baseHost,
encapsulation: ViewEncapsulation.None
})
export class ContainerWidgetComponent extends WidgetComponent implements OnInit, AfterViewInit {
@ -49,17 +48,4 @@ export class ContainerWidgetComponent extends WidgetComponent implements OnInit,
this.content = new ContainerWidgetComponentModel(this.field);
}
}
ngAfterViewInit() {
this.setupMaterialComponents();
}
setupMaterialComponents(): boolean {
// workaround for MDL issues with dynamic components
if (componentHandler) {
componentHandler.upgradeAllRegistered();
return true;
}
return false;
}
}

View File

@ -0,0 +1,22 @@
<md-card class="example-card" class="adf-content-container" *ngIf="content">
<md-card-content *ngIf="showDocumentContent">
<div *ngIf="content.isThumbnailSupported()" >
<img id="thumbnailPreview" class="adf-img-upload-widget" [src]="content.thumbnailUrl">
</div>
<div *ngIf="!content.isThumbnailSupported()">
<i class="material-icons">image</i>
<div id="unsupported-thumbnail" class="adf-content-widget-preview-text">{{ 'FORM.PREVIEW.IMAGE_NOT_AVAILABLE' | translate }}
</div>
</div>
<div class="mdl-card__supporting-text upload-widget__content-text">{{content.name}}</div>
</md-card-content>
<md-card-actions>
<button md-icon-button id="view" (click)="openViewer(content)">
<md-icon class="md-24">zoom_in</md-icon>
</button>
<button md-icon-button id="download" (click)="download(content)">
<md-icon class="md-24">file_download</md-icon>
</button>
</md-card-actions>
</md-card>

View File

@ -0,0 +1,19 @@
.adf {
&-content-container {
}
&-img-upload-widget {
width: 100%;
height: 100%;
border: 1px solid rgba(117, 117, 117, 0.57);
box-shadow: 1px 1px 2px #dddddd;
background-color: #ffffff;
}
&-content-widget-preview-text {
word-wrap: break-word;
word-break: break-all;
text-align: center;
}
}

View File

@ -22,25 +22,23 @@ import { By } from '@angular/platform-browser';
import { AlfrescoTranslationService, ContentService, CoreModule } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { FormService } from '../services/form.service';
import { EcmModelService } from './../services/ecm-model.service';
import { ActivitiContentComponent } from './activiti-content.component';
import { ContentLinkModel } from './widgets/index';
import { EcmModelService } from '../../../services/ecm-model.service';
import { FormService } from '../../../services/form.service';
import { ContentLinkModel } from '../index';
import { ContentWidgetComponent } from './content.widget';
declare let jasmine: any;
describe('ActivitiContentComponent', () => {
describe('ContentWidgetComponent', () => {
let component: ActivitiContentComponent;
let fixture: ComponentFixture<ActivitiContentComponent>;
let component: ContentWidgetComponent;
let fixture: ComponentFixture<ContentWidgetComponent>;
let debug: DebugElement;
let element: HTMLElement;
let serviceForm: FormService;
let serviceContent: ContentService;
let componentHandler: any;
function createFakeImageBlob() {
let data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
return new Blob([data], {type: 'image/png'});
@ -71,7 +69,7 @@ describe('ActivitiContentComponent', () => {
CoreModule.forRoot()
],
declarations: [
ActivitiContentComponent
ContentWidgetComponent
],
providers: [
FormService,
@ -88,15 +86,10 @@ describe('ActivitiContentComponent', () => {
spyOn(translateService, 'get').and.callFake((key) => {
return Observable.of(key);
});
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
window['componentHandler'] = componentHandler;
}));
beforeEach(() => {
fixture = TestBed.createComponent(ActivitiContentComponent);
fixture = TestBed.createComponent(ContentWidgetComponent);
component = fixture.componentInstance;
debug = fixture.debugElement;
element = fixture.nativeElement;

View File

@ -15,18 +15,19 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { AlfrescoTranslationService, ContentService, LogService } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { FormService } from './../services/form.service';
import { ContentLinkModel } from './widgets/core/content-link.model';
import { ContentLinkModel } from '../core/content-link.model';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'adf-content, activiti-content',
templateUrl: './activiti-content.component.html',
styleUrls: ['./activiti-content.component.css']
templateUrl: './content.widget.html',
styleUrls: ['./content.widget.scss'],
encapsulation: ViewEncapsulation.None
})
export class ActivitiContentComponent implements OnChanges {
export class ContentWidgetComponent implements OnChanges {
@Input()
id: string;

View File

@ -23,6 +23,8 @@ export class ContainerColumnModel {
size: number = 12;
fields: FormFieldModel[] = [];
colspan: number = 1;
rowspan: number = 1;
hasFields(): boolean {
return this.fields && this.fields.length > 0;

View File

@ -132,9 +132,9 @@ export class DateFieldValidator implements FormFieldValidator {
];
// Validates that the input string is a valid date formatted as <dateFormat> (default D-M-YYYY)
static isValidDate(dateString: string, dateFormat: string = 'D-M-YYYY'): boolean {
if (dateString) {
let d = moment(dateString.split('T')[0], dateFormat, true);
static isValidDate(inputDate: string, dateFormat: string = 'D-M-YYYY'): boolean {
if (inputDate) {
let d = moment(inputDate, dateFormat, true);
return d.isValid();
}

View File

@ -16,7 +16,6 @@
*/
/* tslint:disable:component-selector */
import * as moment from 'moment';
import { WidgetVisibilityModel } from '../../../models/widget-visibility.model';
import { ContainerColumnModel } from './container-column.model';
@ -44,6 +43,7 @@ export class FormFieldModel extends FormWidgetModel {
required: boolean;
overrideId: boolean;
tab: string;
rowspan: number = 1;
colspan: number = 1;
placeholder: string = null;
minLength: number = 0;
@ -127,7 +127,7 @@ export class FormFieldModel extends FormWidgetModel {
this.name = json.name;
this.type = json.type;
this.required = <boolean> json.required;
this._readOnly = <boolean> json.readOnly;
this._readOnly = <boolean> json.readOnly || json.type === 'readonly';
this.overrideId = <boolean> json.overrideId;
this.tab = json.tab;
this.restUrl = json.restUrl;
@ -157,29 +157,15 @@ export class FormFieldModel extends FormWidgetModel {
this.placeholder = json.placeholder;
}
// <container>
this.numberOfColumns = <number> json.numberOfColumns;
let columnSize: number = 12;
if (this.numberOfColumns > 1) {
columnSize = 12 / this.numberOfColumns;
if (json.type === 'readonly') {
if (json.params && json.params.field && json.params.field.responseVariable) {
this.value = this.getVariablesValue(json.params.field.name, form);
}
}
for (let i = 0; i < this.numberOfColumns; i++) {
let col = new ContainerColumnModel();
col.size = columnSize;
this.columns.push(col);
if (json.type === 'container') {
this.containerFactory(json, form);
}
if (json.fields) {
Object.keys(json.fields).map(key => {
let fields = (json.fields[key] || []).map(f => new FormFieldModel(form, f));
let col = this.columns[parseInt(key, 10) - 1];
col.fields = fields;
this.fields.push(...fields);
});
}
// </container>
}
if (this.hasEmptyValue && this.options && this.options.length > 0) {
@ -189,6 +175,46 @@ export class FormFieldModel extends FormWidgetModel {
this.updateForm();
}
private getVariablesValue(variableName: string, form: FormModel) {
let variable = form.json.variables.find((currentVariable) => {
return currentVariable.name === variableName;
});
if (variable.type === 'boolean') {
return JSON.parse(variable.value);
}
return variable.value;
}
private containerFactory(json: any, form: FormModel): void {
this.numberOfColumns = <number> json.numberOfColumns || 1;
this.fields = json.fields;
this.rowspan = 1;
this.colspan = 1;
if (json.fields) {
for (let currentField in json.fields) {
if (json.fields.hasOwnProperty(currentField)) {
let col = new ContainerColumnModel();
let fields: FormFieldModel[] = (json.fields[currentField] || []).map(f => new FormFieldModel(form, f));
col.fields = fields;
col.rowspan = json.fields[currentField].length;
col.fields.forEach((colFields: any) => {
this.colspan = colFields.colspan > this.colspan ? colFields.colspan : this.colspan;
});
this.rowspan = this.rowspan < col.rowspan ? col.rowspan : this.rowspan;
this.columns.push(col);
}
}
}
}
parseValue(json: any): any {
let value = json.value;
@ -275,7 +301,7 @@ export class FormFieldModel extends FormWidgetModel {
break;
case FormFieldTypes.UPLOAD:
if (this.value && this.value.length > 0) {
this.form.values[this.id] = `${this.value[0].id}`;
this.form.values[this.id] = this.value.map(elem => elem.id).join(',');
} else {
this.form.values[this.id] = null;
}

View File

@ -124,6 +124,7 @@ export class FormModel {
}
}
}
if (json.fields) {
let saveOutcome = new FormOutcomeModel(this, { id: FormModel.SAVE_OUTCOME, name: 'Save', isSystem: true });
let completeOutcome = new FormOutcomeModel(this, { id: FormModel.COMPLETE_OUTCOME, name: 'Complete', isSystem: true });
@ -170,7 +171,10 @@ export class FormModel {
if (field instanceof ContainerModel) {
let container = <ContainerModel> field;
result.push(container.field);
result.push(...container.field.fields);
container.field.columns.forEach((column) => {
result.push(...column.fields);
});
}
}

View File

@ -1,40 +0,0 @@
.date-widget {
width: 100%;
padding-top: 10px;
padding-bottom: 10px;
}
.date-widget--button {
margin-top: 10px;
}
.mdl-date__input{
padding-top: 5px;
padding-bottom: 5px;
}
.mdl-grid__date-widget{
align-items:center;
padding: 0px;
}
.date-widget__invalid .mdl-textfield__input {
border-color: #d50000;
}
.date-widget__invalid .mdl-textfield__label {
color: #d50000;
}
.date-widget__invalid .mdl-textfield__label:after {
background-color: #d50000;
}
.date-widget__invalid .mdl-textfield__error {
visibility: visible !important;
}
.date-widget-button__cell{
margin-top: 0px;
margin-bottom: 0px;
}

View File

@ -1,29 +1,19 @@
<div class="mdl-grid mdl-grid__date-widget {{field.className}}" *ngIf="field?.isVisible" id="data-widget">
<div class="mdl-cell mdl-cell--11-col">
<div class="mdl-textfield mdl-js-textfield date-widget"
[class.date-widget__invalid]="!field.isValid">
<label [attr.for]="field.id">{{field.name}} ({{field.dateDisplayFormat}})<span *ngIf="isRequired()">*</span></label>
<input class="mdl-textfield__input mdl-date__input"
type="text"
[attr.id]="field.id"
[attr.required]="isRequired()"
[value]="field.value"
[(ngModel)]="field.value"
(ngModelChange)="onDateChanged()"
(onOk)="onDateSelected()"
<div class="{{field.className}}" *ngIf="field?.isVisible" id="data-widget" [class.adf-invalid]="!field.isValid || field.validationSummary">
<md-input-container class="adf-date-widget">
<label class="adf-label" [attr.for]="field.id">{{field.name}} ({{field.dateDisplayFormat}})<span *ngIf="isRequired()">*</span></label>
<input mdInput
[id]="field.id"
[mdDatepicker]="datePicker"
[(value)]="field.value"
[required]="isRequired()"
[disabled]="field.readOnly"
[min]="minDate"
[max]="maxDate"
(focusout)="onDateChanged($event.srcElement.value)"
placeholder="{{field.placeholder}}">
<span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span>
</div>
</div>
<div class="mdl-cell mdl-cell--1-col">
<button
[attr.id]="field.id+'-button'"
[disabled]="field.readOnly"
class="mdl-button mdl-js-button mdl-button--icon"
(click)="datePicker.toggle()">
<i class="material-icons">date_range</i>
</button>
</div>
<button class="adf-date-widget-button" mdSuffix [mdDatepickerToggle]="datePicker" [disabled]="field.readOnly"></button>
</md-input-container>
<error-widget [error]="field.validationSummary" ></error-widget>
<md-datepicker #datePicker [touchUi]="true" [startAt]="startAt" (selectedChanged)="onDateChanged($event)"></md-datepicker>
</div>

View File

@ -0,0 +1,33 @@
@import 'theming';
@import '../form';
.adf {
&-date-widget {
.mat-input-suffix {
position: absolute;
margin-top: 42px;
width: 100%;
}
}
&-date-widget-button {
position: relative;
float: right;
}
&-date-input {
padding-top: 5px;
padding-bottom: 5px;
}
&-grid-date-widget {
align-items: center;
padding: 0;
}
&-date-widget-button__cell {
margin-top: 0;
margin-bottom: 0;
}
}

View File

@ -15,11 +15,12 @@
* limitations under the License.
*/
import { ElementRef } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import * as moment from 'moment';
import { CoreModule } from 'ng2-alfresco-core';
import { MATERIAL_MODULE } from '../../../../index';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { ErrorWidgetComponent } from '../error/error.component';
import { EcmModelService } from './../../../services/ecm-model.service';
import { FormService } from './../../../services/form.service';
import { FormFieldModel } from './../core/form-field.model';
@ -30,17 +31,18 @@ describe('DateWidgetComponent', () => {
let widget: DateWidgetComponent;
let fixture: ComponentFixture<DateWidgetComponent>;
let componentHandler;
let nativeElement: any;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
CoreModule.forRoot(),
...MATERIAL_MODULE
],
declarations: [
DateWidgetComponent
DateWidgetComponent,
ErrorWidgetComponent
],
providers: [
FormService,
@ -61,25 +63,31 @@ describe('DateWidgetComponent', () => {
element = fixture.nativeElement;
widget = fixture.componentInstance;
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
});
it('should setup basic date picker settings on init ', () => {
expect(widget.datePicker).toBeUndefined();
widget.ngOnInit();
expect(widget.datePicker).toBeDefined();
});
it('should setup min value for date picker', () => {
let minValue = '13-03-1982';
widget.field = new FormFieldModel(null, {
id: 'date-id',
name: 'date-name',
minValue: minValue
});
widget.ngOnInit();
let expected = moment(minValue, widget.field.dateDisplayFormat);
expect(widget.datePicker._past.isSame(expected)).toBeTruthy();
expect(widget.minDate.isSame(expected)).toBeTruthy();
});
it('should date field be present', () => {
let minValue = '13-03-1982';
widget.field = new FormFieldModel(null, {
minValue: minValue
});
widget.ngOnInit();
expect(element.querySelector('#dropdown-id')).toBeDefined();
});
it('should setup max value for date picker', () => {
@ -90,126 +98,29 @@ describe('DateWidgetComponent', () => {
widget.ngOnInit();
let expected = moment(maxValue, widget.field.dateDisplayFormat);
expect(widget.datePicker._future.isSame(expected)).toBeTruthy();
});
it('should setup default time value for date picker', () => {
let dateValue = '13-03-1982';
widget.field = new FormFieldModel(null, {
type: 'date',
value: '1982-03-13'
});
widget.ngOnInit();
let expected = moment(dateValue, widget.field.dateDisplayFormat);
expect(widget.datePicker.time.isSame(expected)).toBeTruthy();
});
it('should setup trigger element', () => {
widget.elementRef = new ElementRef(nativeElement);
let el = {};
spyOn(nativeElement, 'querySelector').and.returnValue(el);
widget.field = new FormFieldModel(null, {id: 'fake-id'});
widget.ngOnInit();
widget.ngAfterViewChecked();
expect(widget.datePicker.trigger).toBe(el);
});
it('should not setup trigger element', () => {
widget.ngOnInit();
expect(widget.datePicker.trigger).toBeFalsy();
expect(widget.maxDate.isSame(expected)).toBeTruthy();
});
it('should eval visibility on date changed', () => {
spyOn(widget, 'checkVisibility').and.callThrough();
let field = new FormFieldModel(null);
let field = new FormFieldModel(new FormModel(), {
id: 'date-field-id',
name: 'date-name',
value: '9-9-9999',
type: 'date',
readOnly: 'false'
});
widget.field = field;
widget.onDateChanged();
widget.onDateChanged('12/12/2012');
expect(widget.checkVisibility).toHaveBeenCalledWith(field);
});
it('should update picker value on input date changed', () => {
widget.field = new FormFieldModel(new FormModel(), {
type: 'date',
value: '13-03-1982'
});
widget.ngOnInit();
widget.field.value = '31-03-1982';
widget.onDateChanged();
let expected = moment('31-03-1982', widget.field.dateDisplayFormat);
expect(widget.datePicker.time.isSame(expected)).toBeTruthy();
});
it('should update field value on date selected', () => {
widget.elementRef = new ElementRef(nativeElement);
widget.field = new FormFieldModel(new FormModel(), {type: 'date'});
widget.ngOnInit();
let date = '13-3-1982';
widget.datePicker.time = moment(date, widget.field.dateDisplayFormat);
widget.onDateSelected();
expect(widget.field.value).toBe(date);
});
it('should update material textfield on date selected', () => {
spyOn(widget, 'setupMaterialTextField').and.callThrough();
widget.field = new FormFieldModel(new FormModel(), {type: 'date'});
widget.ngOnInit();
widget.datePicker.time = moment();
widget.onDateSelected();
expect(widget.setupMaterialTextField).toHaveBeenCalled();
});
it('should not update material textfield on date selected', () => {
widget.elementRef = undefined;
spyOn(widget, 'setupMaterialTextField').and.callThrough();
widget.field = new FormFieldModel(new FormModel(), {type: 'date'});
widget.ngOnInit();
widget.datePicker.time = moment();
widget.onDateSelected();
expect(widget.setupMaterialTextField).not.toHaveBeenCalled();
});
it('should send field change event when a new date is picked from data picker', (done) => {
spyOn(widget, 'setupMaterialTextField').and.callThrough();
widget.field = new FormFieldModel(new FormModel(), {value: '9-9-9999', type: 'date'});
widget.ngOnInit();
widget.datePicker.time = moment('9-9-9999', widget.field.dateDisplayFormat);
widget.fieldChanged.subscribe((field) => {
expect(field).toBeDefined();
expect(field).not.toBeNull();
expect(field.value).toEqual('9-9-9999');
done();
});
widget.onDateSelected();
});
it('should send field change event when date is changed in input text', (done) => {
spyOn(widget, 'setupMaterialTextField').and.callThrough();
widget.field = new FormFieldModel(null, {value: '9-9-9999', type: 'date'});
widget.ngOnInit();
widget.datePicker.time = moment('9-9-9999', widget.field.dateDisplayFormat);
widget.fieldChanged.subscribe((field) => {
expect(field).toBeDefined();
expect(field).not.toBeNull();
expect(field.value).toEqual('9-9-9999');
done();
});
widget.onDateChanged();
});
describe('template check', () => {
beforeEach(() => {
spyOn(widget, 'setupMaterialTextField').and.stub();
widget.field = new FormFieldModel(new FormModel(), {
id: 'date-field-id',
name: 'date-name',
@ -279,13 +190,13 @@ describe('DateWidgetComponent', () => {
widget.field.readOnly = false;
fixture.detectChanges();
let dateButton = <HTMLButtonElement> element.querySelector('#date-field-id-button');
let dateButton = <HTMLButtonElement> element.querySelector('button');
expect(dateButton.disabled).toBeFalsy();
widget.field.readOnly = true;
fixture.detectChanges();
dateButton = <HTMLButtonElement> element.querySelector('#date-field-id-button');
dateButton = <HTMLButtonElement> element.querySelector('button');
expect(dateButton.disabled).toBeTruthy();
}));
});

View File

@ -17,81 +17,60 @@
/* tslint:disable:component-selector */
import { AfterViewChecked, Component, ElementRef, OnInit } from '@angular/core';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { DateAdapter, MD_DATE_FORMATS } from '@angular/material';
import * as moment from 'moment';
import { Moment } from 'moment';
import { MOMENT_DATE_FORMATS, MomentDateAdapter } from 'ng2-alfresco-core';
import { FormService } from './../../../services/form.service';
import { baseHost , WidgetComponent } from './../widget.component';
declare let mdDateTimePicker: any;
declare var componentHandler: any;
@Component({
selector: 'date-widget',
providers: [
{provide: DateAdapter, useClass: MomentDateAdapter},
{provide: MD_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS}],
templateUrl: './date.widget.html',
styleUrls: ['./date.widget.css'],
host: baseHost
styleUrls: ['./date.widget.scss'],
host: baseHost,
encapsulation: ViewEncapsulation.None
})
export class DateWidgetComponent extends WidgetComponent implements OnInit, AfterViewChecked {
export class DateWidgetComponent extends WidgetComponent implements OnInit {
datePicker: any;
minDate: Moment;
maxDate: Moment;
constructor(public formService: FormService,
public elementRef: ElementRef) {
constructor(public formService: FormService, public dateAdapter: DateAdapter<Moment>) {
super(formService);
}
ngOnInit() {
let settings: any = {
type: 'date',
past: moment().subtract(100, 'years'),
future: moment().add(100, 'years')
};
let momentDateAdapter = <MomentDateAdapter> this.dateAdapter;
momentDateAdapter.overrideDisplyaFormat = this.field.dateDisplayFormat;
if (this.field) {
if (this.field.minValue) {
settings.past = moment(this.field.minValue, this.field.dateDisplayFormat);
this.minDate = moment(this.field.minValue, this.field.dateDisplayFormat);
}
if (this.field.maxValue) {
settings.future = moment(this.field.maxValue, this.field.dateDisplayFormat);
this.maxDate = moment(this.field.maxValue, this.field.dateDisplayFormat);
}
if (this.field.value) {
settings.init = moment(this.field.value, this.field.dateDisplayFormat);
}
}
this.datePicker = new mdDateTimePicker.default(settings);
}
onDateChanged(newDateValue) {
this.field.validationSummary = '';
ngAfterViewChecked() {
if (this.elementRef) {
let dataLocator = '#' + this.field.id;
this.datePicker.trigger = this.elementRef.nativeElement.querySelector(dataLocator);
if (newDateValue) {
let momentDate = moment(newDateValue, this.field.dateDisplayFormat, true);
if (!momentDate.isValid()) {
this.field.validationSummary = this.field.dateDisplayFormat;
}else {
this.field.value = newDateValue;
}
}
onDateChanged() {
if (this.field.value) {
let value = moment(this.field.value, this.field.dateDisplayFormat);
if (!value.isValid()) {
value = moment();
}
this.datePicker.time = value;
}
this.checkVisibility(this.field);
}
onDateSelected() {
let newValue = this.datePicker.time.format(this.field.dateDisplayFormat);
this.field.value = newValue;
this.checkVisibility(this.field);
if (this.elementRef) {
this.setupMaterialTextField(this.elementRef, componentHandler, newValue);
}
}
}

View File

@ -1,3 +1,3 @@
<div class="display-text-widget {{field.className}}">
<div class="adf-display-text-widget {{field.className}}">
<span>{{field.value}}</span>
</div>

View File

@ -0,0 +1 @@
.adf-display-text-widget {}

View File

@ -17,15 +17,16 @@
/* tslint:disable:component-selector */
import { Component } from '@angular/core';
import { Component, ViewEncapsulation } from '@angular/core';
import { FormService } from './../../../services/form.service';
import { baseHost , WidgetComponent } from './../widget.component';
@Component({
selector: 'display-text-widget',
templateUrl: './display-text.widget.html',
styleUrls: ['./display-text.widget.css'],
host: baseHost
styleUrls: ['./display-text.widget.scss'],
host: baseHost,
encapsulation: ViewEncapsulation.None
})
export class DisplayTextWidgetComponentComponent extends WidgetComponent {

View File

@ -1,23 +0,0 @@
.display-value-widget {
width: 100%;
}
.display-value-widget__dynamic-table {
padding: 8px;
}
.display-value-widget__dynamic-table .is-disabled {
background-color: transparent;
border-bottom: 1px dotted rgba(0, 0, 0, .12);
color: rgba(0, 0, 0, .26);
}
.display-value-widget__dynamic-table table {
width: 100%;
}
.display-value-dynamic-table-widget__table-container {
overflow-y: auto;
width: 100%;
}

View File

@ -1,70 +0,0 @@
<div [ngSwitch]="fieldType" class="display-value-widget {{field.className}}" *ngIf="field?.isVisible">
<div *ngSwitchCase="'boolean'">
<label class="mdl-checkbox mdl-js-checkbox" [attr.for]="field.id" >
<input type="checkbox"
[attr.id]="field.id"
[checked]="value"
[(ngModel)]="value"
class="mdl-checkbox__input"
disabled>
<span class="mdl-checkbox__label">{{field.name}}</span>
</label>
</div>
<div *ngSwitchCase="'text'"
class="mdl-textfield mdl-js-textfield text-widget">
<label [attr.for]="field.id">{{field.name}}</label>
<input
class="mdl-textfield__input"
type="text"
[attr.id]="field.id"
[value]="value"
disabled>
</div>
<div *ngSwitchCase="'multi-line-text'"
class="mdl-textfield mdl-js-textfield multiline-text-widget">
<textarea class="mdl-textfield__input"
type="text"
rows="3"
[value]="value"
[attr.id]="field.id"
disabled>
</textarea>
<label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label>
</div>
<div *ngSwitchCase="'hyperlink'" class="hyperlink-widget">
<div>
<div>
<span>{{field.name}}</span>
</div>
<div>
<a [href]="linkUrl" target="_blank" rel="nofollow">{{linkText}}</a>
</div>
</div>
</div>
<div *ngSwitchCase="'dynamic-table'">
<dynamic-table-widget [field]="field" [readOnly]="!tableEditable"></dynamic-table-widget>
</div>
<div *ngSwitchCase="'upload'">
<div *ngIf="hasFile" class="mdl-grid">
<div *ngFor="let file of field.value" class="mdl-cell mdl-cell--6-col">
<adf-content [id]="file.id" [showDocumentContent]="showDocumentContent"></adf-content>
</div>
</div>
</div>
<div *ngSwitchCase="'document'">
<div *ngIf="hasFile">
<adf-content [id]="id" [showDocumentContent]="true"></adf-content>
</div>
</div>
<div *ngSwitchDefault
class="mdl-textfield mdl-js-textfield text-widget is-disabled is-dirty is-upgraded">
<label [attr.for]="field.id">{{field.name}}</label>
<input
class="mdl-textfield__input"
type="text"
[attr.id]="field.id"
[value]="value"
disabled>
</div>
</div>

View File

@ -1,735 +0,0 @@
/*!
* @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 { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
import { ActivitiContentComponent } from '../../activiti-content.component';
import { FormFieldTypes } from '../core/form-field-types';
import { FormModel } from '../core/form.model';
import { EcmModelService } from './../../../services/ecm-model.service';
import { FormService } from './../../../services/form.service';
import { FormFieldModel } from './../core/form-field.model';
import { DisplayValueWidgetComponent } from './display-value.widget';
describe('DisplayValueWidgetComponent', () => {
let widget: DisplayValueWidgetComponent;
let fixture: ComponentFixture<DisplayValueWidgetComponent>;
let element: HTMLElement;
let formService: FormService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
declarations: [
DisplayValueWidgetComponent,
ActivitiContentComponent
],
providers: [
FormService,
EcmModelService,
WidgetVisibilityService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DisplayValueWidgetComponent);
formService = TestBed.get(FormService);
element = fixture.nativeElement;
widget = fixture.componentInstance;
});
it('should require field to setup default value', () => {
widget.field = null;
widget.ngOnInit();
expect(widget.value).toBeUndefined();
});
it('should take field value on init', () => {
let value = '<value>';
widget.field = new FormFieldModel(null, { value: value });
widget.field.params = null;
widget.ngOnInit();
expect(widget.value).toBe(value);
});
it('should setup [BOOLEAN] field', () => {
expect(widget.value).toBeUndefined();
// test TRUE value
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: 'true',
params: {
field: {
type: FormFieldTypes.BOOLEAN
}
}
});
widget.ngOnInit();
expect(widget.value).toBeTruthy();
// test FALSE value
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: 'false',
params: {
field: {
type: FormFieldTypes.BOOLEAN
}
}
});
widget.ngOnInit();
expect(widget.value).toBeFalsy();
});
it('should setup [FUNCTIONAL-GROUP] field', () => {
let groupName: '<group>';
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: {
name: groupName
},
params: {
field: {
type: FormFieldTypes.FUNCTIONAL_GROUP
}
}
});
widget.ngOnInit();
expect(widget.value).toBe(groupName);
});
it('should not setup [FUNCTIONAL-GROUP] field when missing value', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
params: {
field: {
type: FormFieldTypes.FUNCTIONAL_GROUP
}
}
});
widget.ngOnInit();
expect(widget.value).toBeNull();
});
it('should setup [PEOPLE] field', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: {
firstName: 'John',
lastName: 'Doe'
},
params: {
field: {
type: FormFieldTypes.PEOPLE
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('John Doe');
});
it('should not setup [PEOPLE] field whem missing value', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
params: {
field: {
type: FormFieldTypes.PEOPLE
}
}
});
widget.ngOnInit();
expect(widget.value).toBeUndefined();
});
it('should setup [UPLOAD] field', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: [
{ name: 'file1' }
],
params: {
field: {
type: FormFieldTypes.UPLOAD
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('file1');
});
it('should not setup [UPLOAD] field when missing value', () => {
widget.field = new FormFieldModel(null, {
value: null,
params: {
field: {
type: FormFieldTypes.UPLOAD
}
}
});
widget.ngOnInit();
expect(widget.value).toBeNull();
});
it('should not setup [UPLOAD] field when empty value', () => {
widget.field = new FormFieldModel(null, {
value: [],
params: {
field: {
type: FormFieldTypes.UPLOAD
}
}
});
widget.ngOnInit();
expect(widget.value).toBeNull();
});
it('should setup [TYPEAHEAD] field', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
params: {
field: {
type: FormFieldTypes.TYPEAHEAD
}
}
});
spyOn(widget, 'loadRestFieldValue').and.stub();
widget.ngOnInit();
expect(widget.loadRestFieldValue).toHaveBeenCalled();
});
it('should setup [DROPDOWN] field with REST config', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: 'http://test.com',
params: {
field: {
type: FormFieldTypes.DROPDOWN
}
}
});
spyOn(widget, 'loadRestFieldValue').and.stub();
widget.ngOnInit();
expect(widget.loadRestFieldValue).toHaveBeenCalled();
});
it('should setup [RADIO_BUTTONS] field', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: null,
params: {
field: {
type: FormFieldTypes.RADIO_BUTTONS
}
}
});
spyOn(widget, 'loadRadioButtonValue').and.stub();
widget.ngOnInit();
expect(widget.loadRadioButtonValue).toHaveBeenCalled();
});
it('should setup [RADIO_BUTTONS] value by options', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: null,
value: '2',
options: [
{ id: '1', name: 'option 1' },
{ id: '2', name: 'option 2' }
],
params: {
field: {
type: FormFieldTypes.RADIO_BUTTONS
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('option 2');
});
it('should not setup [RADIO_BUTTONS] value with missing option', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: null,
value: '100',
options: [
{ id: '1', name: 'option 1' },
{ id: '2', name: 'option 2' }
],
params: {
field: {
type: FormFieldTypes.RADIO_BUTTONS
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('100');
});
it('should not setup [RADIO_BUTTONS] when missing options', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: null,
value: '100',
options: null,
params: {
field: {
type: FormFieldTypes.RADIO_BUTTONS
}
}
});
widget.field.options = null;
widget.ngOnInit();
expect(widget.value).toBe('100');
});
it('should setup [RADIO_BUTTONS] field with REST config', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: '<url>',
params: {
field: {
type: FormFieldTypes.RADIO_BUTTONS
}
}
});
spyOn(widget, 'loadRestFieldValue').and.stub();
widget.ngOnInit();
expect(widget.loadRestFieldValue).toHaveBeenCalled();
});
it('should setup rest field values with REST options', () => {
spyOn(formService, 'getRestFieldValues').and.returnValue(
Observable.create(observer => {
observer.next([
{ id: '1', name: 'option 1' },
{ id: '2', name: 'option 2' }
]);
observer.complete();
})
);
let form = new FormModel({ taskId: '<id>' });
widget.field = new FormFieldModel(form, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: '<url>',
value: '2',
options: [
{ id: '1', name: 'option 1' },
{ id: '2', name: 'option 2' }
],
params: {
field: {
type: FormFieldTypes.RADIO_BUTTONS
}
}
});
widget.ngOnInit();
expect(formService.getRestFieldValues).toHaveBeenCalled();
expect(widget.value).toBe('option 2');
});
it('should not setup rest field values with missing REST option', () => {
spyOn(formService, 'getRestFieldValues').and.returnValue(
Observable.create(observer => {
observer.next([
{ id: '1', name: 'option 1' },
{ id: '2', name: 'option 2' }
]);
observer.complete();
})
);
let form = new FormModel({ taskId: '<id>' });
widget.field = new FormFieldModel(form, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: '<url>',
value: '100',
params: {
field: {
type: FormFieldTypes.RADIO_BUTTONS
}
}
});
widget.ngOnInit();
expect(formService.getRestFieldValues).toHaveBeenCalled();
expect(widget.value).toBe('100');
});
it('should not setup rest field values with no REST response', () => {
spyOn(formService, 'getRestFieldValues').and.returnValue(
Observable.create(observer => {
observer.next(null);
observer.complete();
})
);
let form = new FormModel({ taskId: '<id>' });
widget.field = new FormFieldModel(form, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: '<url>',
value: '100',
params: {
field: {
type: FormFieldTypes.RADIO_BUTTONS
}
}
});
widget.ngOnInit();
expect(formService.getRestFieldValues).toHaveBeenCalled();
expect(widget.value).toBe('100');
});
it('should handle rest error', (done) => {
const error = 'ERROR';
spyOn(formService, 'getRestFieldValues').and.returnValue(
Observable.throw(error)
);
let form = new FormModel({ taskId: '<id>' });
widget.field = new FormFieldModel(form, {
type: FormFieldTypes.DISPLAY_VALUE,
restUrl: '<url>',
value: '100',
params: {
field: {
type: FormFieldTypes.RADIO_BUTTONS
}
}
});
widget.error.subscribe(() => {
expect(formService.getRestFieldValues).toHaveBeenCalled();
expect(widget.value).toBe('100');
done();
});
widget.ngOnInit();
});
it('should setup [DATE] field with valid date', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: '1982-03-13T00:00:00.000Z',
params: {
field: {
type: FormFieldTypes.DATE
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('13-3-1982');
});
it('should setup [DATE] field with invalid date', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: '<invalid value>',
params: {
field: {
type: FormFieldTypes.DATE
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('<invalid value>');
});
it('should show the [DATE] field with the default format (D-M-YYYY) if the display format is missing', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: '1982-03-13T00:00:00.000Z',
params: {
field: {
type: FormFieldTypes.DATE
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('13-3-1982');
});
it('should show the [DATE] field with the custom display format (MM-DD-YYYY)', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: '1982-03-13T00:00:00.000Z',
dateDisplayFormat: 'MM-DD-YYYY',
params: {
field: {
type: FormFieldTypes.DATE
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('03-13-1982');
});
it('should show the [DATE] field with the custom display format (MM-YY-DD)', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: '1982-03-13T00:00:00.000Z',
dateDisplayFormat: 'MM-YY-DD',
params: {
field: {
type: FormFieldTypes.DATE
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('03-82-13');
});
it('should show the [DATE] field with the custom display format (DD-MM-YYYY)', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: '1982-03-13T00:00:00.000Z',
dateDisplayFormat: 'DD-MM-YYYY',
params: {
field: {
type: FormFieldTypes.DATE
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('13-03-1982');
});
it('should not setup [DATE] field when missing value', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
params: {
field: {
type: FormFieldTypes.DATE
}
}
});
widget.ngOnInit();
expect(widget.value).toBeUndefined();
});
it('should setup [AMOUNT] field with default currency', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: 11,
params: {
field: {
type: FormFieldTypes.AMOUNT
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('$ 11');
});
it('should setup [AMOUNT] field with custom currency', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: 12.6,
currency: 'UAH',
params: {
field: {
type: FormFieldTypes.AMOUNT
}
}
});
widget.ngOnInit();
expect(widget.value).toBe('UAH 12.6');
});
it('should not setup [AMOUNT] field when missing value', () => {
widget.field = new FormFieldModel(null, {
params: {
field: {
type: FormFieldTypes.AMOUNT
}
}
});
widget.ngOnInit();
expect(widget.value).toBeUndefined();
});
it('should setup [HYPERLINK] field', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
hyperlinkUrl: 'www.some-url.com',
displayText: 'Custom URL',
params: {
field: {
type: FormFieldTypes.HYPERLINK
}
}
});
widget.ngOnInit();
expect(widget.linkUrl).toBe(`http://${widget.field.hyperlinkUrl}`);
expect(widget.linkText).toBe(widget.field.displayText);
});
it('should take default value for unknown field type', () => {
const value = '<value>';
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: value,
params: {
field: {
type: '<unknown type>'
}
}
});
widget.ngOnInit();
expect(widget.value).toBe(value);
});
it('should take default value when missing params', () => {
const value = '<value>';
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: value
});
widget.ngOnInit();
expect(widget.value).toBe(value);
});
it('should take default value when missing enclosed field type', () => {
const value = '<value>';
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.DISPLAY_VALUE,
value: value,
params: {
field: {}
}
});
widget.ngOnInit();
expect(widget.value).toBe(value);
});
describe('UI check', () => {
let componentHandler;
beforeEach(async(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
}));
beforeEach(() => {
spyOn(widget, 'setupMaterialTextField').and.stub();
});
afterEach(() => {
fixture.destroy();
TestBed.resetTestingModule();
});
it('should show the checkbox on when [BOOLEAN] field is true', async(() => {
widget.field = new FormFieldModel(null, {
id: 'fake-checkbox-id',
type: FormFieldTypes.DISPLAY_VALUE,
value: 'true',
params: {
field: {
type: FormFieldTypes.BOOLEAN
}
}
});
fixture.detectChanges();
fixture.whenStable()
.then(() => {
let elWidget: any = element.querySelector('#fake-checkbox-id');
expect(elWidget).toBeDefined();
expect(elWidget.checked).toBeTruthy();
});
}));
it('should show the checkbox off when [BOOLEAN] field is false', async(() => {
widget.field = new FormFieldModel(null, {
id: 'fake-checkbox-id',
type: FormFieldTypes.DISPLAY_VALUE,
value: 'false',
params: {
field: {
type: FormFieldTypes.BOOLEAN
}
}
});
fixture.detectChanges();
fixture.whenStable()
.then(() => {
let elWidget: any = element.querySelector('#fake-checkbox-id');
expect(elWidget).toBeDefined();
expect(elWidget.checked).toBeFalsy();
});
}));
it('should show the dropdown value taken from options when field has options', async(() => {
widget.field = new FormFieldModel(null, {
id: 'fake-dropdown-id',
type: FormFieldTypes.DISPLAY_VALUE,
value: '1',
options: [
{ id: '1', name: 'Option 1' },
{ id: '2', name: 'Option 2' }
],
params: {
field: {
type: FormFieldTypes.DROPDOWN
}
}
});
fixture.whenStable()
.then(() => {
fixture.detectChanges();
let elWidget: any = element.querySelector('#fake-dropdown-id');
expect(elWidget).toBeDefined();
expect(elWidget.value).toBe('Option 1');
});
}));
it('should show the dropdown value taken from value when field has no options', async(() => {
widget.field = new FormFieldModel(null, {
id: 'fake-dropdown-id',
type: FormFieldTypes.DISPLAY_VALUE,
value: 'FAKE',
params: {
field: {
type: FormFieldTypes.DROPDOWN
}
}
});
fixture.whenStable()
.then(() => {
fixture.detectChanges();
let elWidget: any = element.querySelector('#fake-dropdown-id');
expect(elWidget).toBeDefined();
expect(elWidget.value).toBe('FAKE');
});
}));
});
});

View File

@ -1,229 +0,0 @@
/*!
* @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.
*/
/* tslint:disable:component-selector */
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import * as moment from 'moment';
import { FormService } from '../../../services/form.service';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
import { FormFieldTypes } from '../core/form-field-types';
import { NumberFieldValidator } from '../core/form-field-validator';
import { FormFieldOption } from './../core/form-field-option';
import { baseHost , WidgetComponent } from './../widget.component';
@Component({
selector: 'display-value-widget',
templateUrl: './display-value.widget.html',
styleUrls: ['./display-value.widget.css'],
host: baseHost
})
export class DisplayValueWidgetComponent extends WidgetComponent implements OnInit {
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
value: any;
fieldType: string;
id: any;
// hyperlink
linkUrl: string;
linkText: string;
// dynamic table
tableEditable = false;
// upload/attach
hasFile: boolean = false;
showDocumentContent: boolean = true;
constructor(public formService: FormService,
private visibilityService: WidgetVisibilityService) {
super(formService);
}
ngOnInit() {
if (this.field) {
this.value = this.field.value;
this.visibilityService.refreshEntityVisibility(this.field);
if (this.field.params) {
if (this.field.params['showDocumentContent'] !== undefined) {
this.showDocumentContent = !!this.field.params['showDocumentContent'];
}
if (this.field.params['tableEditable'] !== undefined) {
this.tableEditable = !!this.field.params['tableEditable'];
}
let originalField = this.field.params['field'];
if (originalField && originalField.type) {
this.fieldType = originalField.type;
switch (originalField.type) {
case FormFieldTypes.BOOLEAN:
this.value = this.field.value === 'true' ? true : false;
break;
case FormFieldTypes.FUNCTIONAL_GROUP:
if (this.field.value) {
this.value = this.field.value.name;
} else {
this.value = null;
}
break;
case FormFieldTypes.PEOPLE:
let model = this.field.value;
if (model) {
let displayName = `${model.firstName} ${model.lastName}`;
this.value = displayName.trim();
}
break;
case FormFieldTypes.UPLOAD:
let files = this.field.value || [];
if (files.length > 0) {
this.value = decodeURI(files[0].name);
this.id = files[0].id;
this.hasFile = true;
} else {
this.value = null;
this.hasFile = false;
}
break;
case FormFieldTypes.DOCUMENT:
const file = this.field.value;
if (file) {
this.value = decodeURI(file.name);
this.id = file.id;
this.hasFile = true;
} else {
this.value = null;
this.hasFile = false;
}
break;
case FormFieldTypes.TYPEAHEAD:
this.loadRestFieldValue();
break;
case FormFieldTypes.DROPDOWN:
if (this.field.restUrl) {
this.loadRestFieldValue();
} else {
this.value = this.field.hasOptions() ? this.field.getOptionName() : this.value;
}
break;
case FormFieldTypes.RADIO_BUTTONS:
if (this.field.restUrl) {
this.loadRestFieldValue();
} else {
this.loadRadioButtonValue();
}
break;
case FormFieldTypes.DATE:
if (this.value) {
let dateValue;
if (NumberFieldValidator.isNumber(this.value)) {
dateValue = moment(this.value);
} else {
dateValue = moment(this.value.split('T')[0], 'YYYY-M-D');
}
if (dateValue && dateValue.isValid()) {
const displayFormat = this.field.dateDisplayFormat || this.field.defaultDateFormat;
this.value = dateValue.format(displayFormat);
}
}
break;
case FormFieldTypes.AMOUNT:
if (this.value) {
let currency = this.field.currency || '$';
this.value = `${currency} ${this.field.value}`;
}
break;
case FormFieldTypes.HYPERLINK:
this.linkUrl = this.getHyperlinkUrl(this.field);
this.linkText = this.getHyperlinkText(this.field);
break;
default:
this.value = this.field.value;
break;
}
}
}
this.visibilityService.refreshVisibility(this.field.form);
}
}
loadRadioButtonValue() {
let options = this.field.options || [];
let toSelect = options.find(item => item.id === this.field.value);
if (toSelect) {
this.value = toSelect.name;
} else {
this.value = this.field.value;
}
}
loadRestFieldValue() {
if (this.field.form.taskId) {
this.getValuesByTaskId();
} else {
this.getValuesByProcessDefinitionId();
}
}
getValuesByProcessDefinitionId() {
this.formService
.getRestFieldValuesByProcessId(
this.field.form.processDefinitionId,
this.field.id
)
.subscribe(
(result: FormFieldOption[]) => {
let options = result || [];
let toSelect = options.find(item => item.id === this.field.value);
this.field.options = options;
if (toSelect) {
this.value = toSelect.name;
} else {
this.value = this.field.value;
}
this.visibilityService.refreshVisibility(this.field.form);
},
(error) => {
this.value = this.field.value;
}
);
}
getValuesByTaskId() {
this.formService
.getRestFieldValues(this.field.form.taskId, this.field.id)
.subscribe(
(result: FormFieldOption[]) => {
let options = result || [];
let toSelect = options.find(item => item.id === this.field.value);
this.field.options = options;
if (toSelect) {
this.value = toSelect.name;
} else {
this.value = this.field.value;
}
this.visibilityService.refreshVisibility(this.field.form);
},
(error) => {
this.error.emit(error);
this.value = this.field.value;
}
);
}
}

View File

@ -1,27 +0,0 @@
.dropdown-widget {
width: 100%;
padding-top: 20px;
padding-bottom: 20px;
}
.dropdown-widget__select {
width: 100%;
margin-top: 5px;
margin-bottom: 5px;
}
.dropdown-widget__invalid .dropdown-widget__select {
border-color: #d50000;
}
.dropdown-widget__invalid .dropdown-widget__label {
color: #d50000;
}
.dropdown-widget__invalid .dropdown-widget__label:after {
background-color: #d50000;
}
.dropdown-widget__invalid .mdl-textfield__error {
visibility: visible !important;
}

View File

@ -1,12 +1,14 @@
<div class="dropdown-widget {{field.className}}"
[class.dropdown-widget__invalid]="!field.isValid" *ngIf="field?.isVisible">
<label class="dropdown-widget__label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<select class="dropdown-widget__select"
[attr.id]="field.id"
<div class="adf-dropdown-widget {{field.className}}"
[class.adf-invalid]="!field.isValid" [class.adf-readonly]="field.readOnly" *ngIf="field?.isVisible">
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<md-select class="adf-select"
[id]="field.id"
[(ngModel)]="field.value"
[disabled]="field.readOnly"
(ngModelChange)="checkVisibility(field)">
<option *ngFor="let opt of field.options" [value]="getOptionValue(opt, field.value)" [id]="opt.id">{{opt.name}}</option>
</select>
<span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span>
<md-option *ngFor="let opt of field.options"
[value]="getOptionValue(opt, field.value)"
[id]="opt.id">{{opt.name}}</md-option>
</md-select>
<error-widget [error]="field.validationSummary" ></error-widget>
</div>

View File

@ -0,0 +1,14 @@
@import 'theming';
@import '../form';
.adf {
&-dropdown-widget {
width: 100%;
padding-top: 16px;
}
&-select {
width: 100%;
}
}

View File

@ -16,11 +16,14 @@
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CoreModule } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { MATERIAL_MODULE } from '../../../../index';
import { EcmModelService } from '../../../services/ecm-model.service';
import { FormService } from '../../../services/form.service';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
import { ErrorWidgetComponent } from '../error/error.component';
import { FormFieldOption } from './../core/form-field-option';
import { FormFieldModel } from './../core/form-field.model';
import { FormModel } from './../core/form.model';
@ -28,16 +31,40 @@ import { DropdownWidgetComponent } from './dropdown.widget';
describe('DropdownWidgetComponent', () => {
function openSelect() {
const dropdown = fixture.debugElement.query(By.css('[class="mat-select-trigger"]'));
dropdown.triggerEventHandler('click', null);
fixture.detectChanges();
}
let formService: FormService;
let widget: DropdownWidgetComponent;
let visibilityService: WidgetVisibilityService;
let fixture: ComponentFixture<DropdownWidgetComponent>;
let element: HTMLElement;
beforeEach(() => {
formService = new FormService(null, null, null);
visibilityService = new WidgetVisibilityService(null, null);
widget = new DropdownWidgetComponent(formService, visibilityService, null);
let fakeOptionList: FormFieldOption[] = [
{id: 'opt_1', name: 'option_1'},
{id: 'opt_2', name: 'option_2'},
{id: 'opt_3', name: 'option_3'}];
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
...MATERIAL_MODULE
],
declarations: [DropdownWidgetComponent, ErrorWidgetComponent],
providers: [FormService, EcmModelService, WidgetVisibilityService]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(DropdownWidgetComponent);
widget = fixture.componentInstance;
element = fixture.nativeElement;
formService = TestBed.get(FormService);
visibilityService = TestBed.get(WidgetVisibilityService);
widget.field = new FormFieldModel(new FormModel());
});
}));
it('should require field with restUrl', () => {
spyOn(formService, 'getRestFieldValues').and.stub();
@ -76,12 +103,12 @@ describe('DropdownWidgetComponent', () => {
it('should preserve empty option when loading fields', () => {
let restFieldValue: FormFieldOption = <FormFieldOption> {id: '1', name: 'Option1'};
spyOn(formService, 'getRestFieldValues').and.returnValue(
Observable.create(observer => {
spyOn(formService, 'getRestFieldValues').and.callFake(() => {
return Observable.create(observer => {
observer.next([restFieldValue]);
observer.complete();
})
);
});
});
let form = new FormModel({taskId: '<id>'});
let emptyOption: FormFieldOption = <FormFieldOption> {id: 'empty', name: 'Empty'};
@ -100,87 +127,69 @@ describe('DropdownWidgetComponent', () => {
});
describe('when template is ready', () => {
let dropdownWidgetComponent: DropdownWidgetComponent;
let fixture: ComponentFixture<DropdownWidgetComponent>;
let element: HTMLElement;
let componentHandler;
let stubFormService;
let fakeOptionList: FormFieldOption[] = [{
id: 'opt_1',
name: 'option_1'
}, {
id: 'opt_2',
name: 'option_2'
}, { id: 'opt_3', name: 'option_3' }];
beforeEach(async(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [DropdownWidgetComponent],
providers: [FormService, EcmModelService, WidgetVisibilityService]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(DropdownWidgetComponent);
dropdownWidgetComponent = fixture.componentInstance;
element = fixture.nativeElement;
});
}));
describe('and dropdown is populated via taskId', () => {
beforeEach(async(() => {
stubFormService = fixture.debugElement.injector.get(FormService);
visibilityService = fixture.debugElement.injector.get(WidgetVisibilityService);
spyOn(visibilityService, 'refreshVisibility').and.stub();
spyOn(stubFormService, 'getRestFieldValues').and.returnValue(Observable.of(fakeOptionList));
dropdownWidgetComponent.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
spyOn(formService, 'getRestFieldValues').and.callFake(() => {
return Observable.of(fakeOptionList);
});
widget.field = new FormFieldModel(new FormModel({taskId: 'fake-task-id'}), {
id: 'dropdown-id',
name: 'date-name',
type: 'dropdown',
readOnly: 'false',
restUrl: 'fake-rest-url'
});
dropdownWidgetComponent.field.emptyOption = { id: 'empty', name: 'Choose one...' };
dropdownWidgetComponent.field.isVisible = true;
widget.field.emptyOption = {id: 'empty', name: 'Choose one...'};
widget.field.isVisible = true;
fixture.detectChanges();
}));
it('should show visible dropdown widget', async(() => {
expect(element.querySelector('#dropdown-id')).toBeDefined();
expect(element.querySelector('#dropdown-id')).not.toBeNull();
expect(element.querySelector('#opt_1')).not.toBeNull();
expect(element.querySelector('#opt_2')).not.toBeNull();
expect(element.querySelector('#opt_3')).not.toBeNull();
openSelect();
const optOne = fixture.debugElement.queryAll(By.css('[id="md-option-1"]'));
const optTwo = fixture.debugElement.queryAll(By.css('[id="md-option-2"]'));
const optThree = fixture.debugElement.queryAll(By.css('[id="md-option-3"]'));
expect(optOne).not.toBeNull();
expect(optTwo).not.toBeNull();
expect(optThree).not.toBeNull();
}));
it('should select the default value when an option is chosen as default', async(() => {
dropdownWidgetComponent.field.value = 'option_2';
widget.field.value = 'option_2';
widget.ngOnInit();
fixture.detectChanges();
fixture.whenStable()
.then(() => {
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
expect(dropDownElement).not.toBeNull();
expect(element.querySelector('#opt_2')).not.toBeNull();
expect(dropDownElement.value).toBe('option_2');
expect(dropDownElement.selectedOptions[0].textContent).toBe('option_2');
let dropDownElement: any = element.querySelector('#dropdown-id');
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('option_2');
expect(dropDownElement.attributes['ng-reflect-model'].textContent).toBe('option_2');
});
}));
it('should select the empty value when no default is chosen', async(() => {
dropdownWidgetComponent.field.value = 'empty';
widget.field.value = 'empty';
widget.ngOnInit();
fixture.detectChanges();
openSelect();
fixture.whenStable()
.then(() => {
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
expect(dropDownElement).not.toBeNull();
expect(dropDownElement.value).toBe('empty');
expect(dropDownElement.selectedOptions[0].textContent).toBe('Choose one...');
let dropDownElement: any = element.querySelector('#dropdown-id');
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty');
});
}));
it('should be not visibile when isVisible is false', async(() => {
dropdownWidgetComponent.field.isVisible = false;
it('should be not visible when isVisible is false', async(() => {
widget.field.isVisible = false;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
@ -189,11 +198,11 @@ describe('DropdownWidgetComponent', () => {
});
}));
it('should became visibile when isVisible is true', async(() => {
dropdownWidgetComponent.field.isVisible = false;
it('should became visible when isVisible is true', async(() => {
widget.field.isVisible = false;
fixture.detectChanges();
expect(element.querySelector('#dropdown-id')).toBeNull();
dropdownWidgetComponent.field.isVisible = true;
widget.field.isVisible = true;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
@ -205,57 +214,65 @@ describe('DropdownWidgetComponent', () => {
describe('and dropdown is populated via processDefinitionId', () => {
beforeEach(async(() => {
stubFormService = fixture.debugElement.injector.get(FormService);
visibilityService = fixture.debugElement.injector.get(WidgetVisibilityService);
spyOn(visibilityService, 'refreshVisibility').and.stub();
spyOn(stubFormService, 'getRestFieldValuesByProcessId').and.returnValue(Observable.of(fakeOptionList));
dropdownWidgetComponent.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), {
spyOn(formService, 'getRestFieldValuesByProcessId').and.callFake(() => {
return Observable.of(fakeOptionList);
});
widget.field = new FormFieldModel(new FormModel({processDefinitionId: 'fake-process-id'}), {
id: 'dropdown-id',
name: 'date-name',
type: 'dropdown',
readOnly: 'false',
restUrl: 'fake-rest-url'
});
dropdownWidgetComponent.field.emptyOption = { id: 'empty', name: 'Choose one...' };
dropdownWidgetComponent.field.isVisible = true;
widget.field.emptyOption = {id: 'empty', name: 'Choose one...'};
widget.field.isVisible = true;
fixture.detectChanges();
}));
it('should show visible dropdown widget', async(() => {
expect(element.querySelector('#dropdown-id')).toBeDefined();
expect(element.querySelector('#dropdown-id')).not.toBeNull();
expect(element.querySelector('#opt_1')).not.toBeNull();
expect(element.querySelector('#opt_2')).not.toBeNull();
expect(element.querySelector('#opt_3')).not.toBeNull();
openSelect();
const optOne = fixture.debugElement.queryAll(By.css('[id="md-option-1"]'));
const optTwo = fixture.debugElement.queryAll(By.css('[id="md-option-2"]'));
const optThree = fixture.debugElement.queryAll(By.css('[id="md-option-3"]'));
expect(optOne).not.toBeNull();
expect(optTwo).not.toBeNull();
expect(optThree).not.toBeNull();
}));
it('should select the default value when an option is chosen as default', async(() => {
dropdownWidgetComponent.field.value = 'option_2';
widget.field.value = 'option_2';
widget.ngOnInit();
fixture.detectChanges();
fixture.whenStable()
.then(() => {
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
expect(dropDownElement).not.toBeNull();
expect(element.querySelector('#opt_2')).not.toBeNull();
expect(dropDownElement.value).toBe('option_2');
expect(dropDownElement.selectedOptions[0].textContent).toBe('option_2');
let dropDownElement: any = element.querySelector('#dropdown-id');
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('option_2');
expect(dropDownElement.attributes['ng-reflect-model'].textContent).toBe('option_2');
});
}));
it('should select the empty value when no default is chosen', async(() => {
dropdownWidgetComponent.field.value = 'empty';
widget.field.value = 'empty';
widget.ngOnInit();
fixture.detectChanges();
openSelect();
fixture.whenStable()
.then(() => {
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
expect(dropDownElement).not.toBeNull();
expect(dropDownElement.value).toBe('empty');
expect(dropDownElement.selectedOptions[0].textContent).toBe('Choose one...');
let dropDownElement: any = element.querySelector('#dropdown-id');
expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty');
});
}));
it('should be disabled when the field is readonly', async(() => {
dropdownWidgetComponent.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), {
widget.field = new FormFieldModel(new FormModel({processDefinitionId: 'fake-process-id'}), {
id: 'dropdown-id',
name: 'date-name',
type: 'dropdown',
@ -268,7 +285,7 @@ describe('DropdownWidgetComponent', () => {
.then(() => {
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
expect(dropDownElement).not.toBeNull();
expect(dropDownElement.disabled).toBeTruthy();
expect(dropDownElement.getAttribute('aria-disabled')).toBe('true');
});
}));
});

View File

@ -17,7 +17,7 @@
/* tslint:disable:component-selector */
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { LogService } from 'ng2-alfresco-core';
import { FormService } from '../../../services/form.service';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
@ -27,8 +27,9 @@ import { baseHost , WidgetComponent } from './../widget.component';
@Component({
selector: 'dropdown-widget',
templateUrl: './dropdown.widget.html',
styleUrls: ['./dropdown.widget.css'],
host: baseHost
styleUrls: ['./dropdown.widget.scss'],
host: baseHost,
encapsulation: ViewEncapsulation.None
})
export class DropdownWidgetComponent extends WidgetComponent implements OnInit {

View File

@ -1,42 +0,0 @@
.dynamic-table-widget {
padding: 8px;
}
.dynamic-table-widget__buttons {
margin-top: 10px;
}
.dynamic-table-widget__row-selected,
.dynamic-table-widget__row-selected:hover {
background-color: #eef !important;
}
.dynamic-table-widget__table-container {
overflow-y: auto;
width: 100%;
}
.dynamic-table-widget__table {
width: 100%;
}
.dynamic-table-widget__invalid .dynamic-table-widget__label {
color: #d50000;
}
.dynamic-table-widget__invalid .dynamic-table-widget__table-container {
border: 1px solid #d50000;
}
.dynamic-table-widget__invalid .dynamic-table-widget__table {
border: none;
}
.dynamic-table-widget__summary {
visibility: hidden;
color: #d50000;
}
.dynamic-table-widget__invalid .dynamic-table-widget__summary {
visibility: visible !important;
}

View File

@ -1,23 +1,21 @@
<div class="dynamic-table-widget {{field.className}}"
[class.dynamic-table-widget__invalid]="!isValid()" *ngIf="field?.isVisible">
<div class="dynamic-table-widget__label">{{content.name}}</div>
<div class="{{field.className}}"
[class.adf-invalid]="!isValid()" *ngIf="field?.isVisible">
<div class="adf-label">{{content.name}}<span *ngIf="isRequired()">*</span></div>
<div *ngIf="!editMode">
<div class="dynamic-table-widget__table-container">
<table class="mdl-data-table mdl-js-data-table dynamic-table-widget__table" id="dynamic-table-{{content.id}}">
<div class="adf-table-container">
<table class="full-width adf-dynamic-table" id="dynamic-table-{{content.id}}">
<thead>
<tr>
<th *ngFor="let column of content.visibleColumns"
class="mdl-data-table__cell--non-numeric">
<th *ngFor="let column of content.visibleColumns">
{{column.name}}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of content.rows; let idx = index" tabindex="0" id="{{content.id}}-row-{{idx}}"
[class.dynamic-table-widget__row-selected]="row.selected" (keyup)="onKeyPressed($event, row)">
[class.adf-dynamic-table-widget__row-selected]="row.selected" (keyup)="onKeyPressed($event, row)">
<td *ngFor="let column of content.visibleColumns"
class="mdl-data-table__cell--non-numeric"
(click)="onRowClicked(row)">
{{ getCellValue(row, column) }}
</td>
@ -26,28 +24,29 @@
</table>
</div>
<div class="dynamic-table-widget__buttons" *ngIf="!readOnly">
<button class="mdl-button mdl-js-button mdl-button--icon"
<div *ngIf="!readOnly">
<button md-button
[disabled]="!hasSelection()"
(click)="moveSelectionUp()">
<i class="material-icons">arrow_upward</i>
</button>
<button class="mdl-button mdl-js-button mdl-button--icon"
<button md-button
[disabled]="!hasSelection()"
(click)="moveSelectionDown()">
<i class="material-icons">arrow_downward</i>
</button>
<button class="mdl-button mdl-js-button mdl-button--icon"
<button md-button
[disabled]="field.readOnly"
id="{{content.id}}-add-row"
(click)="addNewRow()">
<i class="material-icons">add_circle_outline</i>
</button>
<button class="mdl-button mdl-js-button mdl-button--icon"
<button md-button
[disabled]="!hasSelection()"
(click)="deleteSelection()">
<i class="material-icons">remove_circle_outline</i>
</button>
<button class="mdl-button mdl-js-button mdl-button--icon"
<button md-button
[disabled]="!hasSelection()"
(click)="editSelection()">
<i class="material-icons">edit</i>
@ -62,7 +61,5 @@
(save)="onSaveChanges()"
(cancel)="onCancelChanges()">
</row-editor>
<div class="dynamic-table-widget__summary">{{content?.field.validationSummary}}</div>
<error-widget [error]="field.validationSummary" ></error-widget>
</div>

View File

@ -0,0 +1,162 @@
@import 'theming';
@import '../form';
$dynamic-table-font-size: 14px !default;
$dynamic-table-header-font-size: 12px !default;
$dynamic-table-header-sort-icon-size: 16px !default;
$dynamic-table-header-color: $alfresco-secondary-text-color !default;
$dynamic-table-header-sorted-color: $alfresco-primary-text-color !default;
$dynamic-table-header-sorted-icon-hover-color: $alfresco-disabled-text-color !default;
$dynamic-table-divider-color: $alfresco-divider-color !default;
$dynamic-table-hover-color: #eeeeee !default;
$dynamic-table-selection-color: #e0f7fa !default;
$dynamic-table-dividers: 1px solid $dynamic-table-divider-color !default;
$dynamic-table-row-height: 56px !default;
$dynamic-table-column-spacing: 36px !default;
$dynamic-table-column-padding: $dynamic-table-column-spacing / 2;
$dynamic-table-card-padding: 24px !default;
$dynamic-table-cell-top: $dynamic-table-card-padding / 2;
$dynamic-table-drag-border: 1px dashed rgb(68,138,255);
.adf {
&-dynamic-table {
width: 100%;
position: relative;
border: $dynamic-table-dividers;
white-space: nowrap;
font-size: $dynamic-table-font-size;
background-color: unquote("rgb(#{$alfresco-white})");
/* Firefox fixes */
border-collapse: unset;
border-spacing: 0;
thead {
padding-bottom: 3px;
}
tbody {
tr {
position: relative;
height: $dynamic-table-row-height;
@include material-animation-default(0.28s);
transition-property: background-color;
&:hover {
background-color: $dynamic-table-hover-color;
}
&.is-selected, &.is-selected:hover {
background-color: $dynamic-table-selection-color;
}
&:focus {
outline-offset: -1px;
outline-width: 1px;
outline-color: rgb(68,138,255);
outline-style: solid;
}
}
}
td, th {
padding: 0 $dynamic-table-column-padding 12px $dynamic-table-column-padding;
text-align: center;
&:first-of-type {
padding-left: 24px;
}
&:last-of-type {
padding-right: 24px;
}
}
td {
color: $alfresco-secondary-text-color;
position: relative;
vertical-align: middle;
height: $dynamic-table-row-height;
border-top: $dynamic-table-dividers;
border-bottom: $dynamic-table-dividers;
padding-top: $dynamic-table-cell-top;
box-sizing: border-box;
@include no-select;
cursor: default;
}
th {
@include no-select;
cursor: pointer;
position: relative;
vertical-align: bottom;
text-overflow: ellipsis;
font-weight: bold;
line-height: 24px;
letter-spacing: 0;
height: $dynamic-table-row-height;
font-size: $dynamic-table-header-font-size;
color: $dynamic-table-header-color;
padding-bottom: 8px;
box-sizing: border-box;
&.sortable {
@include no-select;
&:hover {
cursor: pointer;
}
}
&.adf-dynamic-table__header--sorted-asc,
&.adf-dynamic-table__header--sorted-desc {
color: $dynamic-table-header-sorted-color;
&:before {
@include typo-icon;
font-size: $dynamic-table-header-sort-icon-size;
content: "\e5d8";
margin-right: 5px;
vertical-align: sub;
}
&:hover {
cursor: pointer;
&:before {
color: $dynamic-table-header-sorted-icon-hover-color;
}
}
}
&.adf-dynamic-table__header--sorted-desc:before {
content: "\e5db";
}
}
.adf-dynamic-table-cell {
text-align: left;
cursor: default;
&--text {
text-align: left;
}
&--number {
text-align: right;
}
&--image {
text-align: left;
img {
width: 24px;
height: 24px;
}
}
}
.full-width {
width: 100%;
color: $alfresco-primary-text-color;
}
}
}

View File

@ -18,8 +18,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LogServiceMock } from 'ng2-alfresco-core';
import { CoreModule, LogService } from 'ng2-alfresco-core';
import { MATERIAL_MODULE } from '../../../../index';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
import { ErrorWidgetComponent } from '../error/error.component';
import { EcmModelService } from './../../../services/ecm-model.service';
import { FormService } from './../../../services/form.service';
import { FormFieldModel, FormFieldTypes, FormModel } from './../core/index';
@ -79,15 +81,16 @@ describe('DynamicTableWidgetComponent', () => {
let element: HTMLElement;
let table: DynamicTableModel;
let logService: LogService;
let componentHandler: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
CoreModule.forRoot(),
...MATERIAL_MODULE
],
declarations: [DynamicTableWidgetComponent, RowEditorComponent,
DropdownEditorComponent, DateEditorComponent, BooleanEditorComponent, TextEditorComponent],
DropdownEditorComponent, DateEditorComponent, BooleanEditorComponent,
TextEditorComponent, ErrorWidgetComponent],
providers: [
FormService,
{provide: LogService, useClass: LogServiceMock},
@ -337,11 +340,6 @@ describe('DynamicTableWidgetComponent', () => {
describe('when template is ready', () => {
beforeEach(async(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
}));
beforeEach(() => {
widget.field = new FormFieldModel(new FormModel({taskId: 'fake-task-id'}), fakeFormField);
widget.field.type = FormFieldTypes.DYNAMIC_TABLE;
@ -368,7 +366,7 @@ describe('DynamicTableWidgetComponent', () => {
fixture.whenStable().then(() => {
let selectedRow = element.querySelector('#fake-dynamic-table-row-0');
expect(selectedRow.className).toBe('dynamic-table-widget__row-selected');
expect(selectedRow.className).toBe('adf-dynamic-table-widget__row-selected');
});
}));

View File

@ -17,10 +17,9 @@
/* tslint:disable:component-selector */
import { ChangeDetectorRef, Component, ElementRef, Input, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewEncapsulation } from '@angular/core';
import { LogService } from 'ng2-alfresco-core';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
import { FormFieldModel } from '../core/form-field.model';
import { FormService } from './../../../services/form.service';
import { baseHost , WidgetComponent } from './../widget.component';
import { DynamicTableColumn, DynamicTableModel, DynamicTableRow } from './dynamic-table.widget.model';
@ -28,19 +27,14 @@ import { DynamicTableColumn, DynamicTableModel, DynamicTableRow } from './dynami
@Component({
selector: 'dynamic-table-widget',
templateUrl: './dynamic-table.widget.html',
styleUrls: ['./dynamic-table.widget.css'],
host: baseHost
styleUrls: ['./dynamic-table.widget.scss'],
host: baseHost,
encapsulation: ViewEncapsulation.None
})
export class DynamicTableWidgetComponent extends WidgetComponent implements OnInit {
ERROR_MODEL_NOT_FOUND = 'Table model not found';
@Input()
field: FormFieldModel;
@Input()
readOnly: boolean = false;
content: DynamicTableModel;
editMode: boolean = false;

View File

@ -1,11 +1,11 @@
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" [attr.for]="column.id">
<input
class="mdl-checkbox__input"
type="checkbox"
[attr.id]="column.id"
<label [attr.for]="column.id">
<md-checkbox
color="primary"
[id]="column.id"
[checked]="table.getCellValue(row, column)"
[required]="column.required"
[disabled]="!column.editable"
(change)="onValueChanged(row, column, $event)">
<span class="mdl-checkbox__label">{{column.name}}</span>
<span class="adf-checkbox-label">{{column.name}}</span>
</md-checkbox>
</label>

View File

@ -0,0 +1,13 @@
@import 'theming';
.adf {
&-checkbox-label {
position: relative;
cursor: pointer;
font-size: 16px;
line-height: 24px;
margin: 0;
}
}

View File

@ -29,7 +29,7 @@ describe('BooleanEditorComponent', () => {
it('should update row value on change', () => {
let row = <DynamicTableRow> { value: {} };
let column = <DynamicTableColumn> { id: 'key' };
let event = { target: { checked: true } };
let event = { checked: true } ;
component.onValueChanged(row, column, event);
expect(row.value[column.id]).toBeTruthy();

View File

@ -21,9 +21,9 @@ import { Component, Input } from '@angular/core';
import { DynamicTableColumn, DynamicTableModel, DynamicTableRow } from './../../dynamic-table.widget.model';
@Component({
selector: 'alf-boolean-editor',
selector: 'adf-boolean-editor',
templateUrl: './boolean.editor.html',
styleUrls: ['./boolean.editor.css']
styleUrls: ['./boolean.editor.scss']
})
export class BooleanEditorComponent {
@ -37,7 +37,7 @@ export class BooleanEditorComponent {
column: DynamicTableColumn;
onValueChanged(row: DynamicTableRow, column: DynamicTableColumn, event: any) {
let value: boolean = (<HTMLInputElement> event.target).checked;
let value: boolean = (<HTMLInputElement> event).checked;
row.value[column.id] = value;
}

View File

@ -1,7 +0,0 @@
.date-editor {
width: 100%;
}
.date-editor--button {
margin-top: 15px;
}

View File

@ -1,23 +1,17 @@
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--11-col">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label date-editor">
<input id="dateInput"
class="mdl-textfield__input"
<div>
<md-input-container class="adf-date-editor">
<label [attr.for]="column.id">{{column.name}} (d-M-yyyy)</label>
<input mdInput
id="dateInput"
type="text"
[mdDatepicker]="datePicker"
[value]="value"
[attr.id]="column.id"
[id]="column.id"
[required]="column.required"
[disabled]="!column.editable"
(keyup)="onDateChanged($event)"
(onOk)="onDateSelected($event)">
<label class="mdl-textfield__label" [attr.for]="column.id">{{column.name}} (d-M-yyyy)</label>
</div>
</div>
<div *ngIf="column.editable" class="mdl-cell mdl-cell--1-col">
<button
class="mdl-button mdl-js-button mdl-button--icon date-editor--button"
(click)="datePicker.toggle()">
<i class="material-icons">date_range</i>
</button>
</div>
(focusout)="onDateChanged($event.srcElement.value)">
<button mdSuffix *ngIf="column.editable"
[mdDatepickerToggle]="datePicker" class="adf-date-editor-button"></button>
</md-input-container>
<md-datepicker #datePicker (selectedChanged)="onDateChanged($event)" [touchUi]="true"></md-datepicker>
</div>

View File

@ -0,0 +1,12 @@
@import 'theming';
.adf {
&-date-editor {
width: 100%;
}
&-date-editor-button {
position: relative;
top: 25px;
}
}

View File

@ -15,8 +15,8 @@
* limitations under the License.
*/
import { ElementRef } from '@angular/core';
import * as moment from 'moment';
import { MomentDateAdapter } from 'ng2-alfresco-core';
import { FormFieldModel, FormModel } from '../../../index';
import { DynamicTableColumn, DynamicTableModel, DynamicTableRow } from './../../dynamic-table.widget.model';
import { DateEditorComponent } from './date.editor';
@ -24,7 +24,6 @@ import { DateEditorComponent } from './date.editor';
describe('DateEditorComponent', () => {
let nativeElement: any;
let elementRef: ElementRef;
let component: DateEditorComponent;
let row: DynamicTableRow;
let column: DynamicTableColumn;
@ -41,118 +40,24 @@ describe('DateEditorComponent', () => {
table = new DynamicTableModel(field);
table.rows.push(row);
table.columns.push(column);
elementRef = new ElementRef(nativeElement);
component = new DateEditorComponent(elementRef);
component = new DateEditorComponent(new MomentDateAdapter());
component.table = table;
component.row = row;
component.column = column;
});
it('should setup date picker on init', () => {
let trigger = {};
spyOn(nativeElement, 'querySelector').and.returnValue(trigger);
component.ngOnInit();
let settings = component.settings;
expect(settings.type).toBe('date');
expect(settings.future.year()).toBe(moment().year() + 100);
expect(settings.init.isSame(moment('14-03-1879', component.DATE_FORMAT))).toBeTruthy();
expect(component.datePicker.trigger).toBe(trigger);
});
it('should require cell value to setup initial date', () => {
row.value[column.id] = null;
component.ngOnInit();
expect(component.settings.init).toBeUndefined();
});
it('should require dom element to setup trigger', () => {
component = new DateEditorComponent(null);
component.table = table;
component.row = row;
component.column = column;
component.ngOnInit();
expect(component.datePicker.trigger).toBeFalsy();
});
it('should update fow value on change', () => {
component.ngOnInit();
component.datePicker.time = moment('14-03-1879', 'DD-MM-YYYY');
component.onDateSelected(null);
let newDate = moment('14-03-1879', 'DD-MM-YYYY');
component.onDateChanged(newDate);
expect(row.value[column.id]).toBe('1879-03-14T00:00:00.000Z');
});
it('should update material textfield on date selected', () => {
component.ngOnInit();
component.datePicker.time = moment('14-03-1879', 'DD-MM-YYYY');
spyOn(component, 'updateMaterialTextField').and.stub();
component.onDateSelected(null);
expect(component.updateMaterialTextField).toHaveBeenCalled();
});
it('should require dom element to update material textfield on change', () => {
component = new DateEditorComponent(null);
component.table = table;
component.row = row;
component.column = column;
component.ngOnInit();
component.datePicker.time = moment('14-03-1879', 'DD-MM-YYYY');
spyOn(component, 'updateMaterialTextField').and.stub();
component.onDateSelected(null);
expect(component.updateMaterialTextField).not.toHaveBeenCalled();
});
it('should require dom element to update material textfield', () => {
let result = component.updateMaterialTextField(null, 'value');
expect(result).toBeFalsy();
});
it('should require native dom element to update material textfield', () => {
elementRef.nativeElement = null;
let result = component.updateMaterialTextField(elementRef, 'value');
expect(result).toBeFalsy();
});
it('should require input element to update material textfield', () => {
spyOn(nativeElement, 'querySelector').and.returnValue(null);
let result = component.updateMaterialTextField(elementRef, 'value');
expect(result).toBeFalsy();
});
it('should update material textfield with new value', () => {
let called = false;
const value = '<value>';
spyOn(nativeElement, 'querySelector').and.returnValue({
MaterialTextfield: {
change: function (val) {
called = true;
expect(val).toBe(value);
}
}
});
component.updateMaterialTextField(elementRef, value);
expect(called).toBeTruthy();
});
it('should update picker when input changed', () => {
const input = '14-03-2016';
let event = { target: { value: input } };
component.ngOnInit();
component.onDateChanged(event);
expect(component.datePicker.time.isSame(moment(input, 'DD-MM-YYYY'))).toBeTruthy();
});
it('should update row value upon user input', () => {
const input = '14-03-2016';
let event = { target: { value: input } };
component.ngOnInit();
component.onDateChanged(event);
component.onDateChanged(input);
let actual = row.value[column.id];
expect(actual).toBe('2016-03-14T00:00:00.000Z');
@ -160,12 +65,11 @@ describe('DateEditorComponent', () => {
it('should flush value on user input', () => {
spyOn(table, 'flushValue').and.callThrough();
let event = { target: { value: 'value' } };
const input = '14-03-2016';
component.ngOnInit();
component.onDateChanged(event);
component.onDateChanged(input);
expect(table.flushValue).toHaveBeenCalled();
});
});

Some files were not shown because too many files have changed in this diff Show More