Business central blog
Subscribe to blog and get news about new posts.

How to create file drag & drop factbox in Business Central?

Today I'm going to talk about how we can use JavaScript AddIn to create a drag & drop factbox for files in Business Central.

Agenda

First, we need to figure out how to implement Drag & Drop with JavaScript. I recommend the MDN documentation which describes the implementation in great detail. But our scenario is even simpler because my idea is to drag and drop files into a ListPart Factbox in BC.
Having understood a little bit about the main events that are involved in Drag&Drop, I propose to take a look at this example. In this sandbox, we can see one of the possible implementations of File Drag & Drop.
So, our task is to replace the default events dragenter, dragover, dragleave, drop, with our functions. To do this we will use addEventListener. But before that, we need to designate an area of the screen that will have the draggable property. In our case, it will be a paging factbox with the name "FDD Drag & Drop Factbox".

nodes = window.parent.document.querySelectorAll('div[controlname="FDD Drag & Drop Factbox"]'); //find all controls by page name
FactBox = nodes[nodes.length - 1]; //get last control
Here it is important to find all possible HTML nodes of this page by querySelectorAll because in Business Central you can open several pages without closing the previous ones. For example, our File Drag & Drop Factbox can be placed on Customer List and Customer Card, which means that after opening Customer Card from Customer List Business Central builds Child-Parent node structure CustomerList --> Customer Card. So technically we will have two File Drag & Drop Factbox in the structure, although we will actually see the child factbox of Customer Card. Since we are interested in the factbox that is in our field of view, we have to find exactly it.
Now we prevent the standard default events:

// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
    FactBox.addEventListener(eventName, preventDefaults, false)
    document.body.addEventListener(eventName, preventDefaults, false)
});

function preventDefaults (e) {
    e.preventDefault()
    e.stopPropagation()
}
Add the highlight effect upon the dragging process and unhighlight it after:

// Highlight drop area when item is dragged over it
['dragenter', 'dragover'].forEach(eventName => {
    FactBox.addEventListener(eventName, highlight, false)
});

//Unhiglight
['dragleave', 'drop'].forEach(eventName => {
    FactBox.addEventListener(eventName, unhighlight, false)
});

function highlight(e) {
    FactBox.style.border = "thick dotted blue";
}

function unhighlight(e) {
    FactBox.style.border = "";
}
And now the most important part is to directly read the files and send these files to the previously prepared ControlAddIn event OnFileUpload(FileName: Text; FileAsText: Text; IsLastFile: Boolean). To do this, I loop through each file since we can drag and drop multiple files at once and read the files as Base64 text using FileReader.

// Handle dropped files
FactBox.addEventListener('drop', handleDrop, false)

var filesCount = 0;

function handleDrop(e) {
    var dt = e.dataTransfer
    var files = dt.files

    handleFiles(files)
}

function handleFiles(files) {
    files = [...files]
    filesCount = files.length
    files.forEach(uploadFile)
}

function uploadFile(file, i) {
    var reader = new FileReader();
    reader.addEventListener("loadend", function() {
        // reader.result contains the contents of blob
        var base64data = reader.result;
        Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('OnFileUpload',[file.name,base64data,filesCount == (i + 1)]);
     });
     reader.readAsDataURL(file);
}
For the demo, I use the Document Attachments table for the factbox. We just need to create an entry in the table at each occurrence of the OnFileUpload trigger, pre-saving the file. Note the line FileAsText.Substring(FileAsText.IndexOf(',') + 1), it is important to decrypt the string without alias file that we are not interested in. It turns out that JS FileReader passes Base64 in fileAlias+base64 format. For example for pdf document it will be data:application/pdf;base64,<base64>.

trigger OnFileUpload(FileName: Text; FileAsText: Text; IsLastFile: Boolean)
var
    Base64Convert: Codeunit "Base64 Convert";
    TempBlob: Codeunit "Temp Blob";
    FileInStream: InStream;
    FileOutStream: OutStream;
begin
    TempBlob.CreateOutStream(FileOutStream, TextEncoding::UTF8);
    Base64Convert.FromBase64(FileAsText.Substring(FileAsText.IndexOf(',') + 1), FileOutStream);
    TempBlob.CreateInStream(FileInStream, TextEncoding::UTF8);

    Rec.ID := 0;
    Rec.SaveAttachmentFromStream(FileInStream, GetSourceRecRef(), FileName);

    if IsLastFile then
        CurrPage.Update(false);
end;
To test-drive the demo, go to Customer List or Customer Card.
Source code and application is available on github:
https://github.com/Drakonian/drag-and-drop-factbox
August 2, 2022