mutationobserver.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. CKEDITOR.define( [
  7. 'utils-diff',
  8. 'treeview/observer/observer',
  9. 'treeview/element',
  10. 'treeview/text',
  11. 'treeview/position'
  12. ], ( diff, Observer, ViewElement, ViewText, Position ) => {
  13. class MutationObserver extends Observer {
  14. constructor() {
  15. super();
  16. this.config = {
  17. childList: true,
  18. characterData: true,
  19. characterDataOldValue: true,
  20. subtree: true
  21. };
  22. }
  23. /**
  24. * @method init
  25. * @param {treeView.TreeView}
  26. */
  27. init( treeView ) {
  28. this.domRoot = treeView.domRoot;
  29. this._mutationObserver = new window.MutationObserver( this._onMutations.bind( this ) );
  30. }
  31. /**
  32. * @method attach
  33. */
  34. attach() {
  35. this._mutationObserver.observe( this.domRoot, this.config );
  36. }
  37. /**
  38. * @method detach
  39. */
  40. detach() {
  41. this._mutationObserver.disconnect();
  42. }
  43. _onMutations( mutations ) {
  44. // Use set for deduplication.
  45. const mutatedTexts = new Set();
  46. const mutatedElements = new Set();
  47. for ( let mutation of mutations ) {
  48. if ( mutation.type === 'childList' ) {
  49. const element = ViewElement.getCorespondingElement( mutation.target );
  50. if ( element ) {
  51. mutatedElements.add( element );
  52. }
  53. }
  54. }
  55. for ( let mutation of mutations ) {
  56. if ( mutation.type === 'characterData' ) {
  57. const text = Text.getCorespondingText( mutation.target );
  58. if ( text && !mutatedElements.has( text.parent ) ) {
  59. mutatedTexts.add( text );
  60. }
  61. }
  62. }
  63. for ( let text of mutatedTexts ) {
  64. text.markToSync( ViewText.TEXT_NEEDS_UPDATE );
  65. this.fire( 'text', text ); // TODO: new text
  66. }
  67. for ( let viewElement of mutatedElements ) {
  68. const domElement = viewElement.domElement;
  69. const domChildren = domElement.childNodes;
  70. const viewChildren = viewElement.getChildren();
  71. viewElement.markToSync( ViewElement.CHILDREN_NEED_UPDATE );
  72. const actions = diff( viewChildren, domChildren, compareNodes );
  73. let domOffset = 0;
  74. let viewOffset = 0;
  75. for ( let action of actions ) {
  76. if ( action === diff.EQUAL ) {
  77. domOffset++;
  78. viewOffset++;
  79. } else if ( action === diff.INSERT ) {
  80. this.fire( 'insert', new Position( viewElement, viewOffset ), domToView( domChildren[ domOffset ] ) );
  81. domOffset++;
  82. } else if ( action === diff.DELETE ) {
  83. this.fire( 'delete', viewChildren[ viewOffset ] );
  84. viewOffset++;
  85. }
  86. }
  87. // TODO: fire order
  88. // TODO: update positions?
  89. // TODO: delete and insert -> move
  90. // delete text and insert text -> 'text' event
  91. }
  92. this.treeView.render();
  93. function compareNodes( viewNode, domNode ) {
  94. // Elements.
  95. if ( domNode instanceof HTMLElement && viewNode instanceof ViewElement ) {
  96. return domNode === viewNode.DOMElement;
  97. }
  98. // Texts.
  99. else if ( domNode instanceof Text && viewNode instanceof ViewText ) {
  100. return domNode.data === viewNode.getText();
  101. }
  102. // Not matching types.
  103. return false;
  104. }
  105. function domToView( domElement ) {
  106. let viewElement = ViewElement.getCorespondingElement( domElement );
  107. if ( viewElement ) {
  108. return viewElement;
  109. }
  110. // TODO
  111. }
  112. }
  113. }
  114. return MutationObserver;
  115. } );