8
0

attributeoperation.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module engine/model/operation/attributeoperation
  7. */
  8. import Operation from './operation';
  9. import Range from '../range';
  10. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  11. import { _setAttribute } from './utils';
  12. import isEqual from '@ckeditor/ckeditor5-utils/src/lib/lodash/isEqual';
  13. /**
  14. * Operation to change nodes' attribute.
  15. *
  16. * Using this class you can add, remove or change value of the attribute.
  17. *
  18. * @extends module:engine/model/operation/operation~Operation
  19. */
  20. export default class AttributeOperation extends Operation {
  21. /**
  22. * Creates an operation that changes, removes or adds attributes.
  23. *
  24. * If only `newValue` is set, attribute will be added on a node. Note that all nodes in operation's range must not
  25. * have an attribute with the same key as the added attribute.
  26. *
  27. * If only `oldValue` is set, then attribute with given key will be removed. Note that all nodes in operation's range
  28. * must have an attribute with that key added.
  29. *
  30. * If both `newValue` and `oldValue` are set, then the operation will change the attribute value. Note that all nodes in
  31. * operation's ranges must already have an attribute with given key and `oldValue` as value
  32. *
  33. * @param {module:engine/model/range~Range} range Range on which the operation should be applied.
  34. * @param {String} key Key of an attribute to change or remove.
  35. * @param {*} oldValue Old value of the attribute with given key or `null`, if attribute was not set before.
  36. * @param {*} newValue New value of the attribute with given key or `null`, if operation should remove attribute.
  37. * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
  38. * can be applied or `null` if the operation operates on detached (non-document) tree.
  39. */
  40. constructor( range, key, oldValue, newValue, baseVersion ) {
  41. super( baseVersion );
  42. /**
  43. * Range on which operation should be applied.
  44. *
  45. * @readonly
  46. * @member {module:engine/model/range~Range}
  47. */
  48. this.range = Range.createFromRange( range );
  49. /**
  50. * Key of an attribute to change or remove.
  51. *
  52. * @readonly
  53. * @member {String}
  54. */
  55. this.key = key;
  56. /**
  57. * Old value of the attribute with given key or `null`, if attribute was not set before.
  58. *
  59. * @readonly
  60. * @member {*}
  61. */
  62. this.oldValue = oldValue === undefined ? null : oldValue;
  63. /**
  64. * New value of the attribute with given key or `null`, if operation should remove attribute.
  65. *
  66. * @readonly
  67. * @member {*}
  68. */
  69. this.newValue = newValue === undefined ? null : newValue;
  70. }
  71. /**
  72. * @inheritDoc
  73. */
  74. get type() {
  75. if ( this.oldValue === null ) {
  76. return 'addAttribute';
  77. } else if ( this.newValue === null ) {
  78. return 'removeAttribute';
  79. } else {
  80. return 'changeAttribute';
  81. }
  82. }
  83. /**
  84. * Creates and returns an operation that has the same parameters as this operation.
  85. *
  86. * @returns {module:engine/model/operation/attributeoperation~AttributeOperation} Clone of this operation.
  87. */
  88. clone() {
  89. return new AttributeOperation( this.range, this.key, this.oldValue, this.newValue, this.baseVersion );
  90. }
  91. /**
  92. * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
  93. *
  94. * @returns {module:engine/model/operation/attributeoperation~AttributeOperation}
  95. */
  96. getReversed() {
  97. return new AttributeOperation( this.range, this.key, this.newValue, this.oldValue, this.baseVersion + 1 );
  98. }
  99. /**
  100. * @inheritDoc
  101. */
  102. _validate() {
  103. for ( const item of this.range.getItems() ) {
  104. if ( this.oldValue !== null && !isEqual( item.getAttribute( this.key ), this.oldValue ) ) {
  105. /**
  106. * Changed node has different attribute value than operation's old attribute value.
  107. *
  108. * @error attribute-operation-wrong-old-value
  109. * @param {module:engine/model/item~Item} item
  110. * @param {String} key
  111. * @param {*} value
  112. */
  113. throw new CKEditorError(
  114. 'attribute-operation-wrong-old-value: Changed node has different attribute value than operation\'s ' +
  115. 'old attribute value.',
  116. { item, key: this.key, value: this.oldValue }
  117. );
  118. }
  119. if ( this.oldValue === null && this.newValue !== null && item.hasAttribute( this.key ) ) {
  120. /**
  121. * The attribute with given key already exists for the given node.
  122. *
  123. * @error attribute-operation-attribute-exists
  124. * @param {module:engine/model/node~Node} node
  125. * @param {String} key
  126. */
  127. throw new CKEditorError(
  128. 'attribute-operation-attribute-exists: The attribute with given key already exists.',
  129. { node: item, key: this.key }
  130. );
  131. }
  132. }
  133. }
  134. /**
  135. * @inheritDoc
  136. */
  137. _execute() {
  138. // If value to set is same as old value, don't do anything.
  139. if ( !isEqual( this.oldValue, this.newValue ) ) {
  140. // Execution.
  141. _setAttribute( this.range, this.key, this.newValue );
  142. }
  143. }
  144. /**
  145. * @inheritDoc
  146. */
  147. static get className() {
  148. return 'engine.model.operation.AttributeOperation';
  149. }
  150. /**
  151. * Creates `AttributeOperation` object from deserilized object, i.e. from parsed JSON string.
  152. *
  153. * @param {Object} json Deserialized JSON object.
  154. * @param {module:engine/model/document~Document} document Document on which this operation will be applied.
  155. * @returns {module:engine/model/operation/attributeoperation~AttributeOperation}
  156. */
  157. static fromJSON( json, document ) {
  158. return new AttributeOperation( Range.fromJSON( json.range, document ), json.key, json.oldValue, json.newValue, json.baseVersion );
  159. }
  160. }