Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 872f4ff

Browse files
author
Piotr Jasiun
authored
Merge pull request #1421 from ckeditor/t/1418
Feature: Introduced `ModelDocument#change:data` event. Closes #1418.
2 parents 3e74554 + 2b80b89 commit 872f4ff

12 files changed

+485
-104
lines changed

src/model/differ.js

+22-3
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export default class Differ {
171171
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
172172
const markerRange = marker.getRange();
173173

174-
this.bufferMarkerChange( marker.name, markerRange, markerRange );
174+
this.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData );
175175
}
176176

177177
break;
@@ -189,17 +189,20 @@ export default class Differ {
189189
* @param {module:engine/model/range~Range|null} oldRange Marker range before the change or `null` if the marker has just
190190
* been created.
191191
* @param {module:engine/model/range~Range|null} newRange Marker range after the change or `null` if the marker was removed.
192+
* @param {Boolean} affectsData Flag indicating whether marker affects the editor data.
192193
*/
193-
bufferMarkerChange( markerName, oldRange, newRange ) {
194+
bufferMarkerChange( markerName, oldRange, newRange, affectsData ) {
194195
const buffered = this._changedMarkers.get( markerName );
195196

196197
if ( !buffered ) {
197198
this._changedMarkers.set( markerName, {
198199
oldRange,
199-
newRange
200+
newRange,
201+
affectsData
200202
} );
201203
} else {
202204
buffered.newRange = newRange;
205+
buffered.affectsData = affectsData;
203206

204207
if ( buffered.oldRange == null && buffered.newRange == null ) {
205208
// The marker is going to be removed (`newRange == null`) but it did not exist before the first buffered change
@@ -243,6 +246,22 @@ export default class Differ {
243246
return result;
244247
}
245248

249+
/**
250+
* Checks whether some of buffered marker affects the editor data or whether some element changed.
251+
*
252+
* @returns {Boolean} `true` if buffered markers or changes in elements affects the editor data.
253+
*/
254+
hasDataChanges() {
255+
for ( const [ , change ] of this._changedMarkers ) {
256+
if ( change.affectsData ) {
257+
return true;
258+
}
259+
}
260+
261+
// If markers do not affect the data, check whether there are some changes in elements.
262+
return this._changesInElement.size > 0;
263+
}
264+
246265
/**
247266
* Calculates the diff between the old model tree state (the state before the first buffered operations since the last {@link #reset}
248267
* call) and the new model tree state (actual one). It should be called after all buffered operations are executed.

src/model/document.js

+31-9
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,16 @@ export default class Document {
147147
// Wait for `_change` event from model, which signalizes that outermost change block has finished.
148148
// When this happens, check if there were any changes done on document, and if so, call post fixers,
149149
// fire `change` event for features and conversion and then reset the differ.
150+
// Fire `change:data` event when at least one operation or buffered marker changes the data.
150151
this.listenTo( model, '_change', ( evt, writer ) => {
151152
if ( !this.differ.isEmpty || hasSelectionChanged ) {
152153
this._callPostFixers( writer );
153154

154-
this.fire( 'change', writer.batch );
155+
if ( this.differ.hasDataChanges() ) {
156+
this.fire( 'change:data', writer.batch );
157+
} else {
158+
this.fire( 'change', writer.batch );
159+
}
155160

156161
this.differ.reset();
157162
hasSelectionChanged = false;
@@ -163,12 +168,12 @@ export default class Document {
163168
// are modified using `model.markers` collection, not through `MarkerOperation`).
164169
this.listenTo( model.markers, 'update', ( evt, marker, oldRange, newRange ) => {
165170
// Whenever marker is updated, buffer that change.
166-
this.differ.bufferMarkerChange( marker.name, oldRange, newRange );
171+
this.differ.bufferMarkerChange( marker.name, oldRange, newRange, marker.affectsData );
167172

168173
if ( oldRange === null ) {
169174
// If this is a new marker, add a listener that will buffer change whenever marker changes.
170175
marker.on( 'change', ( evt, oldRange ) => {
171-
this.differ.bufferMarkerChange( marker.name, oldRange, marker.getRange() );
176+
this.differ.bufferMarkerChange( marker.name, oldRange, marker.getRange(), marker.affectsData );
172177
} );
173178
}
174179
} );
@@ -379,18 +384,35 @@ export default class Document {
379384
* console.log( 'The Document has changed!' );
380385
* } );
381386
*
382-
* If, however, you only want to be notified about structure changes, then check whether the
383-
* {@link module:engine/model/differ~Differ differ} contains any changes:
387+
* If, however, you only want to be notified about the data changes, then use the
388+
* {@link module:engine/model/document~Document#event:change:data change:data} event,
389+
* which fires for document structure changes and marker changes (which affects the data).
384390
*
385-
* model.document.on( 'change', () => {
386-
* if ( model.document.differ.getChanges().length > 0 ) {
387-
* console.log( 'The Document has changed!' );
388-
* }
391+
* model.document.on( 'change:data', () => {
392+
* console.log( 'The data has changed!' );
389393
* } );
390394
*
391395
* @event change
392396
* @param {module:engine/model/batch~Batch} batch The batch that was used in the executed changes block.
393397
*/
398+
399+
/**
400+
* Fired when the data changes, which includes:
401+
* * document structure changes,
402+
* * marker changes (which affects the data).
403+
*
404+
* If you want to be notified about the data changes, then listen to this event:
405+
*
406+
* model.document.on( 'change:data', () => {
407+
* console.log( 'The data has changed!' );
408+
* } );
409+
*
410+
* If you would like to listen to all document's changes, then look at the
411+
* {@link module:engine/model/document~Document#event:change change} event.
412+
*
413+
* @event change:data
414+
* @param {module:engine/model/batch~Batch} batch The batch that was used in the executed changes block.
415+
*/
394416
}
395417

396418
mix( Document, EmitterMixin );

src/model/markercollection.js

+41-10
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,11 @@ export default class MarkerCollection {
8888
* @param {String|module:engine/model/markercollection~Marker} markerOrName Name of marker to set or marker instance to update.
8989
* @param {module:engine/model/range~Range} range Marker range.
9090
* @param {Boolean} [managedUsingOperations=false] Specifies whether the marker is managed using operations.
91+
* @param {Boolean} [affectsData=false] Specifies whether the marker affects the data produced by the data pipeline
92+
* (is persisted in the editor's data).
9193
* @returns {module:engine/model/markercollection~Marker} `Marker` instance which was added or updated.
9294
*/
93-
_set( markerOrName, range, managedUsingOperations = false ) {
95+
_set( markerOrName, range, managedUsingOperations = false, affectsData = false ) {
9496
const markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;
9597
const oldMarker = this._markers.get( markerName );
9698

@@ -108,6 +110,11 @@ export default class MarkerCollection {
108110
hasChanged = true;
109111
}
110112

113+
if ( typeof affectsData === 'boolean' && affectsData != oldMarker.affectsData ) {
114+
oldMarker._affectsData = affectsData;
115+
hasChanged = true;
116+
}
117+
111118
if ( hasChanged ) {
112119
this.fire( 'update:' + markerName, oldMarker, oldRange, range );
113120
}
@@ -116,7 +123,7 @@ export default class MarkerCollection {
116123
}
117124

118125
const liveRange = LiveRange.createFromRange( range );
119-
const marker = new Marker( markerName, liveRange, managedUsingOperations );
126+
const marker = new Marker( markerName, liveRange, managedUsingOperations, affectsData );
120127

121128
this._markers.set( markerName, marker );
122129
this.fire( 'update:' + markerName, marker, null, range );
@@ -313,8 +320,10 @@ class Marker {
313320
* @param {String} name Marker name.
314321
* @param {module:engine/model/liverange~LiveRange} liveRange Range marked by the marker.
315322
* @param {Boolean} managedUsingOperations Specifies whether the marker is managed using operations.
323+
* @param {Boolean} affectsData Specifies whether the marker affects the data produced by the data pipeline
324+
* (is persisted in the editor's data).
316325
*/
317-
constructor( name, liveRange, managedUsingOperations ) {
326+
constructor( name, liveRange, managedUsingOperations, affectsData ) {
318327
/**
319328
* Marker's name.
320329
*
@@ -324,24 +333,33 @@ class Marker {
324333
this.name = name;
325334

326335
/**
327-
* Flag indicates if the marker is managed using operations or not.
336+
* Range marked by the marker.
328337
*
329338
* @protected
339+
* @member {module:engine/model/liverange~LiveRange}
340+
*/
341+
this._liveRange = this._attachLiveRange( liveRange );
342+
343+
/**
344+
* Flag indicates if the marker is managed using operations or not.
345+
*
346+
* @private
330347
* @member {Boolean}
331348
*/
332349
this._managedUsingOperations = managedUsingOperations;
333350

334351
/**
335-
* Range marked by the marker.
352+
* Specifies whether the marker affects the data produced by the data pipeline
353+
* (is persisted in the editor's data).
336354
*
337355
* @private
338-
* @member {module:engine/model/liverange~LiveRange} #_liveRange
356+
* @member {Boolean}
339357
*/
340-
this._liveRange = this._attachLiveRange( liveRange );
358+
this._affectsData = affectsData;
341359
}
342360

343361
/**
344-
* Returns value of flag indicates if the marker is managed using operations or not.
362+
* A value indicating if the marker is managed using operations.
345363
* See {@link ~Marker marker class description} to learn more about marker types.
346364
* See {@link module:engine/model/writer~Writer#addMarker}.
347365
*
@@ -355,6 +373,19 @@ class Marker {
355373
return this._managedUsingOperations;
356374
}
357375

376+
/**
377+
* A value indicating if the marker changes the data.
378+
*
379+
* @returns {Boolean}
380+
*/
381+
get affectsData() {
382+
if ( !this._liveRange ) {
383+
throw new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.' );
384+
}
385+
386+
return this._affectsData;
387+
}
388+
358389
/**
359390
* Returns current marker start position.
360391
*
@@ -382,7 +413,7 @@ class Marker {
382413
}
383414

384415
/**
385-
* Returns a range that represents current state of marker.
416+
* Returns a range that represents the current state of the marker.
386417
*
387418
* Keep in mind that returned value is a {@link module:engine/model/range~Range Range}, not a
388419
* {@link module:engine/model/liverange~LiveRange LiveRange}. This means that it is up-to-date and relevant only
@@ -402,7 +433,7 @@ class Marker {
402433
}
403434

404435
/**
405-
* Binds new live range to marker and detach the old one if is attached.
436+
* Binds new live range to the marker and detach the old one if is attached.
406437
*
407438
* @protected
408439
* @param {module:engine/model/liverange~LiveRange} liveRange Live range to attach

src/model/operation/markeroperation.js

+18-6
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ export default class MarkerOperation extends Operation {
2020
* @param {module:engine/model/range~Range} newRange Marker range after the change.
2121
* @param {module:engine/model/markercollection~MarkerCollection} markers Marker collection on which change should be executed.
2222
* @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
23+
* @param {Boolean} affectsData Specifies whether the marker operation affects the data produced by the data pipeline
24+
* (is persisted in the editor's data).
2325
* can be applied or `null` if the operation operates on detached (non-document) tree.
2426
*/
25-
constructor( name, oldRange, newRange, markers, baseVersion ) {
27+
constructor( name, oldRange, newRange, markers, baseVersion, affectsData ) {
2628
super( baseVersion );
2729

2830
/**
@@ -49,6 +51,15 @@ export default class MarkerOperation extends Operation {
4951
*/
5052
this.newRange = newRange ? Range.createFromRange( newRange ) : null;
5153

54+
/**
55+
* Specifies whether the marker operation affects the data produced by the data pipeline
56+
* (is persisted in the editor's data).
57+
*
58+
* @readonly
59+
* @member {Boolean}
60+
*/
61+
this.affectsData = affectsData;
62+
5263
/**
5364
* Marker collection on which change should be executed.
5465
*
@@ -71,7 +82,7 @@ export default class MarkerOperation extends Operation {
7182
* @returns {module:engine/model/operation/markeroperation~MarkerOperation} Clone of this operation.
7283
*/
7384
clone() {
74-
return new MarkerOperation( this.name, this.oldRange, this.newRange, this._markers, this.baseVersion );
85+
return new MarkerOperation( this.name, this.oldRange, this.newRange, this._markers, this.baseVersion, this.affectsData );
7586
}
7687

7788
/**
@@ -80,7 +91,7 @@ export default class MarkerOperation extends Operation {
8091
* @returns {module:engine/model/operation/markeroperation~MarkerOperation}
8192
*/
8293
getReversed() {
83-
return new MarkerOperation( this.name, this.newRange, this.oldRange, this._markers, this.baseVersion + 1 );
94+
return new MarkerOperation( this.name, this.newRange, this.oldRange, this._markers, this.baseVersion + 1, this.affectsData );
8495
}
8596

8697
/**
@@ -89,7 +100,7 @@ export default class MarkerOperation extends Operation {
89100
_execute() {
90101
const type = this.newRange ? '_set' : '_remove';
91102

92-
this._markers[ type ]( this.name, this.newRange, true );
103+
this._markers[ type ]( this.name, this.newRange, true, this.affectsData );
93104
}
94105

95106
/**
@@ -111,7 +122,7 @@ export default class MarkerOperation extends Operation {
111122
}
112123

113124
/**
114-
* Creates `MarkerOperation` object from deserilized object, i.e. from parsed JSON string.
125+
* Creates `MarkerOperation` object from deserialized object, i.e. from parsed JSON string.
115126
*
116127
* @param {Object} json Deserialized JSON object.
117128
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.
@@ -123,7 +134,8 @@ export default class MarkerOperation extends Operation {
123134
json.oldRange ? Range.fromJSON( json.oldRange, document ) : null,
124135
json.newRange ? Range.fromJSON( json.newRange, document ) : null,
125136
document.model.markers,
126-
json.baseVersion
137+
json.baseVersion,
138+
json.affectsData
127139
);
128140
}
129141
}

src/model/operation/operationfactory.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ operations[ RootAttributeOperation.className ] = RootAttributeOperation;
3737
*/
3838
export default class OperationFactory {
3939
/**
40-
* Creates concrete `Operation` object from deserilized object, i.e. from parsed JSON string.
40+
* Creates concrete `Operation` object from deserialized object, i.e. from parsed JSON string.
4141
*
4242
* @param {Object} json Deserialized JSON object.
4343
* @param {module:engine/model/document~Document} document Document on which this operation will be applied.

0 commit comments

Comments
 (0)