guideIntegration

The basic instructions for integrating Letters with your application are available in Quick start guide. This article goes through some additional useful topics.

# Finish editing button

Letters comes with a finish editing button that can be used for exiting the application, for example when showing Letters in a modal window.

The finish editing button.

In order to show the finish editing button, provide the finishEditing configuration option. The action property defines a callback that will be executed.

Letters.create( document.body, {
    cloudServices: {
        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/',
        documentId: 'dogs'
    },
    finishEditing: {
        show: true,
        action: () => { alert( 'finished'); }
    }
} );

You can also use the event fired when the finish editing button is clicked:

Letters.create( document.body, {
    cloudServices: {
        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/',
        documentId: 'dogs'
    },
    finishEditing: {
        show: true
    }
} ).then( ( letters ) => {
    letters.on( 'finish', () => { alert( 'finished' ); } );
} );

Note that if the button is enabled, the user can also trigger the finish action with the keyboard (using the Esc key).
There is also no default action attached to the finish event.

# Sharing the document

It is possible to enable the share button in the top bar using the share option:

Letters.create( document.body, {
    cloudServices: {
        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/',
        documentId: '21'
    },
    share: {
        url: 'https://example.com/share/21',
        message: '<h4>Invite users to collaborate with them.</h4><p>Everybody with this link will be able to edit the document with you.</p>'
    }
} );

The share button lets you invite users to collaborate on the same document in a convenient manner. When the user clicks the share button, a panel is shown with the provided share URL.

The share button and balloon panel.

As an integrator, you need to take care of creating Letters with a proper documentId under the provided URL. Letters only offers a simple way to display the URL to the user, but it will not add any special logic to that given URL.

In the example above, a balloon with “https://example.com/share/21” as the share link will be displayed to the user. To make it work well when another user gets and opens this URL, your application should handle the request to that URL and launch Letters there with the same document ID (21).

# Bootstrapping and loading

The default way to load Letters is to call the Letters.create() method. It bootstraps Letters UI first, with the top bar, finish editing and share buttons and the loading animation. Then it loads the editor, connects it to CKEditor Cloud Services and replaces the loading animation with the editor content.

However, you may want to perform some additional time-consuming action, like loading some data from the server. In such case, you may want to display Letters UI with the loading animation, perform your action and then, when it is finished, load the editor. This is possible to achieve using the letters.bootstrap() and letters.load() methods, instead of Letters.create():

const letters = new Letters( {
    cloudServices: {
        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/',
        documentId: 'dogs'
    }
} );

// Bootstrap the UI and the loading screen.
letters.bootstrap( document.body );

// You can execute asynchronous actions here, like loading data.
var loadData = new Promise( ( resolve ) => {
        // You can overwrite the configuration at this stage.
        letters.config.title = 'foo';
        letters.config.body = '<p>bar</p>';

        setTimeout(resolve, 3000 );
} );

// Finally load the Letters editor.
loadData.then( () => letters.load() );

# Saving and document versions

The document is temporarily stored in the cloud by CKEditor Cloud Services only when there are connected users. It means that when the last user disconnects, the content should be saved in the database on your server. However, there might be a conflict when two collaborating users are saving the same document and the older document overwrites the new one.

To prevent race conditions, Letters provides an additional variable: cloudDocumentVersion, available via letters.editor.plugins.get( 'CollaborationClient' ).cloudDocumentVersion. It is a number that defines which client has a newer version of the document. This number should be stored in the database together with the content. When any client wants to save the content, document versions should be compared. The document should be saved only when the version is higher. Otherwise it means that this is an older version of the document and there is already a newer version saved.

# Autosave

Letters also provides the change event which can be used to implement autosave. This event is throttled, so it is not fired too often, but it also listens on all ways the user can close the editor to fire the event just before Letters is closed.

To implement autosave you should do the following. On the client side:

letters.on( 'change', ( evt, data ) => {
    // Note: Pseudocode - sendToServer does not exist.
    sendToServer( {
        title: letters.getTitle(),
        body: letters.getBody(),
        cloudDocumentVersion: letters.editor.plugins.get( 'CollaborationClient' ).cloudDocumentVersion
    } );
} );

On the server side:

// Is this client sending a newer version of the document
// than the one already saved?
// Note: Pseudocode again.
if ( data.cloudDocumentVersion > database.get( 'cloudDocumentVersion' ) ) {
    // Save the document.
    database.set( 'title', data.title );
    database.set( 'body', data.body );

    // And bump the document version.
    database.set( 'cloudDocumentVersion', data.cloudDocumentVersion );
}

# Collaborative Comments

Collaborative comments are already enabled in the default Letters build. You can read more about the feature and configuring it in the Collaborative comments guide in the CKEditor 5 Framework documentation.

Letters can be run in “comment-only” mode, where the users are allowed only to add, edit and remove comments. To do so, please refer to the Comment-only mode guide.

# letters.editor

Letters uses a custom editor based on the CKEditor 5 Editor. It is available as the letters.editor property and is very useful whenever you want to integrate Letters with your application.

For instance, if you want to bold the currently selected content, use:

letters.editor.execute( 'bold' );

See the Introduction to CKEditor 5 Framework architecture to learn more about the editor API.

What is important, Letters has a special mechanism to handle errors and crashes. It restarts the editor, creating a new instance of it. It means that after the restart letters.editor will not be the same instance as it was before. Because of it, you should not store this variable nor modify the editor directly. Use custom plugins if you want to change the editor behavior.

# UMD package

Letters is distributed as a Universal Module Definition (UMD) package. It means that it can be loaded as an ES6 module:

import Letters from 'letters';

Letters.create( element, config );

It can also be loaded as a CommonJS or AMD module, for instance:

requirejs( [ 'Letters' ], ( Letters ) => {
    Letters.create( element, config );
} );

Or as a global JavaScipt object, if there is no module format provided:

Letters.create( element, config );