CoreMedia CKEditor 5 Plugins
    Preparing search index...

    Module ckeditor5-data-facade

    Provides an additional control layer for setting and getting data.

    Data Facade

    API Documentation

    Module: @coremedia/ckeditor5-data-facade

    This module provides a way to prevent possibly unintended normalization of data during a set/get data flow.

    In short, it ensures the following behavior, illustrated in pseudocode:

    newData = "<p>Hello Data Facade!</p>"
    
    setData(newData)
    currentData = getData()
    
    assert newData === currentData
    

    To achieve this, all data communication from and to the outside must be tunneled through the data facade. Thus, to achieve strict equivalence of set and immediately retrieved data, the pseudocode above changes to:

    
    newData = "<p>Hello Data Facade!</p>"
    
    dataFacade.setData(newData)
    currentData = dataFacade.getData()
    
    assert newData === currentData
    

    Normalization starts, when, for example, providing options when getting data to trim these data.

    But also, CKEditor 5 does some normalization either implicitly or explicitly.

    Unless you are making use of the General HTML Support (GHS) feature, CKEditor 5 will strip any contents within the data, that cannot be controlled or created by corresponding commands.

    As an example, assume, you have not enabled the bold style. You will experience this behavior (pseudocode, without data facade in place):

    newData = "<p>Hello <strong>World</strong>!</p>"
    
    setData(newData)
    currentData = getData()
    
    assert currentData === "<p>Hello World!</p>"
    

    There is also some normalization that is related to the representation of data within the CKEditor model layer. This layer provides no means, for example, to guarantee a given order of attributes.

    This behavior is perfectly fine for HTML representation, as there are various semantically equivalent ways to represent the data. For example, all the following are semantically equal, and it depends on the internal processing of CKEditor, which one is preferred.

    inputData = `<p class="C1 C2" lang="en">Hello!</p>`
    
    setData(inputData)
    currentData = getData()
    
    assert currentData in [
      inputData,
      `<p class="C2 C1" lang="en">Hello!</p>`, // Changed Class Order
      `<p lang="en" class="C1 C2">Hello!</p>`, // Changed Attribute Order
      `<p lang="en" class="C2 C1">Hello!</p>`, // Changed Class And Attribute Order
    ]
    

    Thus, CKEditor may reorder attributes as well as attribute values, where applicable.

    You should use this data facade when your external data storage, such as a CMS, does not expect a change of data in the scenario sketched above. More complex systems may trigger subsequent actions, that may be expensive, such as triggering a translation agency to translate the new data. In the context of CoreMedia CMS, such a change may trigger a so-called auto-checkout of a document, blocking any other editors from working on that document, although the current editor did not intend to apply any changes.

    If it is intended, that CKEditor 5 performs normalization of data, and you want to store these normalized data immediately, you should stick to the standard patterns, such as using the Autosave plugin and directly getting and setting data at CKEditor.

    Despite not normalizing just set data, the data facade and its underlying controller also ignore options on get (such as trimming). Only an optional set rootName is respected and is meant to provide the same behavior as for a multi-root editor.

    The data facade feature also comes with support for lazy CKEditor 5 initialization, as it is typically used in CoreMedia Studio.

    The standalone mode moves the data-control completely outside CKEditor 5. In this case, you will not add any related plugin (despite Autosave possibly).

    A typical setup may look as follows:

    import { DataFacadeController } from "@coremedia/ckeditor5-data-facade";

    const standaloneController = new DataFacadeController();

    // You may set (and get) data already now.
    standaloneController.setData("<p>Hello Standalone!</p>");

    await ClassicEditor
    .create( document.querySelector( '#editor' ), {
    plugins: [
    Autosave,
    ],
    autosave: {
    save() {
    return saveData(standaloneController.getData());
    },
    waitingTime: 5000,
    },
    })
    .then( (editor) => {
    // Inform controller, that editor is now available.
    standaloneController.init(editor);
    })
    .catch( (error) => {
    console.error( error );
    } );

    // Will now either provide cached data or data directly provided by
    // CKEditor instance.
    standaloneController.getData();

    If you do not require lazy initialization of CKEditor 5 instances, you can also use the embedded mode via data facade plugin.

    A typical setup may look as follows:

    import { DataFacade } from "@coremedia/ckeditor5-data-facade";

    ClassicEditor
    .create( document.querySelector( '#editor' ), {
    plugins: [
    // Transitive Dependency on Autosave.
    DataFacade,
    ],
    autosave: {
    // no save needed
    waitingTime: 5000, // in ms
    },
    dataFacade: {
    save(controller) {
    // saveData providing a promise to store data
    // in an external data storage.
    return saveData( controller.getData() );
    },
    }
    })
    .then( (editor) => {
    editor.plugins.get(DataFacade).setData("<p>Hello Embedded!</p>");
    })
    .catch( (error) => {
    console.error( error );
    } );

    In mixed mode, you can combine lazy initialization and embedded mode. You just need to ensure to switch to delegating mode for the previous standalone controller during the initialization phase of the CKEditor instance.

    When to choose mixed mode? There may no explicit recommendation, when you have to decide between standalone and mixed mode. The differences are minimal.

    The mixed mode mainly exists, so that you will not have different "ideas" of the current data. Having the mixed mode, you may have several standalone controllers bound to one CKEditor 5 instance. Note though, that there is no specified contract for the order of "first-time binding" and possibly already set data within the yet unbound standalone controllers.

    Another possible benefit of the mixed mode: Plugins that require it, may depend on and use the DataFacade for their own purpose.

    A typical setup of the mixed mode may look as follows:

    import { DataFacade, DataFacadeController } from "@coremedia/ckeditor5-data-facade";

    const standaloneController = new DataFacadeController();

    // You may set (and get) data already now.
    standaloneController.setData("<p>Hello Standalone!</p>");

    ClassicEditor
    .create( document.querySelector( '#editor' ), {
    plugins: [
    // Transitive Dependency on Autosave.
    DataFacade,
    ],
    autosave: {
    // no save needed
    waitingTime: 5000, // in ms
    },
    dataFacade: {
    save(controller) {
    // saveData providing a promise to store data
    // in an external data storage.
    return saveData( controller.getData() );
    }
    }
    })
    .then( (editor) => {
    // Will turn the controller into delegating mode.
    standaloneController.init(editor);
    })
    .catch( (error) => {
    console.error( error );
    } );

    // Now the standalone controller delegates to the
    // embedded controller.
    standaloneController.setData("<p>Hello Delegating!</p>");

    If you want to reduce the number of CKEditor 5 instances, as used, for example, in CoreMedia Studio Document Forms, you may reuse a given CKEditor 5 instance. We describe this as using the CKEditor 5 instance within a new context.

    To illustrate it even more, think of the CKEditor 5 previously bound to the text property of document/1. Now we switch to editing document/2 and reuse the CKEditor 5 instance for the text property of that document. Due to asynchronous behavior, you may experience a data flow like this:

    const editor = CKEditor@42AC
    
    thread1: editor.setData(document1.text)
    thread2: editor.setData(document2.text)
    thread1: editor.getData()
    

    Without further adaptations, thread1 will now read the data of document/2 and will not even be aware of it, possibly writing these data back to document/1.

    To provide some contextual awareness, you may provide context information that will signal any mismatched behavior. This context information is provided as an option to the corresponding methods. In pseudocode, it roughly looks like this, given the example above:

    const editor = CKEditor@42AC
    
    thread1: editor.setData(document1.text, { context: "document/1" })
    thread2: editor.setData(document2.text, { context: "document/2" })
    thread1: editor.getData({ context: "document/1" })
    

    In this case, getData will throw a ContextMismatchError, which you may use to apply corresponding countermeasures, that at least should prevent the data of document/2 to be written to document/1.

    Classes

    ContextMismatchError
    DataFacade
    DataFacadeController

    Interfaces

    DataApi
    DataContextOptions
    DataFacadeConfig

    Type Aliases

    DataControllerInstance
    GetDataApi
    GetDataOptions
    GetDataParameters
    GetDataType
    Save
    SetDataApi
    SetDataData
    SetDataOptions
    SetDataParameters
    SetDataType

    Functions

    isContextMismatchError