ModelCloneHelper.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import {
  2. forEach,
  3. filter,
  4. some,
  5. sortBy,
  6. isArray
  7. } from 'min-dash';
  8. import {
  9. IGNORED_PROPERTIES
  10. } from './ModelCloneUtils';
  11. function isAllowedIn(extProp, type) {
  12. var allowedIn = extProp.meta.allowedIn;
  13. // '*' is a wildcard, which means any element is allowed to use this property
  14. if (allowedIn.length === 1 && allowedIn[0] === '*') {
  15. return true;
  16. }
  17. return allowedIn.indexOf(type) !== -1;
  18. }
  19. function isType(element, types) {
  20. return some(types, function(type) {
  21. return typeof element === type;
  22. });
  23. }
  24. /**
  25. * A bpmn properties cloning interface
  26. *
  27. */
  28. export default function ModelCloneHelper(eventBus, bpmnFactory) {
  29. this._eventBus = eventBus;
  30. this._bpmnFactory = bpmnFactory;
  31. }
  32. ModelCloneHelper.prototype.clone = function(refElement, newElement, properties) {
  33. var self = this;
  34. // hasNestedProperty: property allows us to avoid ending up with empty (xml) tags
  35. // f.ex: if extensionElements.values is empty, don't set it
  36. var context = {
  37. newElement: newElement,
  38. hasNestedProperty: false
  39. };
  40. // we want the extensionElements to be cloned last
  41. // so that they can check certain properties
  42. properties = sortBy(properties, function(prop) {
  43. return prop === 'bpmn:extensionElements';
  44. });
  45. forEach(properties, function(propName) {
  46. var refElementProp = refElement.get(propName),
  47. newElementProp = newElement.get(propName),
  48. propDescriptor = newElement.$model.getPropertyDescriptor(newElement, propName),
  49. newProperty, name;
  50. // we're not interested in cloning:
  51. // - same values from simple types
  52. // - cloning id's
  53. // - cloning reference elements
  54. if (newElementProp === refElementProp) {
  55. return;
  56. }
  57. if (propDescriptor && (propDescriptor.isId || propDescriptor.isReference)) {
  58. return;
  59. }
  60. // if the property is of type 'boolean', 'string', 'number' or 'null', just set it
  61. if (isType(refElementProp, [ 'boolean', 'string', 'number' ]) || refElementProp === null) {
  62. newElement.set(propName, refElementProp);
  63. return;
  64. }
  65. if (isArray(refElementProp)) {
  66. forEach(refElementProp, function(extElement) {
  67. var newProp;
  68. context.refTopLevelProperty = extElement;
  69. newProp = self._deepClone(extElement, context);
  70. if (context.hasNestedProperty) {
  71. newProp.$parent = newElement;
  72. newElementProp.push(newProp);
  73. }
  74. context.hasNestedProperty = false;
  75. });
  76. } else {
  77. name = propName.replace(/bpmn:/, '');
  78. context.refTopLevelProperty = refElementProp;
  79. newProperty = self._deepClone(refElementProp, context);
  80. if (context.hasNestedProperty) {
  81. newProperty.$parent = newElement;
  82. newElement.set(name, newProperty);
  83. }
  84. context.hasNestedProperty = false;
  85. }
  86. });
  87. return newElement;
  88. };
  89. ModelCloneHelper.prototype._deepClone = function _deepClone(propertyElement, context) {
  90. var self = this;
  91. var eventBus = this._eventBus;
  92. var bpmnFactory = this._bpmnFactory;
  93. var newProp = bpmnFactory.create(propertyElement.$type);
  94. var properties = filter(Object.keys(propertyElement), function(prop) {
  95. var descriptor = newProp.$model.getPropertyDescriptor(newProp, prop);
  96. if (descriptor && (descriptor.isId || descriptor.isReference)) {
  97. return false;
  98. }
  99. // we need to make sure we don't clone certain properties
  100. // which we cannot easily know if they hold references or not
  101. if (IGNORED_PROPERTIES.indexOf(prop) !== -1) {
  102. return false;
  103. }
  104. // make sure we don't copy the type
  105. return prop !== '$type';
  106. });
  107. if (!properties.length) {
  108. context.hasNestedProperty = true;
  109. }
  110. forEach(properties, function(propName) {
  111. // check if the propertyElement has this property defined
  112. if (propertyElement[propName] !== undefined &&
  113. (propertyElement[propName].$type || isArray(propertyElement[propName]))) {
  114. if (isArray(propertyElement[propName])) {
  115. newProp[propName] = [];
  116. forEach(propertyElement[propName], function(property) {
  117. var extProp = propertyElement.$model.getTypeDescriptor(property.$type),
  118. newDeepProp;
  119. // we're not going to copy undefined types
  120. if (!extProp) {
  121. return;
  122. }
  123. var canClone = eventBus.fire('property.clone', {
  124. newElement: context.newElement,
  125. refTopLevelProperty: context.refTopLevelProperty,
  126. propertyDescriptor: extProp
  127. });
  128. if (!canClone) {
  129. // if can clone is 'undefined' or 'false'
  130. // check for the meta information if it is allowed
  131. if (propertyElement.$type === 'bpmn:ExtensionElements' &&
  132. extProp.meta && extProp.meta.allowedIn &&
  133. !isAllowedIn(extProp, context.newElement.$type)) {
  134. return false;
  135. }
  136. }
  137. newDeepProp = self._deepClone(property, context);
  138. newDeepProp.$parent = newProp;
  139. if (!newProp[propName]) {
  140. newProp[propName] = [];
  141. }
  142. context.hasNestedProperty = true;
  143. newProp[propName].push(newDeepProp);
  144. });
  145. } else if (propertyElement[propName].$type) {
  146. newProp[propName] = self._deepClone(propertyElement[propName], context);
  147. if (newProp[propName]) {
  148. context.hasNestedProperty = true;
  149. newProp[propName].$parent = newProp;
  150. }
  151. }
  152. } else {
  153. context.hasNestedProperty = true;
  154. // just assign directly if it's a value
  155. newProp[propName] = propertyElement[propName];
  156. }
  157. });
  158. return newProp;
  159. };