ASP.NET Core 3.1 - Copy and Paste
This article will demonstrate the implementation of clipboard copy and paste functions from a Razor page. I will assume you have created a new ASP.NET Core 3.1 Razor Pages project. See Tutorial: Get started with Razor Pages in ASP.NET Core .
This article is part of the Bootstrap Native Project series.
The Bootstrap Native Project (BSNP) is deployed to preview. kenhaggerty. com. The BSNP implements Bootstrap Native and the scaffolded ASP.NET Core Identity UI with user and page enhancements. I encourage you to evaluate Account Management: Sign In Services, 2FA, and Personal Data. If you encounter an issue, I will probably be notified but please send me details in an email.
Access to the BSNP source code may be purchased on KenHaggerty.Com at Manage > Assets.
I enjoy writing these articles. It often enhances and clarifies my coding. The research project is a result of a lot of refactoring and hopefully provides logical segues for the articles. Thank you for supporting my efforts.
I discovered a few UX issues after the deployment of the Bootstrap Native Project (BSNP). BSNP implements ASP.NET Core Identity for user management. Identity allows the user to configure and enable two-factor authentication (2FA). A unique key is used to add an account to a mobile authenticator app. I implemented a QR Code generator which generates an image of the key. Authenticator apps allow the camera to input the code. This benefit is lost when you use the mobile device to configure 2FA.
To encourage 2FA adoption, I developed copy and paste to clipboard functions. Using document. execCommand("copy"); to transfer the key to the authenticator app was easy to implement, but document. execCommand("paste"); requires a clipboard read permission. Both have restrictions for the way they are implemented. The new Clipboard API provides asynchronous access to read and write the clipboard contents and provides the interface to manage the clipboard read permission. The Clipboard API and the clipboard read permission when supported allows the user to transfer the verification code from the authenticator app to the web app.
You should detect and respond to current permissions. The clipboard-write permission is granted by default. Firefox does not support the Clipboard API - readText function and can be detected when the navigator. clipboard. readText function is undefined. The clipboard-read permission can be detected with navigator. permissions. query({ name: 'clipboard-read' }) which will return a state of "prompt", "granted", or "denied". The prompt state indicates the user will be prompted to give permission the first time the readText function is called. The result also allows you to attach an event listener to the permission's on change event.
let allowedEl = document.querySelector('.allowed'); let blockedEl = document.querySelector('.blocked'); let notSupportedEl = document.querySelector('.not-supported'); function handleDenied() { // clipboard-write - granted by default if (navigator.clipboard.readText) { navigator.permissions.query({ name: 'clipboard-read' }) .then(function (result) { if (result.state == 'denied') { allowedEl.classList.add('d-none'); blockedEl.classList.remove('d-none'); } result.onchange = function () { if (result.state == 'denied') { allowedEl.classList.add('d-none'); blockedEl.classList.remove('d-none'); } else { blockedEl.classList.add('d-none'); allowedEl.classList.remove('d-none'); } } }) .catch((err) => { console.error(err); }); } else { console.error('Clipboard readText function not found.'); allowedEl.classList.add('d-none'); notSupportedEl.classList.remove('d-none'); } }
I edited a couple of icons for the copy and paste buttons and employed Bootstrap Native's tooltip to give the user feedback of pass/fail. Tooltips display on mobile devices with the show command.
The navigator. clipboard. writeText and navigator. clipboard. readText functions should be called from inside a short running user-generated event handler such as click or keypress otherwise a DOMException: Document is not focused. error is thrown. The writeText continues to copy the text to the clipboard but the readText ends execution with little indication of the error.
Clipboard API - writeText:
let copyButton = document.querySelector('.btn-copy'); copyButton.dataset.title = 'Copy'; new BSN.Tooltip(copyButton, { placement: 'top', animation: 'fade', delay: 100 }); copyButton.addEventListener('click', async function (e) { e.preventDefault(); let txt = e.currentTarget.nextElementSibling.value; navigator.clipboard.writeText(txt) .then(() => { copyButton.dataset.title = 'Copied to clipboard.'; new BSN.Tooltip(copyButton, { placement: 'top', animation: 'fade', delay: 100 }).show(); }) .catch((err) => { console.error(err); copyButton.dataset.title = 'Permission denied.'; new BSN.Tooltip(copyButton, { placement: 'top', animation: 'fade', delay: 100 }).show(); }); }); copyButton.addEventListener('hidden.bs.tooltip', function (e) { copyButton.dataset.title = 'Copy'; });
Clipboard API - readText:
let pasteButton = document.querySelector('.btn-paste'); pasteButton.dataset.title = 'Paste'; new BSN.Tooltip(pasteButton, { placement: 'top', animation: 'fade', delay: 100 }); let pasteInput = document.querySelector('.txt-paste'); pasteButton.addEventListener('click', async function (e) { e.preventDefault(); navigator.clipboard.readText() .then(txt => { pasteButton.dataset.title = 'Pasted from clipboard.'; new BSN.Tooltip(pasteButton, { placement: 'top', animation: 'fade', delay: 100 }).show(); if (txt == '') txt = '<EMPTY>'; pasteInput.value = txt; }) .catch((err) => { pasteButton.dataset.title = 'Permission denied.'; new BSN.Tooltip(pasteButton, { placement: 'top', animation: 'fade', delay: 100 }).show(); console.error(err); pasteInput.value = '<BLOCKED>'; }); }); pasteButton.addEventListener('hidden.bs.tooltip', function () { pasteButton.dataset.title = 'Paste'; pasteButton.blur(); });
I developed a copy and paste demo for the BSNP. Live Demo
Comments(0)