• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ckeditor / ckeditor5-vue2 / #248

pending completion
#248

travis-ci

19 of 19 branches covered (100.0%)

Branch coverage included in aggregate %.

45 of 45 relevant lines covered (100.0%)

28.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/ckeditor.js
1
/**
2
 * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
 * For licensing, see LICENSE.md.
4
 */
5

6
/* global window, console */
7

8
import { debounce } from 'lodash-es';
9

10
const SAMPLE_READ_ONLY_LOCK_ID = 'Integration Sample';
1✔
11
const INPUT_EVENT_DEBOUNCE_WAIT = 300;
1✔
12

13
export default {
14
        name: 'ckeditor',
15

16
        created() {
17
                const { CKEDITOR_VERSION } = window;
51✔
18

19
                // Starting from v34.0.0, CKEditor 5 introduces a lock mechanism enabling/disabling the read-only mode.
20
                // As it is a breaking change between major releases of the integration, the component requires using
21
                // CKEditor 5 in version 34 or higher.
22
                if ( CKEDITOR_VERSION ) {
51✔
23
                        const [ major ] = CKEDITOR_VERSION.split( '.' ).map( Number );
50✔
24

25
                        if ( major < 34 ) {
50✔
26
                                console.warn( 'The <CKEditor> component requires using CKEditor 5 in version 34 or higher.' );
1✔
27
                        }
28
                } else {
29
                        console.warn( 'Cannot find the "CKEDITOR_VERSION" in the "window" scope.' );
1✔
30
                }
31
        },
32

33
        render( createElement ) {
34
                return createElement( this.tagName );
58✔
35
        },
36

37
        props: {
38
                editor: {
39
                        type: Function,
40
                        default: null
41
                },
42
                value: {
43
                        type: String,
44
                        default: ''
45
                },
46
                config: {
47
                        type: Object,
48
                        default: () => ( {} )
47✔
49
                },
50
                tagName: {
51
                        type: String,
52
                        default: 'div'
53
                },
54
                disabled: {
55
                        type: Boolean,
56
                        default: false
57
                }
58
        },
59

60
        data() {
61
                return {
51✔
62
                        // Don't define it in #props because it produces a warning.
63
                        // https://vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
64
                        $_instance: null,
65

66
                        $_lastEditorData: {
67
                                type: String,
68
                                default: ''
69
                        }
70
                };
71
        },
72

73
        mounted() {
74
                // Clone the config first so it never gets mutated (across multiple editor instances).
75
                // https://github.com/ckeditor/ckeditor5-vue/issues/101
76
                const editorConfig = Object.assign( {}, this.config );
51✔
77

78
                if ( this.value ) {
51✔
79
                        editorConfig.initialData = this.value;
6✔
80
                }
81

82
                this.editor.create( this.$el, editorConfig )
51✔
83
                        .then( editor => {
84
                                // Save the reference to the $_instance for further use.
85
                                this.$_instance = editor;
50✔
86

87
                                this.$_setUpEditorEvents();
50✔
88

89
                                // Synchronize the editor content. The #value may change while the editor is being created, so the editor content has to be
90
                                // synchronized with these potential changes as soon as it is ready.
91
                                if ( this.value !== editorConfig.initialData ) {
50✔
92
                                        editor.setData( this.value );
44✔
93
                                }
94

95
                                // Set initial disabled state.
96
                                if ( this.disabled ) {
50✔
97
                                        editor.enableReadOnlyMode( SAMPLE_READ_ONLY_LOCK_ID );
2✔
98
                                }
99

100
                                // Let the world know the editor is ready.
101
                                this.$emit( 'ready', editor );
50✔
102
                        } )
103
                        .catch( error => {
104
                                console.error( error );
1✔
105
                        } );
106
        },
107

108
        beforeDestroy() {
109
                if ( this.$_instance ) {
46✔
110
                        this.$_instance.destroy();
39✔
111
                        this.$_instance = null;
39✔
112
                }
113

114
                // Note: By the time the editor is destroyed (promise resolved, editor#destroy fired)
115
                // the Vue component will not be able to emit any longer. So emitting #destroy a bit earlier.
116
                this.$emit( 'destroy', this.$_instance );
46✔
117
        },
118

119
        watch: {
120
                value( value ) {
121
                        // Synchronize changes of #value. There are two sources of changes:
122
                        //
123
                        //                   External value change        ──────╮
124
                        //                                                      ╰─────> ┏━━━━━━━━━━━┓
125
                        //                                                              ┃ Component ┃
126
                        //                                                      ╭─────> ┗━━━━━━━━━━━┛
127
                        //                   Internal data change         ──────╯
128
                        //             (typing, commands, collaboration)
129
                        //
130
                        // Case 1: If the change was external (via props), the editor data must be synced with
131
                        // the component using $_instance#setData() and it is OK to destroy the selection.
132
                        //
133
                        // Case 2: If the change is the result of internal data change, the #value is the same as
134
                        // this.$_lastEditorData, which has been cached on #change:data. If we called
135
                        // $_instance#setData() at this point, that would demolish the selection.
136
                        //
137
                        // To limit the number of $_instance#setData() which is time-consuming when there is a
138
                        // lot of data we make sure:
139
                        //    * the new value is at least different than the old value (Case 1.)
140
                        //    * the new value is different than the last internal instance state (Case 2.)
141
                        //
142
                        // See: https://github.com/ckeditor/ckeditor5-vue/issues/42.
143
                        if ( this.$_instance && value !== this.$_lastEditorData ) {
5✔
144
                                this.$_instance.setData( value );
4✔
145
                        }
146
                },
147

148
                // Synchronize changes of #disabled.
149
                disabled( readOnlyMode ) {
150
                        if ( readOnlyMode ) {
2✔
151
                                this.$_instance.enableReadOnlyMode( SAMPLE_READ_ONLY_LOCK_ID );
1✔
152
                        } else {
153
                                this.$_instance.disableReadOnlyMode( SAMPLE_READ_ONLY_LOCK_ID );
1✔
154
                        }
155
                }
156
        },
157

158
        methods: {
159
                $_setUpEditorEvents() {
160
                        const editor = this.$_instance;
50✔
161

162
                        // Use the leading edge so the first event in the series is emitted immediately.
163
                        // Failing to do so leads to race conditions, for instance, when the component value
164
                        // is set twice in a time span shorter than the debounce time.
165
                        // See https://github.com/ckeditor/ckeditor5-vue/issues/149.
166
                        const emitDebouncedInputEvent = debounce( evt => {
50✔
167
                                // Cache the last editor data. This kind of data is a result of typing,
168
                                // editor command execution, collaborative changes to the document, etc.
169
                                // This data is compared when the component value changes in a 2-way binding.
170
                                const data = this.$_lastEditorData = editor.getData();
2✔
171

172
                                // The compatibility with the v-model and general Vue.js concept of input–like components.
173
                                this.$emit( 'input', data, evt, editor );
2✔
174
                        }, INPUT_EVENT_DEBOUNCE_WAIT, { leading: true } );
175

176
                        // Debounce emitting the #input event. When data is huge, $_instance#getData()
177
                        // takes a lot of time to execute on every single key press and ruins the UX.
178
                        //
179
                        // See: https://github.com/ckeditor/ckeditor5-vue/issues/42
180
                        editor.model.document.on( 'change:data', emitDebouncedInputEvent );
50✔
181

182
                        editor.editing.view.document.on( 'focus', evt => {
50✔
183
                                this.$emit( 'focus', evt, editor );
1✔
184
                        } );
185

186
                        editor.editing.view.document.on( 'blur', evt => {
50✔
187
                                this.$emit( 'blur', evt, editor );
1✔
188
                        } );
189
                }
190
        }
191
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc