Report an issue

guideUsing context with collaboration features

# Collaboration features and multiple editors

Most applications use only one editor instance per a web page or a form. In these integrations, the editor is treated as the topmost entity which initializes features and coordinates their work. Collaboration features like sidebar or presence list are linked with this single editor instance and handle only the changes happening within this editor instance.

This does not create a good user experience for applications that need to present multiple editors on one web page, though. In that case, each editor instance has its own sidebar and presence list. Also, multiple, separate connections need to be handled for data exchange.

The Context class was introduced to solve such issues. It organizes multiple editors into one environment. Some of the features, instead of being initialized for a singular editor, can be initialized for the whole context. Then, each editor can use the same instance of the feature, which means that there can be one common presence list or sidebar linked with multiple editors.

Note that only selected plugins are prepared to work as context plugins. See the API documentation for collaboration features to learn which plugins can be used as context plugins.

# Collaboration features and no editor

A context plugin that is added to a context is ready as soon as the context is created. This allows for using the context plugins without creating an editor at all!

Thanks to that you can provide features like comments on non-editor form fields that are no longer just editor features but are deeply integrated with your application.

# Channel ID

The channel ID is used to identify a data storage for collaboration features that a given editor or context should connect to. If you are using multiple editors, each of them should use a different channel ID to connect to a specific document. Additionally, if you would like to use collaboration features outside of an editor, you should also specify a unique channel ID for the context.

To set the channel ID, use the config.collaboration.channelId configuration property. See the code snippets below.

The channel ID is frequently used as a parameter or data property in the comments API. If you are preparing a custom integration using the comments API, you can use the channel ID to recognize whether the comment was added to an editor instance or to a context.

# Preparing a build with the context

Complementary to this guide, we provide a ready-to-use sample and an example of Angular integration.

You may use them as an example or as a starting point for your own integration.

The context can be used both with standalone collaboration features and real-time collaboration features. Below is an example featuring real-time collaboration.

We recommend reading about preparing a custom build in real-time collaboration features integration guide. The samples below will be based on this guide.

To use Context, add it to your build and export together with the editor class.

import ContextBase from '@ckeditor/ckeditor5-core/src/context';
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';

// Editor plugins:
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';

// Real-time collaboration plugins are editor plugins:
import RealTimeCollaborativeEditing from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativeediting';
import RealTimeCollaborativeComments from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativecomments';
import RealTimeCollaborativeTrackChanges from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativetrackchanges';

// Context plugins:
import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices'
import CloudServicesCommentsAdapter from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativecomments/cloudservicescommentsadapter';
import CommentsRepository from '@ckeditor/ckeditor5-comments/src/comments/commentsrepository';
import NarrowSidebar from '@ckeditor/ckeditor5-comments/src/annotations/narrowsidebar';
import WideSidebar from '@ckeditor/ckeditor5-comments/src/annotations/widesidebar';
import PresenceList from '@ckeditor/ckeditor5-real-time-collaboration/src/presencelist';

class Context extends ContextBase {}

// Plugins to include in the context.
Context.builtinPlugins = [
    CloudServices,
    CloudServicesCommentsAdapter,
    CommentsRepository,
    NarrowSidebar,
    PresenceList,
    WideSidebar
];

Context.defaultConfig = {
    // The default configuration for the context plugins:
    sidebar: {
        container: document.querySelector( '#sidebar' )
    },
    presenceList: {
        container: document.querySelector( '#presence-list-container' )
    },
    // Configuration shared between the editors:
    language: 'en',
    toolbar: {
        items: [
            'bold', 'italic', '|', 'undo', 'redo', '|',
            'comment', 'trackChanges'
        ]
    },
    comments: {
        editorConfig: {
            plugins: [ Essentials, Paragraph, Bold, Italic ]
        }
    }
};

class ClassicEditor extends ClassicEditorBase {}

// Plugins to include in the build.
ClassicEditor.builtinPlugins = [
    Essentials, Paragraph, Bold, Italic, Heading,
    // Real-time collaboration plugins are editor plugins:
    RealTimeCollaborativeEditing, RealTimeCollaborativeComments, RealTimeCollaborativeTrackChanges
];

export default { ClassicEditor, Context };

# Using the context in an integration

After your build is ready, you need to configure and initialize the context and the editor.

const { ClassicEditor: Editor, Context } = ClassicEditor;

const channelId = 'channel-id';

// Create a configuration, passing additional configuration options:
const contextConfiguration = {
    // The configuration for real-time collaboration features, shared between the editors:
    cloudServices: {
        // PROVIDE CORRECT VALUES HERE:
        tokenUrl: 'https://example.com/cs-token-endpoint',
        uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/',
        webSocketUrl: 'your-organization-id.cke-cs.com/ws/'
    },
    // Collaboration configuration for the context:
    collaboration: {
        channelId
    }
};

// An example of initialization of the context and multiple editors:
const context = Context.create( contextConfiguration ).then( context => {
    // After the context is ready, initialize an editor on all elements in the DOM with the `.editor` class:
    for ( const editorElement of document.querySelectorAll( '.editor' ) ) {
        const editorConfig = {
            // Pass the context to the editor.
            context,
            collaboration: {
                // Each editor should connect to its document.
                // Create a unique channel ID for an editor (document).
                channelId: channelId + '-' + editorElement.id
            }
        };

        Editor.create( editorElement, editorConfig ).then( editor => {
            // ...
            // You can do something with the editor instance after it was initialized.
        } );
    }
} );

Or you can use the simpler async/await pattern:

( async () => {
    const context = await Context.create( contextConfiguration );

    for ( const editorElement of document.querySelectorAll( '.editor' ) ) {
        const editorConfig = {
            context,
            collaboration: {
                channelId: channelId + '-' + editorElement.id
            }
        };

        const editor = await Editor.create( editorElement, editorConfig );

        // ...
        // You can do something with the editor instance after it was initialized.
    }
} )();

# Context and watchdog

Similarly to an editor instance, context can be integrated with a watchdog to handle the errors that may happen in the editor or context features. Please refer to the watchdog documentation to learn how to use the context watchdog.

# Full implementation

Below are code snippets showcasing the use of a context and a watchdog together with real-time collaboration features.

Build the source file:

import ContextBase from '@ckeditor/ckeditor5-core/src/context';
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';

// Import the watchdog for the context:
import ContextWatchdog from '@ckeditor/ckeditor5-watchdog/src/contextwatchdog';

// Editor plugins:
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';

// Real-time collaboration plugins are editor plugins:
import RealTimeCollaborativeEditing from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativeediting';
import RealTimeCollaborativeComments from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativecomments';
import RealTimeCollaborativeTrackChanges from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativetrackchanges';

// Context plugins:
import CloudServicesCommentsAdapter from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativecomments/cloudservicescommentsadapter';
import CommentsRepository from '@ckeditor/ckeditor5-comments/src/comments/commentsrepository';
import NarrowSidebar from '@ckeditor/ckeditor5-comments/src/annotations/narrowsidebar';
import WideSidebar from '@ckeditor/ckeditor5-comments/src/annotations/widesidebar';
import PresenceList from '@ckeditor/ckeditor5-real-time-collaboration/src/presencelist';

class Context extends ContextBase {}

// Plugins to include in the context.
Context.builtinPlugins = [
    CloudServicesCommentsAdapter,
    CommentsRepository,
    NarrowSidebar,
    WideSidebar
    PresenceList
];

Context.defaultConfig = {
    // Default configuration for the context plugins:
    sidebar: {
        container: document.querySelector( '#sidebar' )
    },
    presenceList: {
        container: document.querySelector( '#presence-list-container' )
    },
    // The configuration shared between the editors:
    language: 'en',
    toolbar: {
        items: [
            'bold', 'italic', '|', 'undo', 'redo', '|',
            'comment', 'trackChanges'
        ]
    },
    comments: {
        editorConfig: {
            plugins: [ Essentials, Paragraph, Bold, Italic ]
        }
    }
};

class ClassicEditor extends ClassicEditorBase {}

// Plugins to include in the build.
ClassicEditor.builtinPlugins = [
    Essentials, Paragraph, Bold, Italic, Heading,
    // Real-time collaboration plugins are editor plugins:
    RealTimeCollaborativeEditing, RealTimeCollaborativeComments, RealTimeCollaborativeTrackChanges
];

export default { ClassicEditor, Context, ContextWatchdog };

The updated HTML file should look as follows:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CKEditor 5 Collaboration – Hello World!</title>

    <style type="text/css">
        #presence-list-container {
            width: 800px;
            margin: 0 auto;
        }

        #container {
            display: flex;
            position: relative;
            width: 800px;
            margin: 0 auto;
        }

        .form {
            width: 500px;
        }

        #container .ck.ck-editor {
            width: 100%;
        }

        #sidebar {
            width: 300px;
            padding: 0 10px;
        }
    </style>
</head>

<body>
    <div id="presence-list-container"></div>

    <div id="container">
        <div class="form">
            <div class="editor" id="editor-1">
                <h2>Editor 1</h2>
                <p>Foo bar baz</p>
            </div>
            <div class="editor" id="editor-2">
                <h2>Editor 2</h2>
                <p>Foo bar baz</p>
            </div>
        </div>
        <div id="sidebar"></div>
    </div>

    <script src="../build/ckeditor.js"></script>
    <script>
        ( async () => {
            const { ClassicEditor: Editor, Context, ContextWatchdog } = ClassicEditor;

            const channelId = 'channel-id';

            const watchdog = new ContextWatchdog( Context );

            // Create the configuration, passing additional configuration options:
            const contextConfiguration = {
                // The configuration for real-time collaboration features, shared between the editors:
                cloudServices: {
                    // PROVIDE CORRECT VALUES HERE:
                    tokenUrl: 'https://example.com/cs-token-endpoint',
                    uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/',
                    webSocketUrl: 'your-organization-id.cke-cs.com/ws/'
                },
                // Collaboration configuration for the context:
                collaboration: {
                    channelId
                }
            };

            await watchdog.create( contextConfiguration );

            for ( const editorElement of document.querySelectorAll( '.editor' ) ) {
                await watchdog.add( {
                    id: editorElement.id,
                    type: 'editor',
                    sourceElementOrData: editorElement,
                    config: {
                        collaboration: {
                            // Create a unique channel ID for an editor (document).
                            channelId: channelId + '-' + editorElement.id
                        }
                        // You do not need to pass the context to the configuration
                        // if you are using the context watchdog.
                    },
                    creator: ( element, config ) => Editor.create( element, config )
                } );
            }
        } )();
    </script>
</body>
</html>

# Demo

Share the complete URL of this page with your colleagues to collaborate in real time!

Bilingual Personality Disorder

This may be the first time you hear about this made-up disorder but it actually isn’t so far from the truth. As recent studies show, the language you speak has more effects on you than you realize. According to the studies, the language a person speaks affects their cognition, behavior, emotions and hence their personality.

Research

This shouldn’t come as a surprise since we already know that different regions of the brain become more active depending on the activity. The structure, information and especially the culture of languages varies substantially and the language a person speaks is an essential element of daily life.