Programmatic Copy and Paste in the Editor
Environment
Product | Editor for Blazor |
Description
How to implement Copy and Paste buttons in the Editor toolbar?
How can I copy and paste in the Editor component programmatically?
Solution
To programmatically copy from the Editor:
- Use a custom Editor tool that relies on plain HTML and completely client-side event handling. Do not use
@onclick
directives or Razor component events. - On tool click (pure JavaScript event), obtain the window selection.
- Use the
write()
method of the browserClipboard
.
To programmatically paste in the Editor:
- Use a custom Editor tool that relies on plain HTML and completely client-side event handling. Do not use
@onclick
directives or Razor component events. - On tool click (pure JavaScript event), use the
read()
method of the browser Clipboard. - Pass the clipboard content to the .NET runtime with
JSInterop
. - Use the
insertHtml
Editor command. For more information, see the section on programmatic execution and analyze if you will be pasting block content or inline content.
@using Telerik.Blazor.Components.Editor
@inject IJSRuntime js
@implements IDisposable
<TelerikEditor @ref="@EditorRef"
@bind-Value="@EditorValue"
Tools="@Tools"
Height="600px"
Id="editor1">
<EditorCustomTools>
<EditorCustomTool Name="CustomCopy">
<button class="k-button k-button-solid k-rounded-md k-button-md k-button-solid-base"
onclick="copyFromEditor()">
<span class="k-button-text">Copy from Editor</span>
</button>
</EditorCustomTool>
<EditorCustomTool Name="CustomPaste">
<button class="k-button k-button-solid k-rounded-md k-button-md k-button-solid-base"
onclick="pasteInEditor()">
<span class="k-button-text">Paste in Editor</span>
</button>
</EditorCustomTool>
</EditorCustomTools>
</TelerikEditor>
@* Move the JavaScript to a separate JS file! *@
<script suppress-error="BL9992">
var dotNetReference;
function getDotNetRef(ref) {
dotNetReference = ref;
}
function copyFromEditor() {
// When using the Iframe EditMode:
var windowObject = document.querySelector("#editor1 iframe").contentWindow;
// When using the Div EditMode:
//var windowObject = window;
var sel = windowObject.getSelection();
var html = "";
if (sel.rangeCount) {
// Extract the selected content from multiple tags.
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
var data = [new ClipboardItem({
'text/plain': new Blob([html], {type: 'text/plain'}),
'text/html': new Blob([html], {type: 'text/html'})
})];
navigator.clipboard.write(data).then( () => {
// success
},
() => {
// failure
}
);
}
async function pasteInEditor() {
var clipboardContent = await navigator.clipboard.read();
var textBlob;
var textContent = "";
var htmlBlob;
var htmlContent = "";
for (const clipboardItem of clipboardContent) {
for (const type of clipboardItem.types) {
if (type == "text/plain") {
textBlob = await clipboardItem.getType(type);
// textContent will still contain tags
textContent = await new Response(textBlob).text();
} else if (type == "text/html") {
htmlBlob = await clipboardItem.getType(type);
// htmlContent will contain inline styles
htmlContent = await new Response(htmlBlob).text();
}
}
}
dotNetReference.invokeMethodAsync("InsertClipboard", textContent);
}
</script>
@code {
private TelerikEditor EditorRef { get; set; } = null!;
private string EditorValue { get; set; } = "<h1>Heading One</h1><ol><li>List item 1</li><li>List item 2</li></ol><p>This is a paragraph.</p>";
private List<IEditorTool> Tools { get; set; } = null!;
// Replace __Main with the actual name of the Razor component.
private DotNetObjectReference<__Main>? DotNetRef;
protected override Task OnInitializedAsync()
{
DotNetRef = DotNetObjectReference.Create(this);
Tools = new List<IEditorTool>(EditorToolSets.All);
Tools.Add(new CustomTool("CustomCopy"));
Tools.Add(new CustomTool("CustomPaste"));
return base.OnInitializedAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await js.InvokeVoidAsync("getDotNetRef", DotNetRef);
}
await base.OnAfterRenderAsync(firstRender);
}
[JSInvokable]
public async Task InsertClipboard(string clipboardContent)
{
// The insertHtml command can work with block or inline content,
// but you need to know what you are getting.
var blockTags = new string[] { "<h", "<p", "<div", "<ul", "<ol", "<li", "<table" };
bool inlineInsert = !blockTags.Any(tag => clipboardContent.Contains(tag));
// The insertHtml command expects one tag maximum,
// so if there are more, wrap them.
int childTags = System.Text.RegularExpressions.Regex.Matches(clipboardContent, "<").Count;
string wrapTag = inlineInsert ? "span" : "div";
if (childTags > 0)
{
clipboardContent = $"<{wrapTag}>{clipboardContent}</{wrapTag}>";
}
await EditorRef.ExecuteAsync(new HtmlCommandArgs("insertHtml", clipboardContent, inlineInsert));
}
public void Dispose()
{
DotNetRef?.Dispose();
}
}
Notes
- Replace
__Main
with the actual name of the Razor component, which holds the Editor. - The above example makes a few assumptions, which affect the overall implementation. Adjust the example, according to your scenario and requirements.
- The code uses a lot of standard JavaScript API and general logic, which are not related to the Editor component.
- Copy-pasting a collection of several HTML tags, for example, list items, may produce unexpected or undesired results, depending on the paste location.
-
Programmatic copying and pasting in the browser requires specific implementation and user permissions. The app controls only one of the following challenges:
- Some users may prohibit programmatic clipboard access.
- Browsers may prompt users to approve programmatic pasting every time.
- Browsers prohibit programmatic clipboard access unless the access occurs in a user event. This means you can't use Blazor
@onclick
handlers andJSInterop
calls from the server to the browser, because the JavaScript execution doesn't occur as a result of user events.