In my project I use a Power Pages portal with Bootstrap version 3.4.1 which has a page for managing company's frame agreements where users can create new documents, edit information and upload files to Azure Storage.
For user inputs I use Bootstrap's modal where I disabled closing the modal with ESC to avoid accidental close when working with multiple inputs. It was not so straightforward as it may seem at first glance.
One of the functionalities provided is deletion of files, and that's where a <dialog>
element is handy. However, my implementation has some modifications when it comes to rendering a modal dialog and handling the dialog's return value.
How dialog is used
A typical frame agreement has multiple attachments which look like this on a Bootstrap modal:
Clicking of Delete ⌫ brings up a confirmation dialog followed by deletion if Yes is clicked or cancelling the process otherwise:
Standard dialog's functionality
Usually you hardcode a dialog element on an html page, pop it up to the user with the .showModal()
method when required and hide either with dialog's buttons clicked, ESC or .hide()
method.
What is modified
Since I have multiple files, and the dialog shall be file-specific reflecting file's metadata like name, id and other details, I decided to render the dialog each time and remove after, instead of clearing innerHTML
, showing and hiding.
For simplicity, hiding and removal logic is done inline in the form of event handler content attributes which is discouraged on mdn but is a valid part of the HTML standard without any "bad practice" comments.
Existing setup for files
- Metadata of each file is stored in a Dataverse table.
- File content is stored in Azure Storage as blob.
Deletion logic
The Delete ⌫ button has the following HTML:
<a class="btn btn-default delete-file"
data-filedetails="882a60d2-5b40-ef11-8409-6045bd8728a9|id-10924|confidential test.pdf">
Delete ⌫
</a>
Attribute data-filedetails
keeps relevant metadata for deletion. It consists of 3 components we would need to delete the file:
- File guid in the Dataverse table
- Container name in Azure Storage
- Blob name in Azure Storage
The components are split by |
symbol.
The following callback function sits on the Delete ⌫ button click event:
function renderDeleteDialog(t) {
// t => file delete button
const id = 'confirmation-dialog'
document.getElementById(id)?.remove()
// remove the dialoge it case it's present in DOM
const fileDetailsStr = t.dataset.filedetails
// fileDetailsStr => '882a60d2-5b40-ef11-8409-6045bd8728a9|id-10924|confidential test.pdf'
const fileDetailsArr = fileDetailsStr.split('|')
// fileDetailsArr => [
// '882a60d2-5b40-ef11-8409-6045bd8728a9',
// 'id-10924',
// 'confidential test.pdf'
// ]
const dialog = `
<dialog id="${id}"
onclose="this.remove();"
data-filedetails="${fileDetailsStr}">
<h4>Delete <i>${fileDetailsArr[2]}</i>?</h4>
No or <kbd>ESC</kbd> to cancel, Yes to proceed.
<div>
<button autofocus onclick="this.closest('dialog').close();">
No
</button>
<button onclick="const d = this.closest('dialog');d.close();deleteFile(d.dataset.filedetails);">
Yes
</button>
</div>
</dialog>`
const fragment = document.createRange().createContextualFragment(dialog)
document.querySelector('body').appendChild(fragment)
// rendering dialog as the DOM Node
document.getElementById(id).showModal()
// showing the dialog
}
Here's a rendered HTML of the dialog:
<dialog
id="confirmation-dialog"
onclose="this.remove();"
data-filedetails="d24abf61-5e38-ef11-8409-6045bd9c795d|id-10924|testpdf.pdf"
open="">
<h4>Delete <i>testpdf.pdf</i>?</h4>
No or <kbd>ESC</kbd> to cancel, Yes to proceed.
<div>
<button
autofocus=""
onclick="this.closest('dialog').close();">
No
</button>
<button
onclick="const d = this.closest('dialog');d.close();deleteFile(d.dataset.filedetails);">
Yes
</button>
</div>
</dialog>
Explanation of the inline logic
Clicking No or hitting ESC will run this.closest('dialog').close()
where this
is the No button, from which we go up the DOM finding a parent dialog
and invoking the .close()
method.
this.remove()
on the dialog
element will remove it from the DOM on onclose
event.
When Yes is clicked, the following code will be executed:
const d = this.closest('dialog');
// d is the dialog element
d.close();
// hide the dialog element which will be followed by removal
// from the DOM with this.remove() on onclose event of the dialog element
deleteFile(d.dataset.filedetails);
// at this time the dialog is removed from the DOM
// but variable d still exists and used to
// run the deleteFile function passing the
// selected file metadata with d.dataset.filedetails =>
// 'd24abf61-5e38-ef11-8409-6045bd9c795d|id-10924|testpdf.pdf'
// deleteFile() parses the params using guid to remove the record
// from the dataverse table and container name and blob name to
// delete the blob in Azure Storage
this.closest('dialog').close()
where this
is the No button, from which we go up the DOM finding a parent dialog
and invoking the .close()
method.
Top comments (0)