'use strict'; var forEach = require('lodash/collection/forEach'), filter = require('lodash/collection/filter'), any = require('lodash/collection/any'), isArray = require('lodash/lang/isArray'); var IGNORED_PROPERTIES = require('./ModelCloneUtils').IGNORED_PROPERTIES; function isAllowedIn(extProp, type) { var allowedIn = extProp.meta.allowedIn; // '*' is a wildcard, which means any element is allowed to use this property if (allowedIn.length === 1 && allowedIn[0] === '*') { return true; } return allowedIn.indexOf(type) !== -1; } function isType(element, types) { return any(types, function(type) { return typeof element === type; }); } /** * A bpmn properties cloning interface * */ function ModelCloneHelper() {} module.exports = ModelCloneHelper; ModelCloneHelper.prototype.clone = function(oldElement, newElement, properties) { this._newElement = newElement; forEach(properties, function(propName) { var oldElementProp = oldElement.get(propName), newElementProp = newElement.get(propName), propDescriptor = newElement.$model.getPropertyDescriptor(newElement, propName), name; // we're not interested in cloning: // - same values from simple types // - cloning id's // - cloning reference elements if (newElementProp === oldElementProp || (propDescriptor && (propDescriptor.isId || propDescriptor.isReference))) { return; } // if the property is of type 'boolean', 'string', 'number' or 'null', just set it if (isType(oldElementProp, [ 'boolean', 'string', 'number' ]) || oldElementProp === null) { newElement.set(propName, oldElementProp); return; } if (isArray(oldElementProp)) { forEach(oldElementProp, function(extElement) { var newProp = this._deepClone(extElement); newProp.$parent = newElement; newElementProp.push(newProp); }, this); } else { name = propName.replace(/bpmn:/, ''); newElement[name] = this._deepClone(oldElementProp); } }, this); return newElement; }; ModelCloneHelper.prototype._deepClone = function _deepClone(element) { var newElement = this._newElement; var newProp = element.$model.create(element.$type); var properties = filter(Object.keys(element), function(prop) { var descriptor = newProp.$model.getPropertyDescriptor(newProp, prop); if (descriptor && (descriptor.isId || descriptor.isReference)) { return false; } // we need to make sure we don't clone certain properties // which we cannot easily know if they hold references or not if (IGNORED_PROPERTIES.indexOf(prop) !== -1) { return false; } // make sure we don't copy the type return prop !== '$type'; }); forEach(properties, function(propName) { // check if the element has this property defined if (element[propName] !== undefined && (element[propName].$type || isArray(element[propName]))) { if (isArray(element[propName])) { newProp[propName] = []; forEach(element[propName], function(property) { var extProp = element.$model.getTypeDescriptor(property.$type), newDeepProp; // we're not going to copy undefined types if (!extProp) { return; } // make sure only allowed extensionElements are copied if (element.$type === 'bpmn:ExtensionElements' && extProp.meta && extProp.meta.allowedIn && !isAllowedIn(extProp, newElement.$type)) { return; } newDeepProp = this._deepClone(property); newDeepProp.$parent = newProp; newProp[propName].push(newDeepProp); }, this); } else if (element[propName].$type) { newProp[propName] = this._deepClone(element[propName]); newProp[propName].$parent = newProp; } } else { // just assign directly if it's a value newProp[propName] = element[propName]; } }, this); return newProp; };