MS365BC JSON Viewer FactBox
In my daily work I often encounter interfaces and web services of any kind. Whether inbound or outbound, they usually use some kind of payload in the form of JSON these days. No matter if it’s the outgoing object or the response of the web service.
Since I fundamentally implement strong logging in my extensions, it would be great if you could see the content of these requests and responses in e.g. the logging overview. This involves me storing the JSON-data in blob fields in my logging table.
To achieve this visualization, I created a little and universal MS365BC JSON Viewer FactBox. You could embed this FactBox where ever you have some kind of JSON InStream to show. It uses a free JavaScript component.
When importing both request as well as the response JSON, you could switch between them using an action.
Preparing the AL project
AL
To start we have to create our control addin. Here we define the resources to load and the basic sizing of our FactBox.
controladdin JsonViewer
{
StartupScript = 'src/Ressources/Scripts/json-viewer-startup.js';
Scripts = 'src/Ressources/Scripts/json-viewer.js', 'src/Ressources/Scripts/json-viewer-functions.js';
StyleSheets = 'src/Ressources/StyleSheets/json-viewer.css';
HorizontalStretch = true;
HorizontalShrink = true;
MinimumWidth = 250;
VerticalShrink = true;
VerticalStretch = true;
RequestedHeight = 550;
event OnControlAddInReady();
event OnJsonViewerReady();
procedure InitializeControl();
procedure LoadDocument(data: JsonObject; maxLvl: Integer; colAt: Integer);
}
In the next step we create the MSBC365 JSON Viewer FactBox itself.
/// <summary>
/// JSONViewer FactBox
/// </summary>
page 50013 "API Log Json Viewer"
{
PageType = CardPart;
Caption = 'JSON Viewer';
UsageCategory = None;
layout
{
area(Content)
{
field("Query Direction"; QueryDirection)
{
Visible = ShowQueryDirection;
OptionCaption = 'Request,Response';
Caption = 'Query Direction';
ToolTip = 'Choose Query Direction';
}
usercontrol(JsonViewer; JsonViewer)
{
ApplicationArea = All;
trigger OnControlAddInReady()
begin
InitializeJsonViewer();
end;
trigger OnJsonViewerReady()
begin
IsInitialized := true;
ShowData();
end;
}
}
}
actions
{
area(Processing)
{
action(Request)
{
Visible = ShowQueryDirection;
ApplicationArea = All;
Image = BreakRulesOn;
Enabled = QueryDirection = QueryDirection::Response;
Caption = 'Request';
ToolTip = 'Request';
trigger OnAction()
begin
QueryDirection := QueryDirection::Request;
ShowData();
end;
}
action(Response)
{
Visible = ShowQueryDirection;
ApplicationArea = All;
Image = BreakRulesOff;
Enabled = QueryDirection = QueryDirection::Request;
Caption = 'Response';
ToolTip = 'Response';
trigger OnAction()
begin
QueryDirection := QueryDirection::Response;
ShowData();
end;
}
}
}
var
ShowQueryDirection: Boolean;
QueryDirection: Option Request,Response;
IsInitialized: Boolean;
JObjectRequest: JsonObject;
JObjectResponse: JsonObject;
local procedure InitializeJsonViewer()
begin
CurrPage.JsonViewer.InitializeControl();
end;
local procedure ShowData()
begin
if not IsInitialized then
exit;
//json: json Input value
//maxLvl: Process only to max level, where 0..n, -1 unlimited
//colAt: Collapse at level, where 0..n, -1 unlimited
if QueryDirection = QueryDirection::Request then
CurrPage.JsonViewer.LoadDocument(JObjectRequest, -1, 1)
else
CurrPage.JsonViewer.LoadDocument(JObjectResponse, -1, 1);
end;
procedure SetContent(lIStreamRequest: InStream; lIStreamResponse: InStream)
begin
if not JObjectRequest.ReadFrom(lIStreamRequest) then
Clear(JObjectRequest);
if not JObjectResponse.ReadFrom(lIStreamResponse) then
Clear(JObjectResponse);
ShowQueryDirection := true;
ShowData();
end;
procedure SetRequestContent(lIStream: InStream)
begin
if not JObjectRequest.ReadFrom(lIStream) then
Clear(JObjectRequest);
ShowData();
end;
procedure SetResponseContent(lIStream: InStream)
begin
if not JObjectResponse.ReadFrom(lIStream) then
Clear(JObjectResponse);
ShowData();
end;
}
JavaScript
function InitializeControl() {
var controlAddIn = document.getElementById('controlAddIn');
controlAddIn.innerHTML = '<div style="height:100%;overflow-y:auto;" id="json"></div>';
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('OnJsonViewerReady', null);
}
function LoadDocument(data, maxLvl, colAt) {
var jsonViewer = new JSONViewer();
document.querySelector("#json").innerHTML = '';
document.querySelector("#json").appendChild(jsonViewer.getContainer());
jsonViewer.showJSON(data, maxLvl, colAt);
}
//https://www.kauffmann.nl/2019/02/01/controlling-the-size-of-a-control-add-in/
var iframe = window.frameElement;
iframe.parentElement.style.display = 'flex';
iframe.parentElement.style.flexDirection = 'column';
iframe.parentElement.style.flexGrow = '1';
iframe.style.removeProperty('height');
iframe.style.removeProperty('max-height');
iframe.style.flexGrow = '1';
iframe.style.flexShrink = '1';
iframe.style.flexBasis = 'auto';
iframe.style.paddingBottom = '42px';
//iframe.style.height = '1000px';
//iframe.style.overflow = 'hidden';
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('OnControlAddInReady', null);
The JSON-Viewer component could be downloaded for free over here. Thanks to Roman Makudera for providing such a pretty little helper.
/**
* JSONViewer - by Roman Makudera 2016 (c) MIT licence.
*/
//https://www.cssscript.com/minimal-json-data-formatter-jsonviewer/
//...
Stylesheet
To make it look pretty, we setup our css definition via stylesheet.
.json-viewer {
color: #000;
padding-left: 20px;
}
.json-viewer ul {
list-style-type: none;
margin: 0;
margin: 0 0 0 1px;
border-left: 1px dotted #ccc;
padding-left: 2em;
}
.json-viewer .hide {
display: none;
}
.json-viewer .type-string {
color: #0B7500;
}
.json-viewer .type-date {
color: #CB7500;
}
.json-viewer .type-boolean {
color: #1A01CC;
font-weight: bold;
}
.json-viewer .type-number {
color: #1A01CC;
}
.json-viewer .type-null, .json-viewer .type-undefined {
color: #90a;
}
.json-viewer a.list-link {
color: #000;
text-decoration: none;
position: relative;
}
.json-viewer a.list-link:before {
color: #aaa;
content: "\25BC";
position: absolute;
display: inline-block;
width: 1em;
left: -1em;
}
.json-viewer a.list-link.collapsed:before {
content: "\25B6";
}
.json-viewer a.list-link.empty:before {
content: "";
}
.json-viewer .items-ph {
color: #aaa;
padding: 0 1em;
}
.json-viewer .items-ph:hover {
text-decoration: underline;
}
Usage of the MS365BC JSON Viewer FactBox
After creating these AL files and embedding them in your project you could add the FactBox to whatever page you like. You could then fill it with whatever InStream containing your JSON content in the OnAfterGetCurrRecord() trigger.
First we define the FactBox in our desired Page:
area(FactBoxes)
{
part(JsonViewer; "API Log Json Viewer")
{
ApplicationArea = All;
}
}
And now add the data in OnAfterGetCurrRecord() trigger:
trigger OnAfterGetCurrRecord()
var
lIStreamRequest: InStream;
lIStreamResponse: InStream;
begin
Rec.CalcFields("Request Content", "Response Content");
Rec."Request Content".CreateInStream(lIStreamRequest);
Rec."Response Content".CreateInStream(lIStreamResponse);
CurrPage.JsonViewer.Page.SetContent(lIStreamRequest, lIStreamResponse);
end;
You can find the files on GitHub as well.