rootattributeoperation.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module engine/model/operation/rootattributeoperation
  7. */
  8. import Operation from './operation';
  9. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  10. /**
  11. * Operation to change root element's attribute. Using this class you can add, remove or change value of the attribute.
  12. *
  13. * This operation is needed, because root elements can't be changed through
  14. * @link module:engine/model/operation/attributeoperation~AttributeOperation}.
  15. * It is because {@link module:engine/model/operation/attributeoperation~AttributeOperation}
  16. * requires a range to change and root element can't
  17. * be a part of range because every {@link module:engine/model/position~Position} has to be inside a root.
  18. * {@link module:engine/model/position~Position} can't be created before a root element.
  19. *
  20. * @extends module:engine/model/operation/operation~Operation
  21. */
  22. export default class RootAttributeOperation extends Operation {
  23. /**
  24. * Creates an operation that changes, removes or adds attributes on root element.
  25. *
  26. * @see module:engine/model/operation/attributeoperation~AttributeOperation
  27. * @param {module:engine/model/rootelement~RootElement} root Root element to change.
  28. * @param {String} key Key of an attribute to change or remove.
  29. * @param {*} oldValue Old value of the attribute with given key or `null` if adding a new attribute.
  30. * @param {*} newValue New value to set for the attribute. If `null`, then the operation just removes the attribute.
  31. * @param {Number|null} baseVersion Document {@link module:engine/model/document~Document#version} on which operation
  32. * can be applied or `null` if the operation operates on detached (non-document) tree.
  33. */
  34. constructor( root, key, oldValue, newValue, baseVersion ) {
  35. super( baseVersion );
  36. /**
  37. * Root element to change.
  38. *
  39. * @readonly
  40. * @member {module:engine/model/rootelement~RootElement}
  41. */
  42. this.root = root;
  43. /**
  44. * Key of an attribute to change or remove.
  45. *
  46. * @readonly
  47. * @member {String}
  48. */
  49. this.key = key;
  50. /**
  51. * Old value of the attribute with given key or `null` if adding a new attribute.
  52. *
  53. * @readonly
  54. * @member {*}
  55. */
  56. this.oldValue = oldValue;
  57. /**
  58. * New value to set for the attribute. If `null`, then the operation just removes the attribute.
  59. *
  60. * @readonly
  61. * @member {*}
  62. */
  63. this.newValue = newValue;
  64. }
  65. /**
  66. * @inheritDoc
  67. */
  68. get type() {
  69. if ( this.oldValue === null ) {
  70. return 'addRootAttribute';
  71. } else if ( this.newValue === null ) {
  72. return 'removeRootAttribute';
  73. } else {
  74. return 'changeRootAttribute';
  75. }
  76. }
  77. /**
  78. * Creates and returns an operation that has the same parameters as this operation.
  79. *
  80. * @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation} Clone of this operation.
  81. */
  82. clone() {
  83. return new RootAttributeOperation( this.root, this.key, this.oldValue, this.newValue, this.baseVersion );
  84. }
  85. /**
  86. * See {@link module:engine/model/operation/operation~Operation#getReversed `Operation#getReversed()`}.
  87. *
  88. * @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation}
  89. */
  90. getReversed() {
  91. return new RootAttributeOperation( this.root, this.key, this.newValue, this.oldValue, this.baseVersion + 1 );
  92. }
  93. /**
  94. * @inheritDoc
  95. */
  96. _validate() {
  97. if ( this.root != this.root.root || this.root.is( 'documentFragment' ) ) {
  98. /**
  99. * The element to change is not a root element.
  100. *
  101. * @error rootattribute-operation-not-a-root
  102. * @param {module:engine/model/rootelement~RootElement} root
  103. * @param {String} key
  104. * @param {*} value
  105. */
  106. throw new CKEditorError(
  107. 'rootattribute-operation-not-a-root: The element to change is not a root element.',
  108. { root: this.root, key: this.key }
  109. );
  110. }
  111. if ( this.oldValue !== null && this.root.getAttribute( this.key ) !== this.oldValue ) {
  112. /**
  113. * The attribute which should be removed does not exists for the given node.
  114. *
  115. * @error rootattribute-operation-wrong-old-value
  116. * @param {module:engine/model/rootelement~RootElement} root
  117. * @param {String} key
  118. * @param {*} value
  119. */
  120. throw new CKEditorError(
  121. 'rootattribute-operation-wrong-old-value: Changed node has different attribute value than operation\'s ' +
  122. 'old attribute value.',
  123. { root: this.root, key: this.key }
  124. );
  125. }
  126. if ( this.oldValue === null && this.newValue !== null && this.root.hasAttribute( this.key ) ) {
  127. /**
  128. * The attribute with given key already exists for the given node.
  129. *
  130. * @error rootattribute-operation-attribute-exists
  131. * @param {module:engine/model/rootelement~RootElement} root
  132. * @param {String} key
  133. */
  134. throw new CKEditorError(
  135. 'rootattribute-operation-attribute-exists: The attribute with given key already exists.',
  136. { root: this.root, key: this.key }
  137. );
  138. }
  139. }
  140. /**
  141. * @inheritDoc
  142. */
  143. _execute() {
  144. if ( this.newValue !== null ) {
  145. this.root._setAttribute( this.key, this.newValue );
  146. } else {
  147. this.root._removeAttribute( this.key );
  148. }
  149. }
  150. /**
  151. * @inheritDoc
  152. */
  153. toJSON() {
  154. const json = super.toJSON();
  155. json.root = this.root.toJSON();
  156. return json;
  157. }
  158. /**
  159. * @inheritDoc
  160. */
  161. static get className() {
  162. return 'RootAttributeOperation';
  163. }
  164. /**
  165. * Creates RootAttributeOperation object from deserilized object, i.e. from parsed JSON string.
  166. *
  167. * @param {Object} json Deserialized JSON object.
  168. * @param {module:engine/model/document~Document} document Document on which this operation will be applied.
  169. * @returns {module:engine/model/operation/rootattributeoperation~RootAttributeOperation}
  170. */
  171. static fromJSON( json, document ) {
  172. if ( !document.getRoot( json.root ) ) {
  173. /**
  174. * Cannot create RootAttributeOperation for document. Root with specified name does not exist.
  175. *
  176. * @error rootattributeoperation-fromjson-no-root
  177. * @param {String} rootName
  178. */
  179. throw new CKEditorError(
  180. 'rootattribute-operation-fromjson-no-root: Cannot create RootAttributeOperation. Root with specified name does not exist.',
  181. { rootName: json.root }
  182. );
  183. }
  184. return new RootAttributeOperation( document.getRoot( json.root ), json.key, json.oldValue, json.newValue, json.baseVersion );
  185. }
  186. }