[REGRESSION-AFTER-CLI] Fixing --prod mode (#2633)

* FormRenderingService refactoring

* Move common part to core and use in card view

* Add documentation
This commit is contained in:
Popovics András
2017-11-09 17:12:41 +00:00
committed by Eugenio Romano
parent 34f51e3558
commit e1e9c735b4
11 changed files with 181 additions and 93 deletions

View File

@@ -45,7 +45,7 @@ Displays a configurable property list renderer.
## Details
You define the property list, the CardViewComponent does the rest. Each property represents a card view item (a row) in the card view component. At the time of writing two different kind of card view item (property type) is supported out of the box ([text](#card-text-item) item and [date](#card-date-item) item) but you can define your own custom types as well.
You define the property list, the CardViewComponent does the rest. Each property represents a card view item (a row) in the card view component. At the time of writing three a few kind of card view item (property type) is supported out of the box (e.g: [text](#card-text-item) item and [date](#card-date-item) item) but you can define your own custom types as well.
### Editing
@@ -171,13 +171,16 @@ Card item components are loaded dynamically, which makes you able to define your
Let's consider you want to have a **stardate** type to display Captain Picard's birthday (47457.1). For this, you need to do the following steps.
#### 1. Define the model for the custom type
#### 1. Define the Model for the custom type
Your model has to extend the CardViewBaseItemModel and implement the CardViewItem interface.
Your model has to extend the **CardViewBaseItemModel** and implement the **CardViewItem** and **DynamicComponentModel** interface.
*(You can check how the CardViewTextItemModel is implemented for further guidance.)*
```js
export class CardViewStarDateItemModel extends CardViewBaseItemModel implements CardViewItem {
import { CardViewBaseItemModel, CardViewItem, DynamicComponentModel } from 'ng2-alfresco-core';
export class CardViewStarDateItemModel extends CardViewBaseItemModel implements
CardViewItem, DynamicComponentModel {
type: string = 'star-date';
get displayValue() {
@@ -190,13 +193,9 @@ export class CardViewStarDateItemModel extends CardViewBaseItemModel implements
}
```
The most important part of this model is the value of the **type** attribute. This is how the Card View component will be able to recognise which component is needed to render it dynamically.
#### 2. Define the Component for the custom type
The type is a **hyphen-separated-lowercase-words** string (just like how I wrote it). This will be converted to a PascalCase (or UpperCamelCase) string to find the right component. In our case the Card View component will look for the CardView**StarDate**ItemComponent.
#### 2. Define the component for the custom type
As discussed in the previous step the only important thing here is the naming of your component class ( **CardViewStarDateItemComponent**). Since the selector is not used in this case, you can give any selector name to it, but it makes sense to follow the angular standards.
Create your custom card view item component. Defining the selector is not important, being it a dinamically loaded component, so you can give any selector name to it, but it makes sense to follow the angular standards.
```js
@Component({
@@ -242,6 +241,27 @@ For Angular to be able to load your custom component dynamically, you have to re
export class MyModule {}
```
#### 4. Bind your custom component to the custom model type, enabling Angular's dynamic component loader to find it.
For mapping each type to their Component, there is the **CardItemTypeService**. This service extends the **DynamicComponentMapper** abstract class.
This CardItemTypeService is responible for the type resolution, it contains all the default components (e.g.: text, date, etc...) also. In order to make your component available, you need to extend the list of possible components.
You can extend this list the following way:
```js
@Component({
...
providers: [ CardItemTypeService ] // If you don't want to pollute the main instance of the CardItemTypeService service
...
})
export class SomeParentComponent {
constructor(private cardItemTypeService: CardItemTypeService) {
cardItemTypeService.setComponentTypeResolver('star-date', () => CardViewStarDateItemComponent);
}
}
```
<!-- Don't edit the See also section. Edit seeAlsoGraph.json and run config/generateSeeAlso.js -->
<!-- seealso start -->
## See also

View File

@@ -15,6 +15,7 @@
* limitations under the License.
*/
import { DynamicComponentResolver } from 'ng2-alfresco-core';
import {
AttachWidgetComponent,
FormFieldModel,
@@ -22,7 +23,7 @@ import {
UnknownWidgetComponent,
UploadWidgetComponent
} from './../components/widgets/index';
import { DefaultTypeResolver, FormRenderingService } from './form-rendering.service';
import { FormRenderingService } from './form-rendering.service';
describe('FormRenderingService', () => {
@@ -88,9 +89,9 @@ describe('FormRenderingService', () => {
expect(
() => service.setComponentTypeResolver(
null,
DefaultTypeResolver.fromType(UnknownWidgetComponent)
DynamicComponentResolver.fromType(UnknownWidgetComponent)
)
).toThrowError('fieldType is null or not defined');
).toThrowError('type is null or not defined');
});
it('should require type resolver instance to set resolver for type', () => {
@@ -106,13 +107,13 @@ describe('FormRenderingService', () => {
expect(
() => service.setComponentTypeResolver(
FormFieldTypes.TEXT,
DefaultTypeResolver.fromType(UnknownWidgetComponent)
DynamicComponentResolver.fromType(UnknownWidgetComponent)
)
).toThrowError('already mapped, use override option if you intend replacing existing mapping.');
});
it('should override existing resolver with explicit flag', () => {
let customResolver = DefaultTypeResolver.fromType(UnknownWidgetComponent);
let customResolver = DynamicComponentResolver.fromType(UnknownWidgetComponent);
service.setComponentTypeResolver(FormFieldTypes.TEXT, customResolver, true);
expect(service.getComponentTypeResolver(FormFieldTypes.TEXT)).toBe(customResolver);
});

View File

@@ -16,6 +16,7 @@
*/
import { Injectable, Type } from '@angular/core';
import { DynamicComponentMapper, DynamicComponentResolveFunction, DynamicComponentResolver } from 'ng2-alfresco-core';
import {
AmountWidgetComponent,
@@ -41,30 +42,33 @@ import {
} from './../components/widgets/index';
@Injectable()
export class FormRenderingService {
export class FormRenderingService extends DynamicComponentMapper {
private types: { [key: string]: ComponentTypeResolver } = {
'text': DefaultTypeResolver.fromType(TextWidgetComponent),
'string': DefaultTypeResolver.fromType(TextWidgetComponent),
'integer': DefaultTypeResolver.fromType(NumberWidgetComponent),
'multi-line-text': DefaultTypeResolver.fromType(MultilineTextWidgetComponentComponent),
'boolean': DefaultTypeResolver.fromType(CheckboxWidgetComponent),
'dropdown': DefaultTypeResolver.fromType(DropdownWidgetComponent),
'date': DefaultTypeResolver.fromType(DateWidgetComponent),
'amount': DefaultTypeResolver.fromType(AmountWidgetComponent),
'radio-buttons': DefaultTypeResolver.fromType(RadioButtonsWidgetComponent),
'hyperlink': DefaultTypeResolver.fromType(HyperlinkWidgetComponent),
'readonly-text': DefaultTypeResolver.fromType(DisplayTextWidgetComponentComponent),
'typeahead': DefaultTypeResolver.fromType(TypeaheadWidgetComponent),
'people': DefaultTypeResolver.fromType(PeopleWidgetComponent),
'functional-group': DefaultTypeResolver.fromType(FunctionalGroupWidgetComponent),
'dynamic-table': DefaultTypeResolver.fromType(DynamicTableWidgetComponent),
'container': DefaultTypeResolver.fromType(ContainerWidgetComponent),
'group': DefaultTypeResolver.fromType(ContainerWidgetComponent),
'document': DefaultTypeResolver.fromType(DocumentWidgetComponent)
protected defaultValue: Type<{}> = UnknownWidgetComponent;
protected types: { [key: string]: DynamicComponentResolveFunction } = {
'text': DynamicComponentResolver.fromType(TextWidgetComponent),
'string': DynamicComponentResolver.fromType(TextWidgetComponent),
'integer': DynamicComponentResolver.fromType(NumberWidgetComponent),
'multi-line-text': DynamicComponentResolver.fromType(MultilineTextWidgetComponentComponent),
'boolean': DynamicComponentResolver.fromType(CheckboxWidgetComponent),
'dropdown': DynamicComponentResolver.fromType(DropdownWidgetComponent),
'date': DynamicComponentResolver.fromType(DateWidgetComponent),
'amount': DynamicComponentResolver.fromType(AmountWidgetComponent),
'radio-buttons': DynamicComponentResolver.fromType(RadioButtonsWidgetComponent),
'hyperlink': DynamicComponentResolver.fromType(HyperlinkWidgetComponent),
'readonly-text': DynamicComponentResolver.fromType(DisplayTextWidgetComponentComponent),
'typeahead': DynamicComponentResolver.fromType(TypeaheadWidgetComponent),
'people': DynamicComponentResolver.fromType(PeopleWidgetComponent),
'functional-group': DynamicComponentResolver.fromType(FunctionalGroupWidgetComponent),
'dynamic-table': DynamicComponentResolver.fromType(DynamicTableWidgetComponent),
'container': DynamicComponentResolver.fromType(ContainerWidgetComponent),
'group': DynamicComponentResolver.fromType(ContainerWidgetComponent),
'document': DynamicComponentResolver.fromType(DocumentWidgetComponent)
};
constructor() {
super();
this.types['upload'] = (field: FormFieldModel): Type<{}> => {
if (field) {
let params = field.params;
@@ -76,47 +80,4 @@ export class FormRenderingService {
return UnknownWidgetComponent;
};
}
getComponentTypeResolver(fieldType: string, defaultValue: Type<{}> = UnknownWidgetComponent): ComponentTypeResolver {
if (fieldType) {
return this.types[fieldType] || DefaultTypeResolver.fromType(defaultValue);
}
return DefaultTypeResolver.fromType(defaultValue);
}
setComponentTypeResolver(fieldType: string, resolver: ComponentTypeResolver, override: boolean = false) {
if (!fieldType) {
throw new Error(`fieldType is null or not defined`);
}
if (!resolver) {
throw new Error(`resolver is null or not defined`);
}
let existing = this.types[fieldType];
if (existing && !override) {
throw new Error(`already mapped, use override option if you intend replacing existing mapping.`);
}
this.types[fieldType] = resolver;
}
resolveComponentType(field: FormFieldModel, defaultValue: Type<{}> = UnknownWidgetComponent): Type<{}> {
if (field) {
let resolver = this.getComponentTypeResolver(field.type, defaultValue);
return resolver(field);
}
return defaultValue;
}
}
export type ComponentTypeResolver = (field: FormFieldModel) => Type<{}>;
export class DefaultTypeResolver {
static fromType(type: Type<{}>): ComponentTypeResolver {
return (field: FormFieldModel) => {
return type;
};
}
}

View File

@@ -39,6 +39,7 @@ import { AuthGuardBpm } from './src/services/auth-guard-bpm.service';
import { AuthGuardEcm } from './src/services/auth-guard-ecm.service';
import { AuthGuard } from './src/services/auth-guard.service';
import { AuthenticationService } from './src/services/authentication.service';
import { CardItemTypeService } from './src/services/card-item-types.service';
import { CommentProcessService } from './src/services/comment-process.service';
import { ContentService } from './src/services/content.service';
import { CookieService } from './src/services/cookie.service';
@@ -98,6 +99,8 @@ export { AlfrescoTranslateLoader } from './src/services/translate-loader.service
export { AppConfigService } from './src/services/app-config.service';
export { ThumbnailService } from './src/services/thumbnail.service';
export { UploadService } from './src/services/upload.service';
export { DynamicComponentMapper, DynamicComponentResolveFunction, DynamicComponentResolver } from './src/services/dynamic-component-mapper.service';
export { CardItemTypeService } from './src/services/card-item-types.service';
export { CardViewUpdateService } from './src/services/card-view-update.service';
export { UpdateNotification } from './src/services/card-view-update.service';
export { ClickNotification } from './src/services/card-view-update.service';
@@ -167,6 +170,7 @@ export * from './src/events/base-ui.event';
export * from './src/events/folder-created.event';
export * from './src/events/file.event';
export * from './src/models/card-view-baseitem.model';
export * from './src/models/card-view-textitem.model';
export * from './src/models/card-view-mapitem.model';
export * from './src/models/card-view-dateitem.model';
@@ -219,6 +223,7 @@ export function providers() {
PeopleProcessService,
AppsProcessService,
CommentProcessService,
CardItemTypeService,
AppConfigService
];
}

View File

@@ -22,6 +22,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { CardViewItem } from '../../interface/card-view-item.interface';
import { CardItemTypeService } from '../../services/card-item-types.service';
import { CardViewContentProxyDirective } from './card-view-content-proxy.directive';
import { CardViewItemDispatcherComponent } from './card-view-item-dispatcher.component';
@@ -37,9 +38,13 @@ export class CardViewShinyCustomElementItemComponent {
describe('CardViewItemDispatcherComponent', () => {
let fixture: ComponentFixture<CardViewItemDispatcherComponent>;
let cardItemTypeService: CardItemTypeService;
let component: CardViewItemDispatcherComponent;
beforeEach(async(() => {
cardItemTypeService = new CardItemTypeService();
cardItemTypeService.setComponentTypeResolver('shiny-custom-element', () => CardViewShinyCustomElementItemComponent);
TestBed.configureTestingModule({
imports: [],
declarations: [
@@ -47,7 +52,7 @@ describe('CardViewItemDispatcherComponent', () => {
CardViewShinyCustomElementItemComponent,
CardViewContentProxyDirective
],
providers: []
providers: [ { provide: CardItemTypeService, useValue: cardItemTypeService } ]
});
// entryComponents are not supported yet on TestBed, that is why this ugly workaround:

View File

@@ -20,10 +20,10 @@ import {
ComponentFactoryResolver,
Input,
OnChanges,
Type,
ViewChild
} from '@angular/core';
import { CardViewItem } from '../../interface/card-view-item.interface';
import { CardItemTypeService } from '../../services/card-item-types.service';
import { CardViewContentProxyDirective } from './card-view-content-proxy.directive';
@Component({
@@ -46,7 +46,8 @@ export class CardViewItemDispatcherComponent implements OnChanges {
public ngOnInit;
public ngDoCheck;
constructor(private resolver: ComponentFactoryResolver) {
constructor(private cardItemTypeService: CardItemTypeService,
private resolver: ComponentFactoryResolver) {
const dynamicLifecycleMethods = [
'ngOnInit',
'ngDoCheck',
@@ -72,11 +73,8 @@ export class CardViewItemDispatcherComponent implements OnChanges {
}
private loadComponent() {
const upperCamelCasedType = this.getUpperCamelCase(this.property.type),
className = `CardView${upperCamelCasedType}ItemComponent`;
const factoryClass = this.cardItemTypeService.resolveComponentType(this.property);
const factories = Array.from(this.resolver['_factories'].keys());
const factoryClass = <Type<any>> factories.find((x: any) => x.name === className);
const factory = this.resolver.resolveComponentFactory(factoryClass);
this.componentReference = this.content.viewContainerRef.createComponent(factory);
@@ -84,11 +82,6 @@ export class CardViewItemDispatcherComponent implements OnChanges {
this.componentReference.instance.property = this.property;
}
private getUpperCamelCase(type: string): string {
const camelCasedType = type.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
return camelCasedType[0].toUpperCase() + camelCasedType.substr(1);
}
private proxy(methodName, ...args) {
if (this.componentReference.instance[methodName]) {
this.componentReference.instance[methodName].apply(this.componentReference.instance, args);

View File

@@ -25,13 +25,14 @@
import * as moment from 'moment';
import { CardViewItem } from '../interface/card-view-item.interface';
import { DynamicComponentModel } from '../services/dynamic-component-mapper.service';
import { CardViewBaseItemModel, CardViewItemProperties } from './card-view-baseitem.model';
export interface CardViewDateItemProperties extends CardViewItemProperties {
format?: string;
}
export class CardViewDateItemModel extends CardViewBaseItemModel implements CardViewItem {
export class CardViewDateItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel {
type: string = 'date';
format: string;

View File

@@ -24,9 +24,10 @@
*/
import { CardViewItem } from '../interface/card-view-item.interface';
import { DynamicComponentModel } from '../services/dynamic-component-mapper.service';
import { CardViewBaseItemModel, CardViewItemProperties } from './card-view-baseitem.model';
export class CardViewMapItemModel extends CardViewBaseItemModel implements CardViewItem {
export class CardViewMapItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel {
type: string = 'map';
value: Map<string, string>;

View File

@@ -24,12 +24,13 @@
*/
import { CardViewItem } from '../interface/card-view-item.interface';
import { DynamicComponentModel } from '../services/dynamic-component-mapper.service';
import { CardViewBaseItemModel, CardViewItemProperties } from './card-view-baseitem.model';
export interface CardViewTextItemProperties extends CardViewItemProperties {
multiline?: boolean;
}
export class CardViewTextItemModel extends CardViewBaseItemModel implements CardViewItem {
export class CardViewTextItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel {
type: string = 'text';
multiline: boolean;

View File

@@ -0,0 +1,34 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable, Type } from '@angular/core';
import { CardViewDateItemComponent } from '../components/view/card-view-dateitem.component';
import { CardViewMapItemComponent } from '../components/view/card-view-mapitem.component';
import { CardViewTextItemComponent } from '../components/view/card-view-textitem.component';
import { DynamicComponentMapper, DynamicComponentResolveFunction, DynamicComponentResolver } from '../services/dynamic-component-mapper.service';
@Injectable()
export class CardItemTypeService extends DynamicComponentMapper {
protected defaultValue: Type<{}> = CardViewTextItemComponent;
protected types: { [key: string]: DynamicComponentResolveFunction } = {
'text': DynamicComponentResolver.fromType(CardViewTextItemComponent),
'date': DynamicComponentResolver.fromType(CardViewDateItemComponent),
'map': DynamicComponentResolver.fromType(CardViewMapItemComponent)
};
}

View File

@@ -0,0 +1,66 @@
/*!
* @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 { Type } from '@angular/core';
export interface DynamicComponentModel { type: string; }
export type DynamicComponentResolveFunction = (model: DynamicComponentModel) => Type<{}>;
export class DynamicComponentResolver {
static fromType(type: Type<{}>): DynamicComponentResolveFunction {
return (model: DynamicComponentModel) => {
return type;
};
}
}
export abstract class DynamicComponentMapper {
protected defaultValue: Type<{}> = undefined;
protected types: { [key: string]: DynamicComponentResolveFunction } = {};
getComponentTypeResolver(type: string, defaultValue: Type<{}> = this.defaultValue): DynamicComponentResolveFunction {
if (type) {
return this.types[type] || DynamicComponentResolver.fromType(defaultValue);
}
return DynamicComponentResolver.fromType(defaultValue);
}
setComponentTypeResolver(type: string, resolver: DynamicComponentResolveFunction, override: boolean = false) {
if (!type) {
throw new Error(`type is null or not defined`);
}
if (!resolver) {
throw new Error(`resolver is null or not defined`);
}
let existing = this.types[type];
if (existing && !override) {
throw new Error(`already mapped, use override option if you intend replacing existing mapping.`);
}
this.types[type] = resolver;
}
resolveComponentType(model: DynamicComponentModel, defaultValue: Type<{}> = this.defaultValue): Type<{}> {
if (model) {
let resolver = this.getComponentTypeResolver(model.type, defaultValue);
return resolver(model);
}
return defaultValue;
}
}