Opening the editor in a dialog
Open the editor in a dialog over your page, so that the user can edit a file without leaving the current context. The user moves the dialog by the top edge of its frame, resizes it by the other edges and corners, and closes it with the cross button in the editor.
The editor runs in an iframe from Document Server, so pointer events over the editor never reach your page. The thin frame around the editor belongs to your page, which is why it can move and resize the dialog.
How it works
-
The dialog is a
<dialog>element with a placeholder for the editor and a handle on each edge and corner of the frame. The top edge is a button, so the user can also reach it from the keyboard:<dialog id="editorDialog"><div id="editorContainer"></div><button class="handle" data-edge="top" type="button" aria-label="Move or resize the dialog"></button><div class="handle" data-edge="right"></div><div class="handle" data-edge="bottom"></div><div class="handle" data-edge="left"></div><div class="handle" data-edge="top-left"></div><div class="handle" data-edge="top-right"></div><div class="handle" data-edge="bottom-left"></div><div class="handle" data-edge="bottom-right"></div></dialog> -
When the user opens the dialog, the editor is created inside it:
- The close parameter and the onRequestClose event show the cross button in the editor. The button is displayed only when both are set. If the file has unsaved changes, the editor warns the user before it calls
onRequestClose. - The compactHeader parameter moves the header buttons, including the cross button, to the toolbar row, so the editor takes less space.
- The
widthandheightof100%make the editor follow the size of the dialog. - The dialog gets its size and position before it opens. The editor takes its height from the dialog, so the dialog cannot take its height from the editor.
const dialog = document.getElementById("editorDialog");let docEditor;let editorReady = false;function openEditor(config) {const placeholder = document.createElement("div");placeholder.id = "placeholder";document.getElementById("editorContainer").replaceChildren(placeholder);docEditor = new DocsAPI.DocEditor("placeholder", {...config,editorConfig: {...config.editorConfig,customization: {...config.editorConfig?.customization,close: { visible: true, text: "Close" },compactHeader: true,},},events: {...config.events,onAppReady: () => {editorReady = true;},onRequestClose: () => dialog.close(),},width: "100%",height: "100%",});const width = Math.min(1100, window.innerWidth - 32);const height = Math.min(720, window.innerHeight - 32);Object.assign(dialog.style, {left: `${(window.innerWidth - width) / 2}px`,top: `${(window.innerHeight - height) / 2}px`,width: `${width}px`,height: `${height}px`,});dialog.showModal();} - The close parameter and the onRequestClose event show the cross button in the editor. The button is displayed only when both are set. If the file has unsaved changes, the editor warns the user before it calls
-
The user drags a handle. The pointer is captured on the handle. The top edge changes the position of the dialog, and the other handles change its size in their own direction:
const directions = {"right": { x: 1, y: 0 },"bottom": { x: 0, y: 1 },"left": { x: -1, y: 0 },"top-left": { x: -1, y: -1 },"top-right": { x: 1, y: -1 },"bottom-left": { x: -1, y: 1 },"bottom-right": { x: 1, y: 1 },};let start = null;for (const handle of dialog.querySelectorAll(".handle")) {handle.addEventListener("pointerdown", (e) => {if (e.button !== 0) return;handle.setPointerCapture(e.pointerId);const { left, top, width, height } = dialog.getBoundingClientRect();start = { x: e.clientX, y: e.clientY, left, top, width, height, direction: directions[handle.dataset.edge] };dialog.classList.add("dragging");});handle.addEventListener("pointermove", (e) => {if (!start) return;const dx = e.clientX - start.x;const dy = e.clientY - start.y;let { left, top, width, height, direction } = start;if (!direction) {left += dx;top += dy;} else {if (direction.x) width = Math.max(480, start.width + direction.x * dx);if (direction.x < 0) left = start.left + start.width - width;if (direction.y) height = Math.max(320, start.height + direction.y * dy);if (direction.y < 0) top = start.top + start.height - height;}Object.assign(dialog.style, {left: `${left}px`,top: `${top}px`,width: `${width}px`,height: `${height}px`,});});const stopDragging = () => {start = null;dialog.classList.remove("dragging");};handle.addEventListener("pointerup", stopDragging);handle.addEventListener("pointercancel", stopDragging);}Move and resize the dialog in place, not by moving it to another element. When an iframe is moved in the DOM, the browser reloads it, and the editor opens the file again. Resizing the iframe does not reload it.
-
The padding of the dialog is the frame. The handles are positioned over it and reach a few pixels outside it, so that they are easier to hit than the thin frame. While a handle is dragged, the iframe ignores the pointer. Otherwise the iframe takes the pointer events whenever the cursor gets ahead of the dialog, and the dialog stops following it:
#editorDialog {display: none;flex-direction: column;inset: auto;max-width: none;max-height: none;margin: 0;padding: 6px;overflow: visible;}#editorDialog[open] {display: flex;}.handle {position: absolute;padding: 0;touch-action: none;user-select: none;border: none;background: transparent;}.handle[data-edge="top"] {top: -4px;left: 12px;right: 12px;height: 10px;cursor: move;}.handle[data-edge="bottom-right"] {right: -4px;bottom: -4px;width: 16px;height: 16px;cursor: nwse-resize;}.dragging iframe {pointer-events: none;}The other edges and corners are positioned in the same way, with
ns-resize,ew-resize,nesw-resizeandnwse-resizecursors.The
displayof the dialog is set only while it is open. Adisplayset on a closed<dialog>overrides the browser'sdisplay: noneand shows the dialog on the page.max-widthandmax-heightremove the browser's limits on the size of a modal dialog, so the user can resize it to the full window. -
The Esc key closes a modal dialog without asking the editor, so the editor cannot warn the user about unsaved changes. Cancel the
cancelevent and call the requestClose method instead. The editor warns the user if the file has unsaved changes and then callsonRequestClose. Until the editor is ready, Esc closes the dialog directly. Esc reaches the dialog only while the focus is on your page, for example, on the top edge, not inside the editor.When the dialog is closed, the destroyEditor method removes the editor:
dialog.addEventListener("cancel", (e) => {if (!editorReady) return;e.preventDefault();docEditor.requestClose();});dialog.addEventListener("close", () => {docEditor.destroyEditor();editorReady = false;});If the editor fails to load, it has no cross button. Show a close button of your own in this case, for example, from the onError event, as the sample above does.
Dragging works only with a pointer. Let the user also move and resize the dialog from the keyboard — for example, with the arrow keys and Shift with the arrow keys while the top edge has focus — as the sample above does.