;
36 | let isInEditorSpy;
37 |
38 | beforeEach(() => {
39 | spyOn(ModelManager, 'addListener').and.returnValue(undefined);
40 | isInEditorSpy = spyOn(Utils, 'isInEditor').and.returnValue(false);
41 |
42 | const config = {
43 | declarations: [
44 | AEMContainerComponent,
45 | AEMComponentDirective,
46 | AEMModelProviderComponent,
47 | Test1Component,
48 | Test2Component,
49 | Test3Component,
50 | AEMAllowedComponentsContainerComponent,
51 | AEMResponsiveGridComponent
52 | ]
53 | };
54 |
55 | const override = {
56 | set: {
57 | entryComponents: [
58 | Test1Component,
59 | Test2Component,
60 | Test3Component,
61 | AEMResponsiveGridComponent
62 | ]
63 | }
64 | };
65 |
66 | TestBed.configureTestingModule(config).overrideModule(BrowserDynamicTestingModule, override).compileComponents();
67 |
68 | fixture = TestBed.createComponent(AEMResponsiveGridComponent);
69 | component = fixture.componentInstance;
70 | fixture.detectChanges();
71 | });
72 |
73 | it('should create placeholder', () => {
74 | isInEditorSpy.and.returnValue(true);
75 | component.cqItems = LAYOUT[Constants.ITEMS_PROP];
76 | component.cqItemsOrder = LAYOUT[Constants.ITEMS_ORDER_PROP];
77 | component.gridClassNames = LAYOUT.gridClassNames;
78 | component.columnClassNames = LAYOUT.columnClassNames;
79 | component.classNames = LAYOUT.classNames;
80 | fixture.detectChanges();
81 |
82 | let element = fixture.nativeElement;
83 |
84 | element = element.firstElementChild;
85 | expect(element.querySelector('div[data-cq-data-path="root"][class="test-class-names"]')).toBeDefined();
86 | expect(element.querySelector('div[data-cq-data-path="root/*"][class="new section aem-Grid-newComponent"]')).toBeDefined();
87 | expect(element.querySelector('div[data-cq-data-path="root/responsivegrid/*"][class="new section aem-Grid-newComponent"]')).toBeDefined();
88 | });
89 |
90 | // it('should create the allowed components with the default title and no allowed component', () => {
91 | // isInEditorSpy.and.returnValue(true);
92 | // component.title = TEST_COMPONENT_TITLE;
93 | // component.allowedComponents = {
94 | // applicable: true,
95 | // components: []
96 | // };
97 |
98 | // fixture.detectChanges();
99 |
100 | // const element = fixture.nativeElement.firstElementChild;
101 | // const titleElement = element.querySelector('.' + ALLOWED_COMPONENT_TITLE_CLASS_NAMES);
102 |
103 | // expect(element.classList.contains(ALLOWED_PLACEHOLDER_CLASS_NAMES)).toBeTruthy();
104 | // expect(titleElement).toBeTruthy();
105 | // expect(titleElement.dataset.text).toEqual('No allowed components');
106 | // expect(element.querySelectorAll('.aem-AllowedComponent--component.cq-placeholder.placeholder').length).toEqual(0);
107 | // });
108 |
109 | it('should create the allowed components with a custom title and allowed components', () => {
110 | isInEditorSpy.and.returnValue(true);
111 | component.title = TEST_COMPONENT_TITLE;
112 | component.allowedComponents = {
113 | applicable: true,
114 | components: [
115 | {
116 | path: 'test/components/component1',
117 | title: 'Test component title 1'
118 | },
119 | {
120 | path: 'test/components/component2',
121 | title: 'Test component title 2'
122 | }
123 | ]
124 | };
125 |
126 | fixture.detectChanges();
127 |
128 | const element = fixture.nativeElement.firstElementChild;
129 | const titleElement = element.querySelector('.' + ALLOWED_COMPONENT_TITLE_CLASS_NAMES);
130 |
131 | expect(element.classList.contains(ALLOWED_PLACEHOLDER_CLASS_NAMES)).toBeTruthy();
132 | expect(titleElement).toBeTruthy();
133 | expect(titleElement.dataset.text).toEqual(TEST_COMPONENT_TITLE);
134 | expect(element.querySelectorAll('.aem-AllowedComponent--component.cq-placeholder.placeholder').length).toEqual(2);
135 | });
136 |
137 | // it('should NOT create the allowed components if not in the editor', () => {
138 | // component.title = TEST_COMPONENT_TITLE;
139 | // component.allowedComponents = {
140 | // applicable: true,
141 | // components: [
142 | // {
143 | // path: 'test/components/component1',
144 | // title: 'Test component title 1'
145 | // },
146 | // {
147 | // path: 'test/components/component2',
148 | // title: 'Test component title 2'
149 | // }
150 | // ]
151 | // };
152 |
153 | // fixture.detectChanges();
154 |
155 | // const element = fixture.nativeElement;
156 |
157 | // expect(element.querySelector('.' + ALLOWED_COMPONENT_TITLE_CLASS_NAMES)).toBeNull();
158 | // expect(element.classList.contains(ALLOWED_PLACEHOLDER_CLASS_NAMES)).toBeFalsy();
159 | // expect(element.querySelectorAll('.aem-AllowedComponent--component.cq-placeholder.placeholder').length).toEqual(0);
160 | // expect(element.querySelector('div[data-cq-data-path="root/*"][class="new section aem-Grid-newComponent"]')).toBeDefined();
161 | // expect(element.querySelector('div[data-cq-data-path="root/responsivegrid/*"][class="new section aem-Grid-newComponent"]')).toBeDefined();
162 | // });
163 | });
164 |
--------------------------------------------------------------------------------
/src/lib/layout/component-mapping.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Adobe. All rights reserved.
3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License. You may obtain a copy
5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software distributed under
8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | * OF ANY KIND, either express or implied. See the License for the specific language
10 | * governing permissions and limitations under the License.
11 | */
12 |
13 | import { ComponentMapping as SPAComponentMapping } from '@adobe/aem-spa-component-mapping';
14 | import { Directive, Input, Type } from '@angular/core';
15 |
16 | /**
17 | * Indicated whether force reload is turned on, forcing the model to be refetched on every MapTo instantiation.
18 | */
19 | export interface ReloadForceAble {
20 | cqForceReload?: boolean;
21 | }
22 |
23 | /**
24 | * MappedComponentProperties
25 | * Properties given to every component runtime by the SPA editor.
26 | */
27 | export interface MappedComponentProperties extends ReloadForceAble {
28 | /**
29 | * Path to the model associated with the current instance of the component
30 | */
31 | cqPath: string;
32 |
33 | /**
34 | * Angular item name
35 | */
36 | itemName: string;
37 | }
38 |
39 | /**
40 | * EditConfiguration for a MappedComponent
41 | * @type Type of the MappedComponent, used in isEmpty
42 | */
43 | export interface EditConfig
{
44 | /**
45 | * Label to display if the component is considered empty in author mode
46 | */
47 | emptyLabel?: string;
48 |
49 | /**
50 | * Return whether the component should be considered 'empty'.
51 | * If empty, the component will not be rendered. In author mode, the empty label will be displayed.
52 | * @param props
53 | @type
Type of the MappedComponent
54 | */
55 | isEmpty(props: P): boolean;
56 | }
57 |
58 | /**
59 | * Provides standard implementation for the MappedComponentProperties using @Input
60 | */
61 | @Directive()
62 | export abstract class AbstractMappedComponentDirective implements MappedComponentProperties {
63 | @Input() isInEditor = false;
64 | @Input() cqPath = '';
65 | @Input() itemName = '';
66 | }
67 |
68 | /**
69 | * The current class extends the @adobe/cq-spa-component-mapping#Mapto library and features with Angular specifics such as
70 | *
71 | * - Storing the editing configurations for each resource type
72 | */
73 | export class ComponentMappingWithConfig {
74 | /**
75 | * Store of EditConfig structures
76 | */
77 | private editConfigMap : { [key: string]: EditConfig; } = {};
78 |
79 | constructor(private spaMapping: SPAComponentMapping) {}
80 |
81 | /**
82 | * Stores a component class for the given resource types and also allows to provide an EditConfig object
83 | * @param resourceTypes - List of resource types
84 | * @param clazz - Component class to be stored
85 | * @param [editConfig] - Edit configuration to be stored for the given resource types
86 | * @type Model - The Model interface / class type bound to the editconfig object.
87 | */
88 | map(resourceTypes: string | string[], clazz:Type, editConfig:EditConfig = null) : void {
89 | const innerClass = clazz;
90 |
91 | const resourceList = (typeof resourceTypes === 'string') ? [ resourceTypes ] : resourceTypes;
92 |
93 | resourceList.forEach((entry) => {
94 | if (editConfig) {
95 | this.editConfigMap[entry] = editConfig;
96 | }
97 | this.spaMapping.map(entry, innerClass);
98 | });
99 |
100 | }
101 |
102 | /**
103 | * Stores a component class for the given resource types and also allows to provide an EditConfig object in a Lazy Manner
104 | * @param resourceTypes - List of resource types
105 | * @param lazyClassFunction - A function that returns a promise to give back the designated type / class
106 | * @param [editConfig] - Edit configuration to be stored for the given resource types
107 | * @type Model - The Model interface / class type bound to the editconfig object.
108 | */
109 | lazyMap(resourceTypes, lazyClassFunction: () => Promise>, editConfig:EditConfig = null) {
110 | const innerFunction = lazyClassFunction;
111 |
112 | if (editConfig) {
113 | this.editConfigMap[resourceTypes] = editConfig;
114 | }
115 | this.spaMapping.lazyMap(resourceTypes, innerFunction);
116 | }
117 |
118 | /**
119 | * Returns the component class for the given resourceType
120 | * @param resourceType - Resource type for which the component class has been stored
121 | * @type Model - The Model interface / class type bound to the editconfig object.
122 | */
123 | get(resourceType:string):Type {
124 | return this.spaMapping.get(resourceType) as Type;
125 | }
126 |
127 | /**
128 | * Returns the component class Promise for the given resourceType
129 | * @param resourceType - Resource type for which the component class has been stored
130 | * @type Model - The Model interface / class type bound to the editconfig object.
131 | */
132 | lazyGet(resourceType: string): Promise> {
133 | return this.spaMapping.getLazy(resourceType) as Promise>;
134 | }
135 |
136 | /**
137 | * Returns the EditConfig structure for the given type
138 | * @param resourceType - Resource type for which the configuration has been stored
139 | * @type Model - The Model interface / class type bound to the editconfig object.
140 | */
141 | getEditConfig(resourceType:string):EditConfig {
142 | return this.editConfigMap[resourceType];
143 | }
144 | }
145 |
146 | const componentMapping = new ComponentMappingWithConfig(SPAComponentMapping);
147 |
148 | /**
149 | * Stores a component class for the given resource types and also allows to provide an EditConfig object
150 | * @param resourceTypes - List of resource types
151 | * @type Model - The Model interface / class type that will be Mapped. Bound to the EditConfig configuration.
152 | */
153 | function MapTo(resourceTypes: string | string[]) {
154 | /**
155 | * @param clazz - Component class to be stored
156 | * @param [editConfig] - Edit configuration to be stored for the given resource types
157 | */
158 | return (clazz:Type, editConfig:EditConfig = null): void =>
159 | componentMapping.map(resourceTypes, clazz, editConfig);
160 | }
161 |
162 | /**
163 | * Stores a clazz the lazy way for dynamic imports / code splitting.function that returns a promise
164 | * @param resourceTypes - List of resource types
165 | * @type Model - The Model interface / class type that will be Mapped. Bound to the EditConfig configuration.
166 | */
167 | function LazyMapTo(resourceTypes: string | string[]) {
168 | /**
169 | * @param lazyClassPromise - Function that returns a promise resolving a class
170 | * @param [editConfig] - Edit configuration to be stored for the given resource types
171 | */
172 | return (lazyClassFunction: () => Promise>, editConfig: EditConfig = null): void =>
173 | componentMapping.lazyMap(resourceTypes, lazyClassFunction, editConfig);
174 | }
175 |
176 | export { componentMapping as ComponentMapping, MapTo, LazyMapTo };
177 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /*
2 | 👋 Hi! This file was autogenerated by tslint-to-eslint-config.
3 | https://github.com/typescript-eslint/tslint-to-eslint-config
4 |
5 | It represents the closest reasonable ESLint configuration to this
6 | project's original TSLint configuration.
7 |
8 | We recommend eventually switching this configuration to extend from
9 | the recommended rulesets in typescript-eslint.
10 | https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md
11 |
12 | Happy linting! 💖
13 | */
14 | module.exports = {
15 | "env": {
16 | "browser": true,
17 | "es6": true,
18 | "node": true
19 | },
20 | "ignorePatterns": [
21 | "!.*",
22 | "package-lock.json",
23 | "node_modules/",
24 | "dist/",
25 | ".git/",
26 | "!.*",
27 | "package-lock.json",
28 | "node_modules/",
29 | "dist/",
30 | ".git/",
31 | "tsconfig.json"
32 | ],
33 | "parser": "@typescript-eslint/parser",
34 | "parserOptions": {
35 | "project": "tsconfig.json",
36 | "sourceType": "module"
37 | },
38 | "plugins": [
39 | "eslint-plugin-import",
40 | "@angular-eslint/eslint-plugin",
41 | "@typescript-eslint",
42 | "@typescript-eslint/tslint"
43 | ],
44 | "root": true,
45 | "rules": {
46 | "@angular-eslint/component-class-suffix": "error",
47 | "@angular-eslint/component-selector": [
48 | "error",
49 | {
50 | "type": "element",
51 | "prefix": "aem",
52 | "style": "kebab-case"
53 | }
54 | ],
55 | "@angular-eslint/directive-class-suffix": "error",
56 | "@angular-eslint/directive-selector": [
57 | "error",
58 | {
59 | "type": "attribute",
60 | "prefix": "aem",
61 | "style": "camelCase"
62 | }
63 | ],
64 | "@angular-eslint/no-host-metadata-property": "error",
65 | "@angular-eslint/no-input-rename": "error",
66 | "@angular-eslint/no-inputs-metadata-property": "error",
67 | "@angular-eslint/no-output-on-prefix": "error",
68 | "@angular-eslint/no-output-rename": "error",
69 | "@angular-eslint/no-outputs-metadata-property": "error",
70 | "@angular-eslint/use-lifecycle-interface": "error",
71 | "@angular-eslint/use-pipe-transform-interface": "error",
72 | "@typescript-eslint/consistent-type-definitions": "error",
73 | "@typescript-eslint/dot-notation": "off",
74 | "@typescript-eslint/explicit-member-accessibility": [
75 | "off",
76 | {
77 | "accessibility": "explicit"
78 | }
79 | ],
80 | "@typescript-eslint/indent": "error",
81 | "@typescript-eslint/member-delimiter-style": [
82 | "error",
83 | {
84 | "multiline": {
85 | "delimiter": "semi",
86 | "requireLast": true
87 | },
88 | "singleline": {
89 | "delimiter": "semi",
90 | "requireLast": false
91 | }
92 | }
93 | ],
94 | "@typescript-eslint/member-ordering": "error",
95 | "@typescript-eslint/naming-convention": [
96 | "error",
97 | {
98 | "selector": "variable",
99 | "format": [
100 | "camelCase",
101 | "UPPER_CASE"
102 | ],
103 | "leadingUnderscore": "forbid",
104 | "trailingUnderscore": "forbid"
105 | }
106 | ],
107 | "@typescript-eslint/no-empty-function": "off",
108 | "@typescript-eslint/no-empty-interface": "error",
109 | "@typescript-eslint/no-inferrable-types": [
110 | "error",
111 | {
112 | "ignoreParameters": true
113 | }
114 | ],
115 | "@typescript-eslint/no-misused-new": "error",
116 | "@typescript-eslint/no-non-null-assertion": "error",
117 | "@typescript-eslint/no-shadow": [
118 | "error",
119 | {
120 | "hoist": "all"
121 | }
122 | ],
123 | "@typescript-eslint/no-unused-expressions": "error",
124 | "@typescript-eslint/no-use-before-define": "error",
125 | "@typescript-eslint/prefer-function-type": "error",
126 | "@typescript-eslint/quotes": [
127 | "error",
128 | "single"
129 | ],
130 | "@typescript-eslint/semi": [
131 | "error",
132 | "always"
133 | ],
134 | "@typescript-eslint/type-annotation-spacing": "error",
135 | "@typescript-eslint/unified-signatures": "error",
136 | "arrow-body-style": "error",
137 | "brace-style": [
138 | "error",
139 | "1tbs"
140 | ],
141 | "constructor-super": "error",
142 | "curly": "error",
143 | "dot-notation": "off",
144 | "eol-last": "error",
145 | "eqeqeq": [
146 | "error",
147 | "smart"
148 | ],
149 | "guard-for-in": "error",
150 | "id-denylist": "off",
151 | "id-match": "off",
152 | "import/no-deprecated": "warn",
153 | "indent": "off",
154 | "max-len": [
155 | "error",
156 | {
157 | "code": 140
158 | }
159 | ],
160 | "no-bitwise": "error",
161 | "no-caller": "error",
162 | "no-console": [
163 | "error",
164 | {
165 | "allow": [
166 | "log",
167 | "warn",
168 | "dir",
169 | "timeLog",
170 | "assert",
171 | "clear",
172 | "count",
173 | "countReset",
174 | "group",
175 | "groupEnd",
176 | "table",
177 | "dirxml",
178 | "error",
179 | "groupCollapsed",
180 | "Console",
181 | "profile",
182 | "profileEnd",
183 | "timeStamp",
184 | "context"
185 | ]
186 | }
187 | ],
188 | "no-debugger": "error",
189 | "no-empty": "off",
190 | "no-empty-function": "off",
191 | "no-eval": "error",
192 | "no-fallthrough": "error",
193 | "no-new-wrappers": "error",
194 | "no-restricted-imports": [
195 | "error",
196 | "rxjs/Rx"
197 | ],
198 | "no-shadow": "off",
199 | "no-throw-literal": "error",
200 | "no-trailing-spaces": "error",
201 | "no-undef-init": "error",
202 | "no-underscore-dangle": "off",
203 | "no-unused-expressions": "off",
204 | "no-unused-labels": "error",
205 | "no-use-before-define": "off",
206 | "no-var": "error",
207 | "prefer-const": "error",
208 | "quotes": "off",
209 | "radix": "error",
210 | "semi": "off",
211 | "spaced-comment": [
212 | "error",
213 | "always",
214 | {
215 | "markers": [
216 | "/"
217 | ]
218 | }
219 | ],
220 | "@typescript-eslint/tslint/config": [
221 | "error",
222 | {
223 | "rules": {
224 | "import-spacing": true,
225 | "whitespace": [
226 | true,
227 | "check-branch",
228 | "check-decl",
229 | "check-operator",
230 | "check-separator",
231 | "check-type"
232 | ]
233 | }
234 | }
235 | ]
236 | }
237 | };
238 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [2.0.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.4.0...v2.0.0) (2023-09-13)
2 |
3 |
4 | ### Features
5 |
6 | * trigger release ([d909c3f](https://github.com/adobe/aem-angular-editable-components/commit/d909c3f0233d6594b191c72636a89e39553b2031))
7 |
8 |
9 | * feat!: update angular ([1759bfb](https://github.com/adobe/aem-angular-editable-components/commit/1759bfb73a58b36d1a3c805dd108aeba859b2a4a))
10 | * feat!: update angular version ([d352425](https://github.com/adobe/aem-angular-editable-components/commit/d3524257772aa743beeb21014cfde648b32ddb54))
11 | * feat!: updated the commit message for breaking changes ([9903864](https://github.com/adobe/aem-angular-editable-components/commit/9903864aef0d61a2720aae6a12bf5344e214fd22))
12 |
13 |
14 | ### Reverts
15 |
16 | * Revert "Added ModelManager.initialize" ([018cc79](https://github.com/adobe/aem-angular-editable-components/commit/018cc79958399961ffc375925a2e419f55a1ff16))
17 |
18 |
19 | ### BREAKING CHANGES
20 |
21 | * angular version updated
22 | * angular update to non bacnward-compatible version
23 | * angular version update to non backward-compatible version
24 | * The project was upgraded to Angular v13
25 |
26 | # [2.0.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.4.0...v2.0.0) (2023-09-13)
27 |
28 |
29 | ### Features
30 |
31 | * trigger release ([d909c3f](https://github.com/adobe/aem-angular-editable-components/commit/d909c3f0233d6594b191c72636a89e39553b2031))
32 |
33 |
34 | * feat!: update angular ([1759bfb](https://github.com/adobe/aem-angular-editable-components/commit/1759bfb73a58b36d1a3c805dd108aeba859b2a4a))
35 | * feat!: update angular version ([d352425](https://github.com/adobe/aem-angular-editable-components/commit/d3524257772aa743beeb21014cfde648b32ddb54))
36 | * feat!: updated the commit message for breaking changes ([9903864](https://github.com/adobe/aem-angular-editable-components/commit/9903864aef0d61a2720aae6a12bf5344e214fd22))
37 |
38 |
39 | ### Reverts
40 |
41 | * Revert "Added ModelManager.initialize" ([018cc79](https://github.com/adobe/aem-angular-editable-components/commit/018cc79958399961ffc375925a2e419f55a1ff16))
42 |
43 |
44 | ### BREAKING CHANGES
45 |
46 | * angular version updated
47 | * angular update to non bacnward-compatible version
48 | * angular version update to non backward-compatible version
49 | * The project was upgraded to Angular v13
50 |
51 | # [2.0.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.4.0...v2.0.0) (2023-09-13)
52 |
53 |
54 | ### Features
55 |
56 | * trigger release ([d909c3f](https://github.com/adobe/aem-angular-editable-components/commit/d909c3f0233d6594b191c72636a89e39553b2031))
57 |
58 |
59 | * feat!: update angular ([1759bfb](https://github.com/adobe/aem-angular-editable-components/commit/1759bfb73a58b36d1a3c805dd108aeba859b2a4a))
60 | * feat!: update angular version ([d352425](https://github.com/adobe/aem-angular-editable-components/commit/d3524257772aa743beeb21014cfde648b32ddb54))
61 | * feat!: updated the commit message for breaking changes ([9903864](https://github.com/adobe/aem-angular-editable-components/commit/9903864aef0d61a2720aae6a12bf5344e214fd22))
62 |
63 |
64 | ### Reverts
65 |
66 | * Revert "Added ModelManager.initialize" ([018cc79](https://github.com/adobe/aem-angular-editable-components/commit/018cc79958399961ffc375925a2e419f55a1ff16))
67 |
68 |
69 | ### BREAKING CHANGES
70 |
71 | * angular version updated
72 | * angular update to non bacnward-compatible version
73 | * angular version update to non backward-compatible version
74 | * The project was upgraded to Angular v13
75 |
76 | # [1.4.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.3.0...v1.4.0) (2021-09-20)
77 |
78 |
79 | ### Features
80 |
81 | * basic support for remote SPA ([e34c124](https://github.com/adobe/aem-angular-editable-components/commit/e34c124bb472c5ed24b320da8b41fcb0cf4f301d))
82 | * support for remote angular app editing in AEM ([1671ec2](https://github.com/adobe/aem-angular-editable-components/commit/1671ec2989ae0ae340c84a1c3767b45b829ddd62))
83 |
84 | # [1.3.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.2.1...v1.3.0) (2021-09-02)
85 |
86 |
87 | ### Features
88 |
89 | * **stylesytem:** StyleSystem Support ([3816ec1](https://github.com/adobe/aem-angular-editable-components/commit/3816ec12f578dce4e7f199a2cebc66033cd74c18))
90 |
91 | ## [1.2.1](https://github.com/adobe/aem-angular-editable-components/compare/v1.2.0...v1.2.1) (2021-07-14)
92 |
93 |
94 | ### Bug Fixes
95 |
96 | * **types:** fixing some wrong return types on aemallowedcontainercomponent and aemcontainer component ([47a3505](https://github.com/adobe/aem-angular-editable-components/commit/47a35055df74cd521138fd0f562427da5cbeaac7))
97 |
98 | # [1.2.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.1.0...v1.2.0) (2021-01-12)
99 |
100 |
101 | ### Features
102 |
103 | * Typed MapTo / LazyMapTo ([a2f2e6b](https://github.com/adobe/aem-angular-editable-components/commit/a2f2e6b4e5482bee73ab411342080be66beda742))
104 |
105 | # [1.1.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.7...v1.1.0) (2021-01-11)
106 |
107 |
108 | ### Features
109 |
110 | * support lazy loading ([55452ff](https://github.com/adobe/aem-angular-editable-components/commit/55452ff211270be440bd178aab91c08b711bd0c2))
111 |
112 | ## [1.0.7](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.6...v1.0.7) (2021-01-05)
113 |
114 |
115 | ### Bug Fixes
116 |
117 | * **docs:** remove --mode from docs build after a typedoc breaking change ([3baa981](https://github.com/adobe/aem-angular-editable-components/commit/3baa98167627213a193fce4794dc0ef4a17a699b))
118 |
119 | ## [1.0.6](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.5...v1.0.6) (2020-11-05)
120 |
121 |
122 | ### Bug Fixes
123 |
124 | * docs generation command typo, releasing from dist directory ([78bde15](https://github.com/adobe/aem-angular-editable-components/commit/78bde15d29405ea9c82c0cdfa99ab7f3394662fb))
125 |
126 | ## [1.0.5](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.4...v1.0.5) (2020-11-05)
127 |
128 |
129 | ### Bug Fixes
130 |
131 | * proper pkgRoot for semantic-release ([9051a12](https://github.com/adobe/aem-angular-editable-components/commit/9051a128453218fa403d1e93f9300fe48fe92137))
132 |
133 | ## [1.0.4](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.3...v1.0.4) (2020-11-04)
134 |
135 |
136 | ### Bug Fixes
137 |
138 | * **eslint:** adapt code according to eslint reports ([6fa889c](https://github.com/adobe/aem-angular-editable-components/commit/6fa889c2d45682d65ef48b00c5773890a6b3df95))
139 | * **package.json:** cleanup ([e22e411](https://github.com/adobe/aem-angular-editable-components/commit/e22e411087e2aeef90a72179a10bd1c2f413a134))
140 |
141 | ## [1.0.3](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.2...v1.0.3) (2020-09-03)
142 |
143 |
144 | ### Bug Fixes
145 |
146 | * **semantic-release:** fix the build and release process ([4c0e647](https://github.com/adobe/aem-angular-editable-components/commit/4c0e647026ac96a3e8545df869a67b43150ef31a))
147 | * **semantic-release:** release from dist in a proper way ([133bde0](https://github.com/adobe/aem-angular-editable-components/commit/133bde098e84750c402e17b96b3b7fe0a43157f8))
148 |
149 | ## [1.0.2](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.1...v1.0.2) (2020-09-03)
150 |
151 |
152 | ### Bug Fixes
153 |
154 | * **semantic-release:** release angular library from compiled files ([66e01e1](https://github.com/adobe/aem-angular-editable-components/commit/66e01e16edcd8bcd6d4f01e4a64bebf820962cc3))
155 | * **semantic-release:** release proper package ([972e361](https://github.com/adobe/aem-angular-editable-components/commit/972e3618d43009186cdf25450fd6cf1dda8c3523))
156 |
157 | ## [1.0.1](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.0...v1.0.1) (2020-09-02)
158 |
159 |
160 | ### Bug Fixes
161 |
162 | * **semantic-release:** fix automatic release process ([b8c0d70](https://github.com/adobe/aem-angular-editable-components/commit/b8c0d70b3aab4f0804013c9b010c39c2e51e4244))
163 |
--------------------------------------------------------------------------------
/src/lib/layout/aem-component.directive.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Adobe. All rights reserved.
3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License. You may obtain a copy
5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software distributed under
8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | * OF ANY KIND, either express or implied. See the License for the specific language
10 | * governing permissions and limitations under the License.
11 | */
12 |
13 | import {
14 | AfterViewInit,
15 | ChangeDetectorRef,
16 | Compiler,
17 | ComponentFactory,
18 | ComponentFactoryResolver,
19 | ComponentRef,
20 | Directive,
21 | Injector,
22 | Input,
23 | OnChanges,
24 | OnDestroy,
25 | OnInit,
26 | Renderer2,
27 | Type,
28 | ViewContainerRef
29 | } from '@angular/core';
30 |
31 | import { ComponentMapping, MappedComponentProperties } from './component-mapping';
32 | import { Constants } from './constants';
33 | import { Utils } from './utils';
34 |
35 | /**
36 | * @private
37 | */
38 | const PLACEHOLDER_CLASS_NAME = 'cq-placeholder';
39 |
40 | @Directive({
41 | selector: '[aemComponent]'
42 | })
43 | /**
44 | * The current directive provides advanced capabilities among which are
45 | *
46 | * - The management of the component placeholder in the Page Editor
47 | * - The dynamic instantiation of components based on a component definition
48 | * - The conversion from model fields to properties and injection in the component instance
49 | * - The management of HTMLElement attributes and class names on the native element
50 | */
51 | export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy, OnChanges {
52 |
53 | /**
54 | * Dynamically created component
55 | */
56 | private _component: ComponentRef;
57 | /**
58 | * Model item that corresponds to the current component
59 | */
60 | private _cqItem: any;
61 |
62 | get cqItem(): any {
63 | return this._cqItem;
64 | }
65 |
66 | @Input()
67 | set cqItem(value: any) {
68 | this._cqItem = value;
69 | }
70 |
71 | get changeDetectorRef(): ChangeDetectorRef {
72 | return this._changeDetectorRef;
73 | }
74 |
75 | /**
76 | * Path to the model structure associated with the current component
77 | */
78 | @Input() cqPath: string;
79 |
80 | /**
81 | * Name of the current instance of the component
82 | */
83 | @Input() itemName: string;
84 |
85 | /**
86 | * HtmlElement attributes for the current instance of the component
87 | */
88 | @Input() itemAttrs: any;
89 |
90 | @Input() loaded: boolean;
91 |
92 | @Input() aemComponent;
93 |
94 | constructor(
95 | private renderer: Renderer2,
96 | private viewContainer: ViewContainerRef,
97 | private compiler: Compiler,
98 | private injector: Injector,
99 | private factoryResolver: ComponentFactoryResolver,
100 | private _changeDetectorRef: ChangeDetectorRef) {
101 | }
102 |
103 | async ngOnInit() {
104 |
105 | if (this.type) {
106 | const mappedFn:Type = ComponentMapping.get(this.type);
107 |
108 | if (mappedFn) {
109 | this.renderComponent(mappedFn);
110 | } else {
111 | await this.initializeAsync();
112 | }
113 | } else {
114 | console.warn('no type on ' + this.cqPath);
115 | }
116 |
117 | }
118 |
119 | async initializeAsync() {
120 | const lazyMappedPromise: Promise> = ComponentMapping.lazyGet(this.type);
121 |
122 | try {
123 | const LazyResolvedComponent = await lazyMappedPromise;
124 |
125 | this.renderComponent(LazyResolvedComponent);
126 | this.loaded = true;
127 | this._changeDetectorRef.detectChanges();
128 | } catch (err) {
129 | console.warn(err);
130 | }
131 | }
132 |
133 | ngOnChanges(): void {
134 | this.updateComponentData();
135 | }
136 |
137 | /**
138 | * Returns the type of the cqItem if exists.
139 | */
140 | get type(): string | undefined {
141 | return this.cqItem && this.cqItem[Constants.TYPE_PROP];
142 | }
143 |
144 | /**
145 | * Renders a component dynamically based on the component definition
146 | *
147 | * @param componentDefinition The component definition to render
148 | */
149 | private renderComponent(componentDefinition: Type) {
150 | if (componentDefinition) {
151 | const factory = this.factoryResolver.resolveComponentFactory(componentDefinition);
152 |
153 | this.renderWithFactory(factory);
154 | } else {
155 | throw new Error('No component definition!!');
156 | }
157 | }
158 |
159 | private renderWithFactory(factory: ComponentFactory) {
160 | this.viewContainer.clear();
161 | this._component = this.viewContainer.createComponent(factory);
162 | this.updateComponentData();
163 | }
164 |
165 | /**
166 | * Updates the data of the component based the data of the directive
167 | */
168 | private updateComponentData() {
169 | if (!this._component || !this._component.instance || !this.cqItem) {
170 | return;
171 | }
172 |
173 | const keys = Object.getOwnPropertyNames(this.cqItem);
174 |
175 | keys.forEach((key) => {
176 | let propKey = key;
177 |
178 | if (propKey.startsWith(':')) {
179 | // Transformation of internal properties namespaced with [:] to [cq]
180 | // :myProperty => cqMyProperty
181 | const tempKey = propKey.substr(1);
182 |
183 | propKey = 'cq' + tempKey.substr(0, 1).toUpperCase() + tempKey.substr(1);
184 | }
185 |
186 | this._component.instance[propKey] = this.cqItem[key];
187 | });
188 |
189 | this._component.instance.cqPath = this.cqPath;
190 | this._component.instance.itemName = this.itemName || (this.cqItem && this.cqItem.id);
191 | this.includeAppliedCSSClasses();
192 |
193 | const editConfig = ComponentMapping.getEditConfig(this.type);
194 |
195 | if (editConfig && Utils.isInEditor) {
196 | this.setupPlaceholder(editConfig);
197 | }
198 |
199 | this._changeDetectorRef.detectChanges();
200 | }
201 |
202 | /**
203 | * Adds the applied css class names in to the element
204 | */
205 | private includeAppliedCSSClasses() {
206 | const appliedCssClassNames = this.cqItem[Constants.APPLIED_CLASS_NAMES] || '';
207 |
208 | if (appliedCssClassNames && this._component) {
209 | this.renderer.setAttribute(this._component.location.nativeElement, 'class', appliedCssClassNames);
210 | }
211 | }
212 |
213 | /**
214 | * Adds the specified item attributes to the element
215 | */
216 | private setupItemAttrs() {
217 | if (this.itemAttrs) {
218 | const keys = Object.getOwnPropertyNames(this.itemAttrs);
219 |
220 | keys.forEach((key) => {
221 | if (key === 'class') {
222 | const classes = this.itemAttrs[key].split(' ');
223 |
224 | classes.forEach((itemClass) => {
225 | this.renderer.addClass(this._component.location.nativeElement, itemClass);
226 | });
227 | } else {
228 | this.renderer.setAttribute(this._component.location.nativeElement, key, this.itemAttrs[key]);
229 | }
230 | });
231 | }
232 | }
233 |
234 | /**
235 | * Determines if the placeholder should e displayed.
236 | *
237 | * @param editConfig - the edit config of the directive
238 | */
239 | private usePlaceholder(editConfig) {
240 | return editConfig.isEmpty && typeof editConfig.isEmpty === 'function' && editConfig.isEmpty(this.cqItem);
241 | }
242 |
243 | /**
244 | * Setups the placeholder of needed for the AEM editor
245 | *
246 | * @param editConfig - the editConfig, which will dictate the classes to be added on.
247 | */
248 | private setupPlaceholder(editConfig) {
249 | if (this.usePlaceholder(editConfig)) {
250 | this.renderer.addClass(this._component.location.nativeElement, PLACEHOLDER_CLASS_NAME);
251 | this.renderer.setAttribute(this._component.location.nativeElement, 'data-emptytext', editConfig.emptyLabel);
252 | } else {
253 | this.renderer.removeClass(this._component.location.nativeElement, PLACEHOLDER_CLASS_NAME);
254 | this.renderer.removeAttribute(this._component.location.nativeElement, 'data-emptytext');
255 | }
256 | }
257 |
258 | ngAfterViewInit(): void {
259 | this.setupItemAttrs();
260 | }
261 |
262 | ngOnDestroy(): void {
263 | if (this._component) {
264 | this._component.destroy();
265 | }
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2020 Adobe
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------