diff --git a/angular.json b/angular.json index cf01607..47ee15d 100644 --- a/angular.json +++ b/angular.json @@ -203,6 +203,46 @@ } } }, + "aem-angular-core-spa-wcm-components-contentfragment-v1": { + "projectType": "library", + "root": "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1", + "sourceRoot": "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src", + "prefix": "lib", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.lib.json", + "project": "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.lib.prod.json" + } + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/test.ts", + "tsConfig": "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.spec.json", + "karmaConfig": "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.lib.json", + "projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, "aem-angular-core-spa-wcm-components-tabs-v1": { "projectType": "library", "root": "projects/aem-angular-core-spa-wcm-components/containers/tabs/v1", diff --git a/package.json b/package.json index 11af304..fefa08f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test-cover-libs": "npm-run-all test-container-core test-accordion-v1 test-carousel-v1 test-container-v1 test-tabs-v1 build-spa", "lint": "ng lint", "e2e": "ng e2e", + "cover:library:spa:contentfragment:v1": "ng test aem-angular-core-spa-wcm-components-contentfragment-v1 --code-coverage=true", "cover:library:spa:container:core": "ng test aem-angular-core-spa-wcm-components-core --code-coverage=true", "cover:library:spa:accordion:v1": "ng test aem-angular-core-spa-wcm-components-accordion-v1 --code-coverage=true", "cover:library:spa:carousel:v1": "ng test aem-angular-core-spa-wcm-components-carousel-v1 --code-coverage=true", diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/README.md b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/README.md new file mode 100644 index 0000000..3593816 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/README.md @@ -0,0 +1,24 @@ +# AemAngularCoreSpaWcmComponents + +This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.12. + +## Code scaffolding + +Run `ng generate component component-name --project aem-angular-core-spa-wcm-components` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project aem-angular-core-spa-wcm-components`. +> Note: Don't forget to add `--project aem-angular-core-spa-wcm-components` or else it will be added to the default project in your `angular.json` file. + +## Build + +Run `ng build aem-angular-core-spa-wcm-components` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Publishing + +After building your library with `ng build aem-angular-core-spa-wcm-components`, go to the dist folder `cd dist/aem-angular-core-spa-wcm-components` and run `npm publish`. + +## Running unit tests + +Run `ng test aem-angular-core-spa-wcm-components` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/karma.conf.js b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/karma.conf.js new file mode 100644 index 0000000..45cfae6 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/karma.conf.js @@ -0,0 +1,24 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2020 Adobe Systems Incorporated + ~ + ~ 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. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +const karmaConfigGenerator = require('../../../../../karma-conf-generator'); + +module.exports = function (config) { + return karmaConfigGenerator(config, 'aem-angular-core-spa-wcm-components-container-v1'); +}; \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/ng-package.json b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/ng-package.json new file mode 100644 index 0000000..1862883 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/ng-package.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../../../dist/aem-angular-core-spa-wcm-components/containers/contentfragment/v1", + "lib": { + "entryFile": "src/public-api.ts" + }, + "whitelistedNonPeerDependencies": [ + "." + ] +} \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/package.json b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/package.json new file mode 100644 index 0000000..acd4ade --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/package.json @@ -0,0 +1,16 @@ +{ + "name": "@adobe/aem-core-components-angular-spa-contentfragment-v1", + "version": "0.0.6", + "author": "Adobe Systems Inc.", + "license": "Apache-2.0", + "peerDependencies": { + "@adobe/aem-angular-editable-components": "^1.0.3", + "@adobe/aem-spa-component-mapping": "^1.0.5", + "@adobe/aem-spa-page-model-manager": "^1.0.3", + "@angular/common": "^9.1.12", + "@angular/core": "^9.1.12" + }, + "dependencies": { + "tslib": "^1.10.0" + } +} diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.component.html b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.component.html new file mode 100644 index 0000000..725d687 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.component.html @@ -0,0 +1,7 @@ + +
+ +
ContentFragment is empty.
+
\ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.component.ts b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.component.ts new file mode 100644 index 0000000..cae39fe --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.component.ts @@ -0,0 +1,63 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2020 Adobe Systems Incorporated + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + + +import {Component, Input} from "@angular/core"; +import {ContentFragmentV1Element, ContentFragmentV1Properties} from "./model"; +import {AbstractContainerComponent} from "@adobe/aem-core-components-angular-spa/core"; + +const ITEM_KEY = "fragment"; + +@Component({ + selector: 'core-contentFragment-v1', + host: { + '[class]': 'hostClasses', + '[attr.data-cq-data-path]':'cqPath' + }, + templateUrl: './contentfragment.v1.component.html' +}) +export class ContentFragmentV1Component extends AbstractContainerComponent implements ContentFragmentV1Properties{ + + @Input() description: string; + @Input() elements: { [p: string]: ContentFragmentV1Element }; + @Input() elementsOrder: string[]; + @Input() model: string; + @Input() title: string; + + showFragment(){ + return !ContentFragmentV1IsEmptyFn(this); + } + + showPlaceHolder(){ + return ContentFragmentV1IsEmptyFn(this) && this.isInEditMode; + } + + getItemSelf(){ + return this; + } + + getPathSelf(){ + return this.cqPath; + } + + getItemKey(){ + return ITEM_KEY; + } +} + +export function ContentFragmentV1IsEmptyFn(props:ContentFragmentV1Properties) { + return (props.elementsOrder == null || props.elementsOrder.length === 0) && (props.cqItemsOrder == null || props.cqItemsOrder.length === 0); +} \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.directive.ts b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.directive.ts new file mode 100644 index 0000000..89c4dc4 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.directive.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {Directive, Type} from '@angular/core'; +import {AEMComponentDirective, ComponentMappingProvider} from "@adobe/aem-angular-editable-components"; +import {ContentfragmentV1Registry} from "./contentfragment.v1.registry"; +import {ContentFragmentV1DefaultRenderer} from "./defaultrenderer/contentfragment.v1.defaultrenderer"; + +@Directive({ + selector: '[aemContentFragment]' +}) + +export class ContentFragmentV1Directive extends AEMComponentDirective { + + getFallbackComponent():Type | null{ + return ContentFragmentV1DefaultRenderer; + } + + getComponentMappingProvider():ComponentMappingProvider { + return ContentfragmentV1Registry; + } + + getType(): string | undefined { + return this.cqItem && this.cqItem["model"]; + } + +} diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.module.ts b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.module.ts new file mode 100644 index 0000000..cda37af --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.module.ts @@ -0,0 +1,43 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2020 Adobe Systems Incorporated + ~ + ~ 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 {NgModule} from '@angular/core'; +import {CommonModule} from "@angular/common"; +import {BrowserModule} from "@angular/platform-browser"; +import {RouterModule} from "@angular/router"; +import {ContentFragmentV1Component} from "./contentfragment.v1.component"; +import {SpaAngularEditableComponentsModule} from "@adobe/aem-angular-editable-components"; +import {AemAngularCoreSpaWcmComponentsCore} from "@adobe/aem-core-components-angular-spa/core"; +import {ContentFragmentV1Directive} from "./contentfragment.v1.directive"; +import {ContentFragmentV1DefaultRenderer} from "./defaultrenderer/contentfragment.v1.defaultrenderer"; + +@NgModule({ + imports: [CommonModule, BrowserModule, RouterModule,SpaAngularEditableComponentsModule,AemAngularCoreSpaWcmComponentsCore], + declarations: [ + ContentFragmentV1DefaultRenderer, + ContentFragmentV1Component, + ContentFragmentV1Directive + ], + exports: [ + ContentFragmentV1DefaultRenderer, + ContentFragmentV1Component + ], + entryComponents: [ + ContentFragmentV1DefaultRenderer, + ContentFragmentV1Component, + ], +}) +export class AemAngularCoreWcmComponentsContentFragmentV1 {} \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.registry.ts b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.registry.ts new file mode 100644 index 0000000..b7e7ee3 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.registry.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {ComponentMapping as SPAComponentMapping} from '@adobe/aem-spa-component-mapping'; +import {Type} from '@angular/core'; +import {ContentFragmentV1Properties} from "./model"; +import {ComponentMappingProvider} from '@adobe/aem-angular-editable-components'; + +const MODEL_PREFIX = "ContentFragmentV1-" + + +/** + * The current class extends the @adobe/cq-spa-component-mapping#Mapto library and features with Angular specifics such as + * + * - Storing the editing configurations for each resource type + */ +export class ContentfragmentV1RegistryImpl implements ComponentMappingProvider{ + + constructor(private spaMapping: SPAComponentMapping) {} + + + /** + * Stores a component class for the given resource types and also allows to provide an EditConfig object + * @param models - List of resource types + * @param clazz - Component class to be stored + * @type Model - The Model interface / class type bound to the editconfig object. + */ + mapRenderer(models: string | string[], clazz:Type) : void { + const innerClass = clazz; + + const resourceList = (typeof models === 'string') ? [ models ] : models; + + resourceList.forEach((model) => { + this.spaMapping.map(this.getModelKey(model), innerClass); + }); + + } + + /** + * Stores a component class for the given resource types and also allows to provide an EditConfig object in a Lazy Manner + * @param models - List of models + * @param lazyClassFunction - A function that returns a promise to give back the designated type / class + * @type Model - The Model interface / class type bound to the editconfig object. + */ + lazyMap(models:string|string[], lazyClassFunction: () => Promise>) { + const modelList = (typeof models === 'string') ? [ models ] : models; + + modelList.forEach((entry) => { + this.spaMapping.lazyMap(this.getModelKey(entry), lazyClassFunction); + }); + } + + /** + * Returns the component class for the given resourceType + * @param model - Resource type for which the component class has been stored + * @type Model - The Model interface / class type bound to the editconfig object. + */ + get(model:string):Type { + return this.spaMapping.get(this.getModelKey(model)) as Type; + } + + lazyGet(resourceType: string): Promise> { + return this.spaMapping.getLazy(this.getModelKey(resourceType)) as Promise>; + } + + getModelKey(modelType:string){ + return MODEL_PREFIX + modelType; + } +} + +const contentFragmentRegistry = new ContentfragmentV1RegistryImpl(SPAComponentMapping); + +/** + * Stores a component class for the given resource types and also allows to provide an EditConfig object + * @param resourceTypes - List of resource types + * @type Model - The Model interface / class type that will be Mapped. Bound to the EditConfig configuration. + */ +function MapToContentFragmentModel(resourceTypes: string | string[]) { + /** + * @param clazz - Component class to be stored + * @param [editConfig] - Edit configuration to be stored for the given resource types + */ + return (clazz:Type): void => + contentFragmentRegistry.mapRenderer(resourceTypes, clazz); +} + +/** + * Stores a clazz the lazy way for dynamic imports / code splitting.function that returns a promise + * @param resourceTypes - List of resource types + * @type Model - The Model interface / class type that will be Mapped. Bound to the EditConfig configuration. + */ +function LazyMapToContentFragmentModel(resourceTypes: string | string[]) { + /** + * @param lazyClassPromise - Function that returns a promise resolving a class + * @param [editConfig] - Edit configuration to be stored for the given resource types + */ + return (lazyClassFunction: () => Promise>): void => + contentFragmentRegistry.lazyMap(resourceTypes, lazyClassFunction); +} + +export { contentFragmentRegistry as ContentfragmentV1Registry, MapToContentFragmentModel, LazyMapToContentFragmentModel }; \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.spec.ts b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.spec.ts new file mode 100644 index 0000000..15707a4 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/contentfragment.v1.spec.ts @@ -0,0 +1,222 @@ +/* + * ADOBE CONFIDENTIAL + * + * Copyright 2020 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + */ + +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {BrowserDynamicTestingModule} from '@angular/platform-browser-dynamic/testing'; + +import {ComponentMapping as SPAComponentMapping} from '@adobe/aem-spa-component-mapping'; +import {ContentFragmentV1Component} from './contentfragment.v1.component'; +import { + AEMComponentDirective, + AEMModelProviderComponent, + Constants, + MapTo, + Utils +} from "@adobe/aem-angular-editable-components"; + +import {ModelManager} from '@adobe/aem-spa-page-model-manager'; +import {ContentFragmentV1DefaultRenderer} from "./defaultrenderer/contentfragment.v1.defaultrenderer"; +import {ContentFragmentV1Directive} from "./contentfragment.v1.directive"; +import {MapToContentFragmentModel, LazyMapToContentFragmentModel} from "./contentfragment.v1.registry"; +import {Component, HostBinding, Input, OnInit, Type} from "@angular/core"; +import {AbstractContainerComponent} from "@adobe/aem-core-components-angular-spa/core"; +import {ContentFragmentV1Element, ContentFragmentV1Properties} from "./model"; +import LazyComponent from "../../../../test/lazy-component-wrapper/lazy.component"; + +@Component({ + selector: 'mycontentfragment-renderer', + host: { + '[class]': 'hostClasses', + '[attr.data-cq-data-path]':'cqPath' + }, + template: '
hi!
' +}) +export class MyContentFragmentRenderer extends AbstractContainerComponent implements ContentFragmentV1Properties, OnInit{ + + @HostBinding('class') baseCssClass = 'cmp-mycontentfragment'; + + @Input() description: string; + @Input() elements: { [p: string]: ContentFragmentV1Element }; + @Input() elementsOrder: string[]; + @Input() model: string; + @Input() title: string; + + ngOnInit(): void { + console.log("MyContentFragmentRenderer", this); + } + +} + +describe('ContentFragmentV1', () => { + + const LAYOUT = require('../../../../test/data/content-fragment.json'); + + let component: ContentFragmentV1Component; + let fixture: ComponentFixture; + + let isInEditorSpy; + + + beforeEach(() => { + + spyOn(ModelManager, 'addListener').and.returnValue(undefined); + isInEditorSpy = spyOn(Utils, 'isInEditor').and.returnValue(false); + + TestBed.configureTestingModule({ + declarations: [ + AEMModelProviderComponent, + MyContentFragmentRenderer, + AEMComponentDirective, + ContentFragmentV1Component, + ContentFragmentV1DefaultRenderer, + ContentFragmentV1Directive] + }).overrideModule(BrowserDynamicTestingModule, { + set: { + entryComponents: [ + MyContentFragmentRenderer, + ContentFragmentV1Component, + ContentFragmentV1Directive, + ContentFragmentV1DefaultRenderer, + AEMModelProviderComponent, + AEMComponentDirective + ] + } + }).compileComponents(); + + + SPAComponentMapping.mapping = {}; + SPAComponentMapping.lazyMapping = {}; + MapTo('core-components-examples/wcm-react/components/content-fragment')(ContentFragmentV1Component); + + fixture = TestBed.createComponent(ContentFragmentV1Component); + component = fixture.componentInstance; + }); + + it('should render the default renderer and the mapped renderer', async () => { + + MapToContentFragmentModel('testing-frontend-react/models/custom-model')(MyContentFragmentRenderer); + + isInEditorSpy.and.returnValue(true); + component.cqItems = LAYOUT[Constants.ITEMS_PROP]; + component.cqItemsOrder = LAYOUT[Constants.ITEMS_ORDER_PROP]; + component.classNames = LAYOUT.classNames; + component.description = LAYOUT.description; + component.id = LAYOUT.id; + component.elements = LAYOUT.elements; + component.elementsOrder = LAYOUT.elementsOrder; + component.model = LAYOUT.model; + component.title = LAYOUT.title; + + fixture.detectChanges(); + + await fixture.whenStable(); + + let element = fixture.debugElement.nativeElement; + expect(element.querySelectorAll('core-contentfragment-default-renderer-v1').length).toBeGreaterThan(0); + expect(element.querySelectorAll('.cmp-contentfragment#' + LAYOUT.id).length).toBeGreaterThan(0); + expect(element.querySelectorAll('.cmp-mycontentfragment').length).toBeGreaterThan(0); + expect(element.querySelectorAll('mycontentfragment-renderer').length).toBeGreaterThan(0); + expect(element.querySelectorAll('.cmp-contentfragment__element').length).toBe(5); + expect(element.querySelectorAll('.cmp-mycontentfragment__element').length).toBe(0); + + + }); + + it('should show a placeholder if empty in author mode', async () => { + + MapToContentFragmentModel('testing-frontend-react/models/custom-model')(MyContentFragmentRenderer); + + isInEditorSpy.and.returnValue(true); + component.cqItems = {}; + component.cqItemsOrder = []; + component.classNames = LAYOUT.classNames; + component.description = LAYOUT.description; + component.id = LAYOUT.id; + component.elements = {}; + component.elementsOrder = []; + component.model = LAYOUT.model; + component.title = LAYOUT.title; + + fixture.detectChanges(); + + await fixture.whenStable(); + + let element = fixture.debugElement.nativeElement; + expect(element.querySelectorAll('core-contentfragment-default-renderer-v1').length).toBe(0); + + expect(element.querySelectorAll('.cf-placeholder').length).toBe(1); + + + }); + + it('should not show a placeholder if empty in publish mode', async () => { + + MapToContentFragmentModel('testing-frontend-react/models/custom-model')(MyContentFragmentRenderer); + + isInEditorSpy.and.returnValue(false); + component.cqItems = {}; + component.cqItemsOrder = []; + component.classNames = LAYOUT.classNames; + component.description = LAYOUT.description; + component.id = LAYOUT.id; + component.elements = {}; + component.elementsOrder = []; + component.model = LAYOUT.model; + component.title = LAYOUT.title; + + fixture.detectChanges(); + + await fixture.whenStable(); + + let element = fixture.debugElement.nativeElement; + expect(element.querySelectorAll('core-contentfragment-default-renderer-v1').length).toBe(0); + + expect(element.querySelectorAll('.cf-placeholder').length).toBe(0); + + + }); + + it('should render the lazy content fragment', async () => { + + LazyMapToContentFragmentModel('testing-frontend-react/models/testmodel')( + () => new Promise>((resolve, reject) => { + import('../../../../test/lazy-component-wrapper/lazy.component').then((module) => resolve(module.LazyComponent)).catch(reject) + }) + ); + + isInEditorSpy.and.returnValue(true); + component.cqItems = LAYOUT[Constants.ITEMS_PROP]; + component.cqItemsOrder = LAYOUT[Constants.ITEMS_ORDER_PROP]; + component.classNames = LAYOUT.classNames; + component.description = LAYOUT.description; + component.id = LAYOUT.id; + component.elements = LAYOUT.elements; + component.elementsOrder = LAYOUT.elementsOrder; + component.model = LAYOUT.model; + component.title = LAYOUT.title; + + fixture.detectChanges(); + + await fixture.whenStable(); + let element = fixture.debugElement.nativeElement; + expect(element.querySelectorAll('#lazyContentFragment-' + LAYOUT.id).length).toBe(1); + + }); + + +}); diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/defaultrenderer/contentfragment.v1.defaultrenderer.html b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/defaultrenderer/contentfragment.v1.defaultrenderer.html new file mode 100644 index 0000000..310b249 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/defaultrenderer/contentfragment.v1.defaultrenderer.html @@ -0,0 +1,31 @@ + + +
+

+ +
+ + +
+ + + + +
+ +
+ + +
+

{{name}}

+
value: {{element.value}}
+
datatype: {{element.dataType}}
+
multivalue: {{element.multiValue ? 'true': 'false'}}
+
type: {{element[":type"]}}
+
+
\ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/defaultrenderer/contentfragment.v1.defaultrenderer.ts b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/defaultrenderer/contentfragment.v1.defaultrenderer.ts new file mode 100644 index 0000000..b0c911b --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/defaultrenderer/contentfragment.v1.defaultrenderer.ts @@ -0,0 +1,32 @@ +import {Component, HostBinding, Input} from "@angular/core"; +import {AbstractContainerComponent} from "@adobe/aem-core-components-angular-spa/core"; +import {ContentFragmentV1Element, ContentFragmentV1Properties} from "../model"; + +@Component({ + selector: 'core-contentfragment-default-renderer-v1', + host: { + '[class]': 'hostClasses', + '[attr.data-cq-data-path]':'cqPath' + }, + templateUrl: './contentfragment.v1.defaultrenderer.html' +}) +export class ContentFragmentV1DefaultRenderer extends AbstractContainerComponent implements ContentFragmentV1Properties{ + + @HostBinding('class') baseCssClass = 'cmp-contentfragment'; + + @Input() description: string; + @Input() elements: { [p: string]: ContentFragmentV1Element }; + @Input() elementsOrder: string[]; + @Input() model: string; + @Input() title: string; + + getElement(element:string) { + return this.elements[element]; + } + + shouldShowElements():boolean{ + return this.elementsOrder && this.elementsOrder.length > 0; + } + + +} \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/model.ts b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/model.ts new file mode 100644 index 0000000..92252dc --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/model.ts @@ -0,0 +1,21 @@ +import {MappedComponentProperties} from "@adobe/aem-angular-editable-components"; +import {ContainerProperties, HasBaseCssClass} from "@adobe/aem-core-components-angular-spa/core"; + +export interface ContentFragmentV1Element { + "dataType": string + "value": string + "title": string + ":type": string + "multiValue": boolean +} + +export interface ContentFragmentV1Properties extends ContainerProperties, MappedComponentProperties, HasBaseCssClass { + + id: string + title: string + description: string, + elements: { [key: string]: ContentFragmentV1Element } + elementsOrder: string[] + model: string + +} \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/public-api.ts b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/public-api.ts new file mode 100644 index 0000000..0d3e168 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/public-api.ts @@ -0,0 +1,21 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2020 Adobe Systems Incorporated + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +export * from "./model"; +export * from "./contentfragment.v1.module"; +export * from "./contentfragment.v1.component"; +export * from "./contentfragment.v1.directive"; +export * from "./contentfragment.v1.registry"; \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/test.ts b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/test.ts new file mode 100644 index 0000000..87c7698 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/src/test.ts @@ -0,0 +1,34 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Copyright 2020 Adobe Systems Incorporated + ~ + ~ 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 'core-js/features/reflect'; +import 'zone.js/dist/zone'; +import 'zone.js/dist/zone-testing'; +import {getTestBed} from '@angular/core/testing'; +import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./../', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.lib.json b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.lib.json new file mode 100644 index 0000000..30766f6 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.lib.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.lib.json", + "exclude": [ + "src/test.ts", + "**/*.spec.ts" + ] +} diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.lib.prod.json b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.lib.prod.json new file mode 100644 index 0000000..cbae794 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.lib.prod.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.lib.json", + "angularCompilerOptions": { + "enableIvy": false + } +} diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.spec.json b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.spec.json new file mode 100644 index 0000000..659f815 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.spec.json", + "files": [ + "src/test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tslint.json b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tslint.json new file mode 100644 index 0000000..1f63906 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/containers/contentfragment/v1/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../../tslint.json" +} diff --git a/projects/aem-angular-core-spa-wcm-components/core/src/AbstractContainerComponent.ts b/projects/aem-angular-core-spa-wcm-components/core/src/AbstractContainerComponent.ts index 2bce21d..34f0acd 100644 --- a/projects/aem-angular-core-spa-wcm-components/core/src/AbstractContainerComponent.ts +++ b/projects/aem-angular-core-spa-wcm-components/core/src/AbstractContainerComponent.ts @@ -18,7 +18,7 @@ import {ComponentMapping, AEMAllowedComponentsContainerComponent, MappedComponentProperties, Utils} from "@adobe/aem-angular-editable-components"; import {Component, HostBinding, Injectable, Input, OnDestroy, AfterViewInit, PLATFORM_ID, Inject} from "@angular/core"; -import {ContainerModel, ContainerProperties, Model} from "./common"; +import {ContainerModel, ContainerProperties, HasBaseCssClass, Model} from "./common"; import {isPlatformBrowser} from "@angular/common"; export function ContainerIsEmptyFn(props:ContainerModel){ @@ -29,7 +29,7 @@ export function ContainerIsEmptyFn(props:ContainerModel){ selector: 'aem-core-abstract-container', template: '' }) -export class AbstractContainerComponent extends AEMAllowedComponentsContainerComponent implements ContainerProperties, AfterViewInit, OnDestroy, MappedComponentProperties{ +export class AbstractContainerComponent extends AEMAllowedComponentsContainerComponent implements ContainerProperties, HasBaseCssClass, AfterViewInit, OnDestroy, MappedComponentProperties{ @Input() componentMapping: typeof ComponentMapping = ComponentMapping; @Input() cqForceReload: boolean = false; @Input() cqItems: {[key: string]: Model} = {}; diff --git a/projects/aem-angular-core-spa-wcm-components/core/src/common.ts b/projects/aem-angular-core-spa-wcm-components/core/src/common.ts index 8a54de6..f04024b 100644 --- a/projects/aem-angular-core-spa-wcm-components/core/src/common.ts +++ b/projects/aem-angular-core-spa-wcm-components/core/src/common.ts @@ -22,6 +22,10 @@ export interface ContainerProperties extends MappedComponentProperties { cqItemsOrder: string[]; } +export interface HasBaseCssClass { + baseCssClass?:string; +} + export interface ContainerModel extends Model{ } diff --git a/projects/aem-angular-core-spa-wcm-components/src/aem-angular-core-spa-wcm-components.module.ts b/projects/aem-angular-core-spa-wcm-components/src/aem-angular-core-spa-wcm-components.module.ts index be8c8e0..01ab704 100644 --- a/projects/aem-angular-core-spa-wcm-components/src/aem-angular-core-spa-wcm-components.module.ts +++ b/projects/aem-angular-core-spa-wcm-components/src/aem-angular-core-spa-wcm-components.module.ts @@ -23,6 +23,7 @@ import {AemAngularCoreWcmComponentsAccordionV1} from "@adobe/aem-core-components import {AemAngularCoreWcmComponentsCarouselV1} from "@adobe/aem-core-components-angular-spa/containers/carousel/v1"; import {AemAngularCoreWcmComponentsTabsV1} from "@adobe/aem-core-components-angular-spa/containers/tabs/v1"; import {AemAngularCoreWcmComponentsContainerV1} from "@adobe/aem-core-components-angular-spa/containers/container/v1"; +import {AemAngularCoreWcmComponentsContentFragmentV1} from "@adobe/aem-core-components-angular-spa/containers/contentfragment/v1"; import {AemAngularCoreSpaWcmComponentsCore} from "@adobe/aem-core-components-angular-spa/core"; @NgModule({ @@ -33,6 +34,7 @@ import {AemAngularCoreSpaWcmComponentsCore} from "@adobe/aem-core-components-ang AemAngularCoreWcmComponentsCarouselV1, AemAngularCoreWcmComponentsContainerV1, AemAngularCoreWcmComponentsTabsV1, + AemAngularCoreWcmComponentsContentFragmentV1, AemAngularCoreSpaWcmComponentsCore ], exports: [ @@ -41,6 +43,7 @@ import {AemAngularCoreSpaWcmComponentsCore} from "@adobe/aem-core-components-ang AemAngularCoreWcmComponentsCarouselV1, AemAngularCoreWcmComponentsContainerV1, AemAngularCoreWcmComponentsTabsV1, + AemAngularCoreWcmComponentsContentFragmentV1, AemAngularCoreSpaWcmComponentsCore ] }) diff --git a/projects/aem-angular-core-spa-wcm-components/src/public-api.ts b/projects/aem-angular-core-spa-wcm-components/src/public-api.ts index 4b80431..09cbd20 100644 --- a/projects/aem-angular-core-spa-wcm-components/src/public-api.ts +++ b/projects/aem-angular-core-spa-wcm-components/src/public-api.ts @@ -21,5 +21,5 @@ export * from "@adobe/aem-core-components-angular-spa/containers/accordion/v1"; export * from "@adobe/aem-core-components-angular-spa/containers/carousel/v1"; export * from "@adobe/aem-core-components-angular-spa/containers/container/v1"; export * from "@adobe/aem-core-components-angular-spa/containers/tabs/v1"; - +export * from "@adobe/aem-core-components-angular-spa/containers/contentfragment/v1"; diff --git a/projects/aem-angular-core-spa-wcm-components/test/data/content-fragment.json b/projects/aem-angular-core-spa-wcm-components/test/data/content-fragment.json new file mode 100644 index 0000000..b3dbf2c --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/test/data/content-fragment.json @@ -0,0 +1,109 @@ +{ + "id": "content-fragment-449f30249d", + "title": "niektest", + "description": "", + ":items": { + "custom-content-fragment": { + "id": "content-fragment-36fdd89c42", + "title": "niektest", + "description": "", + ":items": {}, + ":itemsOrder": [], + ":type": "core-components-examples/wcm-react/components/content-fragment", + "elements": { + "name": { + "dataType": "string", + "value": "ChildCF", + "title": "First name", + ":type": "string", + "multiValue": false + }, + "age": { + "dataType": "long", + "value": 18, + "title": "Age", + ":type": "long", + "multiValue": false + }, + "blabla": { + "dataType": "string", + "value": "

asddsdsa

\n

asdadadsads

\n", + "title": "blabla", + ":type": "text/html", + "multiValue": false + }, + "testtest": { + "dataType": "json", + "value": "{\"bla\":\"asd\"}", + "title": "testtest", + ":type": "json", + "multiValue": false + }, + "dropdownField": { + "dataType": "string", + "value": "b", + "title": "Dropdown Field", + ":type": "string", + "multiValue": false + } + }, + "elementsOrder": [ + "name", + "age", + "blabla", + "testtest", + "dropdownField" + ], + "model": "testing-frontend-react/models/custom-model" + } + }, + ":itemsOrder": [ + "custom-content-fragment" + ], + ":type": "core-components-examples/wcm-react/components/content-fragment", + "elements": { + "name": { + "dataType": "string", + "value": "ChildCF", + "title": "First name", + ":type": "string", + "multiValue": false + }, + "age": { + "dataType": "long", + "value": 18, + "title": "Age", + ":type": "long", + "multiValue": false + }, + "blabla": { + "dataType": "string", + "value": "

asddsdsa

\n

asdadadsads

\n", + "title": "blabla", + ":type": "text/html", + "multiValue": false + }, + "testtest": { + "dataType": "json", + "value": "{\"bla\":\"asd\"}", + "title": "testtest", + ":type": "json", + "multiValue": false + }, + "dropdownField": { + "dataType": "string", + "value": "b", + "title": "Dropdown Field", + ":type": "string", + "multiValue": false + } + }, + "elementsOrder": [ + "name", + "age", + "blabla", + "testtest", + "dropdownField" + ], + "model": "testing-frontend-react/models/testmodel" +} \ No newline at end of file diff --git a/projects/aem-angular-core-spa-wcm-components/test/lazy-component-wrapper/lazy.component.ts b/projects/aem-angular-core-spa-wcm-components/test/lazy-component-wrapper/lazy.component.ts new file mode 100644 index 0000000..5d8e694 --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/test/lazy-component-wrapper/lazy.component.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {Component, Input, OnInit} from '@angular/core'; +import {ContentFragmentV1Component,ContentFragmentV1Properties} from "../../containers/contentfragment/v1/src/public-api"; + + +@Component({ + selector: 'lazy-comp', + template: `
hi!
` +}) +/** + * The current class carries the base presentational logic of the AEM Layout Container (aka. Responsive grid) + */ +export class LazyComponent extends ContentFragmentV1Component implements ContentFragmentV1Properties, OnInit { + ngOnInit(): void { + console.log("test") + } + +} + +export default LazyComponent; diff --git a/projects/aem-angular-core-spa-wcm-components/test/lazy-component-wrapper/lazy.entry.ts b/projects/aem-angular-core-spa-wcm-components/test/lazy-component-wrapper/lazy.entry.ts new file mode 100644 index 0000000..06ab75a --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/test/lazy-component-wrapper/lazy.entry.ts @@ -0,0 +1,13 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +export * from './lazy.module'; +export * from './lazy.component'; diff --git a/projects/aem-angular-core-spa-wcm-components/test/lazy-component-wrapper/lazy.module.ts b/projects/aem-angular-core-spa-wcm-components/test/lazy-component-wrapper/lazy.module.ts new file mode 100644 index 0000000..9dac66e --- /dev/null +++ b/projects/aem-angular-core-spa-wcm-components/test/lazy-component-wrapper/lazy.module.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; +import { LazyComponent } from './lazy.component'; + +@NgModule({ + imports: [ CommonModule, BrowserModule, RouterModule ], + declarations: [ + LazyComponent + ] +}) +export class LazyModule { +}