Skip to content

Commit dca8a0f

Browse files
committed
refactor: Implement Structural Injection Pattern for Portal Containers (#8566)
1 parent b08e76c commit dca8a0f

5 files changed

Lines changed: 117 additions & 47 deletions

File tree

apps/portal/view/news/release/MainContainer.mjs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,18 @@ class MainContainer extends SharedContainer {
1919
* @reactive
2020
*/
2121
cls: ['portal-release-maincontainer'],
22-
/**
23-
* @member {String} buttonTextField='id'
24-
*/
25-
buttonTextField: 'id',
26-
/**
27-
* @member {Neo.component.Base} contentComponent=ContentComponent
28-
*/
29-
contentComponent: ContentComponent,
3022
/**
3123
* @member {Neo.controller.Component} controller=MainContainerController
3224
* @reactive
3325
*/
3426
controller: Controller,
27+
/**
28+
* @member {Object} pageContainerConfig
29+
*/
30+
pageContainerConfig: {
31+
buttonTextField : 'id',
32+
contentComponent: ContentComponent
33+
},
3534
/**
3635
* @member {Neo.state.Provider} stateProvider=MainContainerStateProvider
3736
* @reactive

apps/portal/view/news/tickets/MainContainer.mjs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,19 @@ class MainContainer extends SharedContainer {
2020
* @reactive
2121
*/
2222
cls: ['portal-tickets-maincontainer'],
23-
/**
24-
* @member {String} buttonTextField='id'
25-
*/
26-
buttonTextField: 'id',
27-
/**
28-
* @member {Neo.component.Base} contentComponent=CanvasWrapper
29-
*/
30-
contentComponent: CanvasWrapper,
3123
/**
3224
* @member {Neo.controller.Component} controller=MainContainerController
3325
* @reactive
3426
*/
3527
controller: Controller,
3628
/**
37-
* @member {Neo.component.Base} pageContainerModule=PageContainer
29+
* @member {Object} pageContainerConfig
3830
*/
39-
pageContainerModule: PageContainer,
31+
pageContainerConfig: {
32+
module : PageContainer,
33+
buttonTextField : 'id',
34+
contentComponent: CanvasWrapper
35+
},
4036
/**
4137
* @member {Neo.state.Provider} stateProvider=MainContainerStateProvider
4238
* @reactive

apps/portal/view/shared/content/Container.mjs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,6 @@ class MainContainer extends Container {
2222
*/
2323
baseCls: ['portal-shared-content-container', 'neo-container'],
2424
/**
25-
* @member {String} buttonTextField='name'
26-
*/
27-
buttonTextField: 'name',
28-
/**
29-
* @member {Neo.component.Base|null} contentComponent=null
30-
*/
31-
contentComponent: null,
32-
/**
33-
* Default items configuration using the Proxy Config Pattern
3425
* @member {Object} contentItems_
3526
*/
3627
contentItems_: {
@@ -69,10 +60,7 @@ class MainContainer extends Container {
6960
weight : 20
7061
},
7162
pageContainer: {
72-
module : '@config:pageContainerModule',
73-
buttonTextField : '@config:buttonTextField',
74-
contentComponent: '@config:contentComponent',
75-
weight : 30
63+
weight: 30
7664
},
7765
sections: {
7866
module : SectionsContainer,
@@ -87,13 +75,29 @@ class MainContainer extends Container {
8775
*/
8876
layout: {ntype: 'hbox', align: 'stretch'},
8977
/**
90-
* @member {Neo.component.Base} pageContainerModule=PageContainer
78+
* Configuration for the PageContainer item.
79+
* Subclasses override this to swap the module or configure the content.
80+
* @member {Object} pageContainerConfig_
81+
* @reactive
9182
*/
92-
pageContainerModule: PageContainer,
83+
pageContainerConfig_: {
84+
[isDescriptor]: true,
85+
merge : 'deep',
86+
value : {
87+
module : PageContainer,
88+
buttonTextField : 'name',
89+
contentComponent: null
90+
}
91+
},
9392
/**
94-
* @member {Object|null} treeConfig=null
93+
* @member {Object|null} treeConfig_=null
94+
* @reactive
9595
*/
96-
treeConfig: null
96+
treeConfig_: {
97+
[isDescriptor]: true,
98+
merge : 'deep',
99+
value : null
100+
}
97101
}
98102

99103
/**
@@ -104,10 +108,14 @@ class MainContainer extends Container {
104108
let me = this;
105109

106110
if (value) {
107-
// Manually merge treeConfig if it exists
108111
if (me.treeConfig && value.sideNav?.items?.tree) {
109112
Neo.assignDefaults(value.sideNav.items.tree, me.treeConfig);
110113
}
114+
115+
if (me.pageContainerConfig && value.pageContainer) {
116+
Neo.assignDefaults(value.pageContainer, me.pageContainerConfig);
117+
}
118+
111119
me.items = value;
112120
}
113121
}

apps/portal/view/shared/content/PageContainer.mjs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ class PageContainer extends Container {
4343
merge : 'deep',
4444
value : {
4545
content: {
46-
module : '@config:contentComponent',
4746
reference: 'content',
4847
weight : 10,
4948
listeners: {
@@ -98,20 +97,13 @@ class PageContainer extends Container {
9897
*/
9998
afterSetContentItems(value, oldValue) {
10099
if (value) {
100+
if (value.content) {
101+
value.content.module = this.contentComponent || Component;
102+
}
101103
this.items = value;
102104
}
103105
}
104106

105-
/**
106-
* Ensure contentComponent falls back to the default Component if null is passed
107-
* @param {Neo.component.Base|null} value
108-
* @param {Neo.component.Base|null} oldValue
109-
* @returns {Neo.component.Base}
110-
*/
111-
beforeSetContentComponent(value, oldValue) {
112-
return value || Component;
113-
}
114-
115107
/**
116108
* Convenience shortcut
117109
* @member {Neo.button.Base} nextPageButton

test/playwright/unit/container/ItemsMerging.spec.mjs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,79 @@ test.describe('Container Items Merging', () => {
228228
expect(merged.flags.active).toBe(true);
229229
expect(merged.flags.highlighted).toBe(true);
230230
});
231+
232+
test('Structural Injection Pattern: Should inject separate config into items via afterSetContentItems', () => {
233+
// 1. Define Base Container
234+
class BaseContainer extends Container {
235+
static config = {
236+
className: 'Neo.test.BaseInjectionContainer',
237+
// The Skeleton
238+
contentItems_: {
239+
[isDescriptor]: true,
240+
merge : 'deep',
241+
value : {
242+
mainArea: {
243+
ntype : 'component',
244+
weight: 10,
245+
// Default props
246+
cls : ['main-area']
247+
}
248+
}
249+
},
250+
// The Configuration Object to Inject
251+
mainAreaConfig_: {
252+
[isDescriptor]: true,
253+
merge : 'deep',
254+
value : {
255+
text: 'Base Text',
256+
style: {
257+
color: 'red'
258+
}
259+
}
260+
}
261+
}
262+
263+
afterSetContentItems(value) {
264+
if (value) {
265+
// Injection Logic
266+
if (this.mainAreaConfig && value.mainArea) {
267+
Neo.assignDefaults(value.mainArea, this.mainAreaConfig);
268+
}
269+
this.items = value;
270+
}
271+
}
272+
}
273+
BaseContainer = Neo.setupClass(BaseContainer);
274+
275+
// 2. Define Subclass overriding the injected config
276+
class SubContainer extends BaseContainer {
277+
static config = {
278+
className: 'Neo.test.SubInjectionContainer',
279+
// Override the config object, NOT the item directly
280+
mainAreaConfig: {
281+
text: 'Sub Text',
282+
style: {
283+
fontSize: '20px'
284+
},
285+
extraProp: 'foo'
286+
}
287+
}
288+
}
289+
SubContainer = Neo.setupClass(SubContainer);
290+
291+
// 3. Create Instance
292+
const instance = Neo.create(SubContainer);
293+
const mainItem = instance.items[0];
294+
295+
// 4. Assertions
296+
// Check Skeleton properties
297+
expect(mainItem.reference).toBe('mainArea');
298+
expect(mainItem.cls).toEqual(['main-area']);
299+
300+
// Check Injected & Merged properties
301+
expect(mainItem.text).toBe('Sub Text'); // Overridden
302+
expect(mainItem.style.color).toBe('red'); // Inherited from Base mainAreaConfig
303+
expect(mainItem.style.fontSize).toBe('20px'); // Added in Sub mainAreaConfig
304+
expect(mainItem.extraProp).toBe('foo'); // Added
305+
});
231306
});

0 commit comments

Comments
 (0)