[[{“value”:”
Introduction
If you have worked with SAP Cloud Integration, you probably appreciate the “Arrange Horizontally” button for iFlows. You click it, and your cluttered integration steps instantly line up in a clean, horizontal flow. Under the hood, SAP uses Eclipse Layout Kernel (ELK) to handle this stuff.
But for some reason, that neat layout feature is nowhere to be found within Message Mappings. Instead, you are left manually dragging expression blocks around, trying to keep a complex mapping from looking like a bowl of spaghetti. Some people LOVE to throw in complex mapping expressions onto the canvas with no semblance of order in the layout. If you are one of them, your teammates have my condolences.
Since SAP already uses ELK for iFlows, and message mapping expressions are inherently graph nodes, it should be possible to “auto layout” mapping expressions as well. While SAP may or may not ever bring this feature into their official roadmap, we don’t have to wait around for a future release cycle. We can patch it ourselves.
After digging through browser XHR requests, tracking DOM mutations, and exploring SAP’s internal web rendering, I figured out roughly how to do this and bring this “auto layout” to SAP Message Mapping expressions.
The Internal Pipeline
To change how the mapping looks, you first have to understand how SAP handles the data behind the scenes. When you open a message mapping, SAP fetches your <mappingName>.mmap file which is nothing but the ELK json of your current mapping expressions.
Based on my analysis of the UI scripts, the internal rendering pipeline might look something like this:
<mappingName>.mmap ELK JSON
↓
TransformationModel
↓
Marshaller
↓
ExpressionModel
↓
Galileo (UI Framework)
↓
Raphael (SVG Library)
SAP converts the raw ELK JSON into a TransformationModel to track changes, then a Marshaller deserializes it into an ExpressionModel. Finally, SAP uses Galileo and Raphael to render the nodes and connecting lines on your page.
Because the underlying data structure relies entirely on coordinate based graph nodes, we don’t need to fight the framework. We can intercept the model, recalculate the positions ourselves using elkjs, update the UI, and save the clean layout back to SAP.
Intercepting the Mapping Data
By reading the target package name and integration flow name from the current browser URL, we can dynamically look up the reg IDs using SAP’s standard OData API:
- Find the Package ID:
GET /odata/1.0/workspace.svc/ContentEntities.ContentPackages(‘${packageName}’)?$format=json
- Find the Artifact ID:
GET /odata/1.0/workspace.svc/ContentEntities.ContentPackages(‘${packageName}’)/Artifacts?$format=json
- Once we have these, we get the current ELK json by:
GET /api/1.0/workspace/${packageRegId}/artifacts/${flowRegId}/resources/<mappingName>.mmap?modelType=MappingModelTO&path=src%2Fmain%2Fresources%2Fmapping
To modify the layout, we need access to SAP’s internal mapping controller. Because this controller isn’t exposed globally, we can write a deep walk utility that scans the window object to find the active instance by looking for a unique property like _mappingValidator.
Once we find the controller, we can render the “prettified” mapping JSON data directly into the application state, more on that later.
function findMappingController() {
const visited = new WeakSet();
function walk(obj) {
if (!obj || typeof obj !== “object”) return null;
if (visited.has(obj)) return null;
visited.add(obj);
try {
if (obj._mappingValidator) return obj;
for (const key in obj) {
try {
const result = walk(obj[key]);
if (result) return result;
} catch {}
}
} catch {}
return null;
}
let result = null;
for (const key in window) {
try {
result = walk(window[key]);
if (result) break;
} catch {}
}
window.__activeMappingController = result;
return !!result;
}
function extractMappingData() {
const c = window.__activeMappingController;
if (!c) return null;
const t = c._mappingValidator._transformation;
return t.getJSONData ? t.getJSONData() : JSON.parse(JSON.stringify(t));
}
Computing the Clean Layout with ELK
Now that have the “current” JSON model, we need to restructure it into a format that elkjs understands. We map destinations, source nodes, and functions to ELK nodes with fixed dimensions, then create edges between them.
We configure the ELK layout options to use the layered algorithm and set the direction to RIGHT to mirror the exact horizontal aesthetic SAP uses for the iflow horizontal alignment.
import ELK from “elkjs/lib/elk.bundled.js”;
const elk = new ELK();
export async function prettifyModel(model) {
function collectGraphElements(mappings) {
const nodes = new Map();
const edges = [];
function visit(ref, parentId = null) {
if (!ref) return;
const graphicalNode = ref.graphicalNode;
const graphicalFunction = ref.graphicalFunction;
if (graphicalNode) {
const id = graphicalNode.expressionDetails.objectId;
if (!nodes.has(id)) {
nodes.set(id, {
id,
width: 140,
height: 40,
ref: graphicalNode,
});
}
if (parentId) {
edges.push({
id: crypto.randomUUID(),
sources: [id],
targets: [parentId],
});
}
return;
}
if (graphicalFunction) {
const id = graphicalFunction.expressionDetails.objectId;
if (!nodes.has(id)) {
nodes.set(id, {
id,
width: 120,
height: 50,
ref: graphicalFunction,
});
}
if (parentId) {
edges.push({
id: crypto.randomUUID(),
sources: [id],
targets: [parentId],
});
}
for (const pin of graphicalFunction.expressionDetails.pinins || []) {
const expr = pin.pinout?.expressionReference;
visit(expr, id);
}
}
}
for (const mapping of mappings) {
const dst = mapping.destination.expressionDetails;
const dstId = dst.objectId;
nodes.set(dstId, {
id: dstId,
width: 160,
height: 40,
ref: mapping.destination,
});
for (const pin of dst.pinins || []) {
visit(pin.pinout?.expressionReference, dstId);
}
}
return {
nodes: […nodes.values()],
edges,
};
}
function createElkGraph(graph) {
return {
id: “root”,
layoutOptions: {
“elk.algorithm”: “layered”,
“elk.layered.unnecessaryBendpoints”: “true”,
“elk.direction”: “RIGHT”,
“elk.layered.spacing.nodeNodeBetweenLayers”: “5”,
“elk.spacing.nodeNode”: “5”,
},
children: graph.nodes.map((n) => ({
id: n.id,
width: n.width,
height: n.height,
})),
edges: graph.edges,
};
}
async function prettify(mappingJson) {
const graph = collectGraphElements(mappingJson.mappings);
const elkGraph = createElkGraph(graph);
const result = await elk.layout(elkGraph);
const positions = new Map(
result.children.map((c) => [c.id, { x: c.x, y: c.y }])
);
for (const node of graph.nodes) {
const pos = positions.get(node.id);
if (!pos) continue;
node.ref.expressionDetails.position.x = Math.round(pos.x);
node.ref.expressionDetails.position.y = Math.round(pos.y);
}
return mappingJson;
}
return prettify(model);
}
Pushing the Changes Back to the UI
Once ELK returns the computed x and y coordinates, we inject them back into the active transformation model and force Galileo to re render the canvas view.
function applyPrettifiedData(data) {
const c = window.__activeMappingController;
if (!c) return false;
const t = c._mappingValidator._transformation;
t.setJSONMappings(data.mappings);
c._refreshUI();
c._showGraphicalExpression(t.getMapping(c._sCurrentMappingId), false);
return true;
}
Saving the Layout to SAP
Updating the UI canvas allows you to see the clean layout, but we also want this to be a persistent. To do that we must save the current prettified ELK JSON to the backend server so they remain intact the next time anyone opens the mapping artifact.
With the reg IDs already known, we can push the updated mapping file back to the cloud workspace using a standard PUT request.
Note: The API endpoint does not accept raw JSON payloads here. You must compress your JSON data using gzip before passing it along.
PUT /api/1.0/workspace/${packageRegId}/artifacts/${flowRegId}/resources/<mappingName>.mmap?modelType=MappingModelTO&path=src%2Fmain%2Fresources%2Fmapping&name=<mappingName>.mmap&type=messagemapping
To trigger the layout update smoothly, you will need to append a custom HTML button onto SAP’s mapping page to call these functions sequentially. Add a simple loading overlay while ELK does it’s thing and the API updates, and you will have a fully functioning, automatic layout button for your message mappings.
You can package these code blocks into a custom browser extension or inject them via a user script manager like Tampermonkey. If you use existing community tools like the CPI Helper extension, this would make for a great pull request idea to elevate the experience for everyone. If you did everything right, you should be able to get the results as I did:
“}]]
Read More Technology Blog Posts by Members articles
#abap