attributeoperation.js 5.0 KB

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