Contribute to this guide

guide获取和设置数据

在 CKEditor 5 中,有几种方法可以加载和保存数据。在本指南中,您可以了解可用的选项及其优缺点。

# 自动保存

改善用户体验的最佳方法之一是自动保存编辑器数据,因为数据会发生变化。这确保了用户不必记住保存数据,并防止工作丢失。

我们提供自动保存功能,它在需要时自动保存数据(例如发送到服务器)。有关详细信息,请参阅 自动保存 指南。

# 使用数据初始化编辑器

默认情况下,编辑器具有在其初始化的 DOM 元素中的内容。

<div id="editor">
    <!-- This content will appear in the editor if you initialize with this element. -->
    <p>Hello, world!</p>
</div>

但是,如果您无法更改 HTML 或使用 JavaScript 异步加载数据,可以使用 initialData 配置属性来设置编辑器的初始状态。

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        plugins: [ /* ... */ ],
        toolbar: [ /* ... */ ],
        initialData: '<p>Hello, world!</p>'
    } )
    .then( /* ... */ )
    .catch( /* ... */ );

initialData 属性将使用提供的数据初始化编辑器,覆盖 HTML 级别提供的内容。

如果您使用 React 等集成设置编辑器,请查阅文档以获取提供的用于初始化数据的其他属性。

# 使用 getData() 获取编辑器数据

如果您需要按需获取编辑器内容,例如使用 JavaScript API 发送到服务器,则可以使用 getData() 方法。

const data = editor.getData();

为此,您需要存储对 editor 的引用,因为没有全局属性。您可以通过多种方式做到这一点,例如将 editor 分配给在 then() 的回调之外定义的变量

let editor;

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        plugins: [ /* ... */ ],
        toolbar: [ /* ... */ ]
    } )
    .then( newEditor => {
        editor = newEditor;
    } )
    .catch( error => {
        console.error( error );
    } );

// Assuming there is a <button id="submit">Submit</button> in your application.
document.querySelector( '#submit' ).addEventListener( 'click', () => {
    const editorData = editor.getData();

    // ...
} );

getData() 方法可以接受更改返回内容的选项。

# 使用 setData() 替换编辑器数据

在某些情况下,您可能希望按需使用新数据替换编辑器内容。对于此操作,请使用 setData() 方法

editor.setData( '<p>Some text.</p>' );

与获取数据一样,您将需要访问编辑器的实例。请参阅上一节以了解如何保存实例的示例。

# 与 HTML 表单的自动集成

这是集成编辑器的经典方法。它通常用于更简单的 CMS、论坛、评论部分等。

此方法 **仅在经典编辑器中可用**,并且仅在编辑器用于替换 <textarea> 元素时才可用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>CKEditor 5 - Classic editor</title>
    <link rel="stylesheet" href="https://cdn.ckeditor.com/ckeditor5/43.3.0/ckeditor5.css" />
</head>
<body>
    <h1>Classic editor</h1>
    <form action="[URL]" method="post">
        <textarea name="content" id="editor">
            &lt;p&gt;This is some sample content.&lt;/p&gt;
        </textarea>
        <p><input type="submit" value="Submit"></p>
    </form>
    <script type="importmap">
        {
            "imports": {
                "ckeditor5": "https://cdn.ckeditor.com/ckeditor5/43.3.0/ckeditor5.js",
                "ckeditor5/": "https://cdn.ckeditor.com/ckeditor5/43.3.0/"
            }
        }
    </script>
    <script type="module">
      	import {
            ClassicEditor,
            Essentials,
            Paragraph,
            Bold,
            Italic
        } from 'ckeditor5';

        ClassicEditor
            .create( document.querySelector( '#editor' ), {
                plugins: [ Essentials, Paragraph, Bold, Italic ],
                toolbar: [ 'bold', 'italic' ]
            } )
            .catch( error => {
                console.error( error );
            } );
    </script>
</body>
</html>

经典编辑器将在用户提交表单后自动更新 <textarea> 元素的值。您不需要任何其他 JavaScript 代码将编辑器数据发送到服务器。

在您的 HTTP 服务器中,您现在可以从 POST 请求的 content 变量中读取编辑器数据。例如,在 PHP 中,您可以通过以下方式获取它

<?php
    $editor_data = $_POST[ 'content' ];
?>

请注意,替换的 <textarea> 元素会在提交之前由 CKEditor 直接自动更新。如果您需要使用 JavaScript 以编程方式访问替换的 <textarea> 的值(例如,在 onsubmit 处理程序中验证输入的数据),那么 <textarea> 元素可能仍然存储原始数据。为了更新替换的 <textarea> 的值,请使用 editor.updateSourceElement() 方法。

如果您需要使用 JavaScript 在任何时候获取 CKEditor 中的实际数据,请使用 editor.getData() 方法,如下一节所述。

当您将数据库中的数据打印到 HTML 页面中的 <textarea> 元素时,您需要对其进行正确编码。例如,如果您使用 PHP,那么一个最小的解决方案看起来像这样

<?php
    $data = htmlspecialchars("<p>Hello, world!</p>", ENT_QUOTES, 'UTF-8');
?>

