DEV Community

Andrew Elans
Andrew Elans

Posted on

Power Pages: modified <dialog> functionality

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:

Image description

Clicking of Delete ⌫ brings up a confirmation dialog followed by deletion if Yes is clicked or cancelling the process otherwise:

Image description

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

  1. Metadata of each file is stored in a Dataverse table.
  2. 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>
Enter fullscreen mode Exit fullscreen mode

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

}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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)