Handle Drop Event in HTML Editor #1922

Closed
opened 2026-01-29 18:15:39 +00:00 by claunia · 7 comments
Owner

Originally created by @Anspitzen on GitHub (Dec 8, 2025).

Is your feature request related to a problem? Please describe.
There is a Paste Eventhandler for the HtmlEditor, but not for the Drop Event

Describe the solution you'd like
Another EventListener in the javascript, same as the paste event listener.
(Paste has the same implemented DataTransfer object, it is called clipboardData, on drop event it would be called dataTransfer. Same object, so function could be even reused)

Describe alternatives you've considered

  • Implementing it myself on the drop event, but would need to get instance reference of Editor, insert own javascript (copy the current paste function) and invoking it from js.
  • Making a full blazor binding onto the event and also copy paste function, doesn't need reference but have to copy and adapt JS function into C# code
Originally created by @Anspitzen on GitHub (Dec 8, 2025). **Is your feature request related to a problem? Please describe.** There is a Paste Eventhandler for the [HtmlEditor](https://blazor.radzen.com/html-editor), but not for the Drop Event **Describe the solution you'd like** Another EventListener in the javascript, same as the paste event listener. (Paste has the same implemented DataTransfer object, it is called clipboardData, on drop event it would be called dataTransfer. Same object, so function could be even reused) **Describe alternatives you've considered** - Implementing it myself on the drop event, but would need to get instance reference of Editor, insert own javascript (copy the current paste function) and invoking it from js. - Making a full blazor binding onto the event and also copy paste function, doesn't need reference but have to copy and adapt JS function into C# code
Author
Owner

@akorchev commented on GitHub (Dec 8, 2025):

Hi @Anspitzen,

What is the expected API for the Drop event handler? What do you expect to happen? What is your actual requirement - what kind of stuff do you drop in RadzenHtmlEditor?

@akorchev commented on GitHub (Dec 8, 2025): Hi @Anspitzen, What is the expected API for the Drop event handler? What do you expect to happen? What is your actual requirement - what kind of stuff do you drop in RadzenHtmlEditor?
Author
Owner

@Anspitzen commented on GitHub (Dec 8, 2025):

The Drop API is basically the same like the Paste API or drag. Same structured data transfer object (literally called DataTransfer), bound to ondrop.
So the js function at the Paste Listener can be reused,
only every e.clipboardData has to be exchanged with e.dataTransfer.

Usecase: When you mark stuff somewhere (like a document, or files in explorer) and drag&drop them into the Editor, instead of hitting CTRL+C/CTRL+V or context menu copy&paste.
So same procedure as dropping files into the Upload Component for example. Just also for text.

API documentation on mozilla has some mentions about maybe having to cancle event ondragover to keep drop event consistently. Haven't looked into it too deeply.

@Anspitzen commented on GitHub (Dec 8, 2025): The Drop API is basically the same like the Paste API or drag. Same structured data transfer object (literally called DataTransfer), bound to ondrop. So the js function at the [Paste Listener](https://github.com/radzenhq/radzen-blazor/blob/edd85f8ec0beb669635f0fc8ac1e8aa7b050c8ef/Radzen.Blazor/wwwroot/Radzen.Blazor.js#L2051) can be reused, only every e.clipboardData has to be exchanged with e.dataTransfer. Usecase: When you mark stuff somewhere (like a document, or files in explorer) and drag&drop them into the Editor, instead of hitting CTRL+C/CTRL+V or context menu copy&paste. So same procedure as dropping files into the Upload Component for example. Just also for text. API documentation on mozilla has some mentions about maybe having to cancle event ondragover to keep drop event consistently. Haven't looked into it too deeply.
Author
Owner

@akorchev commented on GitHub (Dec 8, 2025):

The Drop API is basically the same like the Paste API or drag

The existing Paste event provides the pasted value as a string. Doesn't seem the same as a drop API.

I am still not sure how this API is supposed to be used. Please provide some sample code that shows what you plan to do.

@akorchev commented on GitHub (Dec 8, 2025): > The Drop API is basically the same like the Paste API or drag The existing Paste event provides the pasted value as a string. Doesn't seem the same as a drop API. I am still not sure how this API is supposed to be used. Please provide some sample code that shows what you plan to do.
Author
Owner

@Anspitzen commented on GitHub (Dec 8, 2025):

ref.dropListener = function (e) {
      var item = e.dataTransfer.items[0];

      if (item.kind == 'file') {
        e.preventDefault();
        var file = item.getAsFile();

        if (uploadUrl) {
            var xhr = new XMLHttpRequest();
            var data = new FormData();
            data.append("file", file);
            xhr.onreadystatechange = function (e) {
                if (xhr.readyState === XMLHttpRequest.DONE) {
                    var status = xhr.status;
                    if (status === 0 || (status >= 200 && status < 400)) {
                        var result = JSON.parse(xhr.responseText);
                        var html = '<img src="' + result.url + '">';
                        if (pasteHasDelegate) {
                            instance.invokeMethodAsync('OnDrop', html)
                                .then(function (html) {
                                    document.execCommand("insertHTML", false, html);
                                });
                        } else {
                          document.execCommand("insertHTML", false, '<img src="' + result.url + '">');
                        }
                        instance.invokeMethodAsync('OnUploadComplete', xhr.responseText);
                    } else {
                        instance.invokeMethodAsync('OnError', xhr.responseText);
                    }
                }
            }
            instance.invokeMethodAsync('GetHeaders').then(function (headers) {
                xhr.open('POST', uploadUrl, true);
                for (var name in headers) {
                    xhr.setRequestHeader(name, headers[name]);
                }
                xhr.send(data);
            });
        } else {
            var reader = new FileReader();
            reader.onload = function (e) {
              var html = '<img src="' + e.target.result + '">';

              if (pasteHasDelegate) {
                instance.invokeMethodAsync('OnDrop', html)
                  .then(function (html) {
                    document.execCommand("insertHTML", false, html);
                  });
              } else {
                document.execCommand("insertHTML", false, html);
              }
            };
            reader.readAsDataURL(file);
        }
      } else if (pasteHasDelegate) {
        e.preventDefault();
        var data = e.dataTransfer.getData('text/html') || e.dataTransfer.getData('text/plain');
        
        const startMarker = "<!--StartFragment-->";
        const endMarker = "<!--EndFragment-->";

        const startIndex = data.indexOf(startMarker);
        const endIndex = data.indexOf(endMarker);

        // check if the pasted data contains fragment markers
        if (startIndex != -1 || endIndex != -1 || endIndex > startIndex) {
            // only paste the fragment
            data = data.substring(startIndex + startMarker.length, endIndex).trim();
        }

        instance.invokeMethodAsync('OnDrop', data)
          .then(function (html) {
            document.execCommand("insertHTML", false, html);
          });
      }
    }

And then register it
ref.addEventListener('drop', ref.dropListener);

@Anspitzen commented on GitHub (Dec 8, 2025): ``` ref.dropListener = function (e) { var item = e.dataTransfer.items[0]; if (item.kind == 'file') { e.preventDefault(); var file = item.getAsFile(); if (uploadUrl) { var xhr = new XMLHttpRequest(); var data = new FormData(); data.append("file", file); xhr.onreadystatechange = function (e) { if (xhr.readyState === XMLHttpRequest.DONE) { var status = xhr.status; if (status === 0 || (status >= 200 && status < 400)) { var result = JSON.parse(xhr.responseText); var html = '<img src="' + result.url + '">'; if (pasteHasDelegate) { instance.invokeMethodAsync('OnDrop', html) .then(function (html) { document.execCommand("insertHTML", false, html); }); } else { document.execCommand("insertHTML", false, '<img src="' + result.url + '">'); } instance.invokeMethodAsync('OnUploadComplete', xhr.responseText); } else { instance.invokeMethodAsync('OnError', xhr.responseText); } } } instance.invokeMethodAsync('GetHeaders').then(function (headers) { xhr.open('POST', uploadUrl, true); for (var name in headers) { xhr.setRequestHeader(name, headers[name]); } xhr.send(data); }); } else { var reader = new FileReader(); reader.onload = function (e) { var html = '<img src="' + e.target.result + '">'; if (pasteHasDelegate) { instance.invokeMethodAsync('OnDrop', html) .then(function (html) { document.execCommand("insertHTML", false, html); }); } else { document.execCommand("insertHTML", false, html); } }; reader.readAsDataURL(file); } } else if (pasteHasDelegate) { e.preventDefault(); var data = e.dataTransfer.getData('text/html') || e.dataTransfer.getData('text/plain'); const startMarker = "<!--StartFragment-->"; const endMarker = "<!--EndFragment-->"; const startIndex = data.indexOf(startMarker); const endIndex = data.indexOf(endMarker); // check if the pasted data contains fragment markers if (startIndex != -1 || endIndex != -1 || endIndex > startIndex) { // only paste the fragment data = data.substring(startIndex + startMarker.length, endIndex).trim(); } instance.invokeMethodAsync('OnDrop', data) .then(function (html) { document.execCommand("insertHTML", false, html); }); } } ``` And then register it `ref.addEventListener('drop', ref.dropListener);`
Author
Owner

@Anspitzen commented on GitHub (Dec 8, 2025):

Of course it would be better to reuse the function, as it is literally the paste function, just the property is named different and the boolflag for dropHasDelegate needs to be handed down from the OnAfterRender as parameter.
so maybe some function for it like (with datatypes for clarification)
function handleDataTransfer(dataTransfer:DataTransfer, HasDelegate:bool, InvokeMehtodeName:string) which gets called from the eventhandler paste and drop both.
Paste would call it like handleDataTransfer(e.clipboardData, paste, "OnPaste")
And Drop would call it handleDataTransfer(e.dataTransfer, drop, "OnDrop")
Then it would need the whole song and dance with the EventCallback in the component, the additional bool parameter for the register function down to js etc.

@Anspitzen commented on GitHub (Dec 8, 2025): Of course it would be better to reuse the function, as it is literally the paste function, just the property is named different and the boolflag for dropHasDelegate needs to be handed down from the OnAfterRender as parameter. so maybe some function for it like (with datatypes for clarification) function handleDataTransfer(dataTransfer:DataTransfer, HasDelegate:bool, InvokeMehtodeName:string) which gets called from the eventhandler paste and drop both. Paste would call it like handleDataTransfer(e.clipboardData, paste, "OnPaste") And Drop would call it handleDataTransfer(e.dataTransfer, drop, "OnDrop") Then it would need the whole song and dance with the EventCallback in the component, the additional bool parameter for the register function down to js etc.
Author
Owner

@akorchev commented on GitHub (Dec 8, 2025):

So you basically want the drop to be used instead of paste? We don't plan to implement this but would consider a pull request. However duplicating the code isn't a good solution.

@akorchev commented on GitHub (Dec 8, 2025): So you basically want the drop to be used instead of paste? We don't plan to implement this but would consider a pull request. However duplicating the code isn't a good solution.
Author
Owner

@Anspitzen commented on GitHub (Dec 8, 2025):

Not an instead, but an additional way for another event with the same format and not quite the same use.
And yes, duplicate code is bad, thats why I would propose a new javascript function for handeling both events the same way, after getting the relevant data handled from event.

I'll look into making a pull request for it

@Anspitzen commented on GitHub (Dec 8, 2025): Not an instead, but an additional way for another event with the same format and not quite the same use. And yes, duplicate code is bad, thats why I would propose a new javascript function for handeling both events the same way, after getting the relevant data handled from event. I'll look into making a pull request for it
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/radzen-blazor#1922