dj-paste — Paste event handling
dj-paste fires a server event when the user pastes content into a bound element. The client extracts structured payload from the ClipboardEvent — plain text, rich HTML, and any attached files — and sends it as a single handler call. No dj-hook needed.
Every chat app, rich-text editor, and data-import UI needs paste handling. dj-paste is the complete clipboard-to-server pipeline.
Quick start
<textarea dj-paste="handle_paste" placeholder="Paste anything..."></textarea>
from djust import LiveView
from djust.decorators import event_handler
class ChatView(LiveView):
template_name = "chat/room.html"
@event_handler
def handle_paste(self, text: str = "", html: str = "",
has_files: bool = False, files: list = None,
**kwargs):
self.last_paste_preview = text[:200]
if has_files:
self.flash("Received %d file(s) — uploading..." % len(files))
What gets sent to the handler
Every dj-paste event call carries four structured params:
| Param | Type | Content |
|---|---|---|
text | str | clipboardData.getData('text/plain') — always a string, even if the clipboard was empty. |
html | str | clipboardData.getData('text/html') — rich paste from Word, Google Docs, a web page, etc. Empty when the source was plain text. |
has_files | bool | True if clipboardData.files was non-empty (i.e. the user copied an image or a file). |
files | list[dict] | File metadata: {"name", "type", "size"}. The actual bytes are NOT in this dict — see Pasting files → uploads. |
The client also forwards any positional args from the attribute syntax:
<textarea dj-paste="handle_paste('chat', 42)"></textarea>
becomes handle_paste('chat', 42, text=..., html=..., ...) on the server. Access them as kwargs["_args"] if needed.
Pasting files → uploads
By default dj-paste only sends metadata about pasted files. To actually upload the bytes, combine dj-paste with dj-upload on the same element — the client routes the clipboard FileList through the existing upload pipeline (the same path file inputs and drag-drop use):
<div dj-paste="handle_paste" dj-upload="chat_image">
Drop or paste an image here.
</div>
class ChatView(LiveView):
uploads = {
"chat_image": {
"max_file_size": 5 * 1024 * 1024,
"accept": "image/*",
}
}
@event_handler
def handle_paste(self, text: str = "", has_files: bool = False, **kwargs):
if has_files:
# Files arrive via the upload pipeline — nothing more to do here.
return
self.draft = text
Pasted files fire the same djust:upload:error / djust:upload:progress events as file-input uploads, so progress indicators and size-limit errors Just Work.
Suppressing the native paste
By default, dj-paste lets the browser's own paste happen too — the pasted text ends up in the textarea as usual, and your handler sees it via the event. That's the right behaviour for most editors and rich-text fields.
When you want to intercept the paste completely — for example when you're routing image paste to an upload slot and do not want the image's data URL dumped into a <div contenteditable> — add dj-paste-suppress:
<div dj-paste="handle_paste"
dj-paste-suppress
dj-upload="chat_image"
contenteditable>
Paste an image here.
</div>
Combining with dj-confirm and dj-lock
dj-paste participates in the standard interaction pipeline:
dj-confirm— show a confirmation dialog before firing the event (useful for "are you sure you want to paste from clipboard?" privacy prompts).dj-lock— skip the event while an earlier handler is still running.
<textarea dj-paste="handle_paste"
dj-confirm="Import clipboard contents?"
dj-lock>
</textarea>
When to reach for dj-paste vs dj-input
dj-inputfires on every keystroke. The server sees the current value of the field. If a user pastes into adj-input-bound textarea, you get the post-paste value as a single update — no paste metadata, no file support.dj-pastefires once per paste. You receive text + HTML + file metadata. The native paste still happens (unless suppressed), so you can pairdj-pastewithdj-inputon the same element without conflict:
<textarea dj-input="set_draft"
dj-paste="handle_rich_paste"
placeholder="Type or paste..."></textarea>
Use dj-input for "keep the server copy of this field in sync" and dj-paste for "react specifically when something was pasted" (image upload, language detection on a code snippet, CSV parsing, markdown preview).
Edge cases
- Missing
clipboardData— very old browsers. The handler bails silently and the native paste still happens. getData('text/html')throws — some browsers block HTML reads from untrusted contexts.dj-pastecatches the error and sendshtml="".- Empty paste —
textandhtmlare empty strings,has_files=False,files=[]. The handler still fires; you can use it as a "user attempted paste" signal. - Multiple files — every file in
clipboardData.filesappears in thefileslist; the upload pipeline uploads them in sequence ifdj-uploadis set.
See also
- File uploads — the
dj-uploadpipeline thatdj-pasteroutes files through. - Event handlers — the
@event_handlerdecorator reference.