@@ -8,8 +8,10 @@ import {
88 type BuildingBlock ,
99 type BuildingBlockConfig ,
1010 type BuildingBlockMetaPath ,
11+ type CustomColumn ,
1112 type RichTextEditor ,
12- bindingContextAbsolute
13+ bindingContextAbsolute ,
14+ type TemplateConfig
1315} from './types' ;
1416import { DOMParser , XMLSerializer } from '@xmldom/xmldom' ;
1517import * as xpath from 'xpath' ;
@@ -22,8 +24,10 @@ import type { Manifest } from '../common/types';
2224import { getMinimumUI5Version } from '@sap-ux/project-access' ;
2325import { detectTabSpacing , extendJSON } from '../common/file' ;
2426import { getManifest , getManifestPath } from '../common/utils' ;
27+ import { getDefaultFragmentContent , setCommonDefaults } from '../common/defaults' ;
2528import { getOrAddNamespace } from './prompts/utils/xml' ;
2629import { i18nNamespaces , translate } from '../i18n' ;
30+ import { applyEventHandlerConfiguration } from '../common/event-handler' ;
2731
2832const PLACEHOLDERS = {
2933 'id' : 'REPLACE_WITH_BUILDING_BLOCK_ID' ,
@@ -60,10 +64,17 @@ export async function generateBuildingBlock<T extends BuildingBlock>(
6064 throw new Error ( `Invalid view path ${ viewOrFragmentPath } .` ) ;
6165 }
6266
67+ const { path : manifestPath , content : manifest } = await getManifest ( basePath , fs ) ;
68+
6369 // Read the view xml and template files and update contents of the view xml file
6470 const xmlDocument = getUI5XmlDocument ( basePath , viewOrFragmentPath , fs ) ;
65- const { content : manifest } = await getManifest ( basePath , fs ) ;
66- const templateDocument = getTemplateDocument ( buildingBlockData , xmlDocument , fs , manifest ) ;
71+ const { updatedAggregationPath, processedBuildingBlockData, hasAggregation, aggregationNamespace } =
72+ processBuildingBlock ( buildingBlockData , xmlDocument , manifestPath , manifest , aggregationPath , fs ) ;
73+ const templateConfig : TemplateConfig = {
74+ hasAggregation,
75+ aggregationNamespace
76+ } ;
77+ const templateDocument = getTemplateDocument ( processedBuildingBlockData , xmlDocument , fs , manifest , templateConfig ) ;
6778
6879 if ( buildingBlockData . buildingBlockType === BuildingBlockType . RichTextEditor ) {
6980 const minUI5Version = manifest ? coerce ( getMinimumUI5Version ( manifest ) ) : undefined ;
@@ -77,7 +88,7 @@ export async function generateBuildingBlock<T extends BuildingBlock>(
7788 fs = updateViewFile (
7889 basePath ,
7990 viewOrFragmentPath ,
80- aggregationPath ,
91+ updatedAggregationPath ,
8192 xmlDocument ,
8293 templateDocument ,
8394 fs ,
@@ -100,6 +111,124 @@ export async function generateBuildingBlock<T extends BuildingBlock>(
100111 return fs ;
101112}
102113
114+ /**
115+ * Updates aggregation path for table columns based on XML document structure.
116+ *
117+ * @param {Document } xmlDocument - The XML document to analyze
118+ * @param {string } aggregationPath - The current aggregation path
119+ * @param {CustomColumn } buildingBlockData - The building block data with embedded fragment
120+ * @returns {object } Object containing the updated aggregation path
121+ */
122+ function updateAggregationPathForTableColumns (
123+ xmlDocument : Document ,
124+ aggregationPath : string ,
125+ buildingBlockData : CustomColumn
126+ ) : { updatedAggregationPath : string ; hasTableColumns : boolean } {
127+ if ( ! buildingBlockData . embededFragment ) {
128+ return { updatedAggregationPath : aggregationPath , hasTableColumns : false } ;
129+ }
130+
131+ const xpathSelect = xpath . useNamespaces ( ( xmlDocument . firstChild as any ) . _nsMap ) ;
132+ const hasColumnsAggregation = xpathSelect ( "//*[local-name()='columns']" , xmlDocument ) ;
133+ if ( hasColumnsAggregation && Array . isArray ( hasColumnsAggregation ) && hasColumnsAggregation . length > 0 ) {
134+ return {
135+ updatedAggregationPath : aggregationPath + `/${ getOrAddNamespace ( xmlDocument ) } :columns` ,
136+ hasTableColumns : true
137+ } ;
138+ } else {
139+ const useDefaultAggregation = xpathSelect ( "//*[local-name()='Column']" , xmlDocument ) ;
140+ if ( useDefaultAggregation && Array . isArray ( useDefaultAggregation ) && useDefaultAggregation . length > 0 ) {
141+ return { updatedAggregationPath : aggregationPath , hasTableColumns : true } ;
142+ }
143+ }
144+
145+ return { updatedAggregationPath : aggregationPath , hasTableColumns : false } ;
146+ }
147+
148+ /**
149+ * Processes custom column building block configuration.
150+ *
151+ * @param {BuildingBlock } buildingBlockData - The building block data
152+ * @param {Document } xmlDocument - The XML document
153+ * @param {string } manifestPath - The manifest file path
154+ * @param {Manifest } manifest - The manifest object
155+ * @param {string } aggregationPath - The aggregation path
156+ * @param {Editor } fs - The memfs editor instance
157+ * @returns {object } Object containing updated aggregation path and processed building block data
158+ */
159+ function processBuildingBlock < T extends BuildingBlock > (
160+ buildingBlockData : T ,
161+ xmlDocument : Document ,
162+ manifestPath : string ,
163+ manifest : Manifest ,
164+ aggregationPath : string ,
165+ fs : Editor
166+ ) : {
167+ updatedAggregationPath : string ;
168+ processedBuildingBlockData : T ;
169+ hasAggregation : boolean ;
170+ aggregationNamespace : string ;
171+ } {
172+ let updatedAggregationPath = aggregationPath ;
173+ let hasAggregation = false ;
174+ let aggregationNamespace = 'macrosTable' ;
175+
176+ if ( isCustomColumn ( buildingBlockData ) && buildingBlockData . embededFragment ) {
177+ const embededFragment = setCommonDefaults ( buildingBlockData . embededFragment , manifestPath , manifest ) ;
178+ const viewPath = join (
179+ embededFragment . path ,
180+ `${ embededFragment . fragmentFile ?? embededFragment . name } .fragment.xml`
181+ ) ;
182+
183+ // Apply event handler
184+ if ( buildingBlockData . embededFragment . eventHandler ) {
185+ buildingBlockData . embededFragment . eventHandler = applyEventHandlerConfiguration (
186+ fs ,
187+ buildingBlockData . embededFragment ,
188+ buildingBlockData . embededFragment . eventHandler ,
189+ {
190+ controllerSuffix : false ,
191+ typescript : buildingBlockData . embededFragment . typescript
192+ }
193+ ) ;
194+ }
195+ buildingBlockData . embededFragment . content = getDefaultFragmentContent (
196+ 'Sample Text' ,
197+ buildingBlockData . embededFragment . eventHandler
198+ ) ;
199+ if ( ! fs . exists ( viewPath ) ) {
200+ fs . copyTpl ( getTemplatePath ( 'common/Fragment.xml' ) , viewPath , buildingBlockData . embededFragment ) ;
201+ }
202+ // check xmlDocument for macrosTable element
203+ const tableColumnsResult = updateAggregationPathForTableColumns (
204+ xmlDocument ,
205+ aggregationPath ,
206+ buildingBlockData
207+ ) ;
208+ updatedAggregationPath = tableColumnsResult . updatedAggregationPath ;
209+ hasAggregation = tableColumnsResult . hasTableColumns ;
210+
211+ aggregationNamespace = getOrAddNamespace ( xmlDocument , 'sap.fe.macros.table' , 'macrosTable' ) ;
212+ }
213+
214+ return {
215+ updatedAggregationPath,
216+ processedBuildingBlockData : buildingBlockData ,
217+ hasAggregation,
218+ aggregationNamespace
219+ } ;
220+ }
221+
222+ /**
223+ * Type guard to check if the building block data is a custom column.
224+ *
225+ * @param {BuildingBlock } data - The building block data to check
226+ * @returns {boolean } True if the data is a custom column
227+ */
228+ function isCustomColumn ( data : BuildingBlock ) : data is CustomColumn {
229+ return data . buildingBlockType === BuildingBlockType . CustomColumn ;
230+ }
231+
103232/**
104233 * Returns the UI5 xml file document (view/fragment).
105234 *
@@ -206,14 +335,16 @@ function getMetaPath(
206335 * @param {Manifest } manifest - the manifest content
207336 * @param {Editor } fs - the memfs editor instance
208337 * @param {boolean } usePlaceholders - apply placeholder values if value for attribute/property is not provided
338+ * @param {Record<string, unknown> } templateConfig - additional template configuration
209339 * @returns {string } the template xml file content
210340 */
211341function getTemplateContent < T extends BuildingBlock > (
212342 buildingBlockData : T ,
213343 viewDocument : Document | undefined ,
214344 manifest : Manifest | undefined ,
215345 fs : Editor ,
216- usePlaceholders ?: boolean
346+ usePlaceholders ?: boolean ,
347+ templateConfig ?: TemplateConfig
217348) : string {
218349 const templateFolderName = buildingBlockData . buildingBlockType ;
219350 const templateFilePath = getTemplatePath ( `/building-block/${ templateFolderName } /View.xml` ) ;
@@ -245,7 +376,8 @@ function getTemplateContent<T extends BuildingBlock>(
245376 fs . read ( templateFilePath ) ,
246377 {
247378 macrosNamespace : viewDocument ? getOrAddNamespace ( viewDocument , 'sap.fe.macros' , 'macros' ) : 'macros' ,
248- data : buildingBlockData
379+ data : buildingBlockData ,
380+ config : templateConfig
249381 } ,
250382 { }
251383 ) ;
@@ -255,12 +387,13 @@ function getTemplateContent<T extends BuildingBlock>(
255387 * Method returns the manifest content for the required dependency library.
256388 *
257389 * @param {Editor } fs - the memfs editor instance
390+ * @param {string } library - the dependency library
258391 * @returns {Promise<string> } Manifest content for the required dependency library.
259392 */
260- export async function getManifestContent ( fs : Editor ) : Promise < string > {
393+ export async function getManifestContent ( fs : Editor , library = 'sap.fe.macros' ) : Promise < string > {
261394 // "sap.fe.macros" is missing - enhance manifest.json for missing "sap.fe.macros"
262395 const templatePath = getTemplatePath ( '/building-block/common/manifest.json' ) ;
263- return render ( fs . read ( templatePath ) , { libraries : { 'sap.fe.macros' : { } } } ) ;
396+ return render ( fs . read ( templatePath ) , { libraries : { [ library ] : { } } } ) ;
264397}
265398
266399/**
@@ -270,15 +403,24 @@ export async function getManifestContent(fs: Editor): Promise<string> {
270403 * @param {Document } viewDocument - the view xml file document
271404 * @param {Editor } fs - the memfs editor instance
272405 * @param {Manifest } manifest - the manifest content
406+ * @param {Record<string, unknown> } templateConfig - additional template configuration
273407 * @returns {Document } the template xml file document
274408 */
275409function getTemplateDocument < T extends BuildingBlock > (
276410 buildingBlockData : T ,
277411 viewDocument : Document | undefined ,
278412 fs : Editor ,
279- manifest : Manifest | undefined
413+ manifest : Manifest | undefined ,
414+ templateConfig : TemplateConfig
280415) : Document {
281- const templateContent = getTemplateContent ( buildingBlockData , viewDocument , manifest , fs ) ;
416+ const templateContent = getTemplateContent (
417+ buildingBlockData ,
418+ viewDocument ,
419+ manifest ,
420+ fs ,
421+ undefined ,
422+ templateConfig
423+ ) ;
282424 const errorHandler = ( level : string , message : string ) => {
283425 throw new Error ( `Unable to parse template file with building block data. Details: [${ level } ] - ${ message } ` ) ;
284426 } ;
0 commit comments