<textarea name="content" id="editor"><?= $data ?></textarea>

因此,<textarea> 将以如下方式打印出来

<textarea>&lt;p&gt;Hello, world!&lt;/p&gt;</textarea>

而不是以如下方式打印

<textarea><p>Hello, world!</p></textarea>

虽然像上面提到的那样简单的内容本身并不需要编码,但对数据进行编码可以防止丢失诸如 <<IMPORTANT> 之类的文本。

# 更新源元素

如果源元素不是 <textarea>,CKEditor 5 会在编辑器销毁后清除其内容。但是,如果您想使用数据管道输出的内容更新源元素,可以使用 updateSourceElementOnDestroy 配置选项。

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        // ...
        updateSourceElementOnDestroy: true
    } );

在配置中启用 updateSourceElementOnDestroy 选项可能会有一些安全隐患,具体取决于您使用的插件。虽然编辑视图是安全的,但数据输出中可能存在一些不安全的内容,因此仅在您了解自己在做什么的情况下才启用此选项。在使用 Markdown、通用 HTML 支持和 HTML 嵌入功能时,尤其要注意。

# 在用户离开页面时发出警报

将编辑器集成到您的网站时,另一个需要考虑的问题是用户可能会在保存数据之前意外离开。这个问题由 自动保存功能 自动处理,但是如果您不使用它而是选择不同的集成方法,您应该考虑处理以下两种情况

  • 用户在保存数据之前离开页面(例如,错误地关闭选项卡或单击某个链接)。
  • 用户保存了数据,但还有一些挂起的操作,例如上传图像。

为了处理前一种情况,您可以监听本机 window#beforeunload 事件。后一种情况可以使用 CKEditor 5 PendingActions 插件处理。

# 演示

以下示例展示了如何将所有这些机制结合使用以启用或禁用“保存”按钮,并阻止用户在未保存数据的情况下离开页面。

// Note: We need to build the editor from source.
import { ClassicEditor, PendingActions } from 'ckeditor5';

let isDirty = false;

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        plugins: [
            PendingActions,

            // ... other plugins
        ]
    } )
    .then( editor => {
        window.editor = editor;

        handleStatusChanges( editor );
        handleSaveButton( editor );
        handleBeforeunload( editor );
    } )
    .catch( err => {
        console.error( err.stack );
    } );

// Handle clicking the "Save" button by sending the data to a
// fake HTTP server (emulated here with setTimeout()).
function handleSaveButton( editor ) {
    const saveButton = document.querySelector( '#save' );
    const pendingActions = editor.plugins.get( 'PendingActions' );

    saveButton.addEventListener( 'click', evt => {
        const data = editor.getData();

        // Register the action of saving the data as a "pending action".
        // All asynchronous actions related to the editor are tracked like this,
        // so later on you only need to check `pendingActions.hasAny` to check
        // whether the editor is busy or not.
        const action = pendingActions.add( 'Saving changes' );

        evt.preventDefault();

        // Save the data to a fake HTTP server.
        setTimeout( () => {
            pendingActions.remove( action );

            // Reset isDirty only if the data did not change in the meantime.
            if ( data == editor.getData() ) {
                isDirty = false;
            }

            updateStatus( editor );
        }, HTTP_SERVER_LAG );
    } );
}

// Listen to new changes (to enable the "Save" button) and to
// pending actions (to show the spinner animation when the editor is busy).
function handleStatusChanges( editor ) {
    editor.plugins.get( 'PendingActions' ).on( 'change:hasAny', () => updateStatus( editor ) );

    editor.model.document.on( 'change:data', () => {
        isDirty = true;

        updateStatus( editor );
    } );
}

// If the user tries to leave the page before the data is saved, ask
// them whether they are sure they want to proceed.
function handleBeforeunload( editor ) {
    const pendingActions = editor.plugins.get( 'PendingActions' );

    window.addEventListener( 'beforeunload', evt => {
        if ( pendingActions.hasAny ) {
            evt.preventDefault();
        }
    } );
}

function updateStatus( editor ) {
    const saveButton = document.querySelector( '#save' );

    // Disables the "Save" button when the data on the server is up to date.
    if ( isDirty ) {
        saveButton.classList.add( 'active' );
    } else {
        saveButton.classList.remove( 'active' );
    }

    // Shows the spinner animation.
    if ( editor.plugins.get( 'PendingActions' ).hasAny ) {
        saveButton.classList.add( 'saving' );
    } else {
        saveButton.classList.remove( 'saving' );
    }
}

如何理解此演示

  • 当数据正在发送到服务器或有任何其他挂起的操作(例如,正在上传图像)时,按钮将变为“正在保存...”。
  • 如果您正在上传图像或数据尚未成功保存,系统会询问您是否要离开页面。您可以通过将大型图像拖放到编辑器中或将“HTTP 服务器延迟”更改为较高的值(例如 9000 毫秒)并单击“保存”按钮来测试这一点。这些操作将使编辑器“忙”更长时间 - 然后尝试离开页面。

更改此编辑器的内容,然后将其保存在服务器上。

HTTP 服务器延迟(毫秒)

服务器数据

<p>Change the content of this editor, then save it on the server.</p>