d885721ff4524ccc64fe7505c93c0f0a81d4cfb3afc67752e186f91fd742154e6579fc242ef67814f419f8fa1312ec1fe09f4269ab165b6fd2d8651906dda4 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import BasePlugin from './../_base';
  2. import jsonpatch from './../../../lib/jsonpatch/json-patch-duplex';
  3. import DataObserver from './dataObserver';
  4. import {arrayEach} from './../../helpers/array';
  5. import {registerPlugin} from './../../plugins';
  6. // Handsontable.hooks.register('afterChangesObserved');
  7. /**
  8. * @plugin ObserveChanges
  9. *
  10. * @description
  11. * This plugin allows to observe data source changes.
  12. *
  13. * By default, the plugin is declared as `undefined`, which makes it disabled.
  14. * Enabling this plugin switches the table into one-way data binding where changes are applied into the data source (outside from the table)
  15. * will be automatically reflected in the table.
  16. *
  17. * ```js
  18. * ...
  19. * // as a boolean
  20. * observeChanges: true,
  21. * ...
  22. * ```
  23. *
  24. * To configure this plugin see {@link Options#observeChanges}.
  25. */
  26. class ObserveChanges extends BasePlugin {
  27. constructor(hotInstance) {
  28. super(hotInstance);
  29. /**
  30. * Instance of {@link DataObserver}.
  31. *
  32. * @type {DataObserver}
  33. */
  34. this.observer = null;
  35. }
  36. /**
  37. * Check if the plugin is enabled in the handsontable settings.
  38. *
  39. * @returns {Boolean}
  40. */
  41. isEnabled() {
  42. return this.hot.getSettings().observeChanges;
  43. }
  44. /**
  45. * Enable plugin for this Handsontable instance.
  46. */
  47. enablePlugin() {
  48. if (this.enabled) {
  49. return;
  50. }
  51. if (!this.observer) {
  52. this.observer = new DataObserver(this.hot.getSourceData());
  53. this._exposePublicApi();
  54. }
  55. this.observer.addLocalHook('change', (patches) => this.onDataChange(patches));
  56. this.addHook('afterCreateRow', () => this.onAfterTableAlter());
  57. this.addHook('afterRemoveRow', () => this.onAfterTableAlter());
  58. this.addHook('afterCreateCol', () => this.onAfterTableAlter());
  59. this.addHook('afterRemoveCol', () => this.onAfterTableAlter());
  60. this.addHook('afterChange', (changes, source) => this.onAfterTableAlter(source));
  61. this.addHook('afterLoadData', (firstRun) => this.onAfterLoadData(firstRun));
  62. super.enablePlugin();
  63. }
  64. /**
  65. * Disable plugin for this Handsontable instance.
  66. */
  67. disablePlugin() {
  68. if (this.observer) {
  69. this.observer.destroy();
  70. this.observer = null;
  71. this._deletePublicApi();
  72. }
  73. super.disablePlugin();
  74. }
  75. /**
  76. * Data change observer.
  77. *
  78. * @private
  79. * @param {Array} patches An array of objects which every item defines coordinates where data was changed.
  80. */
  81. onDataChange(patches) {
  82. if (!this.observer.isPaused()) {
  83. const sourceName = `${this.pluginName}.change`;
  84. const actions = {
  85. add: (patch) => {
  86. if (isNaN(patch.col)) {
  87. this.hot.runHooks('afterCreateRow', patch.row, 1, sourceName);
  88. } else {
  89. this.hot.runHooks('afterCreateCol', patch.col, 1, sourceName);
  90. }
  91. },
  92. remove: (patch) => {
  93. if (isNaN(patch.col)) {
  94. this.hot.runHooks('afterRemoveRow', patch.row, 1, sourceName);
  95. } else {
  96. this.hot.runHooks('afterRemoveCol', patch.col, 1, sourceName);
  97. }
  98. },
  99. replace: (patch) => {
  100. this.hot.runHooks('afterChange', [patch.row, patch.col, null, patch.value], sourceName);
  101. },
  102. };
  103. arrayEach(patches, (patch) => {
  104. if (actions[patch.op]) {
  105. actions[patch.op](patch);
  106. }
  107. });
  108. this.hot.render();
  109. }
  110. this.hot.runHooks('afterChangesObserved');
  111. }
  112. /**
  113. * On after table alter listener. Prevents infinity loop between internal and external data changing.
  114. *
  115. * @private
  116. * @param source
  117. */
  118. onAfterTableAlter(source) {
  119. if (source !== 'loadData') {
  120. this.observer.pause();
  121. this.hot.addHookOnce('afterChangesObserved', () => this.observer.resume());
  122. }
  123. }
  124. /**
  125. * On after load data listener.
  126. *
  127. * @private
  128. * @param {Boolean} firstRun `true` if event was fired first time.
  129. */
  130. onAfterLoadData(firstRun) {
  131. if (!firstRun) {
  132. this.observer.setObservedData(this.hot.getSourceData());
  133. }
  134. }
  135. /**
  136. * Destroy plugin instance.
  137. */
  138. destroy() {
  139. if (this.observer) {
  140. this.observer.destroy();
  141. this._deletePublicApi();
  142. }
  143. super.destroy();
  144. }
  145. /**
  146. * Expose plugins methods to the core.
  147. *
  148. * @private
  149. */
  150. _exposePublicApi() {
  151. const hot = this.hot;
  152. hot.pauseObservingChanges = () => this.observer.pause();
  153. hot.resumeObservingChanges = () => this.observer.resume();
  154. hot.isPausedObservingChanges = () => this.observer.isPaused();
  155. }
  156. /**
  157. * Delete all previously exposed methods.
  158. *
  159. * @private
  160. */
  161. _deletePublicApi() {
  162. const hot = this.hot;
  163. delete hot.pauseObservingChanges;
  164. delete hot.resumeObservingChanges;
  165. delete hot.isPausedObservingChanges;
  166. }
  167. }
  168. export default ObserveChanges;
  169. registerPlugin('observeChanges', ObserveChanges);